From 131257cef1b2677dff8b3acc7ec8490568097af9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 14 Feb 2020 08:31:31 +0100 Subject: [PATCH 001/826] GCodeProcessor basic framework --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 8 ++ src/libslic3r/GCode.hpp | 12 ++ src/libslic3r/GCode/GCodeProcessor.cpp | 177 +++++++++++++++++++++++++ src/libslic3r/GCode/GCodeProcessor.hpp | 87 ++++++++++++ src/libslic3r/Technologies.hpp | 11 ++ 6 files changed, 297 insertions(+) create mode 100644 src/libslic3r/GCode/GCodeProcessor.cpp create mode 100644 src/libslic3r/GCode/GCodeProcessor.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index c8e259caa9..52b5e60f17 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -91,6 +91,8 @@ add_library(libslic3r STATIC GCode/ToolOrdering.hpp GCode/WipeTower.cpp GCode/WipeTower.hpp + GCode/GCodeProcessor.cpp + GCode/GCodeProcessor.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4cbab67a63..e09dc54fe8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -760,6 +760,14 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ throw std::runtime_error(msg); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + m_processor.apply_config(print->config()); + m_processor.reset(); + m_processor.process_file(path_tmp); +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data(); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 0344924a13..8ddf2db61d 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -14,6 +14,11 @@ #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER +#include "GCode/GCodeProcessor.hpp" +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GCodeTimeEstimator.hpp" #include "EdgeGrid.hpp" #include "GCode/Analyzer.hpp" @@ -383,6 +388,13 @@ private: // Analyzer GCodeAnalyzer m_analyzer; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + // Processor + GCodeProcessor m_processor; +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // Write a string into a file. void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); } void _write(FILE* file, const char *what); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp new file mode 100644 index 0000000000..08066ee57e --- /dev/null +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -0,0 +1,177 @@ +#include "../libslic3r.h" +#include "GCodeProcessor.hpp" + +#if ENABLE_GCODE_VIEWER + +static const float INCHES_TO_MM = 25.4f; +static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; + +namespace Slic3r { + +void GCodeProcessor::apply_config(const PrintConfig& config) +{ + m_parser.apply_config(config); +} + +void GCodeProcessor::reset() +{ + m_units = EUnits::Millimeters; + m_global_positioning_type = EPositioningType::Absolute; + m_e_local_positioning_type = EPositioningType::Absolute; + + ::memset(m_start_position.data(), 0, sizeof(AxisCoords)); + ::memset(m_end_position.data(), 0, sizeof(AxisCoords)); + ::memset(m_origin.data(), 0, sizeof(AxisCoords)); + + m_feedrate = 0.0f; + + m_moves.clear(); +} + +void GCodeProcessor::process_file(const std::string& filename) +{ + MoveStep start_step {}; + m_moves.emplace_back(start_step); + m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); + int a = 0; +} + +void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) +{ +/* + std::cout << line.raw() << std::endl; +*/ + + // update start position + m_start_position = m_end_position; + + std::string cmd = line.cmd(); + std::string comment = line.comment(); + + if (cmd.length() > 1) + { + // process command lines + switch (::toupper(cmd[0])) + { + case 'G': + { + switch (::atoi(&cmd[1])) + { + // Move + case 1: { process_G1(line); } + default: { break; } + } + break; + } + case 'M': + { + switch (::atoi(&cmd[1])) + { + default: { break; } + } + break; + } + case 'T': + { + break; + } + default: { break; } + } + } + else if (comment.length() > 1) + { + // process tags embedded into comments + process_comment(line); + } +} + +void GCodeProcessor::process_comment(const GCodeReader::GCodeLine& line) +{ +} + +void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) +{ + auto axis_absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1) + { + bool is_relative = (m_global_positioning_type == EPositioningType::Relative); + if (axis == E) + is_relative |= (m_e_local_positioning_type == EPositioningType::Relative); + + if (lineG1.has(Slic3r::Axis(axis))) + { + float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; + return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret; + } + else + return m_start_position[axis]; + }; + + // updates axes positions from line + for (unsigned char a = X; a <= E; ++a) + { + m_end_position[a] = axis_absolute_position((Axis)a, line); + } + + // updates feedrate from line, if present + if (line.has_f()) + m_feedrate = line.f() * MMMIN_TO_MMSEC; + + // calculates movement deltas + float max_abs_delta = 0.0f; + AxisCoords delta_pos; + for (unsigned char a = X; a <= E; ++a) + { + delta_pos[a] = m_end_position[a] - m_start_position[a]; + max_abs_delta = std::max(max_abs_delta, std::abs(delta_pos[a])); + } + + // no displacement, return + if (max_abs_delta == 0.0f) + return; // <<<<<<<<<<<<<<<<< is this correct for time estimate ? + + // detect move type + EMoveType move_type = EMoveType::Noop; + if (delta_pos[E] < 0.0f) + { + if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f)) + move_type = EMoveType::Travel; + else + move_type = EMoveType::Retract; + } + else if (delta_pos[E] > 0.0f) + { + if ((delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f)) + move_type = EMoveType::Unretract; + else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f)) + move_type = EMoveType::Extrude; + } + else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f)) + move_type = EMoveType::Travel; + + + MoveStep move_step { m_end_position, m_feedrate, move_type }; + m_moves.emplace_back(move_step); + +/* + std::cout << "start: "; + for (unsigned char a = X; a <= E; ++a) + { + std::cout << m_start_position[a]; + if (a != E) + std::cout << ", "; + } + std::cout << " - end: "; + for (unsigned char a = X; a <= E; ++a) + { + std::cout << m_end_position[a]; + if (a != E) + std::cout << ", "; + } + std::cout << "\n"; +*/ +} + +} /* namespace Slic3r */ + +#endif // ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp new file mode 100644 index 0000000000..0a3dae0327 --- /dev/null +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -0,0 +1,87 @@ +#ifndef slic3r_GCodeProcessor_hpp_ +#define slic3r_GCodeProcessor_hpp_ + +#if ENABLE_GCODE_VIEWER +#include "../GCodeReader.hpp" + +namespace Slic3r { + + class GCodeProcessor + { + using AxisCoords = std::array; + + enum class EUnits : unsigned char + { + Millimeters, + Inches + }; + + enum class EPositioningType : unsigned char + { + Absolute, + Relative + }; + + enum class EMoveType : unsigned char + { + Noop, + Retract, + Unretract, + Tool_change, + Travel, + Extrude, + Num_Types + }; + + struct MoveStep + { + AxisCoords position; // mm + float feedrate; // mm/s + EMoveType type; + }; + + using MoveStepsList = std::vector; + + GCodeReader m_parser; + + EUnits m_units; + EPositioningType m_global_positioning_type; + EPositioningType m_e_local_positioning_type; + + AxisCoords m_start_position; // mm + AxisCoords m_end_position; // mm + AxisCoords m_origin; // mm + + float m_feedrate; // mm/s + + MoveStepsList m_moves; + + + public: + GCodeProcessor() { reset(); } + + void apply_config(const PrintConfig& config); + + void reset(); + + // Process the gcode contained in the file with the given filename + // Return false if any error occourred + void process_file(const std::string& filename); + + private: + void process_gcode_line(const GCodeReader::GCodeLine& line); + + // Process tags embedded into comments + void process_comment(const GCodeReader::GCodeLine& line); + + // Move + void process_G1(const GCodeReader::GCodeLine& line); + }; + +} /* namespace Slic3r */ + +#endif // ENABLE_GCODE_VIEWER + +#endif /* slic3r_GCodeProcessor_hpp_ */ + + diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 0728dedc60..4983787861 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -63,4 +63,15 @@ #define ENABLE_BACKWARD_COMPATIBLE_RELOAD_FROM_DISK (1 && ENABLE_2_2_0_BETA1) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//================== +// 2.3.0.alpha1 techs +//================== +#define ENABLE_2_3_0_ALPHA1 1 + +// Enable G-Code viewer +#define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + + #endif // _technologies_h_ From 3b6d334d7bb0331e372cde14c69e153810fc0cbe Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 2 Mar 2020 15:13:23 +0100 Subject: [PATCH 002/826] ENABLE_GCODE_VIEWER - Basic framework for new gcode viewer --- src/PrusaSlicer.cpp | 4 +++ src/libslic3r/GCode.cpp | 34 +++++++++++++++++---- src/libslic3r/GCode.hpp | 10 +++--- src/libslic3r/GCode/GCodeProcessor.cpp | 30 ++++++++++-------- src/libslic3r/GCode/GCodeProcessor.hpp | 32 +++++++++++++------ src/libslic3r/Print.cpp | 12 +++++--- src/libslic3r/Print.hpp | 9 ++++-- src/libslic3r/Technologies.hpp | 2 -- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 22 +++++++++++-- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 6 ++++ src/slic3r/GUI/GLCanvas3D.cpp | 6 ++++ src/slic3r/GUI/GLCanvas3D.hpp | 7 +++++ src/slic3r/GUI/GUI_Preview.cpp | 12 ++++++++ src/slic3r/GUI/GUI_Preview.hpp | 15 +++++++-- src/slic3r/GUI/Plater.cpp | 10 ++++++ tests/fff_print/test_data.cpp | 6 +++- tests/fff_print/test_model.cpp | 4 +++ 17 files changed, 173 insertions(+), 48 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 048aea8869..4557caa7c9 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -443,7 +443,11 @@ int CLI::run(int argc, char **argv) print->process(); if (printer_technology == ptFFF) { // The outfile is processed by a PlaceholderParser. +#if ENABLE_GCODE_VIEWER + outfile = fff_print.export_gcode(outfile, nullptr, nullptr); +#else outfile = fff_print.export_gcode(outfile, nullptr); +#endif // ENABLE_GCODE_VIEWER outfile_final = fff_print.print_statistics().finalize_output_path(outfile); } else { outfile = sla_print.output_filepath(outfile); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6717e961d1..701ca43778 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -698,11 +698,13 @@ std::vector>> GCode::collec return layers_to_print; } -#if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER +void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) +#elif ENABLE_THUMBNAIL_GENERATOR void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) #else void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data) -#endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_GCODE_VIEWER { PROFILE_CLEAR(); @@ -761,13 +763,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ throw std::runtime_error(msg); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER - m_processor.apply_config(print->config()); - m_processor.reset(); m_processor.process_file(path_tmp); + if (result != nullptr) + *result = std::move(m_processor.extract_result()); #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data(); @@ -905,6 +905,25 @@ namespace DoExport { analyzer.set_gcode_flavor(config.gcode_flavor); } +#if ENABLE_GCODE_VIEWER + static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor) + { + processor.reset(); + processor.apply_config(config); + + // send extruder offset data to processor + unsigned int num_extruders = static_cast(config.nozzle_diameter.values.size()); + GCodeProcessor::ExtruderOffsetsMap extruder_offsets; + for (unsigned int id = 0; id < num_extruders; ++id) + { + Vec2d offset = config.extruder_offset.get_at(id); + if (!offset.isApprox(Vec2d::Zero())) + extruder_offsets[id] = offset; + } + processor.set_extruder_offsets(extruder_offsets); + } +#endif // ENABLE_GCODE_VIEWER + static double autospeed_volumetric_limit(const Print &print) { // get the minimum cross-section used in the print @@ -1142,6 +1161,9 @@ void GCode::_do_export(Print& print, FILE* file) // modifies the following: m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled); DoExport::init_gcode_analyzer(print.config(), m_analyzer); +#if ENABLE_GCODE_VIEWER + DoExport::init_gcode_processor(print.config(), m_processor); +#endif // ENABLE_GCODE_VIEWER // resets analyzer's tracking data m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index e3da956c20..4b66d15218 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -14,11 +14,9 @@ #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER #include "GCode/GCodeProcessor.hpp" #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GCodeTimeEstimator.hpp" #include "EdgeGrid.hpp" #include "GCode/Analyzer.hpp" @@ -171,11 +169,13 @@ public: // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). -#if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER + void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, GCodeProcessor::Result* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); +#elif ENABLE_THUMBNAIL_GENERATOR void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); #else void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr); -#endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_GCODE_VIEWER // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Vec2d& origin() const { return m_origin; } @@ -388,12 +388,10 @@ private: // Analyzer GCodeAnalyzer m_analyzer; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER // Processor GCodeProcessor m_processor; #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Write a string into a file. void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 08066ee57e..493321a6e7 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -8,30 +8,26 @@ static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; namespace Slic3r { -void GCodeProcessor::apply_config(const PrintConfig& config) -{ - m_parser.apply_config(config); -} - void GCodeProcessor::reset() { m_units = EUnits::Millimeters; m_global_positioning_type = EPositioningType::Absolute; m_e_local_positioning_type = EPositioningType::Absolute; - ::memset(m_start_position.data(), 0, sizeof(AxisCoords)); - ::memset(m_end_position.data(), 0, sizeof(AxisCoords)); - ::memset(m_origin.data(), 0, sizeof(AxisCoords)); + std::fill(m_start_position.begin(), m_start_position.end(), 0.0f); + std::fill(m_end_position.begin(), m_end_position.end(), 0.0f); + std::fill(m_origin.begin(), m_origin.end(), 0.0f); m_feedrate = 0.0f; + m_extruder_id = 0; - m_moves.clear(); + m_result.reset(); } void GCodeProcessor::process_file(const std::string& filename) { - MoveStep start_step {}; - m_moves.emplace_back(start_step); + MoveVertex start_vertex {}; + m_result.moves.emplace_back(start_vertex); m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); int a = 0; } @@ -149,9 +145,17 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f)) move_type = EMoveType::Travel; + // correct position by extruder offset + Vec3d extruder_offset = Vec3d::Zero(); + auto it = m_extruder_offsets.find(m_extruder_id); + if (it != m_extruder_offsets.end()) + extruder_offset = Vec3d(it->second(0), it->second(1), 0.0); - MoveStep move_step { m_end_position, m_feedrate, move_type }; - m_moves.emplace_back(move_step); + MoveVertex vertex; + vertex.position = Vec3d(m_end_position[0], m_end_position[1], m_end_position[2]) + extruder_offset; + vertex.feedrate = m_feedrate; + vertex.type = move_type; + m_result.moves.emplace_back(vertex); /* std::cout << "start: "; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 0a3dae0327..7fa1733b78 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -33,15 +33,24 @@ namespace Slic3r { Num_Types }; - struct MoveStep + struct MoveVertex { - AxisCoords position; // mm - float feedrate; // mm/s - EMoveType type; + Vec3d position{ Vec3d::Zero() }; // mm + float feedrate{ 0.0f }; // mm/s + // type of the move terminating at this vertex + EMoveType type{ EMoveType::Noop }; }; - using MoveStepsList = std::vector; + public: + typedef std::map ExtruderOffsetsMap; + struct Result + { + std::vector moves; + void reset() { moves = std::vector(); } + }; + + private: GCodeReader m_parser; EUnits m_units; @@ -53,17 +62,22 @@ namespace Slic3r { AxisCoords m_origin; // mm float m_feedrate; // mm/s + unsigned int m_extruder_id; + ExtruderOffsetsMap m_extruder_offsets; - MoveStepsList m_moves; - + Result m_result; public: GCodeProcessor() { reset(); } - void apply_config(const PrintConfig& config); - + void apply_config(const PrintConfig& config) { m_parser.apply_config(config); } void reset(); + void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets) { m_extruder_offsets = extruder_offsets; } + + const Result& get_result() const { return m_result; } + Result&& extract_result() { return std::move(m_result); } + // Process the gcode contained in the file with the given filename // Return false if any error occourred void process_file(const std::string& filename); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index d5a1fa178d..d342c9056d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1619,11 +1619,13 @@ void Print::process() // The export_gcode may die for various reasons (fails to process output_filename_format, // write error into the G-code, cannot execute post-processing scripts). // It is up to the caller to show an error message. -#if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER +std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) +#elif ENABLE_THUMBNAIL_GENERATOR std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) #else std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) -#endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_GCODE_VIEWER { // output everything to a G-code file // The following call may die if the output_filename_format template substitution fails. @@ -1640,11 +1642,13 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa // The following line may die for multiple reasons. GCode gcode; -#if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER + gcode.do_export(this, path.c_str(), preview_data, result, thumbnail_cb); +#elif ENABLE_THUMBNAIL_GENERATOR gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb); #else gcode.do_export(this, path.c_str(), preview_data); -#endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_GCODE_VIEWER return path.c_str(); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7b326472e4..ef55482224 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -14,6 +14,9 @@ #if ENABLE_THUMBNAIL_GENERATOR #include "GCode/ThumbnailData.hpp" #endif // ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER +#include "GCode/GCodeProcessor.hpp" +#endif // ENABLE_GCODE_VIEWER #include "libslic3r.h" @@ -364,11 +367,13 @@ public: void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). -#if ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER + std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); +#elif ENABLE_THUMBNAIL_GENERATOR std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); #else std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data); -#endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_GCODE_VIEWER // methods for handling state bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e5a2f3faee..4cebe98b1c 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -48,7 +48,6 @@ #define ENABLE_2_2_0_BETA1 1 -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //================== // 2.3.0.alpha1 techs //================== @@ -56,7 +55,6 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // _technologies_h_ diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 548a19f776..126a77ae74 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -89,11 +89,13 @@ void BackgroundSlicingProcess::process_fff() assert(m_print == m_fff_print); m_print->process(); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); -#if ENABLE_THUMBNAIL_GENERATOR - m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); +#if ENABLE_GCODE_VIEWER + m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_gcode_result, m_thumbnail_cb); +#elif ENABLE_THUMBNAIL_GENERATOR + m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); #else m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); -#endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_GCODE_VIEWER if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { @@ -377,6 +379,19 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn assert(m_print != nullptr); assert(config.opt_enum("printer_technology") == m_print->technology()); Print::ApplyStatus invalidated = m_print->apply(model, config); +#if ENABLE_GCODE_VIEWER + if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF && + !this->m_fff_print->is_step_done(psGCodeExport)) + { + // Some FFF status was invalidated, and the G-code was not exported yet. + // Let the G-code preview UI know that the final G-code preview is not valid. + // In addition, this early memory deallocation reduces memory footprint. + if (m_gcode_preview_data != nullptr) + m_gcode_preview_data->reset(); + else if (m_gcode_result != nullptr) + m_gcode_result->reset(); + } +#else if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF && m_gcode_preview_data != nullptr && ! this->m_fff_print->is_step_done(psGCodeExport)) { // Some FFF status was invalidated, and the G-code was not exported yet. @@ -384,6 +399,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn // In addition, this early memory deallocation reduces memory footprint. m_gcode_preview_data->reset(); } +#endif // ENABLE_GCODE_VIEWER return invalidated; } diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index c8ece38f09..5b6f09d270 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -52,6 +52,9 @@ public: #if ENABLE_THUMBNAIL_GENERATOR void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } #endif // ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER + void set_gcode_result(GCodeProcessor::Result* result) { m_gcode_result = result; } +#endif // ENABLE_GCODE_VIEWER // The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished // and the background processing will transition into G-code export. @@ -159,6 +162,9 @@ private: // Callback function, used to write thumbnails into gcode. ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; #endif // ENABLE_THUMBNAIL_GENERATOR +#if ENABLE_GCODE_VIEWER + GCodeProcessor::Result* m_gcode_result = nullptr; +#endif // ENABLE_GCODE_VIEWER // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b5319a2f18..b96b8b47f1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2480,6 +2480,12 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio volume->indexed_vertex_array.finalize_geometry(gl_initialized); } +#if ENABLE_GCODE_VIEWER +void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result) +{ +} +#endif // ENABLE_GCODE_VIEWER + void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors) { const Print *print = this->fff_print(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9ae1278800..0dc2b26dd1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -13,6 +13,9 @@ #include "Gizmos/GLGizmosManager.hpp" #include "GUI_ObjectLayers.hpp" #include "MeshUtils.hpp" +#if ENABLE_GCODE_VIEWER +#include "libslic3r/GCode/GCodeProcessor.hpp" +#endif // ENABLE_GCODE_VIEWER #include @@ -577,6 +580,10 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); +#if ENABLE_GCODE_VIEWER + void load_gcode_preview_2(const GCodeProcessor::Result& gcode_result); +#endif // ENABLE_GCODE_VIEWER + void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors); void load_sla_preview(); void load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 5afdb3bb43..d0493adfc6 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -163,9 +163,15 @@ void View3D::render() m_canvas->set_as_dirty(); } +#if ENABLE_GCODE_VIEWER Preview::Preview( wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, + BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, GCodeProcessor::Result* gcode_result, std::function schedule_background_process_func) +#else +Preview::Preview( + wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) +#endif // ENABLE_GCODE_VIEWER : m_canvas_widget(nullptr) , m_canvas(nullptr) , m_double_slider_sizer(nullptr) @@ -181,6 +187,9 @@ Preview::Preview( , m_config(config) , m_process(process) , m_gcode_preview_data(gcode_preview_data) +#if ENABLE_GCODE_VIEWER + , m_gcode_result(gcode_result) +#endif // ENABLE_GCODE_VIEWER , m_number_extruders(1) , m_preferred_color_mode("feature") , m_loaded(false) @@ -860,6 +869,9 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); +#if ENABLE_GCODE_VIEWER + m_canvas->load_gcode_preview_2(*m_gcode_result); +#endif // ENABLE_GCODE_VIEWER m_loaded = true; } else { // Load the initial preview based on slices, not the final G-code. diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index cc8f153251..f0e7abfeca 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -6,6 +6,9 @@ #include #include "libslic3r/Model.hpp" +#if ENABLE_GCODE_VIEWER +#include "libslic3r/GCode/GCodeProcessor.hpp" +#endif // ENABLE_GCODE_VIEWER class wxNotebook; class wxGLCanvas; @@ -90,6 +93,9 @@ class Preview : public wxPanel DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; GCodePreviewData* m_gcode_preview_data; +#if ENABLE_GCODE_VIEWER + GCodeProcessor::Result* m_gcode_result; +#endif // ENABLE_GCODE_VIEWER #ifdef __linux__ // We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955. @@ -109,8 +115,13 @@ class Preview : public wxPanel DoubleSlider::Control* m_slider {nullptr}; public: - Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, - BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); +#if ENABLE_GCODE_VIEWER + Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, + BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, GCodeProcessor::Result* gcode_result, std::function schedule_background_process = []() {}); +#else + Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, + BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = []() {}); +#endif // ENABLE_GCODE_VIEWER virtual ~Preview(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3171818875..3886d5f359 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1440,6 +1440,9 @@ struct Plater::priv Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; +#if ENABLE_GCODE_VIEWER + Slic3r::GCodeProcessor::Result gcode_result; +#endif // ENABLE_GCODE_VIEWER // GUI elements wxSizer* panel_sizer{ nullptr }; @@ -1990,6 +1993,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); background_process.set_gcode_preview_data(&gcode_preview_data); +#if ENABLE_GCODE_VIEWER + background_process.set_gcode_result(&gcode_result); +#endif // ENABLE_GCODE_VIEWER #if ENABLE_THUMBNAIL_GENERATOR background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) { @@ -2015,7 +2021,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this); view3D = new View3D(q, bed, camera, view_toolbar, &model, config, &background_process); +#if ENABLE_GCODE_VIEWER + preview = new Preview(q, bed, camera, view_toolbar, &model, config, &background_process, &gcode_preview_data, &gcode_result, [this]() { schedule_background_process(); }); +#else preview = new Preview(q, bed, camera, view_toolbar, &model, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); +#endif // ENABLE_GCODE_VIEWER panels.push_back(view3D); panels.push_back(preview); diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index b3c16f4b01..50b3383254 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -244,8 +244,12 @@ std::string gcode(Print & print) boost::filesystem::path temp = boost::filesystem::unique_path(); print.set_status_silent(); print.process(); +#if ENABLE_GCODE_VIEWER + print.export_gcode(temp.string(), nullptr, nullptr); +#else print.export_gcode(temp.string(), nullptr); - std::ifstream t(temp.string()); +#endif // ENABLE_GCODE_VIEWER + std::ifstream t(temp.string()); std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); boost::nowide::remove(temp.string().c_str()); return str; diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index 3378a83638..3d3b54ef02 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -50,7 +50,11 @@ SCENARIO("Model construction", "[Model]") { print.apply(model, config); print.process(); boost::filesystem::path temp = boost::filesystem::unique_path(); +#if ENABLE_GCODE_VIEWER + print.export_gcode(temp.string(), nullptr, nullptr); +#else print.export_gcode(temp.string(), nullptr); +#endif // ENABLE_GCODE_VIEWER REQUIRE(boost::filesystem::exists(temp)); REQUIRE(boost::filesystem::is_regular_file(temp)); REQUIRE(boost::filesystem::file_size(temp) > 0); From 35e963a566e9199d0010891312bbb3667d45ebe9 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 30 Mar 2020 09:01:50 +0200 Subject: [PATCH 003/826] Small refactoring --- src/libslic3r/GCode.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6a3a0bee24..28fa2ca28f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -906,13 +906,13 @@ namespace DoExport { processor.apply_config(config); // send extruder offset data to processor - unsigned int num_extruders = static_cast(config.nozzle_diameter.values.size()); GCodeProcessor::ExtruderOffsetsMap extruder_offsets; - for (unsigned int id = 0; id < num_extruders; ++id) + const size_t num_extruders = config.nozzle_diameter.values.size(); + for (size_t id = 0; id < num_extruders; ++id) { - Vec2d offset = config.extruder_offset.get_at(id); + const Vec2d& offset = config.extruder_offset.get_at(id); if (!offset.isApprox(Vec2d::Zero())) - extruder_offsets[id] = offset; + extruder_offsets[static_cast(id)] = offset; } processor.set_extruder_offsets(extruder_offsets); } From 956f7a4593887237f68150eb9da855d50a030e5f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 2 Apr 2020 12:03:18 +0200 Subject: [PATCH 004/826] GCodeProcessor additions: process G90 lines process G91 lines process G92 lines process M82 lines process M83 lines process T lines process extrusion role/width/height comment tags debug output --- src/libslic3r/GCode.cpp | 43 +++-- src/libslic3r/GCode/Analyzer.cpp | 29 ++- src/libslic3r/GCode/Analyzer.hpp | 12 ++ src/libslic3r/GCode/GCodeProcessor.cpp | 251 +++++++++++++++++++------ src/libslic3r/GCode/GCodeProcessor.hpp | 66 +++++-- src/libslic3r/GCode/WipeTower.cpp | 15 ++ src/libslic3r/Technologies.hpp | 7 +- src/slic3r/GUI/GLCanvas3D.cpp | 10 + 8 files changed, 349 insertions(+), 84 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 28fa2ca28f..3ea365b3cc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -780,6 +780,10 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ // starts analyzer calculations if (m_enable_analyzer) { +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + m_analyzer.close_debug_output_file(); +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data" << log_memory_info(); m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); m_analyzer.reset(); @@ -897,24 +901,17 @@ namespace DoExport { // tell analyzer about the gcode flavor analyzer.set_gcode_flavor(config.gcode_flavor); - } + +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + analyzer.open_debug_output_file(); +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + } #if ENABLE_GCODE_VIEWER static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor) { processor.reset(); processor.apply_config(config); - - // send extruder offset data to processor - GCodeProcessor::ExtruderOffsetsMap extruder_offsets; - const size_t num_extruders = config.nozzle_diameter.values.size(); - for (size_t id = 0; id < num_extruders; ++id) - { - const Vec2d& offset = config.extruder_offset.get_at(id); - if (!offset.isApprox(Vec2d::Zero())) - extruder_offsets[static_cast(id)] = offset; - } - processor.set_extruder_offsets(extruder_offsets); } #endif // ENABLE_GCODE_VIEWER @@ -1340,6 +1337,11 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // adds tag for analyzer _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); +#if ENABLE_GCODE_VIEWER + // adds tag for processor + _write_format(file, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erCustom); +#endif // ENABLE_GCODE_VIEWER + // Write the custom start G-code _writeln(file, start_gcode); @@ -1493,6 +1495,11 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // adds tag for analyzer _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); +#if ENABLE_GCODE_VIEWER + // adds tag for processor + _write_format(file, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erCustom); +#endif // ENABLE_GCODE_VIEWER + // Process filament-specific gcode in extruder order. { DynamicConfig config; @@ -3112,6 +3119,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, { m_last_analyzer_extrusion_role = path.role(); sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role)); +#if ENABLE_GCODE_VIEWER + gcode += buf; + sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role)); +#endif // ENABLE_GCODE_VIEWER gcode += buf; } @@ -3127,6 +3138,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, m_last_width = path.width; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); gcode += buf; +#if ENABLE_GCODE_VIEWER + sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); + gcode += buf; +#endif // ENABLE_GCODE_VIEWER } if (last_was_wipe_tower || (m_last_height != path.height)) @@ -3134,6 +3149,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, m_last_height = path.height; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height); gcode += buf; +#if ENABLE_GCODE_VIEWER + sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_last_height); + gcode += buf; +#endif // ENABLE_GCODE_VIEWER } } diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 442d5ec839..8a2ba7804b 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -174,6 +174,19 @@ bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role) return ((erPerimeter <= role) && (role < erMixed)); } +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT +void GCodeAnalyzer::open_debug_output_file() +{ + boost::filesystem::path path("d:/analyzer.output"); + m_debug_output.open(path.string()); +} + +void GCodeAnalyzer::close_debug_output_file() +{ + m_debug_output.close(); +} +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) { // processes 'special' comments contained in line @@ -350,7 +363,7 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) if (delta_pos[E] < 0.0f) { if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f)) - type = GCodeMove::Move; + type = GCodeMove::Move; else type = GCodeMove::Retract; } @@ -922,6 +935,20 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) Vec3d start_position = _get_start_position() + extruder_offset; Vec3d end_position = _get_end_position() + extruder_offset; it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_fan_speed(), _get_cp_color_id()); + +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + if (m_debug_output.good()) + { + m_debug_output << std::to_string((int)type); + m_debug_output << ", " << std::to_string((int)_get_extrusion_role()); + m_debug_output << ", " << Slic3r::to_string(_get_end_position()); + m_debug_output << ", " << std::to_string(extruder_id); + m_debug_output << ", " << std::to_string(_get_feedrate()); + m_debug_output << ", " << std::to_string(_get_width()); + m_debug_output << ", " << std::to_string(_get_height()); + m_debug_output << "\n"; + } +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT } bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index cd5654a745..fb5ef3b4c1 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -8,6 +8,10 @@ #include "../Point.hpp" #include "../GCodeReader.hpp" +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT +#include +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + namespace Slic3r { class GCodePreviewData; @@ -147,6 +151,14 @@ public: static bool is_valid_extrusion_role(ExtrusionRole role); +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT +private: + boost::nowide::ofstream m_debug_output; +public: + void open_debug_output_file(); + void close_debug_output_file(); +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + private: // Processes the given gcode line void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 493321a6e7..ffb4e17146 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -6,19 +6,47 @@ static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; +static bool is_valid_extrusion_role(int value) +{ + return ((int)Slic3r::erNone <= value) && (value <= (int)Slic3r::erMixed); +} + namespace Slic3r { +const std::string GCodeProcessor::Extrusion_Role_Tag = "_PROCESSOR_EXTRUSION_ROLE:"; +const std::string GCodeProcessor::Width_Tag = "_PROCESSOR_WIDTH:"; +const std::string GCodeProcessor::Height_Tag = "_PROCESSOR_HEIGHT:"; + +void GCodeProcessor::apply_config(const PrintConfig& config) +{ + m_parser.apply_config(config); + + size_t extruders_count = config.nozzle_diameter.values.size(); + if (m_extruder_offsets.size() != extruders_count) + m_extruder_offsets.resize(extruders_count); + + for (size_t id = 0; id < extruders_count; ++id) + { + Vec2f offset = config.extruder_offset.get_at(id).cast(); + m_extruder_offsets[id] = Vec3f(offset(0), offset(1), 0.0f); + } +} + void GCodeProcessor::reset() { m_units = EUnits::Millimeters; m_global_positioning_type = EPositioningType::Absolute; m_e_local_positioning_type = EPositioningType::Absolute; - + m_extruder_offsets = std::vector(1, Vec3f::Zero()); + std::fill(m_start_position.begin(), m_start_position.end(), 0.0f); std::fill(m_end_position.begin(), m_end_position.end(), 0.0f); std::fill(m_origin.begin(), m_origin.end(), 0.0f); m_feedrate = 0.0f; + m_width = 0.0f; + m_height = 0.0f; + m_extrusion_role = erNone; m_extruder_id = 0; m_result.reset(); @@ -26,10 +54,8 @@ void GCodeProcessor::reset() void GCodeProcessor::process_file(const std::string& filename) { - MoveVertex start_vertex {}; - m_result.moves.emplace_back(start_vertex); + m_result.moves.emplace_back(MoveVertex()); m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); - int a = 0; } void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) @@ -42,8 +68,6 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) m_start_position = m_end_position; std::string cmd = line.cmd(); - std::string comment = line.comment(); - if (cmd.length() > 1) { // process command lines @@ -54,7 +78,13 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) switch (::atoi(&cmd[1])) { // Move - case 1: { process_G1(line); } + case 1: { process_G1(line); break; } + // Set to Absolute Positioning + case 90: { processG90(line); break; } + // Set to Relative Positioning + case 91: { processG91(line); break; } + // Set Position + case 92: { processG92(line); break; } default: { break; } } break; @@ -63,6 +93,10 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { switch (::atoi(&cmd[1])) { + // Set extruder to absolute mode + case 82: { processM82(line); break; } + // Set extruder to relative mode + case 83: { processM83(line); break; } default: { break; } } break; @@ -74,20 +108,52 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) default: { break; } } } - else if (comment.length() > 1) + else { - // process tags embedded into comments - process_comment(line); + std::string comment = line.comment(); + if (comment.length() > 1) + // process tags embedded into comments + process_tags(comment); } } -void GCodeProcessor::process_comment(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_tags(const std::string& comment) { + // extrusion role tag + size_t pos = comment.find(Extrusion_Role_Tag); + if (pos != comment.npos) + { + int role = std::stoi(comment.substr(pos + Extrusion_Role_Tag.length())); + if (is_valid_extrusion_role(role)) + m_extrusion_role = static_cast(role); + else + { + // todo: show some error ? + } + + return; + } + + // width tag + pos = comment.find(Width_Tag); + if (pos != comment.npos) + { + m_width = std::stof(comment.substr(pos + Width_Tag.length())); + return; + } + + // height tag + pos = comment.find(Height_Tag); + if (pos != comment.npos) + { + m_height = std::stof(comment.substr(pos + Height_Tag.length())); + return; + } } void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) { - auto axis_absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1) + auto absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1) { bool is_relative = (m_global_positioning_type == EPositioningType::Relative); if (axis == E) @@ -103,10 +169,36 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) return m_start_position[axis]; }; + auto move_type = [this](const AxisCoords& delta_pos) { + EMoveType type = EMoveType::Noop; + + if (delta_pos[E] < 0.0f) + { + if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) + type = EMoveType::Travel; + else + type = EMoveType::Retract; + } + else if (delta_pos[E] > 0.0f) + { + if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f) + type = EMoveType::Unretract; + else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f)) + type = EMoveType::Extrude; + } + else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) + type = EMoveType::Travel; + + if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f || !is_valid_extrusion_role(m_extrusion_role))) + type = EMoveType::Travel; + + return type; + }; + // updates axes positions from line for (unsigned char a = X; a <= E; ++a) { - m_end_position[a] = axis_absolute_position((Axis)a, line); + m_end_position[a] = absolute_position((Axis)a, line); } // updates feedrate from line, if present @@ -124,56 +216,109 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // no displacement, return if (max_abs_delta == 0.0f) - return; // <<<<<<<<<<<<<<<<< is this correct for time estimate ? + return; - // detect move type - EMoveType move_type = EMoveType::Noop; - if (delta_pos[E] < 0.0f) + // store g1 move + store_move_vertex(move_type(delta_pos)); +} + +void GCodeProcessor::processG90(const GCodeReader::GCodeLine& line) +{ + m_global_positioning_type = EPositioningType::Absolute; +} + +void GCodeProcessor::processG91(const GCodeReader::GCodeLine& line) +{ + m_global_positioning_type = EPositioningType::Relative; +} + +void GCodeProcessor::processG92(const GCodeReader::GCodeLine& line) +{ + float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + bool anyFound = false; + + if (line.has_x()) { - if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f)) - move_type = EMoveType::Travel; - else - move_type = EMoveType::Retract; + m_origin[X] = m_end_position[X] - line.x() * lengthsScaleFactor; + anyFound = true; } - else if (delta_pos[E] > 0.0f) + + if (line.has_y()) { - if ((delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f)) - move_type = EMoveType::Unretract; - else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f)) - move_type = EMoveType::Extrude; + m_origin[Y] = m_end_position[Y] - line.y() * lengthsScaleFactor; + anyFound = true; } - else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f)) - move_type = EMoveType::Travel; - // correct position by extruder offset - Vec3d extruder_offset = Vec3d::Zero(); - auto it = m_extruder_offsets.find(m_extruder_id); - if (it != m_extruder_offsets.end()) - extruder_offset = Vec3d(it->second(0), it->second(1), 0.0); + if (line.has_z()) + { + m_origin[Z] = m_end_position[Z] - line.z() * lengthsScaleFactor; + anyFound = true; + } + if (line.has_e()) + { + // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, + // we set the value taken from the G92 line as the new current position for it + m_end_position[E] = line.e() * lengthsScaleFactor; + anyFound = true; + } + + if (!anyFound && !line.has_unknown_axis()) + { + // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, + // where G92 A0 B0 is called although the extruder axis is till E. + for (unsigned char a = X; a <= E; ++a) + { + m_origin[a] = m_end_position[a]; + } + } +} + +void GCodeProcessor::processM82(const GCodeReader::GCodeLine& line) +{ + m_e_local_positioning_type = EPositioningType::Absolute; +} + +void GCodeProcessor::processM83(const GCodeReader::GCodeLine& line) +{ + m_e_local_positioning_type = EPositioningType::Relative; +} + +void GCodeProcessor::processT(const GCodeReader::GCodeLine& line) +{ + const std::string& cmd = line.cmd(); + if (cmd.length() > 1) + { + unsigned int id = (unsigned int)std::stoi(cmd.substr(1)); + if (m_extruder_id != id) + { + unsigned int extruders_count = (unsigned int)m_extruder_offsets.size(); + if (id >= extruders_count) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; + else + { + m_extruder_id = id; +// if (_get_cp_color_id() != INT_MAX) <<<<<<<<<<<<<<<<<<< TODO +// _set_cp_color_id(m_extruder_color[id]); + } + + // store tool change move + store_move_vertex(EMoveType::Tool_change); + } + } +} + +void GCodeProcessor::store_move_vertex(EMoveType type) +{ MoveVertex vertex; - vertex.position = Vec3d(m_end_position[0], m_end_position[1], m_end_position[2]) + extruder_offset; + vertex.type = type; + vertex.extrusion_role = m_extrusion_role; + vertex.position = Vec3f(m_end_position[0], m_end_position[1], m_end_position[2]) + m_extruder_offsets[m_extruder_id]; vertex.feedrate = m_feedrate; - vertex.type = move_type; + vertex.width = m_width; + vertex.height = m_height; + vertex.extruder_id = m_extruder_id; m_result.moves.emplace_back(vertex); - -/* - std::cout << "start: "; - for (unsigned char a = X; a <= E; ++a) - { - std::cout << m_start_position[a]; - if (a != E) - std::cout << ", "; - } - std::cout << " - end: "; - for (unsigned char a = X; a <= E; ++a) - { - std::cout << m_end_position[a]; - if (a != E) - std::cout << ", "; - } - std::cout << "\n"; -*/ } } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 7fa1733b78..537617485f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -3,11 +3,19 @@ #if ENABLE_GCODE_VIEWER #include "../GCodeReader.hpp" +#include "../Point.hpp" +#include "../ExtrusionEntity.hpp" namespace Slic3r { class GCodeProcessor { + public: + static const std::string Extrusion_Role_Tag; + static const std::string Width_Tag; + static const std::string Height_Tag; + + private: using AxisCoords = std::array; enum class EUnits : unsigned char @@ -33,16 +41,29 @@ namespace Slic3r { Num_Types }; + public: struct MoveVertex { - Vec3d position{ Vec3d::Zero() }; // mm - float feedrate{ 0.0f }; // mm/s - // type of the move terminating at this vertex EMoveType type{ EMoveType::Noop }; - }; + ExtrusionRole extrusion_role{ erNone }; + Vec3f position{ Vec3f::Zero() }; // mm + float feedrate{ 0.0f }; // mm/s + float width{ 0.0f }; // mm + float height{ 0.0f }; // mm + unsigned int extruder_id{ 0 }; - public: - typedef std::map ExtruderOffsetsMap; + std::string to_string() const + { + std::string str = std::to_string((int)type); + str += ", " + std::to_string((int)extrusion_role); + str += ", " + Slic3r::to_string((Vec3d)position.cast()); + str += ", " + std::to_string(extruder_id); + str += ", " + std::to_string(feedrate); + str += ", " + std::to_string(width); + str += ", " + std::to_string(height); + return str; + } + }; struct Result { @@ -56,25 +77,26 @@ namespace Slic3r { EUnits m_units; EPositioningType m_global_positioning_type; EPositioningType m_e_local_positioning_type; + std::vector m_extruder_offsets; AxisCoords m_start_position; // mm AxisCoords m_end_position; // mm AxisCoords m_origin; // mm float m_feedrate; // mm/s + float m_width; // mm + float m_height; // mm + ExtrusionRole m_extrusion_role; unsigned int m_extruder_id; - ExtruderOffsetsMap m_extruder_offsets; Result m_result; public: GCodeProcessor() { reset(); } - void apply_config(const PrintConfig& config) { m_parser.apply_config(config); } + void apply_config(const PrintConfig& config); void reset(); - void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets) { m_extruder_offsets = extruder_offsets; } - const Result& get_result() const { return m_result; } Result&& extract_result() { return std::move(m_result); } @@ -86,11 +108,31 @@ namespace Slic3r { void process_gcode_line(const GCodeReader::GCodeLine& line); // Process tags embedded into comments - void process_comment(const GCodeReader::GCodeLine& line); + void process_tags(const std::string& comment); // Move void process_G1(const GCodeReader::GCodeLine& line); - }; + + // Set to Absolute Positioning + void processG90(const GCodeReader::GCodeLine& line); + + // Set to Relative Positioning + void processG91(const GCodeReader::GCodeLine& line); + + // Set Position + void processG92(const GCodeReader::GCodeLine& line); + + // Set extruder to absolute mode + void processM82(const GCodeReader::GCodeLine& line); + + // Set extruder to relative mode + void processM83(const GCodeReader::GCodeLine& line); + + // Processes T line (Select Tool) + void processT(const GCodeReader::GCodeLine& line); + + void store_move_vertex(EMoveType type); + }; } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index d31adbd8fc..339012d0b4 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -22,6 +22,9 @@ TODO LIST #include #include "Analyzer.hpp" +#if ENABLE_GCODE_VIEWER +#include "GCodeProcessor.hpp" +#endif // ENABLE_GCODE_VIEWER #include "BoundingBox.hpp" #if defined(__linux) || defined(__GNUC__ ) @@ -55,7 +58,15 @@ public: char buf[64]; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming m_gcode += buf; +#if ENABLE_GCODE_VIEWER + sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming + m_gcode += buf; +#endif // ENABLE_GCODE_VIEWER sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); +#if ENABLE_GCODE_VIEWER + m_gcode += buf; + sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erWipeTower); +#endif // ENABLE_GCODE_VIEWER m_gcode += buf; change_analyzer_line_width(line_width); } @@ -65,6 +76,10 @@ public: char buf[64]; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); m_gcode += buf; +#if ENABLE_GCODE_VIEWER + sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); + m_gcode += buf; +#endif // ENABLE_GCODE_VIEWER return *this; } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 44c10fa548..85ce8a2cb2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -60,14 +60,9 @@ // Moves GLCanvas3DManager from being a static member of _3DScene to be a normal member of GUI_App #define ENABLE_NON_STATIC_CANVAS_MANAGER (1 && ENABLE_2_3_0_ALPHA1) - -//================== -// 2.3.0.alpha1 techs -//================== -#define ENABLE_2_3_0_ALPHA1 1 - // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) +#define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (1 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d8b102cee5..de85929ea1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2772,6 +2772,16 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio #if ENABLE_GCODE_VIEWER void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result) { +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + boost::filesystem::path path("d:/processor.output"); + boost::nowide::ofstream out; + out.open(path.string()); + for (const GCodeProcessor::MoveVertex& v : gcode_result.moves) + { + out << v.to_string() << "\n"; + } + out.close(); +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT } #endif // ENABLE_GCODE_VIEWER From ece4f10bf75475057b71a94087cc2db531ca87ce Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 2 Apr 2020 12:30:54 +0200 Subject: [PATCH 005/826] Updated Print.xsp --- xs/xsp/Print.xsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 0952513ca3..160fd3e60c 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -164,7 +164,7 @@ _constant() void export_gcode(char *path_template) %code%{ try { - THIS->export_gcode(path_template, nullptr); + THIS->export_gcode(path_template, nullptr, nullptr); } catch (std::exception& e) { croak("%s\n", e.what()); } From d0ce17656f34219a764dc5c926394096f7946231 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 2 Apr 2020 13:19:42 +0200 Subject: [PATCH 006/826] Added missing includes --- src/libslic3r/GCode/Analyzer.cpp | 3 +++ src/libslic3r/GCode/GCodeProcessor.hpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index a2a809ca3f..885c5e9e87 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -8,6 +8,9 @@ #include "Print.hpp" #include +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT +#include +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT #include "Analyzer.hpp" #include "PreviewData.hpp" diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 537617485f..0fa8187fd3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -6,6 +6,8 @@ #include "../Point.hpp" #include "../ExtrusionEntity.hpp" +#include + namespace Slic3r { class GCodeProcessor From f05de150c58608ff6f59c06cab0e022b08b66795 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 2 Apr 2020 15:52:42 +0200 Subject: [PATCH 007/826] Added another missing include --- src/libslic3r/GCode/Analyzer.cpp | 29 ++++++++++++++++---------- src/libslic3r/GCode/Analyzer.hpp | 7 ------- src/libslic3r/GCode/GCodeProcessor.cpp | 2 ++ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 885c5e9e87..b283d70a9b 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -15,6 +15,13 @@ #include "Analyzer.hpp" #include "PreviewData.hpp" +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT +#include + +// don't worry, this is just temporary +static boost::nowide::ofstream g_debug_output; +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + static const std::string AXIS_STR = "XYZE"; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float INCHES_TO_MM = 25.4f; @@ -181,12 +188,12 @@ bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role) void GCodeAnalyzer::open_debug_output_file() { boost::filesystem::path path("d:/analyzer.output"); - m_debug_output.open(path.string()); + g_debug_output.open(path.string()); } void GCodeAnalyzer::close_debug_output_file() { - m_debug_output.close(); + g_debug_output.close(); } #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT @@ -940,16 +947,16 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_fan_speed(), _get_cp_color_id()); #if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - if (m_debug_output.good()) + if (g_debug_output.good()) { - m_debug_output << std::to_string((int)type); - m_debug_output << ", " << std::to_string((int)_get_extrusion_role()); - m_debug_output << ", " << Slic3r::to_string((Vec3d)_get_end_position().cast()); - m_debug_output << ", " << std::to_string(extruder_id); - m_debug_output << ", " << std::to_string(_get_feedrate()); - m_debug_output << ", " << std::to_string(_get_width()); - m_debug_output << ", " << std::to_string(_get_height()); - m_debug_output << "\n"; + g_debug_output << std::to_string((int)type); + g_debug_output << ", " << std::to_string((int)_get_extrusion_role()); + g_debug_output << ", " << Slic3r::to_string((Vec3d)_get_end_position().cast()); + g_debug_output << ", " << std::to_string(extruder_id); + g_debug_output << ", " << std::to_string(_get_feedrate()); + g_debug_output << ", " << std::to_string(_get_width()); + g_debug_output << ", " << std::to_string(_get_height()); + g_debug_output << "\n"; } #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT } diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 5e5a017357..9d16ab4944 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -8,10 +8,6 @@ #include "../Point.hpp" #include "../GCodeReader.hpp" -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT -#include -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - namespace Slic3r { class GCodePreviewData; @@ -152,9 +148,6 @@ public: static bool is_valid_extrusion_role(ExtrusionRole role); #if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT -private: - boost::nowide::ofstream m_debug_output; -public: void open_debug_output_file(); void close_debug_output_file(); #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index ffb4e17146..01ac11c172 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,6 +1,8 @@ #include "../libslic3r.h" #include "GCodeProcessor.hpp" +#include + #if ENABLE_GCODE_VIEWER static const float INCHES_TO_MM = 25.4f; From 824e4360584550f74b9a953879afa042f67b34fe Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 2 Apr 2020 16:07:54 +0200 Subject: [PATCH 008/826] Hopefully last missing include --- src/slic3r/GUI/GLCanvas3D.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2236e8fab8..85ea44ef14 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -59,6 +59,10 @@ #include #include +#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT +#include +#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + #include #include #include From dce1f24ad81c481ba0c4e8c2bf3a39820e08aaf3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 3 Apr 2020 10:15:46 +0200 Subject: [PATCH 009/826] GCodeProcessor additions: process G10 lines process G11 lines process G22 lines process G23 lines process M106 lines process M107 lines process mm3_per_mm comment tag --- src/libslic3r/GCode.cpp | 4 + src/libslic3r/GCode/Analyzer.cpp | 2 + src/libslic3r/GCode/GCodeProcessor.cpp | 162 +++++++++++++++++++------ src/libslic3r/GCode/GCodeProcessor.hpp | 44 +++++-- src/libslic3r/GCode/WipeTower.cpp | 4 + 5 files changed, 171 insertions(+), 45 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3ea365b3cc..720b9a1fa7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3131,6 +3131,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, m_last_mm3_per_mm = path.mm3_per_mm; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); gcode += buf; +#if ENABLE_GCODE_VIEWER + sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); + gcode += buf; +#endif // ENABLE_GCODE_VIEWER } if (last_was_wipe_tower || (m_last_width != path.width)) diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index b283d70a9b..c7b67647f1 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -956,6 +956,8 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) g_debug_output << ", " << std::to_string(_get_feedrate()); g_debug_output << ", " << std::to_string(_get_width()); g_debug_output << ", " << std::to_string(_get_height()); + g_debug_output << ", " << std::to_string(_get_mm3_per_mm()); + g_debug_output << ", " << std::to_string(_get_fan_speed()); g_debug_output << "\n"; } #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 01ac11c172..0f42c6796e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -18,6 +18,7 @@ namespace Slic3r { const std::string GCodeProcessor::Extrusion_Role_Tag = "_PROCESSOR_EXTRUSION_ROLE:"; const std::string GCodeProcessor::Width_Tag = "_PROCESSOR_WIDTH:"; const std::string GCodeProcessor::Height_Tag = "_PROCESSOR_HEIGHT:"; +const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "_PROCESSOR_MM3_PER_MM:"; void GCodeProcessor::apply_config(const PrintConfig& config) { @@ -48,6 +49,9 @@ void GCodeProcessor::reset() m_feedrate = 0.0f; m_width = 0.0f; m_height = 0.0f; + m_mm3_per_mm = 0.0f; + m_fan_speed = 0.0f; + m_extrusion_role = erNone; m_extruder_id = 0; @@ -79,14 +83,14 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { switch (::atoi(&cmd[1])) { - // Move - case 1: { process_G1(line); break; } - // Set to Absolute Positioning - case 90: { processG90(line); break; } - // Set to Relative Positioning - case 91: { processG91(line); break; } - // Set Position - case 92: { processG92(line); break; } + case 1: { process_G1(line); break; } // Move + case 10: { process_G10(line); break; } // Retract + case 11: { process_G11(line); break; } // Unretract + case 22: { process_G22(line); break; } // Firmware controlled retract + case 23: { process_G23(line); break; } // Firmware controlled unretract + case 90: { process_G90(line); break; } // Set to Absolute Positioning + case 91: { process_G91(line); break; } // Set to Relative Positioning + case 92: { process_G92(line); break; } // Set Position default: { break; } } break; @@ -95,16 +99,17 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { switch (::atoi(&cmd[1])) { - // Set extruder to absolute mode - case 82: { processM82(line); break; } - // Set extruder to relative mode - case 83: { processM83(line); break; } + case 82: { process_M82(line); break; } // Set extruder to absolute mode + case 83: { process_M83(line); break; } // Set extruder to relative mode + case 106: { process_M106(line); break; } // Set fan speed + case 107: { process_M107(line); break; } // Disable fan default: { break; } } break; } case 'T': { + process_T(line); // Select Tool break; } default: { break; } @@ -125,12 +130,19 @@ void GCodeProcessor::process_tags(const std::string& comment) size_t pos = comment.find(Extrusion_Role_Tag); if (pos != comment.npos) { - int role = std::stoi(comment.substr(pos + Extrusion_Role_Tag.length())); - if (is_valid_extrusion_role(role)) - m_extrusion_role = static_cast(role); - else + try { - // todo: show some error ? + int role = std::stoi(comment.substr(pos + Extrusion_Role_Tag.length())); + if (is_valid_extrusion_role(role)) + m_extrusion_role = static_cast(role); + else + { + // todo: show some error ? + } + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Extrusion Role (" << comment << ")."; } return; @@ -140,7 +152,14 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Width_Tag); if (pos != comment.npos) { - m_width = std::stof(comment.substr(pos + Width_Tag.length())); + try + { + m_width = std::stof(comment.substr(pos + Width_Tag.length())); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + } return; } @@ -148,7 +167,29 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Height_Tag); if (pos != comment.npos) { - m_height = std::stof(comment.substr(pos + Height_Tag.length())); + try + { + m_height = std::stof(comment.substr(pos + Height_Tag.length())); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + } + return; + } + + // mm3 per mm tag + pos = comment.find(Mm3_Per_Mm_Tag); + if (pos != comment.npos) + { + try + { + m_mm3_per_mm = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length())); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; + } return; } } @@ -224,17 +265,41 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) store_move_vertex(move_type(delta_pos)); } -void GCodeProcessor::processG90(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) +{ + // stores retract move + store_move_vertex(EMoveType::Retract); +} + +void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line) +{ + // stores unretract move + store_move_vertex(EMoveType::Unretract); +} + +void GCodeProcessor::process_G22(const GCodeReader::GCodeLine& line) +{ + // stores retract move + store_move_vertex(EMoveType::Retract); +} + +void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line) +{ + // stores unretract move + store_move_vertex(EMoveType::Unretract); +} + +void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line) { m_global_positioning_type = EPositioningType::Absolute; } -void GCodeProcessor::processG91(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_G91(const GCodeReader::GCodeLine& line) { m_global_positioning_type = EPositioningType::Relative; } -void GCodeProcessor::processG92(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) { float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; bool anyFound = false; @@ -276,36 +341,61 @@ void GCodeProcessor::processG92(const GCodeReader::GCodeLine& line) } } -void GCodeProcessor::processM82(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_M82(const GCodeReader::GCodeLine& line) { m_e_local_positioning_type = EPositioningType::Absolute; } -void GCodeProcessor::processM83(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_M83(const GCodeReader::GCodeLine& line) { m_e_local_positioning_type = EPositioningType::Relative; } -void GCodeProcessor::processT(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_M106(const GCodeReader::GCodeLine& line) +{ + if (!line.has('P')) + { + // The absence of P means the print cooling fan, so ignore anything else. + float new_fan_speed; + if (line.has_value('S', new_fan_speed)) + m_fan_speed = (100.0f / 255.0f) * new_fan_speed; + else + m_fan_speed = 100.0f; + } +} + +void GCodeProcessor::process_M107(const GCodeReader::GCodeLine& line) +{ + m_fan_speed = 0.0f; +} + +void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) { const std::string& cmd = line.cmd(); if (cmd.length() > 1) { - unsigned int id = (unsigned int)std::stoi(cmd.substr(1)); - if (m_extruder_id != id) + try { - unsigned int extruders_count = (unsigned int)m_extruder_offsets.size(); - if (id >= extruders_count) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; - else + unsigned int id = (unsigned int)std::stoi(cmd.substr(1)); + if (m_extruder_id != id) { - m_extruder_id = id; + unsigned int extruders_count = (unsigned int)m_extruder_offsets.size(); + if (id >= extruders_count) + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; + else + { + m_extruder_id = id; // if (_get_cp_color_id() != INT_MAX) <<<<<<<<<<<<<<<<<<< TODO // _set_cp_color_id(m_extruder_color[id]); - } + } - // store tool change move - store_move_vertex(EMoveType::Tool_change); + // store tool change move + store_move_vertex(EMoveType::Tool_change); + } + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << cmd << ")."; } } } @@ -319,6 +409,8 @@ void GCodeProcessor::store_move_vertex(EMoveType type) vertex.feedrate = m_feedrate; vertex.width = m_width; vertex.height = m_height; + vertex.mm3_per_mm = m_mm3_per_mm; + vertex.fan_speed = m_fan_speed; vertex.extruder_id = m_extruder_id; m_result.moves.emplace_back(vertex); } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 0fa8187fd3..66036728dd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -16,6 +16,7 @@ namespace Slic3r { static const std::string Extrusion_Role_Tag; static const std::string Width_Tag; static const std::string Height_Tag; + static const std::string Mm3_Per_Mm_Tag; private: using AxisCoords = std::array; @@ -52,6 +53,8 @@ namespace Slic3r { float feedrate{ 0.0f }; // mm/s float width{ 0.0f }; // mm float height{ 0.0f }; // mm + float mm3_per_mm{ 0.0f }; + float fan_speed{ 0.0f }; // percentage unsigned int extruder_id{ 0 }; std::string to_string() const @@ -63,6 +66,8 @@ namespace Slic3r { str += ", " + std::to_string(feedrate); str += ", " + std::to_string(width); str += ", " + std::to_string(height); + str += ", " + std::to_string(mm3_per_mm); + str += ", " + std::to_string(fan_speed); return str; } }; @@ -85,9 +90,11 @@ namespace Slic3r { AxisCoords m_end_position; // mm AxisCoords m_origin; // mm - float m_feedrate; // mm/s - float m_width; // mm - float m_height; // mm + float m_feedrate; // mm/s + float m_width; // mm + float m_height; // mm + float m_mm3_per_mm; + float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; unsigned int m_extruder_id; @@ -103,7 +110,6 @@ namespace Slic3r { Result&& extract_result() { return std::move(m_result); } // Process the gcode contained in the file with the given filename - // Return false if any error occourred void process_file(const std::string& filename); private: @@ -115,23 +121,41 @@ namespace Slic3r { // Move void process_G1(const GCodeReader::GCodeLine& line); + // Retract + void process_G10(const GCodeReader::GCodeLine& line); + + // Unretract + void process_G11(const GCodeReader::GCodeLine& line); + + // Firmware controlled Retract + void process_G22(const GCodeReader::GCodeLine& line); + + // Firmware controlled Unretract + void process_G23(const GCodeReader::GCodeLine& line); + // Set to Absolute Positioning - void processG90(const GCodeReader::GCodeLine& line); + void process_G90(const GCodeReader::GCodeLine& line); // Set to Relative Positioning - void processG91(const GCodeReader::GCodeLine& line); + void process_G91(const GCodeReader::GCodeLine& line); // Set Position - void processG92(const GCodeReader::GCodeLine& line); + void process_G92(const GCodeReader::GCodeLine& line); // Set extruder to absolute mode - void processM82(const GCodeReader::GCodeLine& line); + void process_M82(const GCodeReader::GCodeLine& line); // Set extruder to relative mode - void processM83(const GCodeReader::GCodeLine& line); + void process_M83(const GCodeReader::GCodeLine& line); + + // Set fan speed + void process_M106(const GCodeReader::GCodeLine& line); + + // Disable fan + void process_M107(const GCodeReader::GCodeLine& line); // Processes T line (Select Tool) - void processT(const GCodeReader::GCodeLine& line); + void process_T(const GCodeReader::GCodeLine& line); void store_move_vertex(EMoveType type); }; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 339012d0b4..d5d060f773 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -90,6 +90,10 @@ public: char buf[64]; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); m_gcode += buf; +#if ENABLE_GCODE_VIEWER + sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); + m_gcode += buf; +#endif // ENABLE_GCODE_VIEWER return *this; } From 1caac17b0237c36d36b9e5043a36a75422001b6c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 6 Apr 2020 08:55:48 +0200 Subject: [PATCH 010/826] GCodeProcessor additions: process M108 lines process M132 lines process M135 lines process M401 lines process M402 lines --- src/libslic3r/GCode/Analyzer.cpp | 2 +- src/libslic3r/GCode/GCodeProcessor.cpp | 117 ++++++++++++++++++++++++- src/libslic3r/GCode/GCodeProcessor.hpp | 24 +++++ 3 files changed, 138 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index c7b67647f1..5db2ff3dea 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -951,7 +951,7 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) { g_debug_output << std::to_string((int)type); g_debug_output << ", " << std::to_string((int)_get_extrusion_role()); - g_debug_output << ", " << Slic3r::to_string((Vec3d)_get_end_position().cast()); + g_debug_output << ", " << Slic3r::to_string((Vec3d)end_position.cast()); g_debug_output << ", " << std::to_string(extruder_id); g_debug_output << ", " << std::to_string(_get_feedrate()); g_debug_output << ", " << std::to_string(_get_width()); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 0f42c6796e..bbb5f6046e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -24,6 +24,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config) { m_parser.apply_config(config); + m_flavor = config.gcode_flavor; + size_t extruders_count = config.nozzle_diameter.values.size(); if (m_extruder_offsets.size() != extruders_count) m_extruder_offsets.resize(extruders_count); @@ -41,10 +43,13 @@ void GCodeProcessor::reset() m_global_positioning_type = EPositioningType::Absolute; m_e_local_positioning_type = EPositioningType::Absolute; m_extruder_offsets = std::vector(1, Vec3f::Zero()); + m_flavor = gcfRepRap; std::fill(m_start_position.begin(), m_start_position.end(), 0.0f); std::fill(m_end_position.begin(), m_end_position.end(), 0.0f); std::fill(m_origin.begin(), m_origin.end(), 0.0f); + std::fill(m_cached_position.position.begin(), m_cached_position.position.end(), FLT_MAX); + m_cached_position.feedrate = FLT_MAX; m_feedrate = 0.0f; m_width = 0.0f; @@ -103,6 +108,11 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) case 83: { process_M83(line); break; } // Set extruder to relative mode case 106: { process_M106(line); break; } // Set fan speed case 107: { process_M107(line); break; } // Disable fan + case 108: { process_M108(line); break; } // Set tool (Sailfish) + case 132: { process_M132(line); break; } // Recall stored home offsets + case 135: { process_M135(line); break; } // Set tool (MakerWare) + case 401: { process_M401(line); break; } // Repetier: Store x, y and z position + case 402: { process_M402(line); break; } // Repetier: Go to stored position default: { break; } } break; @@ -369,14 +379,113 @@ void GCodeProcessor::process_M107(const GCodeReader::GCodeLine& line) m_fan_speed = 0.0f; } +void GCodeProcessor::process_M108(const GCodeReader::GCodeLine& line) +{ + // These M-codes are used by Sailfish to change active tool. + // They have to be processed otherwise toolchanges will be unrecognised + // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 + + if (m_flavor != gcfSailfish) + return; + + std::string cmd = line.raw(); + size_t pos = cmd.find("T"); + if (pos != std::string::npos) + process_T(cmd.substr(pos)); +} + +void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) +{ + // This command is used by Makerbot to load the current home position from EEPROM + // see: https://github.com/makerbot/s3g/blob/master/doc/GCodeProtocol.md + // Using this command to reset the axis origin to zero helps in fixing: https://github.com/prusa3d/PrusaSlicer/issues/3082 + + if (line.has_x()) + m_origin[X] = 0.0f; + + if (line.has_y()) + m_origin[Y] = 0.0f; + + if (line.has_z()) + m_origin[Z] = 0.0f; + + if (line.has_e()) + m_origin[E] = 0.0f; +} + +void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line) +{ + // These M-codes are used by MakerWare to change active tool. + // They have to be processed otherwise toolchanges will be unrecognised + // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 + + if (m_flavor != gcfMakerWare) + return; + + std::string cmd = line.raw(); + size_t pos = cmd.find("T"); + if (pos != std::string::npos) + process_T(cmd.substr(pos)); +} + +void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line) +{ + if (m_flavor != gcfRepetier) + return; + + for (unsigned char a = 0; a <= 3; ++a) + { + m_cached_position.position[a] = m_start_position[a]; + } + m_cached_position.feedrate = m_feedrate; +} + +void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) +{ + if (m_flavor != gcfRepetier) + return; + + // see for reference: + // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp + // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed) + + bool has_xyz = !(line.has_x() || line.has_y() || line.has_z()); + + float p = FLT_MAX; + for (unsigned char a = X; a <= Z; ++a) + { + if (has_xyz || line.has(a)) + { + p = m_cached_position.position[a]; + if (p != FLT_MAX) + m_start_position[a] = p; + } + } + + p = m_cached_position.position[E]; + if (p != FLT_MAX) + m_start_position[E] = p; + + p = FLT_MAX; + if (!line.has_value(4, p)) + p = m_cached_position.feedrate; + + if (p != FLT_MAX) + m_feedrate = p; +} + void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) { - const std::string& cmd = line.cmd(); - if (cmd.length() > 1) + process_T(line.cmd()); +} + +void GCodeProcessor::process_T(const std::string& command) +{ + if (command.length() > 1) { try { - unsigned int id = (unsigned int)std::stoi(cmd.substr(1)); + unsigned int id = (unsigned int)std::stoi(command.substr(1)); if (m_extruder_id != id) { unsigned int extruders_count = (unsigned int)m_extruder_offsets.size(); @@ -395,7 +504,7 @@ void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) } catch (...) { - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << cmd << ")."; + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ")."; } } } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 66036728dd..a3cfbdc787 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -44,6 +44,12 @@ namespace Slic3r { Num_Types }; + struct CachedPosition + { + AxisCoords position; // mm + float feedrate; // mm/s + }; + public: struct MoveVertex { @@ -85,10 +91,12 @@ namespace Slic3r { EPositioningType m_global_positioning_type; EPositioningType m_e_local_positioning_type; std::vector m_extruder_offsets; + GCodeFlavor m_flavor; AxisCoords m_start_position; // mm AxisCoords m_end_position; // mm AxisCoords m_origin; // mm + CachedPosition m_cached_position; float m_feedrate; // mm/s float m_width; // mm @@ -154,8 +162,24 @@ namespace Slic3r { // Disable fan void process_M107(const GCodeReader::GCodeLine& line); + // Set tool (Sailfish) + void process_M108(const GCodeReader::GCodeLine& line); + + // Recall stored home offsets + void process_M132(const GCodeReader::GCodeLine& line); + + // Set tool (MakerWare) + void process_M135(const GCodeReader::GCodeLine& line); + + // Repetier: Store x, y and z position + void process_M401(const GCodeReader::GCodeLine& line); + + // Repetier: Go to stored position + void process_M402(const GCodeReader::GCodeLine& line); + // Processes T line (Select Tool) void process_T(const GCodeReader::GCodeLine& line); + void process_T(const std::string& command); void store_move_vertex(EMoveType type); }; From 57dad5dfd2b3d01320ed36d14bdf6a2d93bc5c75 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 6 Apr 2020 11:53:15 +0200 Subject: [PATCH 011/826] GCodeProcessor additions: process color change comment tag process pause print comment tag process custom code comment tag process end pause print or custom code comment tag --- src/libslic3r/GCode.cpp | 26 +++++++- src/libslic3r/GCode/Analyzer.cpp | 13 ++-- src/libslic3r/GCode/GCodeProcessor.cpp | 88 +++++++++++++++++++++++--- src/libslic3r/GCode/GCodeProcessor.hpp | 20 ++++++ 4 files changed, 130 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 720b9a1fa7..ee7be7fea7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1823,7 +1823,11 @@ namespace ProcessLayer // Color Change or Tool Change as Color Change. // add tag for analyzer gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n"; - // add tag for time estimator +#if ENABLE_GCODE_VIEWER + // add tag for processor + gcode += "; " + GCodeProcessor::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n"; +#endif // ENABLE_GCODE_VIEWER + // add tag for time estimator gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer @@ -1844,7 +1848,11 @@ namespace ProcessLayer { // add tag for analyzer gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n"; - //! FIXME_in_fw show message during print pause +#if ENABLE_GCODE_VIEWER + // add tag for processor + gcode += "; " + GCodeProcessor::Pause_Print_Tag + "\n"; +#endif // ENABLE_GCODE_VIEWER + //! FIXME_in_fw show message during print pause if (!pause_print_msg.empty()) gcode += "M117 " + pause_print_msg + "\n"; // add tag for time estimator @@ -1854,7 +1862,11 @@ namespace ProcessLayer { // add tag for analyzer gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n"; - // add tag for time estimator +#if ENABLE_GCODE_VIEWER + // add tag for processor + gcode += "; " + GCodeProcessor::Custom_Code_Tag + "\n"; +#endif // ENABLE_GCODE_VIEWER + // add tag for time estimator //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n"; } gcode += custom_code + "\n"; @@ -2316,6 +2328,14 @@ void GCode::process_layer( else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos) gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n"; +#if ENABLE_GCODE_VIEWER + // add tag for processor + if (gcode.find(GCodeProcessor::Pause_Print_Tag) != gcode.npos) + gcode += "\n; " + GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag + "\n"; + else if (gcode.find(GCodeProcessor::Custom_Code_Tag) != gcode.npos) + gcode += "\n; " + GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag + "\n"; +#endif // ENABLE_GCODE_VIEWER + #ifdef HAS_PRESSURE_EQUALIZER // Apply pressure equalization if enabled; // printf("G-code before filter:\n%s\n", gcode.c_str()); diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 5db2ff3dea..974176dbd7 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -674,7 +674,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) return true; } - // color change tag + // pause print tag pos = comment.find(Pause_Print_Tag); if (pos != comment.npos) { @@ -682,7 +682,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) return true; } - // color change tag + // custom code tag pos = comment.find(Custom_Code_Tag); if (pos != comment.npos) { @@ -690,7 +690,7 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) return true; } - // color change tag + // end pause print or custom code tag pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag); if (pos != comment.npos) { @@ -949,10 +949,11 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) #if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT if (g_debug_output.good()) { - g_debug_output << std::to_string((int)type); - g_debug_output << ", " << std::to_string((int)_get_extrusion_role()); - g_debug_output << ", " << Slic3r::to_string((Vec3d)end_position.cast()); + g_debug_output << std::to_string(static_cast(type)); + g_debug_output << ", " << std::to_string(static_cast(_get_extrusion_role())); + g_debug_output << ", " << Slic3r::to_string(static_cast(end_position.cast())); g_debug_output << ", " << std::to_string(extruder_id); + g_debug_output << ", " << std::to_string(_get_cp_color_id()); g_debug_output << ", " << std::to_string(_get_feedrate()); g_debug_output << ", " << std::to_string(_get_width()); g_debug_output << ", " << std::to_string(_get_height()); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index bbb5f6046e..0a61879d80 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -19,6 +19,22 @@ const std::string GCodeProcessor::Extrusion_Role_Tag = "_PROCESSOR_EXTRUSION_ROL const std::string GCodeProcessor::Width_Tag = "_PROCESSOR_WIDTH:"; const std::string GCodeProcessor::Height_Tag = "_PROCESSOR_HEIGHT:"; const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "_PROCESSOR_MM3_PER_MM:"; +const std::string GCodeProcessor::Color_Change_Tag = "_PROCESSOR_COLOR_CHANGE"; +const std::string GCodeProcessor::Pause_Print_Tag = "_PROCESSOR_PAUSE_PRINT"; +const std::string GCodeProcessor::Custom_Code_Tag = "_PROCESSOR_CUSTOM_CODE"; +const std::string GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag = "_PROCESSOR_END_PAUSE_PRINT_OR_CUSTOM_CODE"; + +void GCodeProcessor::CachedPosition::reset() +{ + std::fill(position.begin(), position.end(), FLT_MAX); + feedrate = FLT_MAX; +} + +void GCodeProcessor::CpColor::reset() +{ + counter = 0; + current = 0; +} void GCodeProcessor::apply_config(const PrintConfig& config) { @@ -27,14 +43,19 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_flavor = config.gcode_flavor; size_t extruders_count = config.nozzle_diameter.values.size(); - if (m_extruder_offsets.size() != extruders_count) - m_extruder_offsets.resize(extruders_count); + m_extruder_offsets.resize(extruders_count); for (size_t id = 0; id < extruders_count; ++id) { Vec2f offset = config.extruder_offset.get_at(id).cast(); m_extruder_offsets[id] = Vec3f(offset(0), offset(1), 0.0f); } + + m_extruders_color.resize(extruders_count); + for (size_t id = 0; id < extruders_count; ++id) + { + m_extruders_color[id] = static_cast(id); + } } void GCodeProcessor::reset() @@ -48,8 +69,7 @@ void GCodeProcessor::reset() std::fill(m_start_position.begin(), m_start_position.end(), 0.0f); std::fill(m_end_position.begin(), m_end_position.end(), 0.0f); std::fill(m_origin.begin(), m_origin.end(), 0.0f); - std::fill(m_cached_position.position.begin(), m_cached_position.position.end(), FLT_MAX); - m_cached_position.feedrate = FLT_MAX; + m_cached_position.reset(); m_feedrate = 0.0f; m_width = 0.0f; @@ -59,6 +79,8 @@ void GCodeProcessor::reset() m_extrusion_role = erNone; m_extruder_id = 0; + m_extruders_color = ExtrudersColor(); + m_cp_color.reset(); m_result.reset(); } @@ -202,6 +224,55 @@ void GCodeProcessor::process_tags(const std::string& comment) } return; } + + // color change tag + pos = comment.find(Color_Change_Tag); + if (pos != comment.npos) + { + pos = comment.find_last_of(",T"); + try + { + unsigned int extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1, comment.npos))); + + m_extruders_color[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + ++m_cp_color.counter; + + if (m_extruder_id == extruder_id) + m_cp_color.current = m_extruders_color[extruder_id]; + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; + } + + return; + } + + // pause print tag + pos = comment.find(Pause_Print_Tag); + if (pos != comment.npos) + { + m_cp_color.current = INT_MAX; + return; + } + + // custom code tag + pos = comment.find(Custom_Code_Tag); + if (pos != comment.npos) + { + m_cp_color.current = INT_MAX; + return; + } + + // end pause print or custom code tag + pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag); + if (pos != comment.npos) + { + if (m_cp_color.current == INT_MAX) + m_cp_color.current = m_extruders_color[m_extruder_id]; + + return; + } } void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) @@ -485,17 +556,17 @@ void GCodeProcessor::process_T(const std::string& command) { try { - unsigned int id = (unsigned int)std::stoi(command.substr(1)); + unsigned int id = static_cast(std::stoi(command.substr(1))); if (m_extruder_id != id) { - unsigned int extruders_count = (unsigned int)m_extruder_offsets.size(); + unsigned int extruders_count = static_cast(m_extruder_offsets.size()); if (id >= extruders_count) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; else { m_extruder_id = id; -// if (_get_cp_color_id() != INT_MAX) <<<<<<<<<<<<<<<<<<< TODO -// _set_cp_color_id(m_extruder_color[id]); + if (m_cp_color.current != INT_MAX) + m_cp_color.current = m_extruders_color[id]; } // store tool change move @@ -521,6 +592,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) vertex.mm3_per_mm = m_mm3_per_mm; vertex.fan_speed = m_fan_speed; vertex.extruder_id = m_extruder_id; + vertex.cp_color_id = m_cp_color.current; m_result.moves.emplace_back(vertex); } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index a3cfbdc787..ce1f695dca 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -7,6 +7,7 @@ #include "../ExtrusionEntity.hpp" #include +#include namespace Slic3r { @@ -17,9 +18,14 @@ namespace Slic3r { static const std::string Width_Tag; static const std::string Height_Tag; static const std::string Mm3_Per_Mm_Tag; + static const std::string Color_Change_Tag; + static const std::string Pause_Print_Tag; + static const std::string Custom_Code_Tag; + static const std::string End_Pause_Print_Or_Custom_Code_Tag; private: using AxisCoords = std::array; + using ExtrudersColor = std::vector; enum class EUnits : unsigned char { @@ -48,6 +54,16 @@ namespace Slic3r { { AxisCoords position; // mm float feedrate; // mm/s + + void reset(); + }; + + struct CpColor + { + unsigned int counter; + unsigned int current; + + void reset(); }; public: @@ -62,6 +78,7 @@ namespace Slic3r { float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage unsigned int extruder_id{ 0 }; + unsigned int cp_color_id{ 0 }; std::string to_string() const { @@ -69,6 +86,7 @@ namespace Slic3r { str += ", " + std::to_string((int)extrusion_role); str += ", " + Slic3r::to_string((Vec3d)position.cast()); str += ", " + std::to_string(extruder_id); + str += ", " + std::to_string(cp_color_id); str += ", " + std::to_string(feedrate); str += ", " + std::to_string(width); str += ", " + std::to_string(height); @@ -105,6 +123,8 @@ namespace Slic3r { float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; unsigned int m_extruder_id; + ExtrudersColor m_extruders_color; + CpColor m_cp_color; Result m_result; From 2c69d962398c3ceb17ef620270f8c09d504c4202 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 6 Apr 2020 17:24:11 +0200 Subject: [PATCH 012/826] Reduced size of GCodeProcessor::MoveVertex --- src/libslic3r/GCode/GCodeProcessor.cpp | 16 ++++++++-------- src/libslic3r/GCode/GCodeProcessor.hpp | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 0a61879d80..7fb06bbda0 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -232,9 +232,9 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find_last_of(",T"); try { - unsigned int extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1, comment.npos))); + unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1, comment.npos))); - m_extruders_color[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + m_extruders_color[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview ++m_cp_color.counter; if (m_extruder_id == extruder_id) @@ -252,7 +252,7 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Pause_Print_Tag); if (pos != comment.npos) { - m_cp_color.current = INT_MAX; + m_cp_color.current = 255; return; } @@ -260,7 +260,7 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Custom_Code_Tag); if (pos != comment.npos) { - m_cp_color.current = INT_MAX; + m_cp_color.current = 255; return; } @@ -268,7 +268,7 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag); if (pos != comment.npos) { - if (m_cp_color.current == INT_MAX) + if (m_cp_color.current == 255) m_cp_color.current = m_extruders_color[m_extruder_id]; return; @@ -556,16 +556,16 @@ void GCodeProcessor::process_T(const std::string& command) { try { - unsigned int id = static_cast(std::stoi(command.substr(1))); + unsigned char id = static_cast(std::stoi(command.substr(1))); if (m_extruder_id != id) { - unsigned int extruders_count = static_cast(m_extruder_offsets.size()); + unsigned char extruders_count = static_cast(m_extruder_offsets.size()); if (id >= extruders_count) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; else { m_extruder_id = id; - if (m_cp_color.current != INT_MAX) + if (m_cp_color.current != 255) m_cp_color.current = m_extruders_color[id]; } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index ce1f695dca..54ac546b38 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -25,7 +25,7 @@ namespace Slic3r { private: using AxisCoords = std::array; - using ExtrudersColor = std::vector; + using ExtrudersColor = std::vector; enum class EUnits : unsigned char { @@ -60,8 +60,8 @@ namespace Slic3r { struct CpColor { - unsigned int counter; - unsigned int current; + unsigned char counter; + unsigned char current; void reset(); }; @@ -71,14 +71,14 @@ namespace Slic3r { { EMoveType type{ EMoveType::Noop }; ExtrusionRole extrusion_role{ erNone }; + unsigned char extruder_id{ 0 }; + unsigned char cp_color_id{ 0 }; Vec3f position{ Vec3f::Zero() }; // mm float feedrate{ 0.0f }; // mm/s float width{ 0.0f }; // mm float height{ 0.0f }; // mm float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage - unsigned int extruder_id{ 0 }; - unsigned int cp_color_id{ 0 }; std::string to_string() const { @@ -122,7 +122,7 @@ namespace Slic3r { float m_mm3_per_mm; float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; - unsigned int m_extruder_id; + unsigned char m_extruder_id; ExtrudersColor m_extruders_color; CpColor m_cp_color; From 22cf0396fcd8b51ef8f0cc0155305538379efb9f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 6 Apr 2020 17:32:35 +0200 Subject: [PATCH 013/826] Added missing include --- src/libslic3r/GCode/GCodeProcessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7fb06bbda0..f5094553a5 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -3,6 +3,8 @@ #include +#include + #if ENABLE_GCODE_VIEWER static const float INCHES_TO_MM = 25.4f; From c3eb65c4612c6011cda2bdb9a683d3e12af3295a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 14 Apr 2020 10:02:08 +0200 Subject: [PATCH 014/826] Added class GCodeViewer -> basic render of gcode toolpaths using dedicated shaders --- resources/shaders/extrusions.fs | 45 +++++ resources/shaders/extrusions.vs | 15 ++ resources/shaders/retractions.fs | 45 +++++ resources/shaders/retractions.vs | 15 ++ resources/shaders/toolchanges.fs | 45 +++++ resources/shaders/toolchanges.vs | 15 ++ resources/shaders/travels.fs | 45 +++++ resources/shaders/travels.vs | 15 ++ resources/shaders/unretractions.fs | 45 +++++ resources/shaders/unretractions.vs | 15 ++ src/libslic3r/GCode/GCodeProcessor.cpp | 2 + src/libslic3r/GCode/GCodeProcessor.hpp | 25 +-- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GCodeViewer.cpp | 242 +++++++++++++++++++++++++ src/slic3r/GUI/GCodeViewer.hpp | 63 +++++++ src/slic3r/GUI/GLCanvas3D.cpp | 20 ++ src/slic3r/GUI/GLCanvas3D.hpp | 8 + src/slic3r/GUI/GUI_Preview.cpp | 3 +- 18 files changed, 652 insertions(+), 13 deletions(-) create mode 100644 resources/shaders/extrusions.fs create mode 100644 resources/shaders/extrusions.vs create mode 100644 resources/shaders/retractions.fs create mode 100644 resources/shaders/retractions.vs create mode 100644 resources/shaders/toolchanges.fs create mode 100644 resources/shaders/toolchanges.vs create mode 100644 resources/shaders/travels.fs create mode 100644 resources/shaders/travels.vs create mode 100644 resources/shaders/unretractions.fs create mode 100644 resources/shaders/unretractions.vs create mode 100644 src/slic3r/GUI/GCodeViewer.cpp create mode 100644 src/slic3r/GUI/GCodeViewer.hpp diff --git a/resources/shaders/extrusions.fs b/resources/shaders/extrusions.fs new file mode 100644 index 0000000000..046dade8a9 --- /dev/null +++ b/resources/shaders/extrusions.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec4 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); +} diff --git a/resources/shaders/extrusions.vs b/resources/shaders/extrusions.vs new file mode 100644 index 0000000000..d97adbabe0 --- /dev/null +++ b/resources/shaders/extrusions.vs @@ -0,0 +1,15 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); +} diff --git a/resources/shaders/retractions.fs b/resources/shaders/retractions.fs new file mode 100644 index 0000000000..046dade8a9 --- /dev/null +++ b/resources/shaders/retractions.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec4 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); +} diff --git a/resources/shaders/retractions.vs b/resources/shaders/retractions.vs new file mode 100644 index 0000000000..d97adbabe0 --- /dev/null +++ b/resources/shaders/retractions.vs @@ -0,0 +1,15 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); +} diff --git a/resources/shaders/toolchanges.fs b/resources/shaders/toolchanges.fs new file mode 100644 index 0000000000..046dade8a9 --- /dev/null +++ b/resources/shaders/toolchanges.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec4 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); +} diff --git a/resources/shaders/toolchanges.vs b/resources/shaders/toolchanges.vs new file mode 100644 index 0000000000..d97adbabe0 --- /dev/null +++ b/resources/shaders/toolchanges.vs @@ -0,0 +1,15 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); +} diff --git a/resources/shaders/travels.fs b/resources/shaders/travels.fs new file mode 100644 index 0000000000..046dade8a9 --- /dev/null +++ b/resources/shaders/travels.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec4 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); +} diff --git a/resources/shaders/travels.vs b/resources/shaders/travels.vs new file mode 100644 index 0000000000..d97adbabe0 --- /dev/null +++ b/resources/shaders/travels.vs @@ -0,0 +1,15 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); +} diff --git a/resources/shaders/unretractions.fs b/resources/shaders/unretractions.fs new file mode 100644 index 0000000000..046dade8a9 --- /dev/null +++ b/resources/shaders/unretractions.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec4 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); +} diff --git a/resources/shaders/unretractions.vs b/resources/shaders/unretractions.vs new file mode 100644 index 0000000000..d97adbabe0 --- /dev/null +++ b/resources/shaders/unretractions.vs @@ -0,0 +1,15 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); +} diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index f5094553a5..c75c240020 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -38,6 +38,8 @@ void GCodeProcessor::CpColor::reset() current = 0; } +unsigned int GCodeProcessor::Result::id = 0; + void GCodeProcessor::apply_config(const PrintConfig& config) { m_parser.apply_config(config); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 54ac546b38..1f7af9c29d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -39,17 +39,6 @@ namespace Slic3r { Relative }; - enum class EMoveType : unsigned char - { - Noop, - Retract, - Unretract, - Tool_change, - Travel, - Extrude, - Num_Types - }; - struct CachedPosition { AxisCoords position; // mm @@ -67,6 +56,17 @@ namespace Slic3r { }; public: + enum class EMoveType : unsigned char + { + Noop, + Retract, + Unretract, + Tool_change, + Travel, + Extrude, + Count + }; + struct MoveVertex { EMoveType type{ EMoveType::Noop }; @@ -98,8 +98,9 @@ namespace Slic3r { struct Result { + static unsigned int id; std::vector moves; - void reset() { moves = std::vector(); } + void reset() { ++id; moves = std::vector(); } }; private: diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c8f7e9f1c9..f5f5f6eb62 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -56,6 +56,8 @@ set(SLIC3R_GUI_SOURCES GUI/GLTexture.cpp GUI/GLToolbar.hpp GUI/GLToolbar.cpp + GUI/GCodeViewer.hpp + GUI/GCodeViewer.cpp GUI/Preferences.cpp GUI/Preferences.hpp GUI/Preset.cpp diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp new file mode 100644 index 0000000000..584ef06c32 --- /dev/null +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -0,0 +1,242 @@ +#include "libslic3r/libslic3r.h" +#include "GCodeViewer.hpp" +#include "3DScene.hpp" + +#if ENABLE_GCODE_VIEWER + +#include +#include + +#include + +namespace Slic3r { +namespace GUI { + +static unsigned char buffer_id(GCodeProcessor::EMoveType type) { + return static_cast(type) - static_cast(GCodeProcessor::EMoveType::Retract); +} + +static GCodeProcessor::EMoveType buffer_type(unsigned char id) { + return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); +} + +void GCodeViewer::generate(const GCodeProcessor::Result& gcode_result) +{ + if (m_last_result_id == gcode_result.id) + return; + + m_last_result_id = gcode_result.id; + + // release gpu memory, if used + reset_buffers(); + + // convert data + size_t vertices_count = gcode_result.moves.size(); + for (size_t i = 0; i < vertices_count; ++i) + { + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + Buffer& buffer = m_buffers[buffer_id(curr.type)]; + + switch (curr.type) + { + case GCodeProcessor::EMoveType::Tool_change: + case GCodeProcessor::EMoveType::Retract: + case GCodeProcessor::EMoveType::Unretract: + { + for (int j = 0; j < 3; ++j) + { + buffer.data.insert(buffer.data.end(), curr.position[j]); + } + break; + } + case GCodeProcessor::EMoveType::Extrude: + case GCodeProcessor::EMoveType::Travel: + { + for (int j = 0; j < 3; ++j) + { + buffer.data.insert(buffer.data.end(), prev.position[j]); + } + for (int j = 0; j < 3; ++j) + { + buffer.data.insert(buffer.data.end(), curr.position[j]); + } + break; + } + default: + { + continue; + } + } + } + + // send data to gpu + for (Buffer& buffer : m_buffers) + { + glsafe(::glGenBuffers(1, &buffer.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer.data.size() * sizeof(float), buffer.data.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } +} + +void GCodeViewer::render() const +{ + auto set_color = [](GLint current_program_id, const std::array& color) { + if (current_program_id > 0) + { + GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; + if (color_id >= 0) + { + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); + return; + } + } + BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; + }; + + unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); + unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + + glsafe(::glEnable(GL_DEPTH_TEST)); + + for (unsigned char i = begin_id; i < end_id; ++i) + { + const Buffer& buffer = m_buffers[i]; + if (buffer.vbo_id == 0) + continue; + + const Shader& shader = m_shaders[i]; + if (shader.is_initialized()) + { + shader.start_using(); + + GLint current_program_id; + glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); + + GCodeProcessor::EMoveType type = buffer_type(i); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, Buffer::stride(type), (const void*)0)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + switch (type) + { + case GCodeProcessor::EMoveType::Tool_change: + case GCodeProcessor::EMoveType::Retract: + case GCodeProcessor::EMoveType::Unretract: + { + std::array color = { 0.0f, 1.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type))); + break; + } + case GCodeProcessor::EMoveType::Extrude: + { + std::array color = { 1.0f, 0.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type))); + break; + } + case GCodeProcessor::EMoveType::Travel: + { + std::array color = { 1.0f, 1.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type))); + break; + } + default: + { + break; + } + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + shader.stop_using(); + } + } +} + +bool GCodeViewer::init_shaders() +{ + unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); + unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + + for (unsigned char i = begin_id; i < end_id; ++i) + { + Shader& shader = m_shaders[i]; + std::string vertex_shader_src; + std::string fragment_shader_src; + GCodeProcessor::EMoveType type = buffer_type(i); + switch (type) + { + case GCodeProcessor::EMoveType::Tool_change: + { + vertex_shader_src = "toolchanges.vs"; + fragment_shader_src = "toolchanges.fs"; + break; + } + case GCodeProcessor::EMoveType::Retract: + { + vertex_shader_src = "retractions.vs"; + fragment_shader_src = "retractions.fs"; + break; + } + case GCodeProcessor::EMoveType::Unretract: + { + vertex_shader_src = "unretractions.vs"; + fragment_shader_src = "unretractions.fs"; + break; + } + case GCodeProcessor::EMoveType::Extrude: + { + vertex_shader_src = "extrusions.vs"; + fragment_shader_src = "extrusions.fs"; + break; + } + case GCodeProcessor::EMoveType::Travel: + { + vertex_shader_src = "travels.vs"; + fragment_shader_src = "travels.fs"; + break; + } + default: + { + break; + } + } + + if (!shader.init(vertex_shader_src, fragment_shader_src)) + { + BOOST_LOG_TRIVIAL(error) << "Unable to initialize toolpaths shader: please, check that the files " << vertex_shader_src << " and " << fragment_shader_src << " are available"; + return false; + } + } + + return true; +} + +void GCodeViewer::reset_buffers() +{ + for (Buffer& buffer : m_buffers) + { + // release gpu memory + if (buffer.vbo_id > 0) + glsafe(::glDeleteBuffers(1, &buffer.vbo_id)); + + // release cpu memory + buffer.data = std::vector(); + } +} + +} // namespace GUI +} // namespace Slic3r + +#endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp new file mode 100644 index 0000000000..95250b2034 --- /dev/null +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -0,0 +1,63 @@ +#ifndef slic3r_GCodeViewer_hpp_ +#define slic3r_GCodeViewer_hpp_ + +#if ENABLE_GCODE_VIEWER + +#include "GLShader.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" + +#include + +namespace Slic3r { +namespace GUI { + +class GCodeViewer +{ + struct Buffer + { + unsigned int vbo_id{ 0 }; + std::vector data; + + static size_t stride(GCodeProcessor::EMoveType type) + { + return 3 * sizeof(float); + } + + static size_t record_size(GCodeProcessor::EMoveType type) + { + switch (type) + { + case GCodeProcessor::EMoveType::Tool_change: + case GCodeProcessor::EMoveType::Retract: + case GCodeProcessor::EMoveType::Unretract: { return 3; } + case GCodeProcessor::EMoveType::Extrude: + case GCodeProcessor::EMoveType::Travel: { return 6; } + default: { return 0; } + } + } + }; + + std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + std::vector m_shaders{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + unsigned int m_last_result_id{ 0 }; + +public: + GCodeViewer() = default; + ~GCodeViewer() { reset_buffers(); } + + bool init() { return init_shaders(); } + void generate(const GCodeProcessor::Result& gcode_result); + void render() const; + +private: + bool init_shaders(); + void reset_buffers(); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // ENABLE_GCODE_VIEWER + +#endif // slic3r_GCodeViewer_hpp_ + diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 85ea44ef14..c9c4ab3364 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1678,6 +1678,14 @@ bool GLCanvas3D::init() return false; } +#if ENABLE_GCODE_VIEWER + if (!m_main_toolbar.is_enabled()) + { + if (!m_gcode_viewer.init()) + return false; + } +#endif // ENABLE_GCODE_VIEWER + // on linux the gl context is not valid until the canvas is not shown on screen // we defer the geometry finalization of volumes until the first call to render() m_volumes.finalize_geometry(true); @@ -2109,6 +2117,9 @@ void GLCanvas3D::render() _render_background(); _render_objects(); +#if ENABLE_GCODE_VIEWER + _render_gcode(); +#endif // ENABLE_GCODE_VIEWER _render_sla_slices(); _render_selection(); #if ENABLE_NON_STATIC_CANVAS_MANAGER @@ -2783,6 +2794,8 @@ void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result out << v.to_string() << "\n"; } out.close(); + + m_gcode_viewer.generate(gcode_result); #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT } #endif // ENABLE_GCODE_VIEWER @@ -5440,6 +5453,13 @@ void GLCanvas3D::_render_objects() const m_camera_clipping_plane = ClippingPlane::ClipsNothing(); } +#if ENABLE_GCODE_VIEWER +void GLCanvas3D::_render_gcode() const +{ + m_gcode_viewer.render(); +} +#endif // ENABLE_GCODE_VIEWER + void GLCanvas3D::_render_selection() const { float scale_factor = 1.0; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 0c82f058fe..8c6b3c3f0c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -21,6 +21,7 @@ #endif // !ENABLE_NON_STATIC_CANVAS_MANAGER #if ENABLE_GCODE_VIEWER #include "libslic3r/GCode/GCodeProcessor.hpp" +#include "GCodeViewer.hpp" #endif // ENABLE_GCODE_VIEWER #include @@ -468,6 +469,10 @@ private: bool m_extra_frame_requested; mutable GLVolumeCollection m_volumes; +#if ENABLE_GCODE_VIEWER + GCodeViewer m_gcode_viewer; +#endif // ENABLE_GCODE_VIEWER + Selection m_selection; const DynamicPrintConfig* m_config; Model* m_model; @@ -764,6 +769,9 @@ private: void _render_background() const; void _render_bed(float theta, bool show_axes) const; void _render_objects() const; +#if ENABLE_GCODE_VIEWER + void _render_gcode() const; +#endif // ENABLE_GCODE_VIEWER void _render_selection() const; #if ENABLE_RENDER_SELECTION_CENTER void _render_selection_center() const; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9795095626..0171dd597a 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -946,9 +946,10 @@ void Preview::load_print_as_fff(bool keep_z_range) m_canvas->set_selected_extruder(0); if (gcode_preview_data_valid) { // Load the real G-code preview. - m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); #if ENABLE_GCODE_VIEWER m_canvas->load_gcode_preview_2(*m_gcode_result); +#else + m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); #endif // ENABLE_GCODE_VIEWER m_loaded = true; } else { From bc05ab985c278af18590730f7c253caa332c618b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 14 Apr 2020 16:40:08 +0200 Subject: [PATCH 015/826] GCodeViewer -> Toggle visibility of travel paths, retractions and uretractions --- resources/shaders/retractions.vs | 2 + resources/shaders/toolchanges.vs | 2 + resources/shaders/unretractions.vs | 2 + src/slic3r/GUI/GCodeViewer.cpp | 121 ++++++++++++++++++++++------- src/slic3r/GUI/GCodeViewer.hpp | 36 ++++----- src/slic3r/GUI/GLCanvas3D.cpp | 34 ++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 9 +++ src/slic3r/GUI/GUI_Preview.cpp | 20 +++++ 8 files changed, 173 insertions(+), 53 deletions(-) diff --git a/resources/shaders/retractions.vs b/resources/shaders/retractions.vs index d97adbabe0..ba18073356 100644 --- a/resources/shaders/retractions.vs +++ b/resources/shaders/retractions.vs @@ -12,4 +12,6 @@ void main() // eye_normal = gl_NormalMatrix * gl_Normal; // world_normal_z = gl_Normal.z; gl_Position = ftransform(); + + gl_PointSize = 3.0; } diff --git a/resources/shaders/toolchanges.vs b/resources/shaders/toolchanges.vs index d97adbabe0..ba18073356 100644 --- a/resources/shaders/toolchanges.vs +++ b/resources/shaders/toolchanges.vs @@ -12,4 +12,6 @@ void main() // eye_normal = gl_NormalMatrix * gl_Normal; // world_normal_z = gl_Normal.z; gl_Position = ftransform(); + + gl_PointSize = 3.0; } diff --git a/resources/shaders/unretractions.vs b/resources/shaders/unretractions.vs index d97adbabe0..ba18073356 100644 --- a/resources/shaders/unretractions.vs +++ b/resources/shaders/unretractions.vs @@ -12,4 +12,6 @@ void main() // eye_normal = gl_NormalMatrix * gl_Normal; // world_normal_z = gl_Normal.z; gl_Position = ftransform(); + + gl_PointSize = 3.0; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 584ef06c32..00567eab6e 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include namespace Slic3r { namespace GUI { @@ -20,15 +22,27 @@ static GCodeProcessor::EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); } +void GCodeViewer::Buffer::reset() +{ + // release gpu memory + if (vbo_id > 0) + glsafe(::glDeleteBuffers(1, &vbo_id)); + + // release cpu memory + data = std::vector(); +} + void GCodeViewer::generate(const GCodeProcessor::Result& gcode_result) { if (m_last_result_id == gcode_result.id) return; + auto start_time = std::chrono::high_resolution_clock::now(); + m_last_result_id = gcode_result.id; // release gpu memory, if used - reset_buffers(); + reset(); // convert data size_t vertices_count = gcode_result.moves.size(); @@ -73,16 +87,49 @@ void GCodeViewer::generate(const GCodeProcessor::Result& gcode_result) continue; } } + if (curr.type == GCodeProcessor::EMoveType::Extrude) + m_layers_zs.emplace_back(curr.position[2]); } + std::sort(m_layers_zs.begin(), m_layers_zs.end()); + + // Replace intervals of layers with similar top positions with their average value. + int n = int(m_layers_zs.size()); + int k = 0; + for (int i = 0; i < n;) { + int j = i + 1; + double zmax = m_layers_zs[i] + EPSILON; + for (; j < n && m_layers_zs[j] <= zmax; ++j); + m_layers_zs[k++] = (j > i + 1) ? (0.5 * (m_layers_zs[i] + m_layers_zs[j - 1])) : m_layers_zs[i]; + i = j; + } + if (k < n) + m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); + // send data to gpu for (Buffer& buffer : m_buffers) { - glsafe(::glGenBuffers(1, &buffer.vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer.data.size() * sizeof(float), buffer.data.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + if (buffer.data.size() > 0) + { + glsafe(::glGenBuffers(1, &buffer.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer.data.size() * sizeof(float), buffer.data.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } } + + auto end_time = std::chrono::high_resolution_clock::now(); + std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; +} + +void GCodeViewer::reset() +{ + for (Buffer& buffer : m_buffers) + { + buffer.reset(); + } + + m_layers_zs = std::vector(); } void GCodeViewer::render() const @@ -111,10 +158,12 @@ void GCodeViewer::render() const if (buffer.vbo_id == 0) continue; - const Shader& shader = m_shaders[i]; - if (shader.is_initialized()) + if (!buffer.visible) + continue; + + if (buffer.shader.is_initialized()) { - shader.start_using(); + buffer.shader.start_using(); GLint current_program_id; glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); @@ -122,32 +171,50 @@ void GCodeViewer::render() const GCodeProcessor::EMoveType type = buffer_type(i); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, Buffer::stride(type), (const void*)0)); + glsafe(::glVertexPointer(3, GL_FLOAT, Buffer::vertex_size_bytes(), (const void*)0)); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); switch (type) { case GCodeProcessor::EMoveType::Tool_change: + { + std::array color = { 1.0f, 1.0f, 1.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } case GCodeProcessor::EMoveType::Retract: + { + std::array color = { 1.0f, 0.0f, 1.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } case GCodeProcessor::EMoveType::Unretract: { std::array color = { 0.0f, 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type))); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); break; } case GCodeProcessor::EMoveType::Extrude: { std::array color = { 1.0f, 0.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type))); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); break; } case GCodeProcessor::EMoveType::Travel: { std::array color = { 1.0f, 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::record_size(type))); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); break; } default: @@ -159,11 +226,24 @@ void GCodeViewer::render() const glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - shader.stop_using(); + buffer.shader.stop_using(); } } } +bool GCodeViewer::is_toolpath_visible(GCodeProcessor::EMoveType type) const +{ + size_t id = static_cast(buffer_id(type)); + return (id < m_buffers.size()) ? m_buffers[id].visible : false; +} + +void GCodeViewer::set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible) +{ + size_t id = static_cast(buffer_id(type)); + if (id < m_buffers.size()) + m_buffers[id].visible = visible; +} + bool GCodeViewer::init_shaders() { unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); @@ -171,7 +251,7 @@ bool GCodeViewer::init_shaders() for (unsigned char i = begin_id; i < end_id; ++i) { - Shader& shader = m_shaders[i]; + Shader& shader = m_buffers[i].shader; std::string vertex_shader_src; std::string fragment_shader_src; GCodeProcessor::EMoveType type = buffer_type(i); @@ -223,19 +303,6 @@ bool GCodeViewer::init_shaders() return true; } -void GCodeViewer::reset_buffers() -{ - for (Buffer& buffer : m_buffers) - { - // release gpu memory - if (buffer.vbo_id > 0) - glsafe(::glDeleteBuffers(1, &buffer.vbo_id)); - - // release cpu memory - buffer.data = std::vector(); - } -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 95250b2034..0ed5c337c4 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -16,42 +16,38 @@ class GCodeViewer struct Buffer { unsigned int vbo_id{ 0 }; + Shader shader; std::vector data; + bool visible{ false }; - static size_t stride(GCodeProcessor::EMoveType type) - { - return 3 * sizeof(float); - } + void reset(); - static size_t record_size(GCodeProcessor::EMoveType type) - { - switch (type) - { - case GCodeProcessor::EMoveType::Tool_change: - case GCodeProcessor::EMoveType::Retract: - case GCodeProcessor::EMoveType::Unretract: { return 3; } - case GCodeProcessor::EMoveType::Extrude: - case GCodeProcessor::EMoveType::Travel: { return 6; } - default: { return 0; } - } - } + static size_t vertex_size() { return 3; } + + static size_t vertex_size_bytes() { return vertex_size() * sizeof(float); } }; std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; - std::vector m_shaders{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + unsigned int m_last_result_id{ 0 }; + std::vector m_layers_zs; public: GCodeViewer() = default; - ~GCodeViewer() { reset_buffers(); } + ~GCodeViewer() { reset(); } - bool init() { return init_shaders(); } + bool init() { set_toolpath_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); } void generate(const GCodeProcessor::Result& gcode_result); + void reset(); void render() const; + const std::vector& get_layers_zs() const { return m_layers_zs; }; + + bool is_toolpath_visible(GCodeProcessor::EMoveType type) const; + void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); + private: bool init_shaders(); - void reset_buffers(); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c9c4ab3364..66120668c6 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -932,7 +932,11 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D std::vector> cp_values; cp_values.reserve(custom_gcode_per_print_z.size()); +#if ENABLE_GCODE_VIEWER + const std::vector& print_zs = canvas.get_layers_zs(); +#else std::vector print_zs = canvas.get_current_print_zs(true); +#endif // ENABLE_GCODE_VIEWER for (auto custom_code : custom_gcode_per_print_z) { if (custom_code.gcode != ColorChangeCode) @@ -2303,10 +2307,23 @@ void GLCanvas3D::ensure_on_bed(unsigned int object_idx) } } + +#if ENABLE_GCODE_VIEWER +const std::vector& GLCanvas3D::get_layers_zs() const +{ + return m_gcode_viewer.get_layers_zs(); +} + +void GLCanvas3D::set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible) +{ + m_gcode_viewer.set_toolpath_visible(type, visible); +} +#else std::vector GLCanvas3D::get_current_print_zs(bool active_only) const { return m_volumes.get_current_print_zs(active_only); } +#endif // ENABLE_GCODE_VIEWER void GLCanvas3D::set_toolpaths_range(double low, double high) { @@ -2786,14 +2803,19 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - boost::filesystem::path path("d:/processor.output"); - boost::nowide::ofstream out; - out.open(path.string()); - for (const GCodeProcessor::MoveVertex& v : gcode_result.moves) + static unsigned int last_result_id = 0; + if (last_result_id != gcode_result.id) { - out << v.to_string() << "\n"; + last_result_id = gcode_result.id; + boost::filesystem::path path("d:/processor.output"); + boost::nowide::ofstream out; + out.open(path.string()); + for (const GCodeProcessor::MoveVertex& v : gcode_result.moves) + { + out << v.to_string() << "\n"; + } + out.close(); } - out.close(); m_gcode_viewer.generate(gcode_result); #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 8c6b3c3f0c..592c85c03f 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -549,6 +549,10 @@ public: void reset_volumes(); int check_volumes_outside_state() const; +#if ENABLE_GCODE_VIEWER + void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } +#endif // ENABLE_GCODE_VIEWER + void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void update_instance_printable_state_for_object(size_t obj_idx); @@ -635,7 +639,12 @@ public: void delete_selected(); void ensure_on_bed(unsigned int object_idx); +#if ENABLE_GCODE_VIEWER + const std::vector& get_layers_zs() const; + void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); +#else std::vector get_current_print_zs(bool active_only) const; +#endif // ENABLE_GCODE_VIEWER void set_toolpaths_range(double low, double high); std::vector load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 0171dd597a..2e0c71268c 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -474,6 +474,9 @@ void Preview::reload_print(bool keep_volumes) !keep_volumes) { m_canvas->reset_volumes(); +#if ENABLE_GCODE_VIEWER + m_canvas->reset_gcode_toolpaths(); +#endif // ENABLE_GCODE_VIEWER m_canvas->reset_legend_texture(); m_loaded = false; #ifdef __linux__ @@ -614,21 +617,34 @@ void Preview::on_combochecklist_features(wxCommandEvent& evt) void Preview::on_checkbox_travel(wxCommandEvent& evt) { +#if ENABLE_GCODE_VIEWER + m_canvas->set_toolpath_visible(GCodeProcessor::EMoveType::Travel, m_checkbox_travel->IsChecked()); + refresh_print(); +#else m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked(); m_gcode_preview_data->ranges.feedrate.set_mode(GCodePreviewData::FeedrateKind::TRAVEL, m_gcode_preview_data->travel.is_visible); // Rather than refresh, reload print so that speed color ranges get recomputed (affected by travel visibility) reload_print(); +#endif // ENABLE_GCODE_VIEWER } void Preview::on_checkbox_retractions(wxCommandEvent& evt) { +#if ENABLE_GCODE_VIEWER + m_canvas->set_toolpath_visible(GCodeProcessor::EMoveType::Retract, m_checkbox_retractions->IsChecked()); +#else m_gcode_preview_data->retraction.is_visible = m_checkbox_retractions->IsChecked(); +#endif // ENABLE_GCODE_VIEWER refresh_print(); } void Preview::on_checkbox_unretractions(wxCommandEvent& evt) { +#if ENABLE_GCODE_VIEWER + m_canvas->set_toolpath_visible(GCodeProcessor::EMoveType::Unretract, m_checkbox_unretractions->IsChecked()); +#else m_gcode_preview_data->unretraction.is_visible = m_checkbox_unretractions->IsChecked(); +#endif // ENABLE_GCODE_VIEWER refresh_print(); } @@ -958,7 +974,11 @@ void Preview::load_print_as_fff(bool keep_z_range) } show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); // recalculates zs and update sliders accordingly +#if ENABLE_GCODE_VIEWER + const std::vector& zs = m_canvas->get_layers_zs(); +#else std::vector zs = m_canvas->get_current_print_zs(true); +#endif // ENABLE_GCODE_VIEWER if (zs.empty()) { // all layers filtered out reset_sliders(true); From cc774dece7fa902d674353cf051cdb1b4abdc2c5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 15 Apr 2020 14:31:39 +0200 Subject: [PATCH 016/826] GCodeViewer -> Toggle visibility of shells --- resources/shaders/shells.fs | 13 + resources/shaders/shells.vs | 42 ++++ src/slic3r/GUI/GCodeViewer.cpp | 440 ++++++++++++++++++++------------- src/slic3r/GUI/GCodeViewer.hpp | 21 +- src/slic3r/GUI/GLCanvas3D.cpp | 16 +- src/slic3r/GUI/GLCanvas3D.hpp | 7 +- src/slic3r/GUI/GUI_Preview.cpp | 4 + 7 files changed, 363 insertions(+), 180 deletions(-) create mode 100644 resources/shaders/shells.fs create mode 100644 resources/shaders/shells.vs diff --git a/resources/shaders/shells.fs b/resources/shaders/shells.fs new file mode 100644 index 0000000000..0c3388df70 --- /dev/null +++ b/resources/shaders/shells.fs @@ -0,0 +1,13 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform vec4 uniform_color; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); +} diff --git a/resources/shaders/shells.vs b/resources/shaders/shells.vs new file mode 100644 index 0000000000..bb9c144e65 --- /dev/null +++ b/resources/shaders/shells.vs @@ -0,0 +1,42 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(gl_NormalMatrix * gl_Normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = 0.0; + + if (NdotL > 0.0) + { + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + } + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + intensity.x += max(dot(normal, LIGHT_FRONT_DIR), 0.0) * LIGHT_FRONT_DIFFUSE; + + gl_Position = ftransform(); +} diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 00567eab6e..155c7d765d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1,8 +1,11 @@ #include "libslic3r/libslic3r.h" #include "GCodeViewer.hpp" -#include "3DScene.hpp" #if ENABLE_GCODE_VIEWER +#include "libslic3r/Print.hpp" +#include "GUI_App.hpp" +#include "PresetBundle.hpp" +#include "Camera.hpp" #include #include @@ -30,96 +33,21 @@ void GCodeViewer::Buffer::reset() // release cpu memory data = std::vector(); + data_size = 0; } -void GCodeViewer::generate(const GCodeProcessor::Result& gcode_result) +void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) { if (m_last_result_id == gcode_result.id) return; - auto start_time = std::chrono::high_resolution_clock::now(); - m_last_result_id = gcode_result.id; // release gpu memory, if used reset(); - // convert data - size_t vertices_count = gcode_result.moves.size(); - for (size_t i = 0; i < vertices_count; ++i) - { - // skip first vertex - if (i == 0) - continue; - - const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; - - Buffer& buffer = m_buffers[buffer_id(curr.type)]; - - switch (curr.type) - { - case GCodeProcessor::EMoveType::Tool_change: - case GCodeProcessor::EMoveType::Retract: - case GCodeProcessor::EMoveType::Unretract: - { - for (int j = 0; j < 3; ++j) - { - buffer.data.insert(buffer.data.end(), curr.position[j]); - } - break; - } - case GCodeProcessor::EMoveType::Extrude: - case GCodeProcessor::EMoveType::Travel: - { - for (int j = 0; j < 3; ++j) - { - buffer.data.insert(buffer.data.end(), prev.position[j]); - } - for (int j = 0; j < 3; ++j) - { - buffer.data.insert(buffer.data.end(), curr.position[j]); - } - break; - } - default: - { - continue; - } - } - if (curr.type == GCodeProcessor::EMoveType::Extrude) - m_layers_zs.emplace_back(curr.position[2]); - } - - std::sort(m_layers_zs.begin(), m_layers_zs.end()); - - // Replace intervals of layers with similar top positions with their average value. - int n = int(m_layers_zs.size()); - int k = 0; - for (int i = 0; i < n;) { - int j = i + 1; - double zmax = m_layers_zs[i] + EPSILON; - for (; j < n && m_layers_zs[j] <= zmax; ++j); - m_layers_zs[k++] = (j > i + 1) ? (0.5 * (m_layers_zs[i] + m_layers_zs[j - 1])) : m_layers_zs[i]; - i = j; - } - if (k < n) - m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); - - // send data to gpu - for (Buffer& buffer : m_buffers) - { - if (buffer.data.size() > 0) - { - glsafe(::glGenBuffers(1, &buffer.vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer.data.size() * sizeof(float), buffer.data.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - } - - auto end_time = std::chrono::high_resolution_clock::now(); - std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; + load_toolpaths(gcode_result); + load_shells(print, initialized); } void GCodeViewer::reset() @@ -129,106 +57,15 @@ void GCodeViewer::reset() buffer.reset(); } + m_shells.volumes.clear(); m_layers_zs = std::vector(); } void GCodeViewer::render() const { - auto set_color = [](GLint current_program_id, const std::array& color) { - if (current_program_id > 0) - { - GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; - if (color_id >= 0) - { - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); - return; - } - } - BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; - }; - - unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); - unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); - glsafe(::glEnable(GL_DEPTH_TEST)); - - for (unsigned char i = begin_id; i < end_id; ++i) - { - const Buffer& buffer = m_buffers[i]; - if (buffer.vbo_id == 0) - continue; - - if (!buffer.visible) - continue; - - if (buffer.shader.is_initialized()) - { - buffer.shader.start_using(); - - GLint current_program_id; - glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); - - GCodeProcessor::EMoveType type = buffer_type(i); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, Buffer::vertex_size_bytes(), (const void*)0)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - switch (type) - { - case GCodeProcessor::EMoveType::Tool_change: - { - std::array color = { 1.0f, 1.0f, 1.0f, 1.0f }; - set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - break; - } - case GCodeProcessor::EMoveType::Retract: - { - std::array color = { 1.0f, 0.0f, 1.0f, 1.0f }; - set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - break; - } - case GCodeProcessor::EMoveType::Unretract: - { - std::array color = { 0.0f, 1.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - break; - } - case GCodeProcessor::EMoveType::Extrude: - { - std::array color = { 1.0f, 0.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); - break; - } - case GCodeProcessor::EMoveType::Travel: - { - std::array color = { 1.0f, 1.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)buffer.data.size() / Buffer::vertex_size())); - break; - } - default: - { - break; - } - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - buffer.shader.stop_using(); - } - } + render_toolpaths(); + render_shells(); } bool GCodeViewer::is_toolpath_visible(GCodeProcessor::EMoveType type) const @@ -300,9 +137,264 @@ bool GCodeViewer::init_shaders() } } + if (!m_shells.shader.init("shells.vs", "shells.fs")) + { + BOOST_LOG_TRIVIAL(error) << "Unable to initialize shells shader: please, check that the files shells.vs and shells.fs are available"; + return false; + } + return true; } +void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) +{ + auto start_time = std::chrono::high_resolution_clock::now(); + + // convert data + size_t vertices_count = gcode_result.moves.size(); + for (size_t i = 0; i < vertices_count; ++i) + { + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + Buffer& buffer = m_buffers[buffer_id(curr.type)]; + + switch (curr.type) + { + case GCodeProcessor::EMoveType::Tool_change: + case GCodeProcessor::EMoveType::Retract: + case GCodeProcessor::EMoveType::Unretract: + { + for (int j = 0; j < 3; ++j) + { + buffer.data.insert(buffer.data.end(), curr.position[j]); + } + break; + } + case GCodeProcessor::EMoveType::Extrude: + case GCodeProcessor::EMoveType::Travel: + { + for (int j = 0; j < 3; ++j) + { + buffer.data.insert(buffer.data.end(), prev.position[j]); + } + for (int j = 0; j < 3; ++j) + { + buffer.data.insert(buffer.data.end(), curr.position[j]); + } + break; + } + default: + { + continue; + } + } + + if (curr.type == GCodeProcessor::EMoveType::Extrude) + m_layers_zs.emplace_back(curr.position[2]); + } + + std::sort(m_layers_zs.begin(), m_layers_zs.end()); + + // Replace intervals of layers with similar top positions with their average value. + int n = int(m_layers_zs.size()); + int k = 0; + for (int i = 0; i < n;) { + int j = i + 1; + double zmax = m_layers_zs[i] + EPSILON; + for (; j < n && m_layers_zs[j] <= zmax; ++j); + m_layers_zs[k++] = (j > i + 1) ? (0.5 * (m_layers_zs[i] + m_layers_zs[j - 1])) : m_layers_zs[i]; + i = j; + } + if (k < n) + m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); + + // send data to gpu + for (Buffer& buffer : m_buffers) + { + buffer.data_size = buffer.data.size(); + if (buffer.data_size > 0) + { + glsafe(::glGenBuffers(1, &buffer.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer.data_size * sizeof(float), buffer.data.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + buffer.data = std::vector(); + } + } + + auto end_time = std::chrono::high_resolution_clock::now(); + std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; +} + +void GCodeViewer::load_shells(const Print& print, bool initialized) +{ + if (print.objects().empty()) + // no shells, return + return; + + // adds objects' volumes + int object_id = 0; + for (const PrintObject* obj : print.objects()) + { + const ModelObject* model_obj = obj->model_object(); + + std::vector instance_ids(model_obj->instances.size()); + for (int i = 0; i < (int)model_obj->instances.size(); ++i) + { + instance_ids[i] = i; + } + + m_shells.volumes.load_object(model_obj, object_id, instance_ids, "object", initialized); + + ++object_id; + } + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { + // adds wipe tower's volume + double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); + const PrintConfig& config = print.config(); + size_t extruders_count = config.nozzle_diameter.size(); + if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { + + 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); + double nozzle_diameter = print.config().nozzle_diameter.values[0]; + float depth = print.wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).depth; + float brim_width = print.wipe_tower_data(extruders_count, first_layer_height, nozzle_diameter).brim_width; + + m_shells.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, + !print.is_step_done(psWipeTower), brim_width, initialized); + } + } + + for (GLVolume* volume : m_shells.volumes.volumes) + { + volume->zoom_to_volumes = false; + volume->color[3] = 0.25f; + volume->force_native_color = true; + volume->set_render_color(); + } +} + +void GCodeViewer::render_toolpaths() const +{ + auto set_color = [](GLint current_program_id, const std::array& color) { + if (current_program_id > 0) + { + GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; + if (color_id >= 0) + { + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); + return; + } + } + BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; + }; + + glsafe(::glCullFace(GL_BACK)); + + unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); + unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + + for (unsigned char i = begin_id; i < end_id; ++i) + { + const Buffer& buffer = m_buffers[i]; + if (buffer.vbo_id == 0) + continue; + + if (!buffer.visible) + continue; + + if (buffer.shader.is_initialized()) + { + buffer.shader.start_using(); + + GLint current_program_id; + glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); + + GCodeProcessor::EMoveType type = buffer_type(i); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, Buffer::vertex_size_bytes(), (const void*)0)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + switch (type) + { + case GCodeProcessor::EMoveType::Tool_change: + { + std::array color = { 1.0f, 1.0f, 1.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } + case GCodeProcessor::EMoveType::Retract: + { + std::array color = { 1.0f, 0.0f, 1.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } + case GCodeProcessor::EMoveType::Unretract: + { + std::array color = { 0.0f, 1.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } + case GCodeProcessor::EMoveType::Extrude: + { + std::array color = { 1.0f, 0.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + break; + } + case GCodeProcessor::EMoveType::Travel: + { + std::array color = { 1.0f, 1.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + break; + } + default: + { + break; + } + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + buffer.shader.stop_using(); + } + } +} + +void GCodeViewer::render_shells() const +{ + if (!m_shells.visible || m_shells.volumes.empty() || !m_shells.shader.is_initialized()) + return; + +// glsafe(::glDepthMask(GL_FALSE)); + + m_shells.shader.start_using(); + m_shells.volumes.render(GLVolumeCollection::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix()); + m_shells.shader.stop_using(); + +// glsafe(::glDepthMask(GL_TRUE)); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 0ed5c337c4..92929cffe9 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -4,11 +4,13 @@ #if ENABLE_GCODE_VIEWER #include "GLShader.hpp" +#include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include namespace Slic3r { +class Print; namespace GUI { class GCodeViewer @@ -18,26 +20,34 @@ class GCodeViewer unsigned int vbo_id{ 0 }; Shader shader; std::vector data; + size_t data_size{ 0 }; bool visible{ false }; void reset(); static size_t vertex_size() { return 3; } - static size_t vertex_size_bytes() { return vertex_size() * sizeof(float); } }; + struct Shells + { + GLVolumeCollection volumes; + bool visible{ false }; + Shader shader; + }; + std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; unsigned int m_last_result_id{ 0 }; std::vector m_layers_zs; + Shells m_shells; public: GCodeViewer() = default; ~GCodeViewer() { reset(); } bool init() { set_toolpath_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); } - void generate(const GCodeProcessor::Result& gcode_result); + void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); void reset(); void render() const; @@ -46,8 +56,15 @@ public: bool is_toolpath_visible(GCodeProcessor::EMoveType type) const; void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); + bool are_shells_visible() const { return m_shells.visible; } + void set_shells_visible(bool visible) { m_shells.visible = visible; } + private: bool init_shaders(); + void load_toolpaths(const GCodeProcessor::Result& gcode_result); + void load_shells(const Print& print, bool initialized); + void render_toolpaths() const; + void render_shells() const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 66120668c6..4b8ca69d21 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2122,7 +2122,8 @@ void GLCanvas3D::render() _render_objects(); #if ENABLE_GCODE_VIEWER - _render_gcode(); + if (!m_main_toolbar.is_enabled()) + _render_gcode(); #endif // ENABLE_GCODE_VIEWER _render_sla_slices(); _render_selection(); @@ -2318,6 +2319,11 @@ void GLCanvas3D::set_toolpath_visible(GCodeProcessor::EMoveType type, bool visib { m_gcode_viewer.set_toolpath_visible(type, visible); } + +void GLCanvas3D::set_shells_visible(bool visible) +{ + m_gcode_viewer.set_shells_visible(visible); +} #else std::vector GLCanvas3D::get_current_print_zs(bool active_only) const { @@ -2768,6 +2774,7 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old.finalize_geometry(gl_initialized); } +#if !ENABLE_GCODE_VIEWER static void load_gcode_retractions(const GCodePreviewData::Retraction& retractions, GLCanvas3D::GCodePreviewVolumeIndex::EType extrusion_type, GLVolumeCollection &volumes, GLCanvas3D::GCodePreviewVolumeIndex &volume_index, bool gl_initialized) { // nothing to render, return @@ -2798,6 +2805,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio } volume->indexed_vertex_array.finalize_geometry(gl_initialized); } +#endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result) @@ -2817,11 +2825,12 @@ void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result out.close(); } - m_gcode_viewer.generate(gcode_result); + m_gcode_viewer.load(gcode_result , *this->fff_print(), m_initialized); #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT } #endif // ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors) { const Print *print = this->fff_print(); @@ -2890,6 +2899,7 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const _generate_legend_texture(preview_data, tool_colors); } } +#endif // !ENABLE_GCODE_VIEWER void GLCanvas3D::load_sla_preview() { @@ -6547,6 +6557,7 @@ static inline int hex_digit_to_int(const char c) (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; } +#if !ENABLE_GCODE_VIEWER void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) { BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - start" << m_volumes.log_memory_info() << log_memory_info(); @@ -6866,6 +6877,7 @@ void GLCanvas3D::_load_fff_shells() } } } +#endif // !ENABLE_GCODE_VIEWER // While it looks like we can call // this->reload_scene(true, true) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 592c85c03f..7033f05a0b 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -642,6 +642,7 @@ public: #if ENABLE_GCODE_VIEWER const std::vector& get_layers_zs() const; void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); + void set_shells_visible(bool visible); #else std::vector get_current_print_zs(bool active_only) const; #endif // ENABLE_GCODE_VIEWER @@ -656,9 +657,9 @@ public: #if ENABLE_GCODE_VIEWER void load_gcode_preview_2(const GCodeProcessor::Result& gcode_result); -#endif // ENABLE_GCODE_VIEWER - +#else void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors); +#endif // ENABLE_GCODE_VIEWER void load_sla_preview(); void load_preview(const std::vector& str_tool_colors, const std::vector& color_print_values); void bind_event_handlers(); @@ -833,12 +834,14 @@ private: // Create 3D thick extrusion lines for wipe tower extrusions void _load_wipe_tower_toolpaths(const std::vector& str_tool_colors); +#if !ENABLE_GCODE_VIEWER // generates gcode extrusion paths geometry void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors); // generates gcode travel paths geometry void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors); // generates objects and wipe tower geometry void _load_fff_shells(); +#endif // !ENABLE_GCODE_VIEWER // Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished. void _load_sla_shells(); // sets gcode geometry visibility according to user selection diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2e0c71268c..0c4f655c58 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -650,7 +650,11 @@ void Preview::on_checkbox_unretractions(wxCommandEvent& evt) void Preview::on_checkbox_shells(wxCommandEvent& evt) { +#if ENABLE_GCODE_VIEWER + m_canvas->set_shells_visible(m_checkbox_shells->IsChecked()); +#else m_gcode_preview_data->shell.is_visible = m_checkbox_shells->IsChecked(); +#endif // ENABLE_GCODE_VIEWER refresh_print(); } From 61ab7bbebfaabc21a5a6a08750c8230c9a9bbb39 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 15 Apr 2020 16:29:11 +0200 Subject: [PATCH 017/826] GCodeViewer -> Basic indexed rendering --- resources/shaders/retractions.vs | 2 +- resources/shaders/toolchanges.vs | 2 +- resources/shaders/unretractions.vs | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 153 +++++++++++++++++------------ src/slic3r/GUI/GCodeViewer.hpp | 25 +++-- 5 files changed, 113 insertions(+), 71 deletions(-) diff --git a/resources/shaders/retractions.vs b/resources/shaders/retractions.vs index ba18073356..2cf5ca2dd0 100644 --- a/resources/shaders/retractions.vs +++ b/resources/shaders/retractions.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 3.0; + gl_PointSize = 5.0; } diff --git a/resources/shaders/toolchanges.vs b/resources/shaders/toolchanges.vs index ba18073356..2cf5ca2dd0 100644 --- a/resources/shaders/toolchanges.vs +++ b/resources/shaders/toolchanges.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 3.0; + gl_PointSize = 5.0; } diff --git a/resources/shaders/unretractions.vs b/resources/shaders/unretractions.vs index ba18073356..2cf5ca2dd0 100644 --- a/resources/shaders/unretractions.vs +++ b/resources/shaders/unretractions.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 3.0; + gl_PointSize = 5.0; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 155c7d765d..963f8d2269 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -25,14 +25,23 @@ static GCodeProcessor::EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); } -void GCodeViewer::Buffer::reset() +void GCodeViewer::VBuffer::reset() { // release gpu memory if (vbo_id > 0) glsafe(::glDeleteBuffers(1, &vbo_id)); + vertices_count = 0; +} + +void GCodeViewer::IBuffer::reset() +{ + // release gpu memory + if (ibo_id > 0) + glsafe(::glDeleteBuffers(1, &ibo_id)); + // release cpu memory - data = std::vector(); + data = std::vector(); data_size = 0; } @@ -52,7 +61,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& void GCodeViewer::reset() { - for (Buffer& buffer : m_buffers) + m_vertices.reset(); + + for (IBuffer& buffer : m_buffers) { buffer.reset(); } @@ -150,9 +161,32 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { auto start_time = std::chrono::high_resolution_clock::now(); - // convert data - size_t vertices_count = gcode_result.moves.size(); - for (size_t i = 0; i < vertices_count; ++i) + // vertex data + m_vertices.vertices_count = gcode_result.moves.size(); + if (m_vertices.vertices_count == 0) + return; + + // vertex data -> extract from result + std::vector vertices_data; + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) + { + for (int j = 0; j < 3; ++j) + { + vertices_data.insert(vertices_data.end(), move.position[j]); + } + } + + // vertex data -> send to gpu + glsafe(::glGenBuffers(1, &m_vertices.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices_data.size() * sizeof(float), vertices_data.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + // vertex data -> free ram + vertices_data = std::vector(); + + // indices data -> extract from result + for (size_t i = 0; i < m_vertices.vertices_count; ++i) { // skip first vertex if (i == 0) @@ -161,7 +195,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; - Buffer& buffer = m_buffers[buffer_id(curr.type)]; + IBuffer& buffer = m_buffers[buffer_id(curr.type)]; switch (curr.type) { @@ -169,23 +203,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { - for (int j = 0; j < 3; ++j) - { - buffer.data.insert(buffer.data.end(), curr.position[j]); - } + buffer.data.push_back(static_cast(i)); break; } case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { - for (int j = 0; j < 3; ++j) - { - buffer.data.insert(buffer.data.end(), prev.position[j]); - } - for (int j = 0; j < 3; ++j) - { - buffer.data.insert(buffer.data.end(), curr.position[j]); - } + buffer.data.push_back(static_cast(i - 1)); + buffer.data.push_back(static_cast(i)); break; } default: @@ -193,14 +218,35 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) continue; } } - - if (curr.type == GCodeProcessor::EMoveType::Extrude) - m_layers_zs.emplace_back(curr.position[2]); } + // indices data -> send data to gpu + for (IBuffer& buffer : m_buffers) + { + buffer.data_size = buffer.data.size(); + if (buffer.data_size > 0) + { + glsafe(::glGenBuffers(1, &buffer.ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.data_size * sizeof(unsigned int), buffer.data.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // indices data -> free ram + buffer.data = std::vector(); + } + } + + // layers zs -> extract from result + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) + { + if (move.type == GCodeProcessor::EMoveType::Extrude) + m_layers_zs.emplace_back(move.position[2]); + } + + // layers zs -> sort std::sort(m_layers_zs.begin(), m_layers_zs.end()); - // Replace intervals of layers with similar top positions with their average value. + // layers zs -> replace intervals of layers with similar top positions with their average value. int n = int(m_layers_zs.size()); int k = 0; for (int i = 0; i < n;) { @@ -213,20 +259,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (k < n) m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); - // send data to gpu - for (Buffer& buffer : m_buffers) - { - buffer.data_size = buffer.data.size(); - if (buffer.data_size > 0) - { - glsafe(::glGenBuffers(1, &buffer.vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer.data_size * sizeof(float), buffer.data.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - buffer.data = std::vector(); - } - } - auto end_time = std::chrono::high_resolution_clock::now(); std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; } @@ -285,11 +317,9 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) void GCodeViewer::render_toolpaths() const { auto set_color = [](GLint current_program_id, const std::array& color) { - if (current_program_id > 0) - { + if (current_program_id > 0) { GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; - if (color_id >= 0) - { + if (color_id >= 0) { glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); return; } @@ -302,27 +332,29 @@ void GCodeViewer::render_toolpaths() const unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, VBuffer::vertex_size_bytes(), (const void*)0)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + for (unsigned char i = begin_id; i < end_id; ++i) { - const Buffer& buffer = m_buffers[i]; - if (buffer.vbo_id == 0) + const IBuffer& buffer = m_buffers[i]; + if (buffer.ibo_id == 0) continue; - + if (!buffer.visible) continue; if (buffer.shader.is_initialized()) { - buffer.shader.start_using(); + GCodeProcessor::EMoveType type = buffer_type(i); + buffer.shader.start_using(); + GLint current_program_id; glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); - GCodeProcessor::EMoveType type = buffer_type(i); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vbo_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, Buffer::vertex_size_bytes(), (const void*)0)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); switch (type) { @@ -331,7 +363,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 1.0f, 1.0f, 1.0f, 1.0f }; set_color(current_program_id, color); glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); break; } @@ -340,7 +372,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 1.0f, 0.0f, 1.0f, 1.0f }; set_color(current_program_id, color); glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); break; } @@ -349,7 +381,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 0.0f, 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawArrays(GL_POINTS, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); break; } @@ -357,28 +389,25 @@ void GCodeViewer::render_toolpaths() const { std::array color = { 1.0f, 0.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); + glsafe(::glDrawElements(GL_LINES, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); break; } case GCodeProcessor::EMoveType::Travel: { std::array color = { 1.0f, 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)(buffer.data_size / Buffer::vertex_size()))); - break; - } - default: - { + glsafe(::glDrawElements(GL_LINES, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); break; } } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); buffer.shader.stop_using(); } } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } void GCodeViewer::render_shells() const diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 92929cffe9..6d6a6f8e01 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -15,13 +15,13 @@ namespace GUI { class GCodeViewer { - struct Buffer + // buffer containing vertices data + struct VBuffer { unsigned int vbo_id{ 0 }; - Shader shader; - std::vector data; - size_t data_size{ 0 }; - bool visible{ false }; + size_t vertices_count{ 0 }; + + size_t data_size_bytes() { return vertices_count * vertex_size_bytes(); } void reset(); @@ -29,6 +29,18 @@ class GCodeViewer static size_t vertex_size_bytes() { return vertex_size() * sizeof(float); } }; + // buffer containing indices data + struct IBuffer + { + unsigned int ibo_id{ 0 }; + Shader shader; + std::vector data; + size_t data_size{ 0 }; + bool visible{ false }; + + void reset(); + }; + struct Shells { GLVolumeCollection volumes; @@ -36,7 +48,8 @@ class GCodeViewer Shader shader; }; - std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + VBuffer m_vertices; + std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; unsigned int m_last_result_id{ 0 }; std::vector m_layers_zs; From 75d1e8373d9ed949468b3bd733dee3c2a83fb951 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 16 Apr 2020 15:09:04 +0200 Subject: [PATCH 018/826] GCodeViewer -> extrusion paths colored by extrusion role --- src/libslic3r/GCode/GCodeProcessor.cpp | 2 +- src/libslic3r/GCode/GCodeProcessor.hpp | 6 +- src/slic3r/GUI/GCodeViewer.cpp | 100 ++++++++++++++++++------- src/slic3r/GUI/GCodeViewer.hpp | 28 ++++++- src/slic3r/GUI/GLCanvas3D.cpp | 3 + 5 files changed, 105 insertions(+), 34 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c75c240020..78943dca89 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,4 +1,4 @@ -#include "../libslic3r.h" +#include "libslic3r/libslic3r.h" #include "GCodeProcessor.hpp" #include diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 1f7af9c29d..623a2ae3bd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -2,9 +2,9 @@ #define slic3r_GCodeProcessor_hpp_ #if ENABLE_GCODE_VIEWER -#include "../GCodeReader.hpp" -#include "../Point.hpp" -#include "../ExtrusionEntity.hpp" +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/ExtrusionEntity.hpp" #include #include diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 963f8d2269..a554ecb8ba 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -43,8 +43,44 @@ void GCodeViewer::IBuffer::reset() // release cpu memory data = std::vector(); data_size = 0; + paths = std::vector(); } +bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src) +{ + if (!shader.init(vertex_shader_src, fragment_shader_src)) + { + BOOST_LOG_TRIVIAL(error) << "Unable to initialize toolpaths shader: please, check that the files " << vertex_shader_src << " and " << fragment_shader_src << " are available"; + return false; + } + + return true; +} + +void GCodeViewer::IBuffer::add_path(GCodeProcessor::EMoveType type, ExtrusionRole role) +{ + unsigned int id = static_cast(data.size()); + paths.push_back({ type, role, id, id }); +} + +const std::array, erCount> GCodeViewer::Default_Extrusion_Role_Colors {{ + { 0.00f, 0.00f, 0.00f, 1.0f }, // erNone + { 1.00f, 1.00f, 0.40f, 1.0f }, // erPerimeter + { 1.00f, 0.65f, 0.00f, 1.0f }, // erExternalPerimeter + { 0.00f, 0.00f, 1.00f, 1.0f }, // erOverhangPerimeter + { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill + { 0.84f, 0.20f, 0.84f, 1.0f }, // erSolidInfill + { 1.00f, 0.10f, 0.10f, 1.0f }, // erTopSolidInfill + { 0.60f, 0.60f, 1.00f, 1.0f }, // erBridgeInfill + { 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill + { 0.52f, 0.48f, 0.13f, 1.0f }, // erSkirt + { 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial + { 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface + { 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower + { 0.16f, 0.80f, 0.58f, 1.0f }, // erCustom + { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed +}}; + void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) { if (m_last_result_id == gcode_result.id) @@ -99,40 +135,41 @@ bool GCodeViewer::init_shaders() for (unsigned char i = begin_id; i < end_id; ++i) { - Shader& shader = m_buffers[i].shader; - std::string vertex_shader_src; - std::string fragment_shader_src; - GCodeProcessor::EMoveType type = buffer_type(i); - switch (type) + switch (buffer_type(i)) { case GCodeProcessor::EMoveType::Tool_change: { - vertex_shader_src = "toolchanges.vs"; - fragment_shader_src = "toolchanges.fs"; + if (!m_buffers[i].init_shader("toolchanges.vs", "toolchanges.fs")) + return false; + break; } case GCodeProcessor::EMoveType::Retract: { - vertex_shader_src = "retractions.vs"; - fragment_shader_src = "retractions.fs"; + if (!m_buffers[i].init_shader("retractions.vs", "retractions.fs")) + return false; + break; } case GCodeProcessor::EMoveType::Unretract: { - vertex_shader_src = "unretractions.vs"; - fragment_shader_src = "unretractions.fs"; + if (!m_buffers[i].init_shader("unretractions.vs", "unretractions.fs")) + return false; + break; } case GCodeProcessor::EMoveType::Extrude: { - vertex_shader_src = "extrusions.vs"; - fragment_shader_src = "extrusions.fs"; + if (!m_buffers[i].init_shader("extrusions.vs", "extrusions.fs")) + return false; + break; } case GCodeProcessor::EMoveType::Travel: { - vertex_shader_src = "travels.vs"; - fragment_shader_src = "travels.fs"; + if (!m_buffers[i].init_shader("travels.vs", "travels.fs")) + return false; + break; } default: @@ -140,12 +177,6 @@ bool GCodeViewer::init_shaders() break; } } - - if (!shader.init(vertex_shader_src, fragment_shader_src)) - { - BOOST_LOG_TRIVIAL(error) << "Unable to initialize toolpaths shader: please, check that the files " << vertex_shader_src << " and " << fragment_shader_src << " are available"; - return false; - } } if (!m_shells.shader.init("shells.vs", "shells.fs")) @@ -203,13 +234,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { + buffer.add_path(curr.type, curr.extrusion_role); buffer.data.push_back(static_cast(i)); break; } case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { - buffer.data.push_back(static_cast(i - 1)); + if (prev.type != curr.type) + { + buffer.add_path(curr.type, curr.extrusion_role); + buffer.data.push_back(static_cast(i - 1)); + } + + buffer.paths.back().last = static_cast(buffer.data.size()); buffer.data.push_back(static_cast(i)); break; } @@ -387,16 +425,26 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Extrude: { - std::array color = { 1.0f, 0.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); - glsafe(::glDrawElements(GL_LINES, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); + for (const Path& path : buffer.paths) + { + unsigned int color_id = static_cast(path.role); + if (color_id >= erCount) + color_id = 0; + + set_color(current_program_id, m_extrusion_role_colors[color_id]); + + glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + } break; } case GCodeProcessor::EMoveType::Travel: { std::array color = { 1.0f, 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glDrawElements(GL_LINES, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); + for (const Path& path : buffer.paths) + { + glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + } break; } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 6d6a6f8e01..e44fb01800 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -7,14 +7,14 @@ #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" -#include - namespace Slic3r { class Print; namespace GUI { class GCodeViewer { + static const std::array, erCount> Default_Extrusion_Role_Colors; + // buffer containing vertices data struct VBuffer { @@ -29,16 +29,30 @@ class GCodeViewer static size_t vertex_size_bytes() { return vertex_size() * sizeof(float); } }; - // buffer containing indices data + struct Path + { + GCodeProcessor::EMoveType type{ GCodeProcessor::EMoveType::Noop }; + ExtrusionRole role{ erNone }; + unsigned int first{ 0 }; + unsigned int last{ 0 }; + + bool matches(GCodeProcessor::EMoveType type, ExtrusionRole role) const { return this->type == type && this->role == role; } + }; + + // buffer containing indices data and shader for a specific toolpath type struct IBuffer { unsigned int ibo_id{ 0 }; Shader shader; std::vector data; size_t data_size{ 0 }; + std::vector paths; bool visible{ false }; void reset(); + bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); + + void add_path(GCodeProcessor::EMoveType type, ExtrusionRole role); }; struct Shells @@ -55,11 +69,17 @@ class GCodeViewer std::vector m_layers_zs; Shells m_shells; + std::array, erCount> m_extrusion_role_colors; + public: GCodeViewer() = default; ~GCodeViewer() { reset(); } - bool init() { set_toolpath_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); } + bool init() { + m_extrusion_role_colors = Default_Extrusion_Role_Colors; + set_toolpath_visible(GCodeProcessor::EMoveType::Extrude, true); + return init_shaders(); + } void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); void reset(); void render() const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4b8ca69d21..c7c3eaaf59 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2140,6 +2140,9 @@ void GLCanvas3D::render() // we need to set the mouse's scene position here because the depth buffer // could be invalidated by the following gizmo render methods // this position is used later into on_mouse() to drag the objects +#if ENABLE_GCODE_VIEWER + if (m_picking_enabled) +#endif // ENABLE_GCODE_VIEWER m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast()); _render_current_gizmo(); From 7b0e35e70d8cc63343d4ca9b57769e870728a977 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 16 Apr 2020 15:59:36 +0200 Subject: [PATCH 019/826] GCodeViewer -> Selection of extrusions view type --- src/slic3r/GUI/GCodeViewer.cpp | 36 ++++++++++++++++++++++++++++------ src/slic3r/GUI/GCodeViewer.hpp | 25 +++++++++++++++++++++++ src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_Preview.cpp | 7 +++++++ 5 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a554ecb8ba..d6fc7fd8ef 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -354,6 +354,35 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) void GCodeViewer::render_toolpaths() const { + auto extrusion_color = [this](const Path& path) { + std::array color; + switch (m_view_type) + { + case EViewType::FeatureType: + { + unsigned int color_id = static_cast(path.role); + if (color_id >= erCount) + color_id = 0; + + color = m_extrusion_role_colors[color_id]; + break; + } + case EViewType::Height: + case EViewType::Width: + case EViewType::Feedrate: + case EViewType::FanSpeed: + case EViewType::VolumetricRate: + case EViewType::Tool: + case EViewType::ColorPrint: + default: + { + color = { 1.0f, 1.0f, 1.0f, 1.0f }; + break; + } + } + return color; + }; + auto set_color = [](GLint current_program_id, const std::array& color) { if (current_program_id > 0) { GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; @@ -427,12 +456,7 @@ void GCodeViewer::render_toolpaths() const { for (const Path& path : buffer.paths) { - unsigned int color_id = static_cast(path.role); - if (color_id >= erCount) - color_id = 0; - - set_color(current_program_id, m_extrusion_role_colors[color_id]); - + set_color(current_program_id, extrusion_color(path)); glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); } break; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index e44fb01800..9bc644c4eb 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -62,6 +62,21 @@ class GCodeViewer Shader shader; }; +public: + enum class EViewType : unsigned char + { + FeatureType, + Height, + Width, + Feedrate, + FanSpeed, + VolumetricRate, + Tool, + ColorPrint, + Count + }; + +private: VBuffer m_vertices; std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; @@ -71,6 +86,8 @@ class GCodeViewer std::array, erCount> m_extrusion_role_colors; + EViewType m_view_type{ EViewType::FeatureType }; + public: GCodeViewer() = default; ~GCodeViewer() { reset(); } @@ -86,6 +103,14 @@ public: const std::vector& get_layers_zs() const { return m_layers_zs; }; + EViewType get_view_type() const { return m_view_type; } + void set_view_type(EViewType type) { + if (type == EViewType::Count) + type = EViewType::FeatureType; + + m_view_type = type; + } + bool is_toolpath_visible(GCodeProcessor::EMoveType type) const; void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c7c3eaaf59..9f3c9653c5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2323,6 +2323,11 @@ void GLCanvas3D::set_toolpath_visible(GCodeProcessor::EMoveType type, bool visib m_gcode_viewer.set_toolpath_visible(type, visible); } +void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) +{ + m_gcode_viewer.set_view_type(type); +} + void GLCanvas3D::set_shells_visible(bool visible) { m_gcode_viewer.set_shells_visible(visible); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7033f05a0b..246a298fe4 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -642,6 +642,7 @@ public: #if ENABLE_GCODE_VIEWER const std::vector& get_layers_zs() const; void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); + void set_toolpath_view_type(GCodeViewer::EViewType type); void set_shells_visible(bool visible); #else std::vector get_current_print_zs(bool active_only) const; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 0c4f655c58..f2a56e8a20 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -602,10 +602,17 @@ void Preview::on_choice_view_type(wxCommandEvent& evt) { m_preferred_color_mode = (m_choice_view_type->GetStringSelection() == L("Tool")) ? "tool" : "feature"; int selection = m_choice_view_type->GetCurrentSelection(); +#if ENABLE_GCODE_VIEWER + if (0 <= selection && selection < static_cast(GCodeViewer::EViewType::Count)) + m_canvas->set_toolpath_view_type(static_cast(selection)); + + refresh_print(); +#else if ((0 <= selection) && (selection < (int)GCodePreviewData::Extrusion::Num_View_Types)) m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)selection; reload_print(); +#endif // ENABLE_GCODE_VIEWER } void Preview::on_combochecklist_features(wxCommandEvent& evt) From 9776d7c5a1333787ae88915b6f8e1b01380953cf Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Apr 2020 10:43:29 +0200 Subject: [PATCH 020/826] GCodeViewer -> Toggle visibility of extrusions roles --- src/slic3r/GUI/GCodeViewer.cpp | 12 ++++++++++-- src/slic3r/GUI/GCodeViewer.hpp | 29 +++++++++++++++++++++++------ src/slic3r/GUI/GLCanvas3D.cpp | 9 +++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 3 ++- src/slic3r/GUI/GUI_Preview.cpp | 13 ++++++++++--- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index d6fc7fd8ef..fb899164d3 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -104,6 +104,7 @@ void GCodeViewer::reset() buffer.reset(); } + m_extrusions.reset_role_visibility_flags(); m_shells.volumes.clear(); m_layers_zs = std::vector(); } @@ -121,7 +122,7 @@ bool GCodeViewer::is_toolpath_visible(GCodeProcessor::EMoveType type) const return (id < m_buffers.size()) ? m_buffers[id].visible : false; } -void GCodeViewer::set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible) +void GCodeViewer::set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible) { size_t id = static_cast(buffer_id(type)); if (id < m_buffers.size()) @@ -364,7 +365,7 @@ void GCodeViewer::render_toolpaths() const if (color_id >= erCount) color_id = 0; - color = m_extrusion_role_colors[color_id]; + color = m_extrusions.role_colors[color_id]; break; } case EViewType::Height: @@ -394,6 +395,10 @@ void GCodeViewer::render_toolpaths() const BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; }; + auto is_path_visible = [](unsigned int flags, const Path& path) { + return Extrusions::is_role_visible(flags, path.role); + }; + glsafe(::glCullFace(GL_BACK)); unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); @@ -456,6 +461,9 @@ void GCodeViewer::render_toolpaths() const { for (const Path& path : buffer.paths) { + if (!is_path_visible(m_extrusions.role_visibility_flags, path)) + continue; + set_color(current_program_id, extrusion_color(path)); glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 9bc644c4eb..1020606b86 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -62,6 +62,24 @@ class GCodeViewer Shader shader; }; + struct Extrusions + { + std::array, erCount> role_colors; + unsigned int role_visibility_flags{ 0 }; + + void reset_role_visibility_flags() { + role_visibility_flags = 0; + for (unsigned int i = 0; i < erCount; ++i) + { + role_visibility_flags |= 1 << i; + } + } + + static bool is_role_visible(unsigned int flags, ExtrusionRole role) { + return role < erCount && (flags & (1 << role)) != 0; + } + }; + public: enum class EViewType : unsigned char { @@ -82,10 +100,8 @@ private: unsigned int m_last_result_id{ 0 }; std::vector m_layers_zs; + Extrusions m_extrusions; Shells m_shells; - - std::array, erCount> m_extrusion_role_colors; - EViewType m_view_type{ EViewType::FeatureType }; public: @@ -93,8 +109,8 @@ public: ~GCodeViewer() { reset(); } bool init() { - m_extrusion_role_colors = Default_Extrusion_Role_Colors; - set_toolpath_visible(GCodeProcessor::EMoveType::Extrude, true); + m_extrusions.role_colors = Default_Extrusion_Role_Colors; + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); } void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); @@ -112,7 +128,8 @@ public: } bool is_toolpath_visible(GCodeProcessor::EMoveType type) const; - void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); + void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); + void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } bool are_shells_visible() const { return m_shells.visible; } void set_shells_visible(bool visible) { m_shells.visible = visible; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8d01d2c566..fcefc8f8a8 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2322,9 +2322,14 @@ const std::vector& GLCanvas3D::get_layers_zs() const return m_gcode_viewer.get_layers_zs(); } -void GLCanvas3D::set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible) +void GLCanvas3D::set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible) { - m_gcode_viewer.set_toolpath_visible(type, visible); + m_gcode_viewer.set_toolpath_move_type_visible(type, visible); +} + +void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags) +{ + m_gcode_viewer.set_toolpath_role_visibility_flags(flags); } void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 246a298fe4..fce805b51d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -641,7 +641,8 @@ public: #if ENABLE_GCODE_VIEWER const std::vector& get_layers_zs() const; - void set_toolpath_visible(GCodeProcessor::EMoveType type, bool visible); + void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); + void set_toolpath_role_visibility_flags(unsigned int flags); void set_toolpath_view_type(GCodeViewer::EViewType type); void set_shells_visible(bool visible); #else diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index f2a56e8a20..6b13304c0e 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -308,6 +308,9 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); std::string feature_text = GUI::into_u8(_(L("Feature types"))); std::string feature_items = GUI::into_u8( +#if ENABLE_GCODE_VIEWER + _L("Unknown") + "|" + +#endif // ENABLE_GCODE_VIEWER _(L("Perimeter")) + "|" + _(L("External perimeter")) + "|" + _(L("Overhang perimeter")) + "|" + @@ -618,14 +621,18 @@ void Preview::on_choice_view_type(wxCommandEvent& evt) void Preview::on_combochecklist_features(wxCommandEvent& evt) { int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features); +#if ENABLE_GCODE_VIEWER + m_canvas->set_toolpath_role_visibility_flags(static_cast(flags)); +#else m_gcode_preview_data->extrusion.role_flags = (unsigned int)flags; +#endif // ENABLE_GCODE_VIEWER refresh_print(); } void Preview::on_checkbox_travel(wxCommandEvent& evt) { #if ENABLE_GCODE_VIEWER - m_canvas->set_toolpath_visible(GCodeProcessor::EMoveType::Travel, m_checkbox_travel->IsChecked()); + m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel, m_checkbox_travel->IsChecked()); refresh_print(); #else m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked(); @@ -638,7 +645,7 @@ void Preview::on_checkbox_travel(wxCommandEvent& evt) void Preview::on_checkbox_retractions(wxCommandEvent& evt) { #if ENABLE_GCODE_VIEWER - m_canvas->set_toolpath_visible(GCodeProcessor::EMoveType::Retract, m_checkbox_retractions->IsChecked()); + m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract, m_checkbox_retractions->IsChecked()); #else m_gcode_preview_data->retraction.is_visible = m_checkbox_retractions->IsChecked(); #endif // ENABLE_GCODE_VIEWER @@ -648,7 +655,7 @@ void Preview::on_checkbox_retractions(wxCommandEvent& evt) void Preview::on_checkbox_unretractions(wxCommandEvent& evt) { #if ENABLE_GCODE_VIEWER - m_canvas->set_toolpath_visible(GCodeProcessor::EMoveType::Unretract, m_checkbox_unretractions->IsChecked()); + m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract, m_checkbox_unretractions->IsChecked()); #else m_gcode_preview_data->unretraction.is_visible = m_checkbox_unretractions->IsChecked(); #endif // ENABLE_GCODE_VIEWER From 83816afb3f9a09050769e5896d50de2f9205fa72 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Apr 2020 13:28:25 +0200 Subject: [PATCH 021/826] GCodeViewer -> Added bounding box to fix camera frustum tighting --- src/slic3r/GUI/GCodeViewer.cpp | 4 +++- src/slic3r/GUI/GCodeViewer.hpp | 2 ++ src/slic3r/GUI/GLCanvas3D.cpp | 9 +++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index fb899164d3..e458d933bb 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -104,6 +104,7 @@ void GCodeViewer::reset() buffer.reset(); } + m_bounding_box = BoundingBoxf3(); m_extrusions.reset_role_visibility_flags(); m_shells.volumes.clear(); m_layers_zs = std::vector(); @@ -198,13 +199,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (m_vertices.vertices_count == 0) return; - // vertex data -> extract from result + // vertex data / bounding box -> extract from result std::vector vertices_data; for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { for (int j = 0; j < 3; ++j) { vertices_data.insert(vertices_data.end(), move.position[j]); + m_bounding_box.merge(move.position.cast()); } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 1020606b86..aff2a807dd 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -97,6 +97,7 @@ public: private: VBuffer m_vertices; std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + BoundingBoxf3 m_bounding_box; unsigned int m_last_result_id{ 0 }; std::vector m_layers_zs; @@ -117,6 +118,7 @@ public: void reset(); void render() const; + const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::vector& get_layers_zs() const { return m_layers_zs; }; EViewType get_view_type() const { return m_view_type; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fcefc8f8a8..604f98a79c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2841,9 +2841,8 @@ void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result } out.close(); } - - m_gcode_viewer.load(gcode_result , *this->fff_print(), m_initialized); #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT + m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); } #endif // ENABLE_GCODE_VIEWER @@ -5213,6 +5212,12 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be #else bb.merge(m_bed.get_bounding_box(include_bed_model)); #endif // ENABLE_NON_STATIC_CANVAS_MANAGER + +#if ENABLE_GCODE_VIEWER + if (!m_main_toolbar.is_enabled()) + bb.merge(m_gcode_viewer.get_bounding_box()); +#endif // ENABLE_GCODE_VIEWER + return bb; } From 3a07e8730f0888fd3a401aa375d19b073e08c865 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 18 Apr 2020 10:41:37 +0200 Subject: [PATCH 022/826] GCodeViewer -> Basic legend using imgui --- src/libslic3r/ExtrusionEntity.cpp | 4 ++ src/slic3r/GUI/GCodeViewer.cpp | 85 +++++++++++++++++++++++++++---- src/slic3r/GUI/GCodeViewer.hpp | 6 +++ src/slic3r/GUI/GLCanvas3D.cpp | 4 ++ 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index c0d08c84b7..0b09d16020 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -306,7 +306,11 @@ double ExtrusionLoop::min_mm3_per_mm() const std::string ExtrusionEntity::role_to_string(ExtrusionRole role) { switch (role) { +#if ENABLE_GCODE_VIEWER + case erNone : return L("Unknown"); +#else case erNone : return L("None"); +#endif // ENABLE_GCODE_VIEWER case erPerimeter : return L("Perimeter"); case erExternalPerimeter : return L("External perimeter"); case erOverhangPerimeter : return L("Overhang perimeter"); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e458d933bb..be05030da2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -6,6 +6,8 @@ #include "GUI_App.hpp" #include "PresetBundle.hpp" #include "Camera.hpp" +#include "I18N.hpp" +#include "libslic3r/I18N.hpp" #include #include @@ -108,6 +110,7 @@ void GCodeViewer::reset() m_extrusions.reset_role_visibility_flags(); m_shells.volumes.clear(); m_layers_zs = std::vector(); + m_roles = std::vector(); } void GCodeViewer::render() const @@ -115,6 +118,7 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); render_shells(); + render_overlay(); } bool GCodeViewer::is_toolpath_visible(GCodeProcessor::EMoveType type) const @@ -277,17 +281,17 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } - // layers zs -> extract from result + // layers zs / roles -> extract from result for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { if (move.type == GCodeProcessor::EMoveType::Extrude) m_layers_zs.emplace_back(move.position[2]); + + m_roles.emplace_back(move.extrusion_role); } - // layers zs -> sort - std::sort(m_layers_zs.begin(), m_layers_zs.end()); - // layers zs -> replace intervals of layers with similar top positions with their average value. + std::sort(m_layers_zs.begin(), m_layers_zs.end()); int n = int(m_layers_zs.size()); int k = 0; for (int i = 0; i < n;) { @@ -300,6 +304,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (k < n) m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); + // roles -> remove duplicates + std::sort(m_roles.begin(), m_roles.end()); + m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); + auto end_time = std::chrono::high_resolution_clock::now(); std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; } @@ -363,11 +371,7 @@ void GCodeViewer::render_toolpaths() const { case EViewType::FeatureType: { - unsigned int color_id = static_cast(path.role); - if (color_id >= erCount) - color_id = 0; - - color = m_extrusions.role_colors[color_id]; + color = m_extrusions.role_colors[static_cast(path.role)]; break; } case EViewType::Height: @@ -506,6 +510,69 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); } +void GCodeViewer::render_overlay() const +{ + static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); + static const float ICON_SIZE = 20.0f; + static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + + if (!m_legend_enabled || m_roles.empty()) + return; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + imgui.set_next_window_pos(0, 0, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + imgui.begin(_L("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + switch (m_view_type) + { + case EViewType::FeatureType: { imgui.text(Slic3r::I18N::translate(L("Feature type"))); break; } + case EViewType::Height: { imgui.text(Slic3r::I18N::translate(L("Height (mm)"))); break; } + case EViewType::Width: { imgui.text(Slic3r::I18N::translate(L("Width (mm)"))); break; } + case EViewType::Feedrate: { imgui.text(Slic3r::I18N::translate(L("Speed (mm/s)"))); break; } + case EViewType::FanSpeed: { imgui.text(Slic3r::I18N::translate(L("Fan Speed (%)"))); break; } + case EViewType::VolumetricRate: { imgui.text(Slic3r::I18N::translate(L("Volumetric flow rate (mm³/s)"))); break; } + case EViewType::Tool: { imgui.text(Slic3r::I18N::translate(L("Tool"))); break; } + case EViewType::ColorPrint: { imgui.text(Slic3r::I18N::translate(L("Color Print"))); break; } + } + ImGui::PopStyleColor(); + + ImGui::Separator(); + + switch (m_view_type) + { + case EViewType::FeatureType: + { + for (ExtrusionRole role : m_roles) + { + ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); + draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_SIZE, pos.y + ICON_SIZE), ICON_BORDER_COLOR, 0.0f, 0); + const std::array& role_color = m_extrusions.role_colors[static_cast(role)]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(role_color[0], role_color[1], role_color[2], role_color[3])); + draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_SIZE - 1.0f, pos.y + ICON_SIZE - 1.0f), fill_color); + ImGui::SetCursorPosX(pos.x + ICON_SIZE + 4.0f); + ImGui::AlignTextToFramePadding(); + imgui.text(Slic3r::I18N::translate(ExtrusionEntity::role_to_string(role))); + } + break; + } + case EViewType::Height: { break; } + case EViewType::Width: { break; } + case EViewType::Feedrate: { break; } + case EViewType::FanSpeed: { break; } + case EViewType::VolumetricRate: { break; } + case EViewType::Tool: { break; } + case EViewType::ColorPrint: { break; } + } + + imgui.end(); + ImGui::PopStyleVar(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index aff2a807dd..4ee187144c 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -101,9 +101,11 @@ private: unsigned int m_last_result_id{ 0 }; std::vector m_layers_zs; + std::vector m_roles; Extrusions m_extrusions; Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; + bool m_legend_enabled{ true }; public: GCodeViewer() = default; @@ -136,12 +138,16 @@ public: bool are_shells_visible() const { return m_shells.visible; } void set_shells_visible(bool visible) { m_shells.visible = visible; } + bool is_legend_enabled() const { return m_legend_enabled; } + void enable_legend(bool enable) { m_legend_enabled = enable; } + private: bool init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void render_toolpaths() const; void render_shells() const; + void render_overlay() const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 604f98a79c..ac773102ce 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1941,7 +1941,11 @@ void GLCanvas3D::enable_layers_editing(bool enable) void GLCanvas3D::enable_legend_texture(bool enable) { +#if ENABLE_GCODE_VIEWER + m_gcode_viewer.enable_legend(enable); +#else m_legend_texture_enabled = enable; +#endif // ENABLE_GCODE_VIEWER } void GLCanvas3D::enable_picking(bool enable) From 179dbc7d0e10c6a7d76f1a0438de78d19e3d4e03 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 18 Apr 2020 11:49:20 +0200 Subject: [PATCH 023/826] Tech ENABLE_GCODE_VIEWER -> removed legend texture from GLCanvas3D --- src/slic3r/GUI/GCodeViewer.cpp | 8 ++++---- src/slic3r/GUI/GLCanvas3D.cpp | 14 ++++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 12 ++++++++++++ src/slic3r/GUI/GUI_Preview.cpp | 5 ++++- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index be05030da2..1c828198da 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -513,7 +513,7 @@ void GCodeViewer::render_shells() const void GCodeViewer::render_overlay() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - static const float ICON_SIZE = 20.0f; + static const float ICON_BORDER_SIZE = 20.0f; static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); if (!m_legend_enabled || m_roles.empty()) @@ -550,11 +550,11 @@ void GCodeViewer::render_overlay() const for (ExtrusionRole role : m_roles) { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); - draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_SIZE, pos.y + ICON_SIZE), ICON_BORDER_COLOR, 0.0f, 0); + draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); const std::array& role_color = m_extrusions.role_colors[static_cast(role)]; ImU32 fill_color = ImGui::GetColorU32(ImVec4(role_color[0], role_color[1], role_color[2], role_color[3])); - draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_SIZE - 1.0f, pos.y + ICON_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_SIZE + 4.0f); + draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + 4.0f); ImGui::AlignTextToFramePadding(); imgui.text(Slic3r::I18N::translate(ExtrusionEntity::role_to_string(role))); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ac773102ce..8c3af4a587 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -901,6 +901,7 @@ void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas) generate(m_msg_text, canvas, true, m_is_colored_red); } +#if !ENABLE_GCODE_VIEWER const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; const unsigned char GLCanvas3D::LegendTexture::Default_Background_Color[3] = { (unsigned char)(DEFAULT_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[2] * 255.0f) }; const unsigned char GLCanvas3D::LegendTexture::Error_Background_Color[3] = { (unsigned char)(ERROR_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[2] * 255.0f) }; @@ -1267,6 +1268,7 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); } } +#endif // !ENABLE_GCODE_VIEWER void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const { @@ -1574,7 +1576,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_dirty(true) , m_initialized(false) , m_apply_zoom_to_volumes_filter(false) +#if !ENABLE_GCODE_VIEWER , m_legend_texture_enabled(false) +#endif // !ENABLE_GCODE_VIEWER , m_picking_enabled(false) , m_moving_enabled(false) , m_dynamic_background_enabled(false) @@ -2953,6 +2957,7 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c _update_toolpath_volumes_outside_state(); _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); +#if !ENABLE_GCODE_VIEWER if (color_print_values.empty()) reset_legend_texture(); else { @@ -2961,6 +2966,7 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c const std::vector tool_colors = _parse_colors(str_tool_colors); _generate_legend_texture(preview_data, tool_colors); } +#endif // !ENABLE_GCODE_VIEWER } void GLCanvas3D::bind_event_handlers() @@ -4080,6 +4086,7 @@ Vec2d GLCanvas3D::get_local_mouse_position() const return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y); } +#if !ENABLE_GCODE_VIEWER void GLCanvas3D::reset_legend_texture() { if (m_legend_texture.get_id() != 0) @@ -4088,6 +4095,7 @@ void GLCanvas3D::reset_legend_texture() m_legend_texture.reset(); } } +#endif // !ENABLE_GCODE_VIEWER void GLCanvas3D::set_tooltip(const std::string& tooltip) const { @@ -5556,7 +5564,9 @@ void GLCanvas3D::_render_overlays() const _render_gizmos_overlay(); _render_warning_texture(); +#if !ENABLE_GCODE_VIEWER _render_legend_texture(); +#endif // !ENABLE_GCODE_VIEWER // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed // to correctly place them @@ -5600,6 +5610,7 @@ void GLCanvas3D::_render_warning_texture() const m_warning_texture.render(*this); } +#if !ENABLE_GCODE_VIEWER void GLCanvas3D::_render_legend_texture() const { if (!m_legend_texture_enabled) @@ -5607,6 +5618,7 @@ void GLCanvas3D::_render_legend_texture() const m_legend_texture.render(*this); } +#endif // !ENABLE_GCODE_VIEWER void GLCanvas3D::_render_volumes_for_picking() const { @@ -7096,10 +7108,12 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col return output; } +#if !ENABLE_GCODE_VIEWER void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) { m_legend_texture.generate(preview_data, tool_colors, *this, true); } +#endif // !ENABLE_GCODE_VIEWER void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index fce805b51d..af1f6149c8 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -345,6 +345,7 @@ private: bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false); }; +#if !ENABLE_GCODE_VIEWER class LegendTexture : public GUI::GLTexture { static const int Px_Title_Offset = 5; @@ -371,6 +372,7 @@ private: void render(const GLCanvas3D& canvas) const; }; +#endif // !ENABLE_GCODE_VIEWER #if ENABLE_RENDER_STATISTICS struct RenderStats @@ -445,7 +447,9 @@ private: std::unique_ptr m_retina_helper; #endif bool m_in_render; +#if !ENABLE_GCODE_VIEWER LegendTexture m_legend_texture; +#endif // !ENABLE_GCODE_VIEWER WarningTexture m_warning_texture; wxTimer m_timer; #if !ENABLE_NON_STATIC_CANVAS_MANAGER @@ -483,7 +487,9 @@ private: bool m_initialized; bool m_apply_zoom_to_volumes_filter; mutable std::vector m_hover_volume_idxs; +#if !ENABLE_GCODE_VIEWER bool m_legend_texture_enabled; +#endif // !ENABLE_GCODE_VIEWER bool m_picking_enabled; bool m_moving_enabled; bool m_dynamic_background_enabled; @@ -679,7 +685,9 @@ public: Size get_canvas_size() const; Vec2d get_local_mouse_position() const; +#if !ENABLE_GCODE_VIEWER void reset_legend_texture(); +#endif // !ENABLE_GCODE_VIEWER void set_tooltip(const std::string& tooltip) const; @@ -790,7 +798,9 @@ private: #endif // ENABLE_RENDER_SELECTION_CENTER void _render_overlays() const; void _render_warning_texture() const; +#if !ENABLE_GCODE_VIEWER void _render_legend_texture() const; +#endif // !ENABLE_GCODE_VIEWER void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; @@ -852,8 +862,10 @@ private: void _update_sla_shells_outside_state(); void _show_warning_texture_if_needed(WarningTexture::Warning warning); +#if !ENABLE_GCODE_VIEWER // generates the legend texture in dependence of the current shown view type void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors); +#endif // !ENABLE_GCODE_VIEWER // generates a warning texture containing the given message void _set_warning_texture(WarningTexture::Warning warning, bool state); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6b13304c0e..562058dd95 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -479,8 +479,9 @@ void Preview::reload_print(bool keep_volumes) m_canvas->reset_volumes(); #if ENABLE_GCODE_VIEWER m_canvas->reset_gcode_toolpaths(); -#endif // ENABLE_GCODE_VIEWER +#else m_canvas->reset_legend_texture(); +#endif // ENABLE_GCODE_VIEWER m_loaded = false; #ifdef __linux__ m_volumes_cleanup_required = false; @@ -937,7 +938,9 @@ void Preview::load_print_as_fff(bool keep_z_range) if (! has_layers) { reset_sliders(true); +#if !ENABLE_GCODE_VIEWER m_canvas->reset_legend_texture(); +#endif // !ENABLE_GCODE_VIEWER m_canvas_widget->Refresh(); return; } From 6e5a6f3b43f7a29c6bd0d79207068298772c5be4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Apr 2020 10:52:16 +0200 Subject: [PATCH 024/826] GCodeViewer -> Extrusion toolpaths colored by height --- src/libslic3r/GCode/PreviewData.hpp | 11 ++-- src/slic3r/GUI/GCodeViewer.cpp | 88 +++++++++++++++++++++++++---- src/slic3r/GUI/GCodeViewer.hpp | 58 ++++++++++++++++++- 3 files changed, 137 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index 35bbfa50ac..c0f768088e 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -56,8 +56,7 @@ public: // Color mapping to convert a float into a smooth rainbow of 10 colors. class RangeBase { - public: - + public: virtual void reset() = 0; virtual bool empty() const = 0; virtual float min() const = 0; @@ -73,7 +72,7 @@ public: // Color mapping converting a float in a range between a min and a max into a smooth rainbow of 10 colors. class Range : public RangeBase { - public: + public: Range(); // RangeBase Overrides @@ -97,8 +96,7 @@ public: template class MultiRange : public RangeBase { - public: - + public: void reset() override { bounds = decltype(bounds){}; @@ -160,8 +158,7 @@ public: mode.set(static_cast(range_type_value), enable); } - private: - + private: // Interval bounds struct Bounds { diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1c828198da..10282642c3 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -59,14 +59,38 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con return true; } -void GCodeViewer::IBuffer::add_path(GCodeProcessor::EMoveType type, ExtrusionRole role) +void GCodeViewer::IBuffer::add_path(GCodeProcessor::EMoveType type, ExtrusionRole role, float height) { unsigned int id = static_cast(data.size()); - paths.push_back({ type, role, id, id }); + paths.push_back({ type, role, id, id, height }); +} + +std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const +{ + // Input value scaled to the color range + const float step = step_size(); + const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f + + const size_t color_max_idx = colors.size() - 1; + + // Compute the two colors just below (low) and above (high) the input value + const size_t color_low_idx = std::clamp(static_cast(global_t), std::size_t{ 0 }, color_max_idx); + const size_t color_high_idx = std::clamp(color_low_idx + 1, std::size_t{ 0 }, color_max_idx); + + // Compute how far the value is between the low and high colors so that they can be interpolated + const float local_t = std::min(global_t - static_cast(color_low_idx), 1.0f); // upper limit of 1.0f + + // Interpolate between the low and high colors in RGB space to find exactly which color the input value should get + std::array ret; + for (unsigned int i = 0; i < 4; ++i) + { + ret[i] = lerp(colors[color_low_idx][i], colors[color_high_idx][i], local_t); + } + return ret; } const std::array, erCount> GCodeViewer::Default_Extrusion_Role_Colors {{ - { 0.00f, 0.00f, 0.00f, 1.0f }, // erNone + { 1.00f, 1.00f, 1.00f, 1.0f }, // erNone { 1.00f, 1.00f, 0.40f, 1.0f }, // erPerimeter { 1.00f, 0.65f, 0.00f, 1.0f }, // erExternalPerimeter { 0.00f, 0.00f, 1.00f, 1.0f }, // erOverhangPerimeter @@ -83,6 +107,19 @@ const std::array, erCount> GCodeViewer::Default_Extrusion_R { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed }}; +const std::array, GCodeViewer::Default_Range_Colors_Count> GCodeViewer::Default_Range_Colors {{ + { 0.043f, 0.173f, 0.478f, 1.0f }, + { 0.075f, 0.349f, 0.522f, 1.0f }, + { 0.110f, 0.533f, 0.569f, 1.0f }, + { 0.016f, 0.839f, 0.059f, 1.0f }, + { 0.667f, 0.949f, 0.000f, 1.0f }, + { 0.988f, 0.975f, 0.012f, 1.0f }, + { 0.961f, 0.808f, 0.039f, 1.0f }, + { 0.890f, 0.533f, 0.125f, 1.0f }, + { 0.820f, 0.408f, 0.188f, 1.0f }, + { 0.761f, 0.322f, 0.235f, 1.0f } +}}; + void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) { if (m_last_result_id == gcode_result.id) @@ -108,6 +145,7 @@ void GCodeViewer::reset() m_bounding_box = BoundingBoxf3(); m_extrusions.reset_role_visibility_flags(); + m_extrusions.reset_ranges(); m_shells.volumes.clear(); m_layers_zs = std::vector(); m_roles = std::vector(); @@ -241,17 +279,22 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { - buffer.add_path(curr.type, curr.extrusion_role); + buffer.add_path(curr.type, curr.extrusion_role, curr.height); buffer.data.push_back(static_cast(i)); break; } case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { - if (prev.type != curr.type) + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr.type, curr.extrusion_role); + buffer.add_path(curr.type, curr.extrusion_role, curr.height); buffer.data.push_back(static_cast(i - 1)); + + if (curr.type == GCodeProcessor::EMoveType::Extrude) + { + m_extrusions.ranges.height.update_from(curr.height); + } } buffer.paths.back().last = static_cast(buffer.data.size()); @@ -375,6 +418,10 @@ void GCodeViewer::render_toolpaths() const break; } case EViewType::Height: + { + color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); + break; + } case EViewType::Width: case EViewType::Feedrate: case EViewType::FanSpeed: @@ -513,8 +560,9 @@ void GCodeViewer::render_shells() const void GCodeViewer::render_overlay() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - static const float ICON_BORDER_SIZE = 20.0f; + static const float ICON_BORDER_SIZE = 25.0f; static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + static const float GAP_ICON_TEXT = 5.0f; if (!m_legend_enabled || m_roles.empty()) return; @@ -551,16 +599,34 @@ void GCodeViewer::render_overlay() const { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& role_color = m_extrusions.role_colors[static_cast(role)]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(role_color[0], role_color[1], role_color[2], role_color[3])); + const std::array& color = m_extrusions.role_colors[static_cast(role)]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + 4.0f); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); ImGui::AlignTextToFramePadding(); imgui.text(Slic3r::I18N::translate(ExtrusionEntity::role_to_string(role))); } break; } - case EViewType::Height: { break; } + case EViewType::Height: + { + float step_size = m_extrusions.ranges.height.step_size(); + for (int i = Default_Range_Colors_Count - 1; i >= 0; --i) + { + ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); + draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); + const std::array& color = m_extrusions.ranges.colors[i]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); + draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); + ImGui::AlignTextToFramePadding(); + char buf[1024]; + ::sprintf(buf, "%.*f", 3, m_extrusions.ranges.height.min + static_cast(i) * step_size); + imgui.text(buf); + } + + break; + } case EViewType::Width: { break; } case EViewType::Feedrate: { break; } case EViewType::FanSpeed: { break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 4ee187144c..5185f54090 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -7,6 +7,8 @@ #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" +#include + namespace Slic3r { class Print; namespace GUI { @@ -14,6 +16,8 @@ namespace GUI { class GCodeViewer { static const std::array, erCount> Default_Extrusion_Role_Colors; + static const size_t Default_Range_Colors_Count = 10; + static const std::array, Default_Range_Colors_Count> Default_Range_Colors; // buffer containing vertices data struct VBuffer @@ -29,14 +33,16 @@ class GCodeViewer static size_t vertex_size_bytes() { return vertex_size() * sizeof(float); } }; + // Used to identify different toolpath sub-types inside a IBuffer struct Path { GCodeProcessor::EMoveType type{ GCodeProcessor::EMoveType::Noop }; ExtrusionRole role{ erNone }; unsigned int first{ 0 }; unsigned int last{ 0 }; + float height{ 0.0f }; - bool matches(GCodeProcessor::EMoveType type, ExtrusionRole role) const { return this->type == type && this->role == role; } + bool matches(const GCodeProcessor::MoveVertex& move) const { return type == move.type && role == move.extrusion_role && height == move.height; } }; // buffer containing indices data and shader for a specific toolpath type @@ -52,7 +58,7 @@ class GCodeViewer void reset(); bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); - void add_path(GCodeProcessor::EMoveType type, ExtrusionRole role); + void add_path(GCodeProcessor::EMoveType type, ExtrusionRole role, float height); }; struct Shells @@ -62,10 +68,55 @@ class GCodeViewer Shader shader; }; + // helper to render extrusion paths struct Extrusions { + struct Range + { + float min; + float max; + + Range() { reset(); } + + void update_from(const float value) + { + min = std::min(min, value); + max = std::max(max, value); + } + + void reset() + { + min = FLT_MAX; + max = -FLT_MAX; + } + + float step_size() const { return (max - min) / static_cast(Default_Range_Colors_Count); } + std::array get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const; + }; + + struct Ranges + { + std::array, Default_Range_Colors_Count> colors; + + // Color mapping by layer height. + Range height; +// // Color mapping by extrusion width. +// Range width; +// // Color mapping by feedrate. +// MultiRange feedrate; +// // Color mapping by fan speed. +// Range fan_speed; +// // Color mapping by volumetric extrusion rate. +// Range volumetric_rate; + + void reset() { + height.reset(); + } + }; + std::array, erCount> role_colors; unsigned int role_visibility_flags{ 0 }; + Ranges ranges; void reset_role_visibility_flags() { role_visibility_flags = 0; @@ -75,6 +126,8 @@ class GCodeViewer } } + void reset_ranges() { ranges.reset(); } + static bool is_role_visible(unsigned int flags, ExtrusionRole role) { return role < erCount && (flags & (1 << role)) != 0; } @@ -113,6 +166,7 @@ public: bool init() { m_extrusions.role_colors = Default_Extrusion_Role_Colors; + m_extrusions.ranges.colors = Default_Range_Colors; set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); } From aee80dbd01ff0ba8d2ae7a3881e3fd21d22835cd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Apr 2020 13:24:25 +0200 Subject: [PATCH 025/826] GCodeViewer -> Extrusion toolpaths colored by width --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 75 +++++++++++++++++++--------------- src/slic3r/GUI/GCodeViewer.hpp | 15 ++++--- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 51d5b0fa70..1ed939ba3e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,7 +58,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -#define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (1 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 10282642c3..b240d76f55 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -59,10 +59,10 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con return true; } -void GCodeViewer::IBuffer::add_path(GCodeProcessor::EMoveType type, ExtrusionRole role, float height) +void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ type, role, id, id, height }); + paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const @@ -74,8 +74,8 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value, c const size_t color_max_idx = colors.size() - 1; // Compute the two colors just below (low) and above (high) the input value - const size_t color_low_idx = std::clamp(static_cast(global_t), std::size_t{ 0 }, color_max_idx); - const size_t color_high_idx = std::clamp(color_low_idx + 1, std::size_t{ 0 }, color_max_idx); + const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); + const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); // Compute how far the value is between the low and high colors so that they can be interpolated const float local_t = std::min(global_t - static_cast(color_low_idx), 1.0f); // upper limit of 1.0f @@ -279,7 +279,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { - buffer.add_path(curr.type, curr.extrusion_role, curr.height); + buffer.add_path(curr); buffer.data.push_back(static_cast(i)); break; } @@ -288,12 +288,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr.type, curr.extrusion_role, curr.height); + buffer.add_path(curr); buffer.data.push_back(static_cast(i - 1)); if (curr.type == GCodeProcessor::EMoveType::Extrude) { m_extrusions.ranges.height.update_from(curr.height); + m_extrusions.ranges.width.update_from(curr.width); } } @@ -412,17 +413,9 @@ void GCodeViewer::render_toolpaths() const std::array color; switch (m_view_type) { - case EViewType::FeatureType: - { - color = m_extrusions.role_colors[static_cast(path.role)]; - break; - } - case EViewType::Height: - { - color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); - break; - } - case EViewType::Width: + case EViewType::FeatureType: { color = m_extrusions.role_colors[static_cast(path.role)]; break; } + case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); break; } + case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width, m_extrusions.ranges.colors); break; } case EViewType::Feedrate: case EViewType::FanSpeed: case EViewType::VolumetricRate: @@ -575,6 +568,32 @@ void GCodeViewer::render_overlay() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); + auto add_range = [this, draw_list, &imgui](const Extrusions::Range& range) { + auto add_item = [this, draw_list, &imgui](int i, float value) { + ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); + draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); + const std::array& color = m_extrusions.ranges.colors[i]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); + draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); + ImGui::AlignTextToFramePadding(); + char buf[1024]; + ::sprintf(buf, "%.*f", 3, value); + imgui.text(buf); + }; + + float step_size = range.step_size(); + if (step_size == 0.0f) + add_item(0, range.min); + else + { + for (int i = Default_Range_Colors_Count - 1; i >= 0; --i) + { + add_item(i, range.min + static_cast(i) * step_size); + } + } + }; + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); switch (m_view_type) { @@ -610,24 +629,14 @@ void GCodeViewer::render_overlay() const } case EViewType::Height: { - float step_size = m_extrusions.ranges.height.step_size(); - for (int i = Default_Range_Colors_Count - 1; i >= 0; --i) - { - ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); - draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = m_extrusions.ranges.colors[i]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); - draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); - ImGui::AlignTextToFramePadding(); - char buf[1024]; - ::sprintf(buf, "%.*f", 3, m_extrusions.ranges.height.min + static_cast(i) * step_size); - imgui.text(buf); - } - + add_range(m_extrusions.ranges.height); + break; + } + case EViewType::Width: + { + add_range(m_extrusions.ranges.width); break; } - case EViewType::Width: { break; } case EViewType::Feedrate: { break; } case EViewType::FanSpeed: { break; } case EViewType::VolumetricRate: { break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5185f54090..342f3ba901 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -41,8 +41,11 @@ class GCodeViewer unsigned int first{ 0 }; unsigned int last{ 0 }; float height{ 0.0f }; + float width{ 0.0f }; - bool matches(const GCodeProcessor::MoveVertex& move) const { return type == move.type && role == move.extrusion_role && height == move.height; } + bool matches(const GCodeProcessor::MoveVertex& move) const { + return type == move.type && role == move.extrusion_role && height == move.height && width == move.width; + } }; // buffer containing indices data and shader for a specific toolpath type @@ -57,8 +60,7 @@ class GCodeViewer void reset(); bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); - - void add_path(GCodeProcessor::EMoveType type, ExtrusionRole role, float height); + void add_path(const GCodeProcessor::MoveVertex& move); }; struct Shells @@ -90,7 +92,7 @@ class GCodeViewer max = -FLT_MAX; } - float step_size() const { return (max - min) / static_cast(Default_Range_Colors_Count); } + float step_size() const { return (max - min) / (static_cast(Default_Range_Colors_Count) - 1.0f); } std::array get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const; }; @@ -100,8 +102,8 @@ class GCodeViewer // Color mapping by layer height. Range height; -// // Color mapping by extrusion width. -// Range width; + // Color mapping by extrusion width. + Range width; // // Color mapping by feedrate. // MultiRange feedrate; // // Color mapping by fan speed. @@ -111,6 +113,7 @@ class GCodeViewer void reset() { height.reset(); + width.reset(); } }; From dc3c5db9fe6251306159889ffed1b8ba3035aee5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Apr 2020 13:44:14 +0200 Subject: [PATCH 026/826] GCodeViewer -> Use rgb instead of rgba colors --- resources/shaders/extrusions.fs | 4 +- resources/shaders/retractions.fs | 4 +- resources/shaders/toolchanges.fs | 4 +- resources/shaders/travels.fs | 4 +- resources/shaders/unretractions.fs | 4 +- src/slic3r/GUI/GCodeViewer.cpp | 82 +++++++++++++++--------------- src/slic3r/GUI/GCodeViewer.hpp | 10 ++-- 7 files changed, 56 insertions(+), 56 deletions(-) diff --git a/resources/shaders/extrusions.fs b/resources/shaders/extrusions.fs index 046dade8a9..fc81e487fb 100644 --- a/resources/shaders/extrusions.fs +++ b/resources/shaders/extrusions.fs @@ -13,7 +13,7 @@ const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -uniform vec4 uniform_color; +uniform vec3 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; @@ -41,5 +41,5 @@ void main() // if (world_normal_z < 0.0) // intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); } diff --git a/resources/shaders/retractions.fs b/resources/shaders/retractions.fs index 046dade8a9..fc81e487fb 100644 --- a/resources/shaders/retractions.fs +++ b/resources/shaders/retractions.fs @@ -13,7 +13,7 @@ const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -uniform vec4 uniform_color; +uniform vec3 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; @@ -41,5 +41,5 @@ void main() // if (world_normal_z < 0.0) // intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); } diff --git a/resources/shaders/toolchanges.fs b/resources/shaders/toolchanges.fs index 046dade8a9..fc81e487fb 100644 --- a/resources/shaders/toolchanges.fs +++ b/resources/shaders/toolchanges.fs @@ -13,7 +13,7 @@ const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -uniform vec4 uniform_color; +uniform vec3 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; @@ -41,5 +41,5 @@ void main() // if (world_normal_z < 0.0) // intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); } diff --git a/resources/shaders/travels.fs b/resources/shaders/travels.fs index 046dade8a9..fc81e487fb 100644 --- a/resources/shaders/travels.fs +++ b/resources/shaders/travels.fs @@ -13,7 +13,7 @@ const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -uniform vec4 uniform_color; +uniform vec3 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; @@ -41,5 +41,5 @@ void main() // if (world_normal_z < 0.0) // intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); } diff --git a/resources/shaders/unretractions.fs b/resources/shaders/unretractions.fs index 046dade8a9..fc81e487fb 100644 --- a/resources/shaders/unretractions.fs +++ b/resources/shaders/unretractions.fs @@ -13,7 +13,7 @@ const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) -uniform vec4 uniform_color; +uniform vec3 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; @@ -41,5 +41,5 @@ void main() // if (world_normal_z < 0.0) // intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b240d76f55..52d3927982 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -65,7 +65,7 @@ void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width }); } -std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const +std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const { // Input value scaled to the color range const float step = step_size(); @@ -81,43 +81,43 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value, c const float local_t = std::min(global_t - static_cast(color_low_idx), 1.0f); // upper limit of 1.0f // Interpolate between the low and high colors in RGB space to find exactly which color the input value should get - std::array ret; - for (unsigned int i = 0; i < 4; ++i) + std::array ret; + for (unsigned int i = 0; i < 3; ++i) { ret[i] = lerp(colors[color_low_idx][i], colors[color_high_idx][i], local_t); } return ret; } -const std::array, erCount> GCodeViewer::Default_Extrusion_Role_Colors {{ - { 1.00f, 1.00f, 1.00f, 1.0f }, // erNone - { 1.00f, 1.00f, 0.40f, 1.0f }, // erPerimeter - { 1.00f, 0.65f, 0.00f, 1.0f }, // erExternalPerimeter - { 0.00f, 0.00f, 1.00f, 1.0f }, // erOverhangPerimeter - { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill - { 0.84f, 0.20f, 0.84f, 1.0f }, // erSolidInfill - { 1.00f, 0.10f, 0.10f, 1.0f }, // erTopSolidInfill - { 0.60f, 0.60f, 1.00f, 1.0f }, // erBridgeInfill - { 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill - { 0.52f, 0.48f, 0.13f, 1.0f }, // erSkirt - { 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial - { 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface - { 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower - { 0.16f, 0.80f, 0.58f, 1.0f }, // erCustom - { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed +const std::array, erCount> GCodeViewer::Default_Extrusion_Role_Colors {{ + { 1.00f, 1.00f, 1.00f }, // erNone + { 1.00f, 1.00f, 0.40f }, // erPerimeter + { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter + { 0.00f, 0.00f, 1.00f }, // erOverhangPerimeter + { 0.69f, 0.19f, 0.16f }, // erInternalInfill + { 0.84f, 0.20f, 0.84f }, // erSolidInfill + { 1.00f, 0.10f, 0.10f }, // erTopSolidInfill + { 0.60f, 0.60f, 1.00f }, // erBridgeInfill + { 1.00f, 1.00f, 1.00f }, // erGapFill + { 0.52f, 0.48f, 0.13f }, // erSkirt + { 0.00f, 1.00f, 0.00f }, // erSupportMaterial + { 0.00f, 0.50f, 0.00f }, // erSupportMaterialInterface + { 0.70f, 0.89f, 0.67f }, // erWipeTower + { 0.16f, 0.80f, 0.58f }, // erCustom + { 0.00f, 0.00f, 0.00f } // erMixed }}; -const std::array, GCodeViewer::Default_Range_Colors_Count> GCodeViewer::Default_Range_Colors {{ - { 0.043f, 0.173f, 0.478f, 1.0f }, - { 0.075f, 0.349f, 0.522f, 1.0f }, - { 0.110f, 0.533f, 0.569f, 1.0f }, - { 0.016f, 0.839f, 0.059f, 1.0f }, - { 0.667f, 0.949f, 0.000f, 1.0f }, - { 0.988f, 0.975f, 0.012f, 1.0f }, - { 0.961f, 0.808f, 0.039f, 1.0f }, - { 0.890f, 0.533f, 0.125f, 1.0f }, - { 0.820f, 0.408f, 0.188f, 1.0f }, - { 0.761f, 0.322f, 0.235f, 1.0f } +const std::array, GCodeViewer::Default_Range_Colors_Count> GCodeViewer::Default_Range_Colors {{ + { 0.043f, 0.173f, 0.478f }, + { 0.075f, 0.349f, 0.522f }, + { 0.110f, 0.533f, 0.569f }, + { 0.016f, 0.839f, 0.059f }, + { 0.667f, 0.949f, 0.000f }, + { 0.988f, 0.975f, 0.012f }, + { 0.961f, 0.808f, 0.039f }, + { 0.890f, 0.533f, 0.125f }, + { 0.820f, 0.408f, 0.188f }, + { 0.761f, 0.322f, 0.235f } }}; void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) @@ -410,7 +410,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) void GCodeViewer::render_toolpaths() const { auto extrusion_color = [this](const Path& path) { - std::array color; + std::array color; switch (m_view_type) { case EViewType::FeatureType: { color = m_extrusions.role_colors[static_cast(path.role)]; break; } @@ -423,18 +423,18 @@ void GCodeViewer::render_toolpaths() const case EViewType::ColorPrint: default: { - color = { 1.0f, 1.0f, 1.0f, 1.0f }; + color = { 1.0f, 1.0f, 1.0f }; break; } } return color; }; - auto set_color = [](GLint current_program_id, const std::array& color) { + auto set_color = [](GLint current_program_id, const std::array& color) { if (current_program_id > 0) { GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; if (color_id >= 0) { - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); + glsafe(::glUniform3fv(color_id, 1, (const GLfloat*)color.data())); return; } } @@ -478,7 +478,7 @@ void GCodeViewer::render_toolpaths() const { case GCodeProcessor::EMoveType::Tool_change: { - std::array color = { 1.0f, 1.0f, 1.0f, 1.0f }; + std::array color = { 1.0f, 1.0f, 1.0f }; set_color(current_program_id, color); glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); @@ -487,7 +487,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Retract: { - std::array color = { 1.0f, 0.0f, 1.0f, 1.0f }; + std::array color = { 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); @@ -496,7 +496,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Unretract: { - std::array color = { 0.0f, 1.0f, 0.0f, 1.0f }; + std::array color = { 0.0f, 1.0f, 0.0f }; set_color(current_program_id, color); glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); @@ -517,7 +517,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Travel: { - std::array color = { 1.0f, 1.0f, 0.0f, 1.0f }; + std::array color = { 1.0f, 1.0f, 0.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { @@ -572,8 +572,8 @@ void GCodeViewer::render_overlay() const auto add_item = [this, draw_list, &imgui](int i, float value) { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = m_extrusions.ranges.colors[i]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); + const std::array& color = m_extrusions.ranges.colors[i]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0f)); draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); ImGui::AlignTextToFramePadding(); @@ -618,7 +618,7 @@ void GCodeViewer::render_overlay() const { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = m_extrusions.role_colors[static_cast(role)]; + const std::array& color = m_extrusions.role_colors[static_cast(role)]; ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 342f3ba901..39361050b8 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -15,9 +15,9 @@ namespace GUI { class GCodeViewer { - static const std::array, erCount> Default_Extrusion_Role_Colors; + static const std::array, erCount> Default_Extrusion_Role_Colors; static const size_t Default_Range_Colors_Count = 10; - static const std::array, Default_Range_Colors_Count> Default_Range_Colors; + static const std::array, Default_Range_Colors_Count> Default_Range_Colors; // buffer containing vertices data struct VBuffer @@ -93,12 +93,12 @@ class GCodeViewer } float step_size() const { return (max - min) / (static_cast(Default_Range_Colors_Count) - 1.0f); } - std::array get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const; + std::array get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const; }; struct Ranges { - std::array, Default_Range_Colors_Count> colors; + std::array, Default_Range_Colors_Count> colors; // Color mapping by layer height. Range height; @@ -117,7 +117,7 @@ class GCodeViewer } }; - std::array, erCount> role_colors; + std::array, erCount> role_colors; unsigned int role_visibility_flags{ 0 }; Ranges ranges; From 3ba6ac7836606049394704621768c6a26196bd51 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Apr 2020 16:04:59 +0200 Subject: [PATCH 027/826] GCodeViewer -> Extrusion toolpaths colored by feedrate and ranges calculations dependent on travel paths visibility --- src/slic3r/GUI/GCodeViewer.cpp | 82 +++++++++++++++++++++++----------- src/slic3r/GUI/GCodeViewer.hpp | 11 +++-- src/slic3r/GUI/GLCanvas3D.cpp | 7 ++- src/slic3r/GUI/GLCanvas3D.hpp | 3 +- src/slic3r/GUI/GUI_Preview.cpp | 3 +- 5 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 52d3927982..e14969eddc 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -62,7 +62,7 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width }); + paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const @@ -78,7 +78,7 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value, c const size_t color_high_idx = std::clamp(color_low_idx + 1, 0, color_max_idx); // Compute how far the value is between the low and high colors so that they can be interpolated - const float local_t = std::min(global_t - static_cast(color_low_idx), 1.0f); // upper limit of 1.0f + const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); // Interpolate between the low and high colors in RGB space to find exactly which color the input value should get std::array ret; @@ -134,6 +134,42 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& load_shells(print, initialized); } +void GCodeViewer::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result) +{ + if (m_vertices.vertices_count == 0) + return; + + m_extrusions.reset_ranges(); + + for (size_t i = 0; i < m_vertices.vertices_count; ++i) + { + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + switch (curr.type) + { + case GCodeProcessor::EMoveType::Extrude: + case GCodeProcessor::EMoveType::Travel: + { + if (m_buffers[buffer_id(curr.type)].visible) + { + m_extrusions.ranges.height.update_from(curr.height); + m_extrusions.ranges.width.update_from(curr.width); + m_extrusions.ranges.feedrate.update_from(curr.feedrate); + } + break; + } + default: + { + break; + } + } + } +} + void GCodeViewer::reset() { m_vertices.reset(); @@ -290,12 +326,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { buffer.add_path(curr); buffer.data.push_back(static_cast(i - 1)); - - if (curr.type == GCodeProcessor::EMoveType::Extrude) - { - m_extrusions.ranges.height.update_from(curr.height); - m_extrusions.ranges.width.update_from(curr.width); - } } buffer.paths.back().last = static_cast(buffer.data.size()); @@ -413,19 +443,15 @@ void GCodeViewer::render_toolpaths() const std::array color; switch (m_view_type) { - case EViewType::FeatureType: { color = m_extrusions.role_colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width, m_extrusions.ranges.colors); break; } - case EViewType::Feedrate: + case EViewType::FeatureType: { color = m_extrusions.role_colors[static_cast(path.role)]; break; } + case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); break; } + case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width, m_extrusions.ranges.colors); break; } + case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate, m_extrusions.ranges.colors); break; } case EViewType::FanSpeed: case EViewType::VolumetricRate: case EViewType::Tool: case EViewType::ColorPrint: - default: - { - color = { 1.0f, 1.0f, 1.0f }; - break; - } + default: { color = { 1.0f, 1.0f, 1.0f }; break; } } return color; }; @@ -568,8 +594,8 @@ void GCodeViewer::render_overlay() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); - auto add_range = [this, draw_list, &imgui](const Extrusions::Range& range) { - auto add_item = [this, draw_list, &imgui](int i, float value) { + auto add_range = [this, draw_list, &imgui](const Extrusions::Range& range, unsigned int decimals) { + auto add_item = [this, draw_list, &imgui](int i, float value, unsigned int decimals) { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); const std::array& color = m_extrusions.ranges.colors[i]; @@ -578,18 +604,18 @@ void GCodeViewer::render_overlay() const ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); ImGui::AlignTextToFramePadding(); char buf[1024]; - ::sprintf(buf, "%.*f", 3, value); + ::sprintf(buf, "%.*f", decimals, value); imgui.text(buf); }; float step_size = range.step_size(); if (step_size == 0.0f) - add_item(0, range.min); + add_item(0, range.min, decimals); else { for (int i = Default_Range_Colors_Count - 1; i >= 0; --i) { - add_item(i, range.min + static_cast(i) * step_size); + add_item(i, range.min + static_cast(i) * step_size, decimals); } } }; @@ -619,7 +645,7 @@ void GCodeViewer::render_overlay() const ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); const std::array& color = m_extrusions.role_colors[static_cast(role)]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], color[3])); + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0)); draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); ImGui::AlignTextToFramePadding(); @@ -629,15 +655,19 @@ void GCodeViewer::render_overlay() const } case EViewType::Height: { - add_range(m_extrusions.ranges.height); + add_range(m_extrusions.ranges.height, 3); break; } case EViewType::Width: { - add_range(m_extrusions.ranges.width); + add_range(m_extrusions.ranges.width, 3); + break; + } + case EViewType::Feedrate: + { + add_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::Feedrate: { break; } case EViewType::FanSpeed: { break; } case EViewType::VolumetricRate: { break; } case EViewType::Tool: { break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 39361050b8..34b81ad7a6 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -42,9 +42,10 @@ class GCodeViewer unsigned int last{ 0 }; float height{ 0.0f }; float width{ 0.0f }; + float feedrate{ 0.0f }; bool matches(const GCodeProcessor::MoveVertex& move) const { - return type == move.type && role == move.extrusion_role && height == move.height && width == move.width; + return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && feedrate == move.feedrate; } }; @@ -104,8 +105,8 @@ class GCodeViewer Range height; // Color mapping by extrusion width. Range width; -// // Color mapping by feedrate. -// MultiRange feedrate; + // Color mapping by feedrate. + Range feedrate; // // Color mapping by fan speed. // Range fan_speed; // // Color mapping by volumetric extrusion rate. @@ -114,6 +115,7 @@ class GCodeViewer void reset() { height.reset(); width.reset(); + feedrate.reset(); } }; @@ -173,7 +175,10 @@ public: set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); } + void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); + void refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result); + void reset(); void render() const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8c3af4a587..23cccb76f9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2833,7 +2833,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio #endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result) +void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT static unsigned int last_result_id = 0; @@ -2852,6 +2852,11 @@ void GLCanvas3D::load_gcode_preview_2(const GCodeProcessor::Result& gcode_result #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); } + +void GLCanvas3D::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result) +{ + m_gcode_viewer.refresh_toolpaths_ranges(gcode_result); +} #endif // ENABLE_GCODE_VIEWER #if !ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index af1f6149c8..312eeaa3b6 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -664,7 +664,8 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); #if ENABLE_GCODE_VIEWER - void load_gcode_preview_2(const GCodeProcessor::Result& gcode_result); + void load_gcode_preview(const GCodeProcessor::Result& gcode_result); + void refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result); #else void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 562058dd95..2060511491 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -984,7 +984,8 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. #if ENABLE_GCODE_VIEWER - m_canvas->load_gcode_preview_2(*m_gcode_result); + m_canvas->load_gcode_preview(*m_gcode_result); + m_canvas->refresh_toolpaths_ranges(*m_gcode_result); #else m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); #endif // ENABLE_GCODE_VIEWER From 53d758639f5966b524ed6d9a9cb4008123e612f3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 21 Apr 2020 09:06:43 +0200 Subject: [PATCH 028/826] GCodeViewer -> Extrusion toolpaths colored by fan speed --- src/slic3r/GUI/GCodeViewer.cpp | 33 +++++++++++---------------------- src/slic3r/GUI/GCodeViewer.hpp | 8 +++++--- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e14969eddc..4cffb39bd5 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -62,12 +62,12 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate }); + paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const { - // Input value scaled to the color range + // Input value scaled to the colors range const float step = step_size(); const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f @@ -80,7 +80,7 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value, c // Compute how far the value is between the low and high colors so that they can be interpolated const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); - // Interpolate between the low and high colors in RGB space to find exactly which color the input value should get + // Interpolate between the low and high colors to find exactly which color the input value should get std::array ret; for (unsigned int i = 0; i < 3; ++i) { @@ -108,7 +108,7 @@ const std::array, erCount> GCodeViewer::Default_Extrusion_R }}; const std::array, GCodeViewer::Default_Range_Colors_Count> GCodeViewer::Default_Range_Colors {{ - { 0.043f, 0.173f, 0.478f }, + { 0.043f, 0.173f, 0.478f }, // bluish { 0.075f, 0.349f, 0.522f }, { 0.110f, 0.533f, 0.569f }, { 0.016f, 0.839f, 0.059f }, @@ -117,7 +117,7 @@ const std::array, GCodeViewer::Default_Range_Colors_Count> { 0.961f, 0.808f, 0.039f }, { 0.890f, 0.533f, 0.125f }, { 0.820f, 0.408f, 0.188f }, - { 0.761f, 0.322f, 0.235f } + { 0.761f, 0.322f, 0.235f } // reddish }}; void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) @@ -159,6 +159,7 @@ void GCodeViewer::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_r m_extrusions.ranges.height.update_from(curr.height); m_extrusions.ranges.width.update_from(curr.width); m_extrusions.ranges.feedrate.update_from(curr.feedrate); + m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); } break; } @@ -447,7 +448,7 @@ void GCodeViewer::render_toolpaths() const case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); break; } case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width, m_extrusions.ranges.colors); break; } case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate, m_extrusions.ranges.colors); break; } - case EViewType::FanSpeed: + case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed, m_extrusions.ranges.colors); break; } case EViewType::VolumetricRate: case EViewType::Tool: case EViewType::ColorPrint: @@ -653,22 +654,10 @@ void GCodeViewer::render_overlay() const } break; } - case EViewType::Height: - { - add_range(m_extrusions.ranges.height, 3); - break; - } - case EViewType::Width: - { - add_range(m_extrusions.ranges.width, 3); - break; - } - case EViewType::Feedrate: - { - add_range(m_extrusions.ranges.feedrate, 1); - break; - } - case EViewType::FanSpeed: { break; } + case EViewType::Height: { add_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { add_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { add_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { add_range(m_extrusions.ranges.fan_speed, 0); break; } case EViewType::VolumetricRate: { break; } case EViewType::Tool: { break; } case EViewType::ColorPrint: { break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 34b81ad7a6..644f088424 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -43,9 +43,10 @@ class GCodeViewer float height{ 0.0f }; float width{ 0.0f }; float feedrate{ 0.0f }; + float fan_speed{ 0.0f }; bool matches(const GCodeProcessor::MoveVertex& move) const { - return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && feedrate == move.feedrate; + return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && feedrate == move.feedrate && fan_speed == move.fan_speed; } }; @@ -107,8 +108,8 @@ class GCodeViewer Range width; // Color mapping by feedrate. Range feedrate; -// // Color mapping by fan speed. -// Range fan_speed; + // Color mapping by fan speed. + Range fan_speed; // // Color mapping by volumetric extrusion rate. // Range volumetric_rate; @@ -116,6 +117,7 @@ class GCodeViewer height.reset(); width.reset(); feedrate.reset(); + fan_speed.reset(); } }; From 443a511420523a68cf4517fa158405358c1f6f97 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 21 Apr 2020 11:38:42 +0200 Subject: [PATCH 029/826] GCodeViewer -> Extrusion toolpaths colored by volumetric rate --- src/libslic3r/GCode/GCodeProcessor.hpp | 2 + src/slic3r/GUI/GCodeViewer.cpp | 72 ++++++++++---------------- src/slic3r/GUI/GCodeViewer.hpp | 22 +++----- 3 files changed, 36 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 623a2ae3bd..38adce3329 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -80,6 +80,8 @@ namespace Slic3r { float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage + float volumetric_rate() const { return feedrate * mm3_per_mm; } + std::string to_string() const { std::string str = std::to_string((int)type); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 4cffb39bd5..e02a809934 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -31,7 +31,10 @@ void GCodeViewer::VBuffer::reset() { // release gpu memory if (vbo_id > 0) + { glsafe(::glDeleteBuffers(1, &vbo_id)); + vbo_id = 0; + } vertices_count = 0; } @@ -40,7 +43,10 @@ void GCodeViewer::IBuffer::reset() { // release gpu memory if (ibo_id > 0) + { glsafe(::glDeleteBuffers(1, &ibo_id)); + ibo_id = 0; + } // release cpu memory data = std::vector(); @@ -62,7 +68,7 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed }); + paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate() }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const @@ -160,6 +166,7 @@ void GCodeViewer::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_r m_extrusions.ranges.width.update_from(curr.width); m_extrusions.ranges.feedrate.update_from(curr.feedrate); m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); + m_extrusions.ranges.volumetric_rate.update_from(curr.volumetric_rate()); } break; } @@ -216,48 +223,21 @@ bool GCodeViewer::init_shaders() for (unsigned char i = begin_id; i < end_id; ++i) { + std::string vertex_shader; + std::string fragment_shader; + switch (buffer_type(i)) { - case GCodeProcessor::EMoveType::Tool_change: - { - if (!m_buffers[i].init_shader("toolchanges.vs", "toolchanges.fs")) - return false; + case GCodeProcessor::EMoveType::Tool_change: { vertex_shader = "toolchanges.vs"; fragment_shader = "toolchanges.fs"; break; } + case GCodeProcessor::EMoveType::Retract: { vertex_shader = "retractions.vs"; fragment_shader = "retractions.fs"; break; } + case GCodeProcessor::EMoveType::Unretract: { vertex_shader = "unretractions.vs"; fragment_shader = "unretractions.fs"; break; } + case GCodeProcessor::EMoveType::Extrude: { vertex_shader = "extrusions.vs"; fragment_shader = "extrusions.fs"; break; } + case GCodeProcessor::EMoveType::Travel: { vertex_shader = "travels.vs"; fragment_shader = "travels.fs"; break; } + default: { break; } + } - break; - } - case GCodeProcessor::EMoveType::Retract: - { - if (!m_buffers[i].init_shader("retractions.vs", "retractions.fs")) - return false; - - break; - } - case GCodeProcessor::EMoveType::Unretract: - { - if (!m_buffers[i].init_shader("unretractions.vs", "unretractions.fs")) - return false; - - break; - } - case GCodeProcessor::EMoveType::Extrude: - { - if (!m_buffers[i].init_shader("extrusions.vs", "extrusions.fs")) - return false; - - break; - } - case GCodeProcessor::EMoveType::Travel: - { - if (!m_buffers[i].init_shader("travels.vs", "travels.fs")) - return false; - - break; - } - default: - { - break; - } - } + if (vertex_shader.empty() || fragment_shader.empty() || !m_buffers[i].init_shader(vertex_shader, fragment_shader)) + return false; } if (!m_shells.shader.init("shells.vs", "shells.fs")) @@ -449,7 +429,7 @@ void GCodeViewer::render_toolpaths() const case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width, m_extrusions.ranges.colors); break; } case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate, m_extrusions.ranges.colors); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed, m_extrusions.ranges.colors); break; } - case EViewType::VolumetricRate: + case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate, m_extrusions.ranges.colors); break; } case EViewType::Tool: case EViewType::ColorPrint: default: { color = { 1.0f, 1.0f, 1.0f }; break; } @@ -654,11 +634,11 @@ void GCodeViewer::render_overlay() const } break; } - case EViewType::Height: { add_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { add_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { add_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { add_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::VolumetricRate: { break; } + case EViewType::Height: { add_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { add_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { add_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { add_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::VolumetricRate: { add_range(m_extrusions.ranges.volumetric_rate, 3); break; } case EViewType::Tool: { break; } case EViewType::ColorPrint: { break; } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 644f088424..6f27c68522 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -44,9 +44,11 @@ class GCodeViewer float width{ 0.0f }; float feedrate{ 0.0f }; float fan_speed{ 0.0f }; + float volumetric_rate{ 0.0f }; bool matches(const GCodeProcessor::MoveVertex& move) const { - return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && feedrate == move.feedrate && fan_speed == move.fan_speed; + return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && + feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate(); } }; @@ -82,17 +84,8 @@ class GCodeViewer Range() { reset(); } - void update_from(const float value) - { - min = std::min(min, value); - max = std::max(max, value); - } - - void reset() - { - min = FLT_MAX; - max = -FLT_MAX; - } + void update_from(const float value) { min = std::min(min, value); max = std::max(max, value); } + void reset() { min = FLT_MAX; max = -FLT_MAX; } float step_size() const { return (max - min) / (static_cast(Default_Range_Colors_Count) - 1.0f); } std::array get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const; @@ -110,14 +103,15 @@ class GCodeViewer Range feedrate; // Color mapping by fan speed. Range fan_speed; -// // Color mapping by volumetric extrusion rate. -// Range volumetric_rate; + // Color mapping by volumetric extrusion rate. + Range volumetric_rate; void reset() { height.reset(); width.reset(); feedrate.reset(); fan_speed.reset(); + volumetric_rate.reset(); } }; From 61db595f5328b2b04153217ee63994c5719acd61 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 21 Apr 2020 12:51:58 +0200 Subject: [PATCH 030/826] GCodeViewer -> Refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 29 +++++++++++++++-------------- src/slic3r/GUI/GCodeViewer.hpp | 15 +++++---------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e02a809934..ad15a0bb9d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -71,13 +71,13 @@ void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate() }); } -std::array GCodeViewer::Extrusions::Range::get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const +std::array GCodeViewer::Extrusions::Range::get_color_at(float value) const { // Input value scaled to the colors range const float step = step_size(); const float global_t = (step != 0.0f) ? std::max(0.0f, value - min) / step : 0.0f; // lower limit of 0.0f - const size_t color_max_idx = colors.size() - 1; + const size_t color_max_idx = Range_Colors.size() - 1; // Compute the two colors just below (low) and above (high) the input value const size_t color_low_idx = std::clamp(static_cast(global_t), 0, color_max_idx); @@ -90,12 +90,12 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value, c std::array ret; for (unsigned int i = 0; i < 3; ++i) { - ret[i] = lerp(colors[color_low_idx][i], colors[color_high_idx][i], local_t); + ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); } return ret; } -const std::array, erCount> GCodeViewer::Default_Extrusion_Role_Colors {{ +const std::array, erCount> GCodeViewer::Extrusion_Role_Colors{ { { 1.00f, 1.00f, 1.00f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter @@ -113,7 +113,7 @@ const std::array, erCount> GCodeViewer::Default_Extrusion_R { 0.00f, 0.00f, 0.00f } // erMixed }}; -const std::array, GCodeViewer::Default_Range_Colors_Count> GCodeViewer::Default_Range_Colors {{ +const std::array, GCodeViewer::Range_Colors_Count> GCodeViewer::Range_Colors{ { { 0.043f, 0.173f, 0.478f }, // bluish { 0.075f, 0.349f, 0.522f }, { 0.110f, 0.533f, 0.569f }, @@ -424,12 +424,12 @@ void GCodeViewer::render_toolpaths() const std::array color; switch (m_view_type) { - case EViewType::FeatureType: { color = m_extrusions.role_colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height, m_extrusions.ranges.colors); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width, m_extrusions.ranges.colors); break; } - case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate, m_extrusions.ranges.colors); break; } - case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed, m_extrusions.ranges.colors); break; } - case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate, m_extrusions.ranges.colors); break; } + case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } + case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } + case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } + case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } + case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } + case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: case EViewType::ColorPrint: default: { color = { 1.0f, 1.0f, 1.0f }; break; } @@ -453,6 +453,7 @@ void GCodeViewer::render_toolpaths() const }; glsafe(::glCullFace(GL_BACK)); + glsafe(::glLineWidth(1.0f)); unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); @@ -579,7 +580,7 @@ void GCodeViewer::render_overlay() const auto add_item = [this, draw_list, &imgui](int i, float value, unsigned int decimals) { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = m_extrusions.ranges.colors[i]; + const std::array& color = Range_Colors[i]; ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0f)); draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); @@ -594,7 +595,7 @@ void GCodeViewer::render_overlay() const add_item(0, range.min, decimals); else { - for (int i = Default_Range_Colors_Count - 1; i >= 0; --i) + for (int i = Range_Colors_Count - 1; i >= 0; --i) { add_item(i, range.min + static_cast(i) * step_size, decimals); } @@ -625,7 +626,7 @@ void GCodeViewer::render_overlay() const { ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = m_extrusions.role_colors[static_cast(role)]; + const std::array& color = Extrusion_Role_Colors[static_cast(role)]; ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0)); draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 6f27c68522..a4d42556cb 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -15,9 +15,9 @@ namespace GUI { class GCodeViewer { - static const std::array, erCount> Default_Extrusion_Role_Colors; - static const size_t Default_Range_Colors_Count = 10; - static const std::array, Default_Range_Colors_Count> Default_Range_Colors; + static const std::array, erCount> Extrusion_Role_Colors; + static const size_t Range_Colors_Count = 10; + static const std::array, Range_Colors_Count> Range_Colors; // buffer containing vertices data struct VBuffer @@ -87,14 +87,12 @@ class GCodeViewer void update_from(const float value) { min = std::min(min, value); max = std::max(max, value); } void reset() { min = FLT_MAX; max = -FLT_MAX; } - float step_size() const { return (max - min) / (static_cast(Default_Range_Colors_Count) - 1.0f); } - std::array get_color_at(float value, const std::array, Default_Range_Colors_Count>& colors) const; + float step_size() const { return (max - min) / (static_cast(Range_Colors_Count) - 1.0f); } + std::array get_color_at(float value) const; }; struct Ranges { - std::array, Default_Range_Colors_Count> colors; - // Color mapping by layer height. Range height; // Color mapping by extrusion width. @@ -115,7 +113,6 @@ class GCodeViewer } }; - std::array, erCount> role_colors; unsigned int role_visibility_flags{ 0 }; Ranges ranges; @@ -166,8 +163,6 @@ public: ~GCodeViewer() { reset(); } bool init() { - m_extrusions.role_colors = Default_Extrusion_Role_Colors; - m_extrusions.ranges.colors = Default_Range_Colors; set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); return init_shaders(); } From 4c4485a9b5338a17b4838e94d65761659fa6cd9a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 21 Apr 2020 15:55:26 +0200 Subject: [PATCH 031/826] GCodeViewer -> Extrusion toolpaths colored by tool --- src/slic3r/GUI/BitmapCache.cpp | 5 +++ src/slic3r/GUI/GCodeViewer.cpp | 64 ++++++++++++++++++++++++++++------ src/slic3r/GUI/GCodeViewer.hpp | 12 ++++--- src/slic3r/GUI/GLCanvas3D.cpp | 6 ++-- src/slic3r/GUI/GLCanvas3D.hpp | 2 +- src/slic3r/GUI/GUI_Preview.cpp | 2 +- src/slic3r/GUI/GUI_Utils.hpp | 9 +++++ 7 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 8627ef4cb6..c345f6bf7a 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -3,6 +3,9 @@ #include "libslic3r/Utils.hpp" #include "../Utils/MacDarkMode.hpp" #include "GUI.hpp" +#if ENABLE_GCODE_VIEWER +#include "GUI_Utils.hpp" +#endif // ENABLE_GCODE_VIEWER #include @@ -352,6 +355,7 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi } +#if !ENABLE_GCODE_VIEWER static inline int hex_digit_to_int(const char c) { return @@ -359,6 +363,7 @@ static inline int hex_digit_to_int(const char c) (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; } +#endif // !ENABLE_GCODE_VIEWER bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out) { diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ad15a0bb9d..0b5be19743 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -8,6 +8,9 @@ #include "Camera.hpp" #include "I18N.hpp" #include "libslic3r/I18N.hpp" +#if ENABLE_GCODE_VIEWER +#include "GUI_Utils.hpp" +#endif // ENABLE_GCODE_VIEWER #include #include @@ -27,6 +30,31 @@ static GCodeProcessor::EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); } +std::vector> decode_colors(const std::vector& colors) +{ + static const float INV_255 = 1.0f / 255.0f; + + std::vector> output(colors.size(), {0.0f, 0.0f, 0.0f} ); + for (size_t i = 0; i < colors.size(); ++i) + { + const std::string& color = colors[i]; + const char* c = color.data() + 1; + if ((color.size() == 7) && (color.front() == '#')) + { + for (size_t j = 0; j < 3; ++j) + { + int digit1 = hex_digit_to_int(*c++); + int digit2 = hex_digit_to_int(*c++); + if ((digit1 == -1) || (digit2 == -1)) + break; + + output[i][j] = float(digit1 * 16 + digit2) * INV_255; + } + } + } + return output; +} + void GCodeViewer::VBuffer::reset() { // release gpu memory @@ -68,7 +96,7 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate() }); + paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value) const @@ -95,7 +123,7 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value) c return ret; } -const std::array, erCount> GCodeViewer::Extrusion_Role_Colors{ { +const std::array, erCount> GCodeViewer::Extrusion_Role_Colors {{ { 1.00f, 1.00f, 1.00f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter @@ -113,7 +141,7 @@ const std::array, erCount> GCodeViewer::Extrusion_Role_Colo { 0.00f, 0.00f, 0.00f } // erMixed }}; -const std::array, GCodeViewer::Range_Colors_Count> GCodeViewer::Range_Colors{ { +const std::array, GCodeViewer::Range_Colors_Count> GCodeViewer::Range_Colors {{ { 0.043f, 0.173f, 0.478f }, // bluish { 0.075f, 0.349f, 0.522f }, { 0.110f, 0.533f, 0.569f }, @@ -126,8 +154,9 @@ const std::array, GCodeViewer::Range_Colors_Count> GCodeVie { 0.761f, 0.322f, 0.235f } // reddish }}; -void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) +void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors, const Print& print, bool initialized) { + // avoid processing if called with the same gcode_result if (m_last_result_id == gcode_result.id) return; @@ -136,6 +165,8 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& // release gpu memory, if used reset(); + m_tool_colors = decode_colors(str_tool_colors); + load_toolpaths(gcode_result); load_shells(print, initialized); } @@ -170,10 +201,7 @@ void GCodeViewer::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_r } break; } - default: - { - break; - } + default: { break; } } } } @@ -188,6 +216,7 @@ void GCodeViewer::reset() } m_bounding_box = BoundingBoxf3(); + m_tool_colors = std::vector>(); m_extrusions.reset_role_visibility_flags(); m_extrusions.reset_ranges(); m_shells.volumes.clear(); @@ -430,7 +459,7 @@ void GCodeViewer::render_toolpaths() const case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } - case EViewType::Tool: + case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: default: { color = { 1.0f, 1.0f, 1.0f }; break; } } @@ -640,7 +669,22 @@ void GCodeViewer::render_overlay() const case EViewType::Feedrate: { add_range(m_extrusions.ranges.feedrate, 1); break; } case EViewType::FanSpeed: { add_range(m_extrusions.ranges.fan_speed, 0); break; } case EViewType::VolumetricRate: { add_range(m_extrusions.ranges.volumetric_rate, 3); break; } - case EViewType::Tool: { break; } + case EViewType::Tool: + { + size_t tools_count = m_tool_colors.size(); + for (size_t i = 0; i < tools_count; ++i) + { + ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); + draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); + const std::array& color = m_tool_colors[i]; + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0)); + draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); + ImGui::AlignTextToFramePadding(); + imgui.text((boost::format(Slic3r::I18N::translate(L("Extruder %d"))) % (i + 1)).str()); + } + break; + } case EViewType::ColorPrint: { break; } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index a4d42556cb..956dd0b5d0 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -45,10 +45,12 @@ class GCodeViewer float feedrate{ 0.0f }; float fan_speed{ 0.0f }; float volumetric_rate{ 0.0f }; + unsigned char extruder_id{ 0 }; bool matches(const GCodeProcessor::MoveVertex& move) const { return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && - feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate(); + feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && + extruder_id == move.extruder_id; } }; @@ -146,11 +148,11 @@ public: }; private: + unsigned int m_last_result_id{ 0 }; VBuffer m_vertices; std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; BoundingBoxf3 m_bounding_box; - - unsigned int m_last_result_id{ 0 }; + std::vector> m_tool_colors; std::vector m_layers_zs; std::vector m_roles; Extrusions m_extrusions; @@ -167,7 +169,9 @@ public: return init_shaders(); } - void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); + // extract rendering data from the given parameters + void load(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors, const Print& print, bool initialized); + // recalculate ranges in dependence of what is visible void refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result); void reset(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e174a57833..540896a59f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2835,7 +2835,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio #endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) +void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { #if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT static unsigned int last_result_id = 0; @@ -2852,7 +2852,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) out.close(); } #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); + m_gcode_viewer.load(gcode_result, str_tool_colors , *this->fff_print(), m_initialized); } void GLCanvas3D::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result) @@ -6594,6 +6594,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } +#if !ENABLE_GCODE_VIEWER static inline int hex_digit_to_int(const char c) { return @@ -6602,7 +6603,6 @@ static inline int hex_digit_to_int(const char c) (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; } -#if !ENABLE_GCODE_VIEWER void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) { BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - start" << m_volumes.log_memory_info() << log_memory_info(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 312eeaa3b6..54f6e181e5 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -664,7 +664,7 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); #if ENABLE_GCODE_VIEWER - void load_gcode_preview(const GCodeProcessor::Result& gcode_result); + void load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); void refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result); #else void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2060511491..9476ad1f0b 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -984,7 +984,7 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. #if ENABLE_GCODE_VIEWER - m_canvas->load_gcode_preview(*m_gcode_result); + m_canvas->load_gcode_preview(*m_gcode_result, colors); m_canvas->refresh_toolpaths_ranges(*m_gcode_result); #else m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 0d5249e254..057c72b1d3 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -343,6 +343,15 @@ public: std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics); +#if ENABLE_GCODE_VIEWER +inline int hex_digit_to_int(const char c) +{ + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; +} +#endif // ENABLE_GCODE_VIEWER }} From 7a0df4bcb487c6f6e6fd3d3505ee12a5416df368 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 22 Apr 2020 16:29:07 +0200 Subject: [PATCH 032/826] GCodeViewer -> Extrusion toolpaths colored by color print (wip) + visualization of tool changes, color changes, pause prints, custom gcodes + refactoring --- resources/shaders/colorchanges.fs | 45 +++++ resources/shaders/colorchanges.vs | 17 ++ resources/shaders/customs.fs | 45 +++++ resources/shaders/customs.vs | 17 ++ resources/shaders/pauses.fs | 45 +++++ resources/shaders/pauses.vs | 17 ++ src/libslic3r/GCode.cpp | 2 + src/libslic3r/GCode/GCodeProcessor.cpp | 31 +++- src/libslic3r/GCode/GCodeProcessor.hpp | 7 + src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 226 +++++++++++++++++++------ src/slic3r/GUI/GCodeViewer.hpp | 18 +- src/slic3r/GUI/GLCanvas3D.cpp | 8 +- src/slic3r/GUI/GLCanvas3D.hpp | 6 +- src/slic3r/GUI/GUI_Preview.cpp | 96 ++++++++++- src/slic3r/GUI/GUI_Preview.hpp | 12 ++ 16 files changed, 516 insertions(+), 77 deletions(-) create mode 100644 resources/shaders/colorchanges.fs create mode 100644 resources/shaders/colorchanges.vs create mode 100644 resources/shaders/customs.fs create mode 100644 resources/shaders/customs.vs create mode 100644 resources/shaders/pauses.fs create mode 100644 resources/shaders/pauses.vs diff --git a/resources/shaders/colorchanges.fs b/resources/shaders/colorchanges.fs new file mode 100644 index 0000000000..fc81e487fb --- /dev/null +++ b/resources/shaders/colorchanges.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec3 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); +} diff --git a/resources/shaders/colorchanges.vs b/resources/shaders/colorchanges.vs new file mode 100644 index 0000000000..3b78a59700 --- /dev/null +++ b/resources/shaders/colorchanges.vs @@ -0,0 +1,17 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); + + gl_PointSize = 15.0; +} diff --git a/resources/shaders/customs.fs b/resources/shaders/customs.fs new file mode 100644 index 0000000000..fc81e487fb --- /dev/null +++ b/resources/shaders/customs.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec3 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); +} diff --git a/resources/shaders/customs.vs b/resources/shaders/customs.vs new file mode 100644 index 0000000000..45fa543f45 --- /dev/null +++ b/resources/shaders/customs.vs @@ -0,0 +1,17 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); + + gl_PointSize = 10.0; +} diff --git a/resources/shaders/pauses.fs b/resources/shaders/pauses.fs new file mode 100644 index 0000000000..fc81e487fb --- /dev/null +++ b/resources/shaders/pauses.fs @@ -0,0 +1,45 @@ +#version 110 + +#define INTENSITY_AMBIENT 0.3 +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +uniform vec3 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; +//varying float world_normal_z; + +// x = tainted, y = specular; +vec2 intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + +// // darkens fragments whose normal points downward +// if (world_normal_z < 0.0) +// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); + + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); +} diff --git a/resources/shaders/pauses.vs b/resources/shaders/pauses.vs new file mode 100644 index 0000000000..45fa543f45 --- /dev/null +++ b/resources/shaders/pauses.vs @@ -0,0 +1,17 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; +//// world z component of the normal used to darken the lower side of the toolpaths +//varying float world_normal_z; + +void main() +{ + eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; + eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); +// eye_normal = gl_NormalMatrix * gl_Normal; +// world_normal_z = gl_Normal.z; + gl_Position = ftransform(); + + gl_PointSize = 10.0; +} diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ee7be7fea7..a1114e938e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2329,11 +2329,13 @@ void GCode::process_layer( gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n"; #if ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT // add tag for processor if (gcode.find(GCodeProcessor::Pause_Print_Tag) != gcode.npos) gcode += "\n; " + GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag + "\n"; else if (gcode.find(GCodeProcessor::Custom_Code_Tag) != gcode.npos) gcode += "\n; " + GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag + "\n"; +#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT #endif // ENABLE_GCODE_VIEWER #ifdef HAS_PRESSURE_EQUALIZER diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 78943dca89..2219facb51 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -24,7 +24,9 @@ const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "_PROCESSOR_MM3_PER_MM:"; const std::string GCodeProcessor::Color_Change_Tag = "_PROCESSOR_COLOR_CHANGE"; const std::string GCodeProcessor::Pause_Print_Tag = "_PROCESSOR_PAUSE_PRINT"; const std::string GCodeProcessor::Custom_Code_Tag = "_PROCESSOR_CUSTOM_CODE"; +#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT const std::string GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag = "_PROCESSOR_END_PAUSE_PRINT_OR_CUSTOM_CODE"; +#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT void GCodeProcessor::CachedPosition::reset() { @@ -236,13 +238,20 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find_last_of(",T"); try { - unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1, comment.npos))); + unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1))); m_extruders_color[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview ++m_cp_color.counter; + if (m_cp_color.counter == UCHAR_MAX) + m_cp_color.counter = 0; if (m_extruder_id == extruder_id) + { m_cp_color.current = m_extruders_color[extruder_id]; +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + store_move_vertex(EMoveType::Color_change); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + } } catch (...) { @@ -256,7 +265,11 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Pause_Print_Tag); if (pos != comment.npos) { - m_cp_color.current = 255; +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + store_move_vertex(EMoveType::Pause_Print); +#else + m_cp_color.current = UCHAR_MAX; +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT return; } @@ -264,19 +277,25 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Custom_Code_Tag); if (pos != comment.npos) { - m_cp_color.current = 255; +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + store_move_vertex(EMoveType::Custom_GCode); +#else + m_cp_color.current = UCHAR_MAX; +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT return; } +#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT // end pause print or custom code tag pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag); if (pos != comment.npos) { - if (m_cp_color.current == 255) + if (m_cp_color.current == UCHAR_MAX) m_cp_color.current = m_extruders_color[m_extruder_id]; return; } +#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT } void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) @@ -569,7 +588,9 @@ void GCodeProcessor::process_T(const std::string& command) else { m_extruder_id = id; - if (m_cp_color.current != 255) +#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + if (m_cp_color.current != UCHAR_MAX) +#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT m_cp_color.current = m_extruders_color[id]; } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 38adce3329..cd4e021cae 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -21,7 +21,9 @@ namespace Slic3r { static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; static const std::string Custom_Code_Tag; +#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT static const std::string End_Pause_Print_Or_Custom_Code_Tag; +#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT private: using AxisCoords = std::array; @@ -62,6 +64,11 @@ namespace Slic3r { Retract, Unretract, Tool_change, +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + Color_change, + Pause_Print, + Custom_GCode, +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT Travel, Extrude, Count diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 1ed939ba3e..8d1e19eceb 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT (1 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 0b5be19743..2f039da0b3 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -7,9 +7,10 @@ #include "PresetBundle.hpp" #include "Camera.hpp" #include "I18N.hpp" -#include "libslic3r/I18N.hpp" #if ENABLE_GCODE_VIEWER #include "GUI_Utils.hpp" +#include "DoubleSlider.hpp" +#include "libslic3r/Model.hpp" #endif // ENABLE_GCODE_VIEWER #include @@ -96,7 +97,7 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id }); + paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value) const @@ -123,8 +124,8 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value) c return ret; } -const std::array, erCount> GCodeViewer::Extrusion_Role_Colors {{ - { 1.00f, 1.00f, 1.00f }, // erNone +const std::vector> GCodeViewer::Extrusion_Role_Colors {{ + { 0.50f, 0.50f, 0.50f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter { 0.00f, 0.00f, 1.00f }, // erOverhangPerimeter @@ -141,7 +142,7 @@ const std::array, erCount> GCodeViewer::Extrusion_Role_Colo { 0.00f, 0.00f, 0.00f } // erMixed }}; -const std::array, GCodeViewer::Range_Colors_Count> GCodeViewer::Range_Colors {{ +const std::vector> GCodeViewer::Range_Colors {{ { 0.043f, 0.173f, 0.478f }, // bluish { 0.075f, 0.349f, 0.522f }, { 0.110f, 0.533f, 0.569f }, @@ -154,7 +155,7 @@ const std::array, GCodeViewer::Range_Colors_Count> GCodeVie { 0.761f, 0.322f, 0.235f } // reddish }}; -void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors, const Print& print, bool initialized) +void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) { // avoid processing if called with the same gcode_result if (m_last_result_id == gcode_result.id) @@ -165,17 +166,17 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const std::ve // release gpu memory, if used reset(); - m_tool_colors = decode_colors(str_tool_colors); - load_toolpaths(gcode_result); load_shells(print, initialized); } -void GCodeViewer::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result) +void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { if (m_vertices.vertices_count == 0) return; + m_tool_colors = decode_colors(str_tool_colors); + m_extrusions.reset_ranges(); for (size_t i = 0; i < m_vertices.vertices_count; ++i) @@ -217,6 +218,8 @@ void GCodeViewer::reset() m_bounding_box = BoundingBoxf3(); m_tool_colors = std::vector>(); + m_extruder_ids = std::vector(); +// m_cp_color_ids = std::vector(); m_extrusions.reset_role_visibility_flags(); m_extrusions.reset_ranges(); m_shells.volumes.clear(); @@ -257,11 +260,16 @@ bool GCodeViewer::init_shaders() switch (buffer_type(i)) { - case GCodeProcessor::EMoveType::Tool_change: { vertex_shader = "toolchanges.vs"; fragment_shader = "toolchanges.fs"; break; } - case GCodeProcessor::EMoveType::Retract: { vertex_shader = "retractions.vs"; fragment_shader = "retractions.fs"; break; } - case GCodeProcessor::EMoveType::Unretract: { vertex_shader = "unretractions.vs"; fragment_shader = "unretractions.fs"; break; } - case GCodeProcessor::EMoveType::Extrude: { vertex_shader = "extrusions.vs"; fragment_shader = "extrusions.fs"; break; } - case GCodeProcessor::EMoveType::Travel: { vertex_shader = "travels.vs"; fragment_shader = "travels.fs"; break; } + case GCodeProcessor::EMoveType::Tool_change: { vertex_shader = "toolchanges.vs"; fragment_shader = "toolchanges.fs"; break; } +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + case GCodeProcessor::EMoveType::Color_change: { vertex_shader = "colorchanges.vs"; fragment_shader = "colorchanges.fs"; break; } + case GCodeProcessor::EMoveType::Pause_Print: { vertex_shader = "pauses.vs"; fragment_shader = "pauses.fs"; break; } + case GCodeProcessor::EMoveType::Custom_GCode: { vertex_shader = "customs.vs"; fragment_shader = "customs.fs"; break; } +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + case GCodeProcessor::EMoveType::Retract: { vertex_shader = "retractions.vs"; fragment_shader = "retractions.fs"; break; } + case GCodeProcessor::EMoveType::Unretract: { vertex_shader = "unretractions.vs"; fragment_shader = "unretractions.fs"; break; } + case GCodeProcessor::EMoveType::Extrude: { vertex_shader = "extrusions.vs"; fragment_shader = "extrusions.fs"; break; } + case GCodeProcessor::EMoveType::Travel: { vertex_shader = "travels.vs"; fragment_shader = "travels.fs"; break; } default: { break; } } @@ -322,6 +330,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) switch (curr.type) { case GCodeProcessor::EMoveType::Tool_change: +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + case GCodeProcessor::EMoveType::Color_change: + case GCodeProcessor::EMoveType::Pause_Print: + case GCodeProcessor::EMoveType::Custom_GCode: +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { @@ -365,13 +378,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } - // layers zs / roles -> extract from result + // layers zs / roles / extruder ids / cp color ids -> extract from result for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { if (move.type == GCodeProcessor::EMoveType::Extrude) m_layers_zs.emplace_back(move.position[2]); m_roles.emplace_back(move.extrusion_role); + m_extruder_ids.emplace_back(move.extruder_id); +// m_cp_color_ids.emplace_back(move.cp_color_id); } // layers zs -> replace intervals of layers with similar top positions with their average value. @@ -392,6 +407,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) std::sort(m_roles.begin(), m_roles.end()); m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); + // extruder ids -> remove duplicates + std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); + m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); + +// // cp color ids -> remove duplicates +// std::sort(m_cp_color_ids.begin(), m_cp_color_ids.end()); +// m_cp_color_ids.erase(std::unique(m_cp_color_ids.begin(), m_cp_color_ids.end()), m_cp_color_ids.end()); + auto end_time = std::chrono::high_resolution_clock::now(); std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; } @@ -460,7 +483,7 @@ void GCodeViewer::render_toolpaths() const case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } - case EViewType::ColorPrint: + case EViewType::ColorPrint: { color = m_tool_colors[path.cp_color_id]; break; } default: { color = { 1.0f, 1.0f, 1.0f }; break; } } return color; @@ -482,7 +505,7 @@ void GCodeViewer::render_toolpaths() const }; glsafe(::glCullFace(GL_BACK)); - glsafe(::glLineWidth(1.0f)); + glsafe(::glLineWidth(3.0f)); unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); @@ -522,6 +545,35 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); break; } +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + case GCodeProcessor::EMoveType::Color_change: + { + std::array color = { 1.0f, 0.0f, 0.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } + case GCodeProcessor::EMoveType::Pause_Print: + { + std::array color = { 0.0f, 1.0f, 0.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } + case GCodeProcessor::EMoveType::Custom_GCode: + { + std::array color = { 0.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + break; + } +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Retract: { std::array color = { 1.0f, 0.0f, 1.0f }; @@ -533,7 +585,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Unretract: { - std::array color = { 0.0f, 1.0f, 0.0f }; + std::array color = { 0.0f, 1.0f, 1.0f }; set_color(current_program_id, color); glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); @@ -605,28 +657,32 @@ void GCodeViewer::render_overlay() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); - auto add_range = [this, draw_list, &imgui](const Extrusions::Range& range, unsigned int decimals) { - auto add_item = [this, draw_list, &imgui](int i, float value, unsigned int decimals) { - ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); - draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = Range_Colors[i]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0f)); - draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); - ImGui::AlignTextToFramePadding(); + auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label) { + ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); + draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); + ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0f)); + draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); + ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); + ImGui::AlignTextToFramePadding(); + imgui.text(label); + }; + + auto add_range = [this, draw_list, &imgui, add_item](const Extrusions::Range& range, unsigned int decimals) { + auto add_range_item = [this, draw_list, &imgui, add_item](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); - imgui.text(buf); + add_item(Range_Colors[i], buf); }; float step_size = range.step_size(); if (step_size == 0.0f) - add_item(0, range.min, decimals); + // single item use case + add_range_item(0, range.min, decimals); else { - for (int i = Range_Colors_Count - 1; i >= 0; --i) + for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { - add_item(i, range.min + static_cast(i) * step_size, decimals); + add_range_item(i, range.min + static_cast(i) * step_size, decimals); } } }; @@ -634,14 +690,15 @@ void GCodeViewer::render_overlay() const ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); switch (m_view_type) { - case EViewType::FeatureType: { imgui.text(Slic3r::I18N::translate(L("Feature type"))); break; } - case EViewType::Height: { imgui.text(Slic3r::I18N::translate(L("Height (mm)"))); break; } - case EViewType::Width: { imgui.text(Slic3r::I18N::translate(L("Width (mm)"))); break; } - case EViewType::Feedrate: { imgui.text(Slic3r::I18N::translate(L("Speed (mm/s)"))); break; } - case EViewType::FanSpeed: { imgui.text(Slic3r::I18N::translate(L("Fan Speed (%)"))); break; } - case EViewType::VolumetricRate: { imgui.text(Slic3r::I18N::translate(L("Volumetric flow rate (mm³/s)"))); break; } - case EViewType::Tool: { imgui.text(Slic3r::I18N::translate(L("Tool"))); break; } - case EViewType::ColorPrint: { imgui.text(Slic3r::I18N::translate(L("Color Print"))); break; } + case EViewType::FeatureType: { imgui.text(I18N::translate_utf8(L("Feature type"))); break; } + case EViewType::Height: { imgui.text(I18N::translate_utf8(L("Height (mm)"))); break; } + case EViewType::Width: { imgui.text(I18N::translate_utf8(L("Width (mm)"))); break; } + case EViewType::Feedrate: { imgui.text(I18N::translate_utf8(L("Speed (mm/s)"))); break; } + case EViewType::FanSpeed: { imgui.text(I18N::translate_utf8(L("Fan Speed (%)"))); break; } + case EViewType::VolumetricRate: { imgui.text(I18N::translate_utf8(L("Volumetric flow rate (mm³/s)"))); break; } + case EViewType::Tool: { imgui.text(I18N::translate_utf8(L("Tool"))); break; } + case EViewType::ColorPrint: { imgui.text(I18N::translate_utf8(L("Color Print"))); break; } + default: { break; } } ImGui::PopStyleColor(); @@ -653,14 +710,7 @@ void GCodeViewer::render_overlay() const { for (ExtrusionRole role : m_roles) { - ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); - draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = Extrusion_Role_Colors[static_cast(role)]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0)); - draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); - ImGui::AlignTextToFramePadding(); - imgui.text(Slic3r::I18N::translate(ExtrusionEntity::role_to_string(role))); + add_item(Extrusion_Role_Colors[static_cast(role)], I18N::translate_utf8(ExtrusionEntity::role_to_string(role))); } break; } @@ -674,18 +724,82 @@ void GCodeViewer::render_overlay() const size_t tools_count = m_tool_colors.size(); for (size_t i = 0; i < tools_count; ++i) { - ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); - draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - const std::array& color = m_tool_colors[i]; - ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0)); - draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); - ImGui::AlignTextToFramePadding(); - imgui.text((boost::format(Slic3r::I18N::translate(L("Extruder %d"))) % (i + 1)).str()); + auto it = std::find(m_extruder_ids.begin(), m_extruder_ids.end(), static_cast(i)); + if (it == m_extruder_ids.end()) + continue; + + add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("Extruder %d"))) % (i + 1)).str()); } break; } - case EViewType::ColorPrint: { break; } + case EViewType::ColorPrint: + { + const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; + const int extruders_count = wxGetApp().extruders_edited_cnt(); + if (extruders_count == 1) // single extruder use case + { + if (custom_gcode_per_print_z.empty()) + // no data to show + add_item(m_tool_colors.front(), I18N::translate_utf8(L("Default print color"))); + else + { + std::vector> cp_values; + cp_values.reserve(custom_gcode_per_print_z.size()); + + for (auto custom_code : custom_gcode_per_print_z) + { + if (custom_code.gcode != ColorChangeCode) + continue; + + auto lower_b = std::lower_bound(m_layers_zs.begin(), m_layers_zs.end(), custom_code.print_z - Slic3r::DoubleSlider::epsilon()); + + if (lower_b == m_layers_zs.end()) + continue; + + double current_z = *lower_b; + double previous_z = lower_b == m_layers_zs.begin() ? 0.0 : *(--lower_b); + + // to avoid duplicate values, check adding values + if (cp_values.empty() || + !(cp_values.back().first == previous_z && cp_values.back().second == current_z)) + cp_values.emplace_back(std::make_pair(previous_z, current_z)); + } + + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) // There is no one color change, but there are some pause print or custom Gcode + { + add_item(m_tool_colors.front(), I18N::translate_utf8(L("Default print color"))); +#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + add_item(m_tool_colors.back(), I18N::translate_utf8(L("Pause print or custom G-code"))); +#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + } + else + { + for (int i = items_cnt; i >= 0; --i) + { + // create label for color print item + std::string id_str = std::to_string(i + 1) + ": "; + + if (i == 0) { + add_item(m_tool_colors[i], id_str + (boost::format(I18N::translate_utf8(L("up to %.2f mm"))) % cp_values.front().first).str()); + break; + } + else if (i == items_cnt) { + add_item(m_tool_colors[i], id_str + (boost::format(I18N::translate_utf8(L("above %.2f mm"))) % cp_values[i - 1].second).str()); + continue; + } + add_item(m_tool_colors[i], id_str + (boost::format(I18N::translate_utf8(L("%.2f - %.2f mm"))) % cp_values[i - 1].second % cp_values[i].first).str()); + } + } + } + } + else + { + } + + break; + } + default: { break; } } imgui.end(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 956dd0b5d0..5ce497c976 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -15,9 +15,8 @@ namespace GUI { class GCodeViewer { - static const std::array, erCount> Extrusion_Role_Colors; - static const size_t Range_Colors_Count = 10; - static const std::array, Range_Colors_Count> Range_Colors; + static const std::vector> Extrusion_Role_Colors; + static const std::vector> Range_Colors; // buffer containing vertices data struct VBuffer @@ -46,11 +45,12 @@ class GCodeViewer float fan_speed{ 0.0f }; float volumetric_rate{ 0.0f }; unsigned char extruder_id{ 0 }; + unsigned char cp_color_id{ 0 }; bool matches(const GCodeProcessor::MoveVertex& move) const { return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && - extruder_id == move.extruder_id; + extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; } }; @@ -89,7 +89,7 @@ class GCodeViewer void update_from(const float value) { min = std::min(min, value); max = std::max(max, value); } void reset() { min = FLT_MAX; max = -FLT_MAX; } - float step_size() const { return (max - min) / (static_cast(Range_Colors_Count) - 1.0f); } + float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } std::array get_color_at(float value) const; }; @@ -155,6 +155,8 @@ private: std::vector> m_tool_colors; std::vector m_layers_zs; std::vector m_roles; + std::vector m_extruder_ids; +// std::vector m_cp_color_ids; Extrusions m_extrusions; Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; @@ -170,9 +172,9 @@ public: } // extract rendering data from the given parameters - void load(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors, const Print& print, bool initialized); - // recalculate ranges in dependence of what is visible - void refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result); + void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); + // recalculate ranges in dependence of what is visible and sets tool/print colors + void refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); void reset(); void render() const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 540896a59f..225fb427cf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2835,7 +2835,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio #endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) +void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT static unsigned int last_result_id = 0; @@ -2852,12 +2852,12 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result, out.close(); } #endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - m_gcode_viewer.load(gcode_result, str_tool_colors , *this->fff_print(), m_initialized); + m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); } -void GLCanvas3D::refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result) +void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { - m_gcode_viewer.refresh_toolpaths_ranges(gcode_result); + m_gcode_viewer.refresh(gcode_result, str_tool_colors); } #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 54f6e181e5..25647b5e55 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -664,8 +664,10 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); #if ENABLE_GCODE_VIEWER - void load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); - void refresh_toolpaths_ranges(const GCodeProcessor::Result& gcode_result); + void load_gcode_preview(const GCodeProcessor::Result& gcode_result); + void refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); + void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } + GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } #else void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9476ad1f0b..520534ca7d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -229,6 +229,12 @@ Preview::Preview( , m_checkbox_travel(nullptr) , m_checkbox_retractions(nullptr) , m_checkbox_unretractions(nullptr) +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + , m_checkbox_tool_changes(nullptr) + , m_checkbox_color_changes(nullptr) + , m_checkbox_pause_prints(nullptr) + , m_checkbox_custom_gcodes(nullptr) +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT , m_checkbox_shells(nullptr) , m_checkbox_legend(nullptr) , m_config(config) @@ -330,6 +336,12 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions"))); m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions"))); +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + m_checkbox_tool_changes = new wxCheckBox(this, wxID_ANY, _(L("Tool changes"))); + m_checkbox_color_changes = new wxCheckBox(this, wxID_ANY, _(L("Color changes"))); + m_checkbox_pause_prints = new wxCheckBox(this, wxID_ANY, _(L("Pause prints"))); + m_checkbox_custom_gcodes = new wxCheckBox(this, wxID_ANY, _(L("Custom GCodes"))); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells"))); m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _(L("Legend"))); m_checkbox_legend->SetValue(true); @@ -351,6 +363,16 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + bottom_sizer->Add(m_checkbox_tool_changes, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); + bottom_sizer->Add(m_checkbox_color_changes, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); + bottom_sizer->Add(m_checkbox_pause_prints, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); + bottom_sizer->Add(m_checkbox_custom_gcodes, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(10); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(20); bottom_sizer->Add(m_checkbox_legend, 0, wxEXPAND | wxALL, 5); @@ -533,6 +555,12 @@ void Preview::bind_event_handlers() m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + m_checkbox_tool_changes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_tool_changes, this); + m_checkbox_color_changes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_color_changes, this); + m_checkbox_pause_prints->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_pause_prints, this); + m_checkbox_custom_gcodes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_custom_gcodes, this); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); m_checkbox_legend->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } @@ -545,6 +573,12 @@ void Preview::unbind_event_handlers() m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + m_checkbox_tool_changes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_tool_changes, this); + m_checkbox_color_changes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_color_changes, this); + m_checkbox_pause_prints->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_pause_prints, this); + m_checkbox_custom_gcodes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_custom_gcodes, this); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); m_checkbox_legend->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } @@ -557,6 +591,12 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_travel->Enable(enable); m_checkbox_retractions->Enable(enable); m_checkbox_unretractions->Enable(enable); +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + m_checkbox_tool_changes->Enable(enable); + m_checkbox_color_changes->Enable(enable); + m_checkbox_pause_prints->Enable(enable); + m_checkbox_custom_gcodes->Enable(enable); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT m_checkbox_shells->Enable(enable); m_checkbox_legend->Enable(enable); @@ -570,6 +610,12 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_travel->Show(visible); m_checkbox_retractions->Show(visible); m_checkbox_unretractions->Show(visible); +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + m_checkbox_tool_changes->Show(visible); + m_checkbox_color_changes->Show(visible); + m_checkbox_pause_prints->Show(visible); + m_checkbox_custom_gcodes->Show(visible); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT m_checkbox_shells->Show(visible); m_checkbox_legend->Show(visible); m_label_view_type->Show(visible); @@ -663,6 +709,32 @@ void Preview::on_checkbox_unretractions(wxCommandEvent& evt) refresh_print(); } +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +void Preview::on_checkbox_tool_changes(wxCommandEvent& evt) +{ + m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change, m_checkbox_tool_changes->IsChecked()); + refresh_print(); +} + +void Preview::on_checkbox_color_changes(wxCommandEvent& evt) +{ + m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change, m_checkbox_color_changes->IsChecked()); + refresh_print(); +} + +void Preview::on_checkbox_pause_prints(wxCommandEvent& evt) +{ + m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print, m_checkbox_pause_prints->IsChecked()); + refresh_print(); +} + +void Preview::on_checkbox_custom_gcodes(wxCommandEvent& evt) +{ + m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, m_checkbox_custom_gcodes->IsChecked()); + refresh_print(); +} +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + void Preview::on_checkbox_shells(wxCommandEvent& evt) { #if ENABLE_GCODE_VIEWER @@ -953,26 +1025,46 @@ void Preview::load_print_as_fff(bool keep_z_range) int tool_idx = m_choice_view_type->FindString(_(L("Tool"))); int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type m_choice_view_type->SetSelection(type); +#if ENABLE_GCODE_VIEWER + if (0 <= type && type < static_cast(GCodeViewer::EViewType::Count)) + m_canvas->set_gcode_view_preview_type(static_cast(type)); +#else if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; +#endif // ENABLE_GCODE_VIEWER // If the->SetSelection changed the following line, revert it to "decide yourself". m_preferred_color_mode = "tool_or_feature"; } +#if ENABLE_GCODE_VIEWER + GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); + bool gcode_preview_data_valid = print->is_step_done(psGCodeExport); +#else bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty(); +#endif // ENABLE_GCODE_VIEWER // Collect colors per extruder. std::vector colors; std::vector color_print_values = {}; // set color print values, if it si selected "ColorPrint" view type +#if ENABLE_GCODE_VIEWER + if (gcode_view_type == GCodeViewer::EViewType::ColorPrint) +#else if (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) +#endif // ENABLE_GCODE_VIEWER { colors = wxGetApp().plater()->get_colors_for_color_print(); +#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT colors.push_back("#808080"); // gray color for pause print or custom G-code +#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT if (!gcode_preview_data_valid) color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; } +#if ENABLE_GCODE_VIEWER + else if (gcode_preview_data_valid || gcode_view_type == GCodeViewer::EViewType::Tool) +#else else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) ) +#endif // ENABLE_GCODE_VIEWER { colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(); color_print_values.clear(); @@ -984,8 +1076,8 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. #if ENABLE_GCODE_VIEWER - m_canvas->load_gcode_preview(*m_gcode_result, colors); - m_canvas->refresh_toolpaths_ranges(*m_gcode_result); + m_canvas->load_gcode_preview(*m_gcode_result); + m_canvas->refresh_gcode_preview(*m_gcode_result, colors); #else m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 654ce4dbf2..a5d93a1925 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -98,6 +98,12 @@ class Preview : public wxPanel wxCheckBox* m_checkbox_travel; wxCheckBox* m_checkbox_retractions; wxCheckBox* m_checkbox_unretractions; +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + wxCheckBox* m_checkbox_tool_changes; + wxCheckBox* m_checkbox_color_changes; + wxCheckBox* m_checkbox_pause_prints; + wxCheckBox* m_checkbox_custom_gcodes; +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT wxCheckBox* m_checkbox_shells; wxCheckBox* m_checkbox_legend; @@ -189,6 +195,12 @@ private: void on_checkbox_travel(wxCommandEvent& evt); void on_checkbox_retractions(wxCommandEvent& evt); void on_checkbox_unretractions(wxCommandEvent& evt); +#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT + void on_checkbox_tool_changes(wxCommandEvent& evt); + void on_checkbox_color_changes(wxCommandEvent& evt); + void on_checkbox_pause_prints(wxCommandEvent& evt); + void on_checkbox_custom_gcodes(wxCommandEvent& evt); +#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT void on_checkbox_shells(wxCommandEvent& evt); void on_checkbox_legend(wxCommandEvent& evt); From 7be12e8f1eb2f2e37c39b1a778402a6a2e06fec1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 23 Apr 2020 10:24:03 +0200 Subject: [PATCH 033/826] GCodeViewer -> Completed extrusion toolpaths colored by color print --- src/slic3r/GUI/GCodeViewer.cpp | 120 +++++++++++++++------------------ src/slic3r/GUI/GCodeViewer.hpp | 4 +- 2 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2f039da0b3..75b2fa98de 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -40,10 +40,8 @@ std::vector> decode_colors(const std::vector& { const std::string& color = colors[i]; const char* c = color.data() + 1; - if ((color.size() == 7) && (color.front() == '#')) - { - for (size_t j = 0; j < 3; ++j) - { + if ((color.size() == 7) && (color.front() == '#')) { + for (size_t j = 0; j < 3; ++j) { int digit1 = hex_digit_to_int(*c++); int digit2 = hex_digit_to_int(*c++); if ((digit1 == -1) || (digit2 == -1)) @@ -59,8 +57,7 @@ std::vector> decode_colors(const std::vector& void GCodeViewer::VBuffer::reset() { // release gpu memory - if (vbo_id > 0) - { + if (vbo_id > 0) { glsafe(::glDeleteBuffers(1, &vbo_id)); vbo_id = 0; } @@ -71,8 +68,7 @@ void GCodeViewer::VBuffer::reset() void GCodeViewer::IBuffer::reset() { // release gpu memory - if (ibo_id > 0) - { + if (ibo_id > 0) { glsafe(::glDeleteBuffers(1, &ibo_id)); ibo_id = 0; } @@ -85,8 +81,7 @@ void GCodeViewer::IBuffer::reset() bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src) { - if (!shader.init(vertex_shader_src, fragment_shader_src)) - { + if (!shader.init(vertex_shader_src, fragment_shader_src)) { BOOST_LOG_TRIVIAL(error) << "Unable to initialize toolpaths shader: please, check that the files " << vertex_shader_src << " and " << fragment_shader_src << " are available"; return false; } @@ -117,8 +112,7 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value) c // Interpolate between the low and high colors to find exactly which color the input value should get std::array ret; - for (unsigned int i = 0; i < 3; ++i) - { + for (unsigned int i = 0; i < 3; ++i) { ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); } return ret; @@ -192,8 +186,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { - if (m_buffers[buffer_id(curr.type)].visible) - { + if (m_buffers[buffer_id(curr.type)].visible) { m_extrusions.ranges.height.update_from(curr.height); m_extrusions.ranges.width.update_from(curr.width); m_extrusions.ranges.feedrate.update_from(curr.feedrate); @@ -211,8 +204,7 @@ void GCodeViewer::reset() { m_vertices.reset(); - for (IBuffer& buffer : m_buffers) - { + for (IBuffer& buffer : m_buffers) { buffer.reset(); } @@ -235,7 +227,7 @@ void GCodeViewer::render() const render_overlay(); } -bool GCodeViewer::is_toolpath_visible(GCodeProcessor::EMoveType type) const +bool GCodeViewer::is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const { size_t id = static_cast(buffer_id(type)); return (id < m_buffers.size()) ? m_buffers[id].visible : false; @@ -277,8 +269,7 @@ bool GCodeViewer::init_shaders() return false; } - if (!m_shells.shader.init("shells.vs", "shells.fs")) - { + if (!m_shells.shader.init("shells.vs", "shells.fs")) { BOOST_LOG_TRIVIAL(error) << "Unable to initialize shells shader: please, check that the files shells.vs and shells.fs are available"; return false; } @@ -297,10 +288,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // vertex data / bounding box -> extract from result std::vector vertices_data; - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) - { - for (int j = 0; j < 3; ++j) - { + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + for (int j = 0; j < 3; ++j) { vertices_data.insert(vertices_data.end(), move.position[j]); m_bounding_box.merge(move.position.cast()); } @@ -345,8 +334,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) - { + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr); buffer.data.push_back(static_cast(i - 1)); } @@ -366,8 +354,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (IBuffer& buffer : m_buffers) { buffer.data_size = buffer.data.size(); - if (buffer.data_size > 0) - { + if (buffer.data_size > 0) { glsafe(::glGenBuffers(1, &buffer.ibo_id)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.data_size * sizeof(unsigned int), buffer.data.data(), GL_STATIC_DRAW)); @@ -379,8 +366,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } // layers zs / roles / extruder ids / cp color ids -> extract from result - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) - { + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { if (move.type == GCodeProcessor::EMoveType::Extrude) m_layers_zs.emplace_back(move.position[2]); @@ -432,8 +418,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) const ModelObject* model_obj = obj->model_object(); std::vector instance_ids(model_obj->instances.size()); - for (int i = 0; i < (int)model_obj->instances.size(); ++i) - { + for (int i = 0; i < (int)model_obj->instances.size(); ++i) { instance_ids[i] = i; } @@ -448,7 +433,6 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) const PrintConfig& config = print.config(); size_t extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { - 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); @@ -514,8 +498,7 @@ void GCodeViewer::render_toolpaths() const glsafe(::glVertexPointer(3, GL_FLOAT, VBuffer::vertex_size_bytes(), (const void*)0)); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - for (unsigned char i = begin_id; i < end_id; ++i) - { + for (unsigned char i = begin_id; i < end_id; ++i) { const IBuffer& buffer = m_buffers[i]; if (buffer.ibo_id == 0) continue; @@ -523,8 +506,7 @@ void GCodeViewer::render_toolpaths() const if (!buffer.visible) continue; - if (buffer.shader.is_initialized()) - { + if (buffer.shader.is_initialized()) { GCodeProcessor::EMoveType type = buffer_type(i); buffer.shader.start_using(); @@ -594,8 +576,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Extrude: { - for (const Path& path : buffer.paths) - { + for (const Path& path : buffer.paths) { if (!is_path_visible(m_extrusions.role_visibility_flags, path)) continue; @@ -608,8 +589,7 @@ void GCodeViewer::render_toolpaths() const { std::array color = { 1.0f, 1.0f, 0.0f }; set_color(current_program_id, color); - for (const Path& path : buffer.paths) - { + for (const Path& path : buffer.paths) { glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); } break; @@ -680,8 +660,7 @@ void GCodeViewer::render_overlay() const add_range_item(0, range.min, decimals); else { - for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) - { + for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { add_range_item(i, range.min + static_cast(i) * step_size, decimals); } } @@ -708,8 +687,7 @@ void GCodeViewer::render_overlay() const { case EViewType::FeatureType: { - for (ExtrusionRole role : m_roles) - { + for (ExtrusionRole role : m_roles) { add_item(Extrusion_Role_Colors[static_cast(role)], I18N::translate_utf8(ExtrusionEntity::role_to_string(role))); } break; @@ -722,8 +700,8 @@ void GCodeViewer::render_overlay() const case EViewType::Tool: { size_t tools_count = m_tool_colors.size(); - for (size_t i = 0; i < tools_count; ++i) - { + for (size_t i = 0; i < tools_count; ++i) { + // shows only extruders actually used auto it = std::find(m_extruder_ids.begin(), m_extruder_ids.end(), static_cast(i)); if (it == m_extruder_ids.end()) continue; @@ -736,18 +714,15 @@ void GCodeViewer::render_overlay() const { const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; const int extruders_count = wxGetApp().extruders_edited_cnt(); - if (extruders_count == 1) // single extruder use case - { + if (extruders_count == 1) { // single extruder use case if (custom_gcode_per_print_z.empty()) // no data to show add_item(m_tool_colors.front(), I18N::translate_utf8(L("Default print color"))); - else - { + else { std::vector> cp_values; cp_values.reserve(custom_gcode_per_print_z.size()); - for (auto custom_code : custom_gcode_per_print_z) - { + for (auto custom_code : custom_gcode_per_print_z) { if (custom_code.gcode != ColorChangeCode) continue; @@ -760,41 +735,54 @@ void GCodeViewer::render_overlay() const double previous_z = lower_b == m_layers_zs.begin() ? 0.0 : *(--lower_b); // to avoid duplicate values, check adding values - if (cp_values.empty() || - !(cp_values.back().first == previous_z && cp_values.back().second == current_z)) + if (cp_values.empty() || !(cp_values.back().first == previous_z && cp_values.back().second == current_z)) cp_values.emplace_back(std::make_pair(previous_z, current_z)); } const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) // There is no one color change, but there are some pause print or custom Gcode - { + if (items_cnt == 0) { // There is no one color change, but there are some pause print or custom Gcode add_item(m_tool_colors.front(), I18N::translate_utf8(L("Default print color"))); #if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT add_item(m_tool_colors.back(), I18N::translate_utf8(L("Pause print or custom G-code"))); #endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT } - else - { - for (int i = items_cnt; i >= 0; --i) - { - // create label for color print item - std::string id_str = std::to_string(i + 1) + ": "; + else { + for (int i = items_cnt; i >= 0; --i) { + // create label for color change item + std::string id_str = " (" + std::to_string(i + 1) + ")"; if (i == 0) { - add_item(m_tool_colors[i], id_str + (boost::format(I18N::translate_utf8(L("up to %.2f mm"))) % cp_values.front().first).str()); + add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("up to %.2f mm"))) % cp_values.front().first).str() + id_str); break; } else if (i == items_cnt) { - add_item(m_tool_colors[i], id_str + (boost::format(I18N::translate_utf8(L("above %.2f mm"))) % cp_values[i - 1].second).str()); + add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("above %.2f mm"))) % cp_values[i - 1].second).str() + id_str); continue; } - add_item(m_tool_colors[i], id_str + (boost::format(I18N::translate_utf8(L("%.2f - %.2f mm"))) % cp_values[i - 1].second % cp_values[i].first).str()); + add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("%.2f - %.2f mm"))) % cp_values[i - 1].second % cp_values[i].first).str() + id_str); } } } } - else + else // multi extruder use case { + // extruders + for (unsigned int i = 0; i < (unsigned int)extruders_count; ++i) { + add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("Extruder %d"))) % (i + 1)).str()); + } + + // color changes + int color_change_idx = 1 + static_cast(m_tool_colors.size()) - extruders_count; + size_t last_color_id = m_tool_colors.size() - 1; + for (int i = static_cast(custom_gcode_per_print_z.size()) - 1; i >= 0; --i) { + if (custom_gcode_per_print_z[i].gcode == ColorChangeCode) { + // create label for color change item + std::string id_str = " (" + std::to_string(color_change_idx--) + ")"; + + add_item(m_tool_colors[last_color_id--], + (boost::format(I18N::translate_utf8(L("Color change for Extruder %d at %.2f mm"))) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str() + id_str); + } + } } break; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5ce497c976..669d56cf49 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -150,7 +150,7 @@ public: private: unsigned int m_last_result_id{ 0 }; VBuffer m_vertices; - std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + mutable std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; BoundingBoxf3 m_bounding_box; std::vector> m_tool_colors; std::vector m_layers_zs; @@ -190,7 +190,7 @@ public: m_view_type = type; } - bool is_toolpath_visible(GCodeProcessor::EMoveType type) const; + bool is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const; void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } From 6e2307f56d85dae6cce9eb3ab228dc356ed35698 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 23 Apr 2020 14:02:47 +0200 Subject: [PATCH 034/826] GCodeViewer -> Refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 36 +++++++++++++++++++++------------- src/slic3r/GUI/GCodeViewer.hpp | 5 +++-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 75b2fa98de..8079008ed3 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -10,6 +10,8 @@ #if ENABLE_GCODE_VIEWER #include "GUI_Utils.hpp" #include "DoubleSlider.hpp" +#include "GLToolbar.hpp" +#include "GLCanvas3D.hpp" #include "libslic3r/Model.hpp" #endif // ENABLE_GCODE_VIEWER @@ -211,7 +213,6 @@ void GCodeViewer::reset() m_bounding_box = BoundingBoxf3(); m_tool_colors = std::vector>(); m_extruder_ids = std::vector(); -// m_cp_color_ids = std::vector(); m_extrusions.reset_role_visibility_flags(); m_extrusions.reset_ranges(); m_shells.volumes.clear(); @@ -372,7 +373,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_roles.emplace_back(move.extrusion_role); m_extruder_ids.emplace_back(move.extruder_id); -// m_cp_color_ids.emplace_back(move.cp_color_id); } // layers zs -> replace intervals of layers with similar top positions with their average value. @@ -397,10 +397,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); -// // cp color ids -> remove duplicates -// std::sort(m_cp_color_ids.begin(), m_cp_color_ids.end()); -// m_cp_color_ids.erase(std::unique(m_cp_color_ids.begin(), m_cp_color_ids.end()), m_cp_color_ids.end()); - auto end_time = std::chrono::high_resolution_clock::now(); std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; } @@ -620,11 +616,17 @@ void GCodeViewer::render_shells() const } void GCodeViewer::render_overlay() const +{ + render_legend(); + render_toolbar(); +} + +void GCodeViewer::render_legend() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); static const float ICON_BORDER_SIZE = 25.0f; static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - static const float GAP_ICON_TEXT = 5.0f; + static const float GAP_ICON_TEXT = 7.5f; if (!m_legend_enabled || m_roles.empty()) return; @@ -633,17 +635,19 @@ void GCodeViewer::render_overlay() const imgui.set_next_window_pos(0, 0, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - imgui.begin(_L("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImDrawList* draw_list = ImGui::GetWindowDrawList(); auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label) { + // draw icon ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); - draw_list->AddRect(ImVec2(pos.x, pos.y), ImVec2(pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE), ICON_BORDER_COLOR, 0.0f, 0); - ImU32 fill_color = ImGui::GetColorU32(ImVec4(color[0], color[1], color[2], 1.0f)); - draw_list->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f), fill_color); - ImGui::SetCursorPosX(pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT); - ImGui::AlignTextToFramePadding(); + draw_list->AddRect({ pos.x, pos.y }, { pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE }, ICON_BORDER_COLOR, 0.0f, 0); + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, + { pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + // draw text + ImGui::SetCursorPos({ pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT, pos.y + 0.5f * (ICON_BORDER_SIZE - ImGui::GetTextLineHeight()) }); imgui.text(label); }; @@ -673,7 +677,7 @@ void GCodeViewer::render_overlay() const case EViewType::Height: { imgui.text(I18N::translate_utf8(L("Height (mm)"))); break; } case EViewType::Width: { imgui.text(I18N::translate_utf8(L("Width (mm)"))); break; } case EViewType::Feedrate: { imgui.text(I18N::translate_utf8(L("Speed (mm/s)"))); break; } - case EViewType::FanSpeed: { imgui.text(I18N::translate_utf8(L("Fan Speed (%)"))); break; } + case EViewType::FanSpeed: { imgui.text(I18N::translate_utf8(L("Fan Speed (%%)"))); break; } case EViewType::VolumetricRate: { imgui.text(I18N::translate_utf8(L("Volumetric flow rate (mm³/s)"))); break; } case EViewType::Tool: { imgui.text(I18N::translate_utf8(L("Tool"))); break; } case EViewType::ColorPrint: { imgui.text(I18N::translate_utf8(L("Color Print"))); break; } @@ -794,6 +798,10 @@ void GCodeViewer::render_overlay() const ImGui::PopStyleVar(); } +void GCodeViewer::render_toolbar() const +{ +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 669d56cf49..53d1fbd75e 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -156,10 +156,9 @@ private: std::vector m_layers_zs; std::vector m_roles; std::vector m_extruder_ids; -// std::vector m_cp_color_ids; Extrusions m_extrusions; Shells m_shells; - EViewType m_view_type{ EViewType::FeatureType }; + mutable EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; public: @@ -207,6 +206,8 @@ private: void render_toolpaths() const; void render_shells() const; void render_overlay() const; + void render_legend() const; + void render_toolbar() const; }; } // namespace GUI From 66964c44c10ff7f36c01f80ceedf9bfb483e247b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 23 Apr 2020 15:12:40 +0200 Subject: [PATCH 035/826] GCodeViewer -> Refactoring and code cleanup --- src/libslic3r/GCode.cpp | 10 ------- src/libslic3r/GCode/GCodeProcessor.cpp | 30 +-------------------- src/libslic3r/GCode/GCodeProcessor.hpp | 5 ---- src/libslic3r/GCode/ToolOrdering.cpp | 4 ++- src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/GCodeViewer.cpp | 9 ------- src/slic3r/GUI/GUI_Preview.cpp | 36 +++++++++++++------------- src/slic3r/GUI/GUI_Preview.hpp | 8 +++--- 8 files changed, 26 insertions(+), 77 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a1114e938e..58eb2e5ae7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2328,16 +2328,6 @@ void GCode::process_layer( else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos) gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n"; -#if ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT - // add tag for processor - if (gcode.find(GCodeProcessor::Pause_Print_Tag) != gcode.npos) - gcode += "\n; " + GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag + "\n"; - else if (gcode.find(GCodeProcessor::Custom_Code_Tag) != gcode.npos) - gcode += "\n; " + GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag + "\n"; -#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT -#endif // ENABLE_GCODE_VIEWER - #ifdef HAS_PRESSURE_EQUALIZER // Apply pressure equalization if enabled; // printf("G-code before filter:\n%s\n", gcode.c_str()); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 2219facb51..6d491a88c9 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -24,9 +24,6 @@ const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "_PROCESSOR_MM3_PER_MM:"; const std::string GCodeProcessor::Color_Change_Tag = "_PROCESSOR_COLOR_CHANGE"; const std::string GCodeProcessor::Pause_Print_Tag = "_PROCESSOR_PAUSE_PRINT"; const std::string GCodeProcessor::Custom_Code_Tag = "_PROCESSOR_CUSTOM_CODE"; -#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT -const std::string GCodeProcessor::End_Pause_Print_Or_Custom_Code_Tag = "_PROCESSOR_END_PAUSE_PRINT_OR_CUSTOM_CODE"; -#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT void GCodeProcessor::CachedPosition::reset() { @@ -248,9 +245,7 @@ void GCodeProcessor::process_tags(const std::string& comment) if (m_extruder_id == extruder_id) { m_cp_color.current = m_extruders_color[extruder_id]; -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT store_move_vertex(EMoveType::Color_change); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT } } catch (...) @@ -265,11 +260,7 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Pause_Print_Tag); if (pos != comment.npos) { -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT store_move_vertex(EMoveType::Pause_Print); -#else - m_cp_color.current = UCHAR_MAX; -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT return; } @@ -277,25 +268,9 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Custom_Code_Tag); if (pos != comment.npos) { -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT store_move_vertex(EMoveType::Custom_GCode); -#else - m_cp_color.current = UCHAR_MAX; -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT return; } - -#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT - // end pause print or custom code tag - pos = comment.find(End_Pause_Print_Or_Custom_Code_Tag); - if (pos != comment.npos) - { - if (m_cp_color.current == UCHAR_MAX) - m_cp_color.current = m_extruders_color[m_extruder_id]; - - return; - } -#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT } void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) @@ -588,10 +563,7 @@ void GCodeProcessor::process_T(const std::string& command) else { m_extruder_id = id; -#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT - if (m_cp_color.current != UCHAR_MAX) -#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT - m_cp_color.current = m_extruders_color[id]; + m_cp_color.current = m_extruders_color[id]; } // store tool change move diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index cd4e021cae..05aca4e089 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -21,9 +21,6 @@ namespace Slic3r { static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; static const std::string Custom_Code_Tag; -#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT - static const std::string End_Pause_Print_Or_Custom_Code_Tag; -#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT private: using AxisCoords = std::array; @@ -64,11 +61,9 @@ namespace Slic3r { Retract, Unretract, Tool_change, -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT Color_change, Pause_Print, Custom_GCode, -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT Travel, Extrude, Count diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index db398f06c8..7be15bd351 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -400,7 +400,9 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. - assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) { for (size_t i = 0; i + 1 < m_layer_tools.size();) { const LayerTools < = m_layer_tools[i]; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 8d1e19eceb..1ed939ba3e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,7 +59,6 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT (1 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 8079008ed3..86a9d38a7c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -254,11 +254,9 @@ bool GCodeViewer::init_shaders() switch (buffer_type(i)) { case GCodeProcessor::EMoveType::Tool_change: { vertex_shader = "toolchanges.vs"; fragment_shader = "toolchanges.fs"; break; } -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Color_change: { vertex_shader = "colorchanges.vs"; fragment_shader = "colorchanges.fs"; break; } case GCodeProcessor::EMoveType::Pause_Print: { vertex_shader = "pauses.vs"; fragment_shader = "pauses.fs"; break; } case GCodeProcessor::EMoveType::Custom_GCode: { vertex_shader = "customs.vs"; fragment_shader = "customs.fs"; break; } -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Retract: { vertex_shader = "retractions.vs"; fragment_shader = "retractions.fs"; break; } case GCodeProcessor::EMoveType::Unretract: { vertex_shader = "unretractions.vs"; fragment_shader = "unretractions.fs"; break; } case GCodeProcessor::EMoveType::Extrude: { vertex_shader = "extrusions.vs"; fragment_shader = "extrusions.fs"; break; } @@ -320,11 +318,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) switch (curr.type) { case GCodeProcessor::EMoveType::Tool_change: -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Color_change: case GCodeProcessor::EMoveType::Pause_Print: case GCodeProcessor::EMoveType::Custom_GCode: -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { @@ -523,7 +519,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); break; } -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Color_change: { std::array color = { 1.0f, 0.0f, 0.0f }; @@ -551,7 +546,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); break; } -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT case GCodeProcessor::EMoveType::Retract: { std::array color = { 1.0f, 0.0f, 1.0f }; @@ -746,9 +740,6 @@ void GCodeViewer::render_legend() const const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There is no one color change, but there are some pause print or custom Gcode add_item(m_tool_colors.front(), I18N::translate_utf8(L("Default print color"))); -#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT - add_item(m_tool_colors.back(), I18N::translate_utf8(L("Pause print or custom G-code"))); -#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT } else { for (int i = items_cnt; i >= 0; --i) { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 520534ca7d..6c961a4902 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -229,12 +229,12 @@ Preview::Preview( , m_checkbox_travel(nullptr) , m_checkbox_retractions(nullptr) , m_checkbox_unretractions(nullptr) -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER , m_checkbox_tool_changes(nullptr) , m_checkbox_color_changes(nullptr) , m_checkbox_pause_prints(nullptr) , m_checkbox_custom_gcodes(nullptr) -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER , m_checkbox_shells(nullptr) , m_checkbox_legend(nullptr) , m_config(config) @@ -336,12 +336,12 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions"))); m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions"))); -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER m_checkbox_tool_changes = new wxCheckBox(this, wxID_ANY, _(L("Tool changes"))); m_checkbox_color_changes = new wxCheckBox(this, wxID_ANY, _(L("Color changes"))); m_checkbox_pause_prints = new wxCheckBox(this, wxID_ANY, _(L("Pause prints"))); m_checkbox_custom_gcodes = new wxCheckBox(this, wxID_ANY, _(L("Custom GCodes"))); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells"))); m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _(L("Legend"))); m_checkbox_legend->SetValue(true); @@ -363,7 +363,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER bottom_sizer->Add(m_checkbox_tool_changes, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_checkbox_color_changes, 0, wxEXPAND | wxALL, 5); @@ -372,7 +372,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_checkbox_custom_gcodes, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(20); bottom_sizer->Add(m_checkbox_legend, 0, wxEXPAND | wxALL, 5); @@ -555,12 +555,12 @@ void Preview::bind_event_handlers() m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER m_checkbox_tool_changes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_tool_changes, this); m_checkbox_color_changes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_color_changes, this); m_checkbox_pause_prints->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_pause_prints, this); m_checkbox_custom_gcodes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_custom_gcodes, this); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); m_checkbox_legend->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } @@ -573,12 +573,12 @@ void Preview::unbind_event_handlers() m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER m_checkbox_tool_changes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_tool_changes, this); m_checkbox_color_changes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_color_changes, this); m_checkbox_pause_prints->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_pause_prints, this); m_checkbox_custom_gcodes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_custom_gcodes, this); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); m_checkbox_legend->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } @@ -591,12 +591,12 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_travel->Enable(enable); m_checkbox_retractions->Enable(enable); m_checkbox_unretractions->Enable(enable); -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER m_checkbox_tool_changes->Enable(enable); m_checkbox_color_changes->Enable(enable); m_checkbox_pause_prints->Enable(enable); m_checkbox_custom_gcodes->Enable(enable); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Enable(enable); m_checkbox_legend->Enable(enable); @@ -610,12 +610,12 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_travel->Show(visible); m_checkbox_retractions->Show(visible); m_checkbox_unretractions->Show(visible); -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER m_checkbox_tool_changes->Show(visible); m_checkbox_color_changes->Show(visible); m_checkbox_pause_prints->Show(visible); m_checkbox_custom_gcodes->Show(visible); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Show(visible); m_checkbox_legend->Show(visible); m_label_view_type->Show(visible); @@ -709,7 +709,7 @@ void Preview::on_checkbox_unretractions(wxCommandEvent& evt) refresh_print(); } -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER void Preview::on_checkbox_tool_changes(wxCommandEvent& evt) { m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change, m_checkbox_tool_changes->IsChecked()); @@ -733,7 +733,7 @@ void Preview::on_checkbox_custom_gcodes(wxCommandEvent& evt) m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, m_checkbox_custom_gcodes->IsChecked()); refresh_print(); } -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER void Preview::on_checkbox_shells(wxCommandEvent& evt) { @@ -1053,9 +1053,9 @@ void Preview::load_print_as_fff(bool keep_z_range) #endif // ENABLE_GCODE_VIEWER { colors = wxGetApp().plater()->get_colors_for_color_print(); -#if !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if !ENABLE_GCODE_VIEWER colors.push_back("#808080"); // gray color for pause print or custom G-code -#endif // !ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // !ENABLE_GCODE_VIEWER if (!gcode_preview_data_valid) color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index a5d93a1925..bdbbe79daa 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -98,12 +98,12 @@ class Preview : public wxPanel wxCheckBox* m_checkbox_travel; wxCheckBox* m_checkbox_retractions; wxCheckBox* m_checkbox_unretractions; -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER wxCheckBox* m_checkbox_tool_changes; wxCheckBox* m_checkbox_color_changes; wxCheckBox* m_checkbox_pause_prints; wxCheckBox* m_checkbox_custom_gcodes; -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER wxCheckBox* m_checkbox_shells; wxCheckBox* m_checkbox_legend; @@ -195,12 +195,12 @@ private: void on_checkbox_travel(wxCommandEvent& evt); void on_checkbox_retractions(wxCommandEvent& evt); void on_checkbox_unretractions(wxCommandEvent& evt); -#if ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#if ENABLE_GCODE_VIEWER void on_checkbox_tool_changes(wxCommandEvent& evt); void on_checkbox_color_changes(wxCommandEvent& evt); void on_checkbox_pause_prints(wxCommandEvent& evt); void on_checkbox_custom_gcodes(wxCommandEvent& evt); -#endif // ENABLE_GCODE_VIEWER_SEPARATE_PAUSE_PRINT +#endif // ENABLE_GCODE_VIEWER void on_checkbox_shells(wxCommandEvent& evt); void on_checkbox_legend(wxCommandEvent& evt); From 90d5cf173596063e2ccf323c01ec81d88e2a5f68 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 23 Apr 2020 15:46:21 +0200 Subject: [PATCH 036/826] Fix to previous commit --- src/libslic3r/GCode/ToolOrdering.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 7be15bd351..db398f06c8 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -400,9 +400,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) { for (size_t i = 0; i + 1 < m_layer_tools.size();) { const LayerTools < = m_layer_tools[i]; From 81a29169aee1e9af381c360a8005897189b06b6e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 24 Apr 2020 08:46:31 +0200 Subject: [PATCH 037/826] GCodeViewer -> Coloring of travel paths --- src/libslic3r/GCode/GCodeProcessor.cpp | 3 ++- src/libslic3r/GCode/GCodeProcessor.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 28 +++++++++++++++++++------- src/slic3r/GUI/GCodeViewer.hpp | 2 ++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 6d491a88c9..9dc3964436 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -582,7 +582,8 @@ void GCodeProcessor::store_move_vertex(EMoveType type) MoveVertex vertex; vertex.type = type; vertex.extrusion_role = m_extrusion_role; - vertex.position = Vec3f(m_end_position[0], m_end_position[1], m_end_position[2]) + m_extruder_offsets[m_extruder_id]; + vertex.position = Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z]) + m_extruder_offsets[m_extruder_id]; + vertex.delta_extruder = m_end_position[E] - m_start_position[E]; vertex.feedrate = m_feedrate; vertex.width = m_width; vertex.height = m_height; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 05aca4e089..2212148367 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -76,6 +76,7 @@ namespace Slic3r { unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; Vec3f position{ Vec3f::Zero() }; // mm + float delta_extruder{ 0.0f }; // mm float feedrate{ 0.0f }; // mm/s float width{ 0.0f }; // mm float height{ 0.0f }; // mm diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 86a9d38a7c..aac22be6f6 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -94,7 +94,7 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ move.type, move.extrusion_role, id, id, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); + paths.push_back({ move.type, move.extrusion_role, id, id, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value) const @@ -138,6 +138,12 @@ const std::vector> GCodeViewer::Extrusion_Role_Colors {{ { 0.00f, 0.00f, 0.00f } // erMixed }}; +const std::vector> GCodeViewer::Travel_Colors {{ + { 0.0f, 0.0f, 0.5f }, // Move + { 0.0f, 0.5f, 0.0f }, // Extrude + { 0.5f, 0.0f, 0.0f } // Retract +}}; + const std::vector> GCodeViewer::Range_Colors {{ { 0.043f, 0.173f, 0.478f }, // bluish { 0.075f, 0.349f, 0.522f }, @@ -186,14 +192,17 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: switch (curr.type) { case GCodeProcessor::EMoveType::Extrude: + { + m_extrusions.ranges.height.update_from(curr.height); + m_extrusions.ranges.width.update_from(curr.width); + m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); + m_extrusions.ranges.volumetric_rate.update_from(curr.volumetric_rate()); + [[fallthrough]]; + } case GCodeProcessor::EMoveType::Travel: { if (m_buffers[buffer_id(curr.type)].visible) { - m_extrusions.ranges.height.update_from(curr.height); - m_extrusions.ranges.width.update_from(curr.width); m_extrusions.ranges.feedrate.update_from(curr.feedrate); - m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); - m_extrusions.ranges.volumetric_rate.update_from(curr.volumetric_rate()); } break; } @@ -465,6 +474,12 @@ void GCodeViewer::render_toolpaths() const return color; }; + auto travel_color = [this](const Path& path) { + return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : + ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : + Travel_Colors[0] /* Move */); + }; + auto set_color = [](GLint current_program_id, const std::array& color) { if (current_program_id > 0) { GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; @@ -577,9 +592,8 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Travel: { - std::array color = { 1.0f, 1.0f, 0.0f }; - set_color(current_program_id, color); for (const Path& path : buffer.paths) { + set_color(current_program_id, (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path)); glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); } break; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 53d1fbd75e..70e20ce0b1 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -16,6 +16,7 @@ namespace GUI { class GCodeViewer { static const std::vector> Extrusion_Role_Colors; + static const std::vector> Travel_Colors; static const std::vector> Range_Colors; // buffer containing vertices data @@ -39,6 +40,7 @@ class GCodeViewer ExtrusionRole role{ erNone }; unsigned int first{ 0 }; unsigned int last{ 0 }; + float delta_extruder{ 0.0f }; float height{ 0.0f }; float width{ 0.0f }; float feedrate{ 0.0f }; From d8091b7ad76fb580b8f22b151bc4c76134d6f3e2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 24 Apr 2020 16:12:38 +0200 Subject: [PATCH 038/826] ENABLE_GCODE_VIEWER -> Preview toolbar checkboxes moved into a new combo --- src/slic3r/GUI/GCodeViewer.cpp | 17 +++ src/slic3r/GUI/GCodeViewer.hpp | 4 +- src/slic3r/GUI/GLCanvas3D.cpp | 9 +- src/slic3r/GUI/GLCanvas3D.hpp | 3 +- src/slic3r/GUI/GUI.cpp | 25 ++--- src/slic3r/GUI/GUI.hpp | 6 +- src/slic3r/GUI/GUI_Preview.cpp | 190 +++++++++++++-------------------- src/slic3r/GUI/GUI_Preview.hpp | 22 ++-- 8 files changed, 123 insertions(+), 153 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3d8d288d17..e6ddba8d11 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -250,6 +250,23 @@ void GCodeViewer::set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, m_buffers[id].visible = visible; } +void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) +{ + auto is_flag_set = [flags](unsigned int flag) { + return (flags& (1 << flag)) != 0; + }; + + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel, is_flag_set(0)); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract, is_flag_set(1)); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract, is_flag_set(2)); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change, is_flag_set(3)); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change, is_flag_set(4)); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print, is_flag_set(5)); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, is_flag_set(6)); + m_shells.visible = is_flag_set(7); + enable_legend(is_flag_set(8)); +} + bool GCodeViewer::init_shaders() { unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 6e7cc2f172..bfd61e8d4c 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -194,9 +194,7 @@ public: bool is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const; void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } - - bool are_shells_visible() const { return m_shells.visible; } - void set_shells_visible(bool visible) { m_shells.visible = visible; } + void set_options_visibility_from_flags(unsigned int flags); bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 225fb427cf..ab2dee6938 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2332,9 +2332,9 @@ const std::vector& GLCanvas3D::get_layers_zs() const return m_gcode_viewer.get_layers_zs(); } -void GLCanvas3D::set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible) +void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags) { - m_gcode_viewer.set_toolpath_move_type_visible(type, visible); + m_gcode_viewer.set_options_visibility_from_flags(flags); } void GLCanvas3D::set_toolpath_role_visibility_flags(unsigned int flags) @@ -2346,11 +2346,6 @@ void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) { m_gcode_viewer.set_view_type(type); } - -void GLCanvas3D::set_shells_visible(bool visible) -{ - m_gcode_viewer.set_shells_visible(visible); -} #else std::vector GLCanvas3D::get_current_print_zs(bool active_only) const { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 25647b5e55..7a295b900d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -647,10 +647,9 @@ public: #if ENABLE_GCODE_VIEWER const std::vector& get_layers_zs() const; - void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); + void set_gcode_options_visibility_from_flags(unsigned int flags); void set_toolpath_role_visibility_flags(unsigned int flags); void set_toolpath_view_type(GCodeViewer::EViewType type); - void set_shells_visible(bool visible); #else std::vector get_current_print_zs(bool active_only) const; #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index caeb8da034..dd9ddcab8f 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -259,7 +259,7 @@ void warning_catcher(wxWindow* parent, const wxString& message) msg.ShowModal(); } -void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) +void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items) { if (comboCtrl == nullptr) return; @@ -273,8 +273,9 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string comboCtrl->EnablePopupAnimation(false); comboCtrl->SetPopupControl(popup); - popup->SetStringValue(from_u8(text)); - popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); + wxString title = from_u8(text); + popup->SetStringValue(title); + popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); }); popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); }); @@ -282,16 +283,16 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string std::vector items_str; boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off); - for (const std::string& item : items_str) - { - popup->Append(from_u8(item)); - } + // each item must be composed by 2 parts + assert(items_str.size() %2 == 0); - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { - popup->Check(i, initial_value); - } - } + for (size_t i = 0; i < items_str.size(); i += 2) + { + wxString label = from_u8(items_str[i]); + popup->Append(label); + popup->Check(i / 2, items_str[i + 1] == "1"); + } + } } int combochecklist_get_flags(wxComboCtrl* comboCtrl) diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index a54288df45..6690b21207 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -49,9 +49,9 @@ inline void show_info(wxWindow* parent, const std::string& message,const std::st void warning_catcher(wxWindow* parent, const wxString& message); // Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items. -// Items are all initialized to the given value. -// Items must be separated by '|', for example "Item1|Item2|Item3", and so on. -void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value); +// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true). +// For example "Item1|0|Item2|1|Item3|0", and so on. +void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items); // Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl, // encoded inside an int. diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6c961a4902..876d212fb4 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -224,19 +224,17 @@ Preview::Preview( , m_double_slider_sizer(nullptr) , m_label_view_type(nullptr) , m_choice_view_type(nullptr) - , m_label_show_features(nullptr) + , m_label_show(nullptr) , m_combochecklist_features(nullptr) +#if ENABLE_GCODE_VIEWER + , m_combochecklist_options(nullptr) +#else , m_checkbox_travel(nullptr) , m_checkbox_retractions(nullptr) , m_checkbox_unretractions(nullptr) -#if ENABLE_GCODE_VIEWER - , m_checkbox_tool_changes(nullptr) - , m_checkbox_color_changes(nullptr) - , m_checkbox_pause_prints(nullptr) - , m_checkbox_custom_gcodes(nullptr) -#endif // ENABLE_GCODE_VIEWER , m_checkbox_shells(nullptr) , m_checkbox_legend(nullptr) +#endif // ENABLE_GCODE_VIEWER , m_config(config) , m_process(process) , m_gcode_preview_data(gcode_preview_data) @@ -308,43 +306,53 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_choice_view_type->Append(_(L("Color Print"))); m_choice_view_type->SetSelection(0); - m_label_show_features = new wxStaticText(this, wxID_ANY, _(L("Show"))); + m_label_show = new wxStaticText(this, wxID_ANY, _(L("Show"))); m_combochecklist_features = new wxComboCtrl(); m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); - std::string feature_text = GUI::into_u8(_(L("Feature types"))); std::string feature_items = GUI::into_u8( #if ENABLE_GCODE_VIEWER - _L("Unknown") + "|" + + _L("Unknown") + "|1|" + #endif // ENABLE_GCODE_VIEWER - _(L("Perimeter")) + "|" + - _(L("External perimeter")) + "|" + - _(L("Overhang perimeter")) + "|" + - _(L("Internal infill")) + "|" + - _(L("Solid infill")) + "|" + - _(L("Top solid infill")) + "|" + - _(L("Bridge infill")) + "|" + - _(L("Gap fill")) + "|" + - _(L("Skirt")) + "|" + - _(L("Support material")) + "|" + - _(L("Support material interface")) + "|" + - _(L("Wipe tower")) + "|" + - _(L("Custom")) + _(L("Perimeter")) + "|1|" + + _(L("External perimeter")) + "|1|" + + _(L("Overhang perimeter")) + "|1|" + + _(L("Internal infill")) + "|1|" + + _(L("Solid infill")) + "|1|" + + _(L("Top solid infill")) + "|1|" + + _(L("Bridge infill")) + "|1|" + + _(L("Gap fill")) + "|1|" + + _(L("Skirt")) + "|1|" + + _(L("Support material")) + "|1|" + + _(L("Support material interface")) + "|1|" + + _(L("Wipe tower")) + "|1|" + + _(L("Custom")) + "|1" ); - Slic3r::GUI::create_combochecklist(m_combochecklist_features, feature_text, feature_items, true); + Slic3r::GUI::create_combochecklist(m_combochecklist_features, GUI::into_u8(_(L("Feature types"))), feature_items); +#if ENABLE_GCODE_VIEWER + m_combochecklist_options = new wxComboCtrl(); + m_combochecklist_options->Create(this, wxID_ANY, _(L("Options")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); + std::string options_items = GUI::into_u8( + _(L("Travel")) + "|0|" + + _(L("Retractions")) + "|0|" + + _(L("Unretractions")) + "|0|" + + _(L("Tool changes")) + "|0|" + + _(L("Color changes")) + "|0|" + + _(L("Pause prints")) + "|0|" + + _(L("Custom GCodes")) + "|0|" + + _(L("Shells")) + "|0|" + + _(L("Legend")) + "|1" + ); + Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_(L("Options"))), options_items); +#else m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions"))); m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions"))); -#if ENABLE_GCODE_VIEWER - m_checkbox_tool_changes = new wxCheckBox(this, wxID_ANY, _(L("Tool changes"))); - m_checkbox_color_changes = new wxCheckBox(this, wxID_ANY, _(L("Color changes"))); - m_checkbox_pause_prints = new wxCheckBox(this, wxID_ANY, _(L("Pause prints"))); - m_checkbox_custom_gcodes = new wxCheckBox(this, wxID_ANY, _(L("Custom GCodes"))); -#endif // ENABLE_GCODE_VIEWER m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells"))); m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _(L("Legend"))); m_checkbox_legend->SetValue(true); +#endif // ENABLE_GCODE_VIEWER wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); @@ -354,8 +362,11 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); bottom_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); - bottom_sizer->Add(m_label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5); + bottom_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL, 5); bottom_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5); +#if ENABLE_GCODE_VIEWER + bottom_sizer->Add(m_combochecklist_options, 0, wxEXPAND | wxALL, 5); +#else bottom_sizer->AddSpacer(20); bottom_sizer->Add(m_checkbox_travel, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); @@ -363,19 +374,10 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); -#if ENABLE_GCODE_VIEWER - bottom_sizer->Add(m_checkbox_tool_changes, 0, wxEXPAND | wxALL, 5); - bottom_sizer->AddSpacer(10); - bottom_sizer->Add(m_checkbox_color_changes, 0, wxEXPAND | wxALL, 5); - bottom_sizer->AddSpacer(10); - bottom_sizer->Add(m_checkbox_pause_prints, 0, wxEXPAND | wxALL, 5); - bottom_sizer->AddSpacer(10); - bottom_sizer->Add(m_checkbox_custom_gcodes, 0, wxEXPAND | wxALL, 5); - bottom_sizer->AddSpacer(10); -#endif // ENABLE_GCODE_VIEWER bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(20); bottom_sizer->Add(m_checkbox_legend, 0, wxEXPAND | wxALL, 5); +#endif // ENABLE_GCODE_VIEWER wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0); @@ -552,17 +554,15 @@ void Preview::bind_event_handlers() this->Bind(wxEVT_SIZE, &Preview::on_size, this); m_choice_view_type->Bind(wxEVT_CHOICE, &Preview::on_choice_view_type, this); m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); +#if ENABLE_GCODE_VIEWER + m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); +#else m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); -#if ENABLE_GCODE_VIEWER - m_checkbox_tool_changes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_tool_changes, this); - m_checkbox_color_changes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_color_changes, this); - m_checkbox_pause_prints->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_pause_prints, this); - m_checkbox_custom_gcodes->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_custom_gcodes, this); -#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); m_checkbox_legend->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); +#endif // ENABLE_GCODE_VIEWER } void Preview::unbind_event_handlers() @@ -570,54 +570,48 @@ void Preview::unbind_event_handlers() this->Unbind(wxEVT_SIZE, &Preview::on_size, this); m_choice_view_type->Unbind(wxEVT_CHOICE, &Preview::on_choice_view_type, this); m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); +#if ENABLE_GCODE_VIEWER + m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); +#else m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); -#if ENABLE_GCODE_VIEWER - m_checkbox_tool_changes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_tool_changes, this); - m_checkbox_color_changes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_color_changes, this); - m_checkbox_pause_prints->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_pause_prints, this); - m_checkbox_custom_gcodes->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_custom_gcodes, this); -#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); m_checkbox_legend->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); +#endif // ENABLE_GCODE_VIEWER } void Preview::show_hide_ui_elements(const std::string& what) { bool enable = (what == "full"); - m_label_show_features->Enable(enable); + m_label_show->Enable(enable); m_combochecklist_features->Enable(enable); - m_checkbox_travel->Enable(enable); +#if ENABLE_GCODE_VIEWER + m_combochecklist_options->Enable(enable); +#else + m_checkbox_travel->Enable(enable); m_checkbox_retractions->Enable(enable); m_checkbox_unretractions->Enable(enable); -#if ENABLE_GCODE_VIEWER - m_checkbox_tool_changes->Enable(enable); - m_checkbox_color_changes->Enable(enable); - m_checkbox_pause_prints->Enable(enable); - m_checkbox_custom_gcodes->Enable(enable); -#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Enable(enable); m_checkbox_legend->Enable(enable); +#endif // ENABLE_GCODE_VIEWER enable = (what != "none"); m_label_view_type->Enable(enable); m_choice_view_type->Enable(enable); bool visible = (what != "none"); - m_label_show_features->Show(visible); + m_label_show->Show(visible); m_combochecklist_features->Show(visible); +#if ENABLE_GCODE_VIEWER + m_combochecklist_options->Show(visible); +#else m_checkbox_travel->Show(visible); m_checkbox_retractions->Show(visible); m_checkbox_unretractions->Show(visible); -#if ENABLE_GCODE_VIEWER - m_checkbox_tool_changes->Show(visible); - m_checkbox_color_changes->Show(visible); - m_checkbox_pause_prints->Show(visible); - m_checkbox_custom_gcodes->Show(visible); -#endif // ENABLE_GCODE_VIEWER m_checkbox_shells->Show(visible); m_checkbox_legend->Show(visible); +#endif // ENABLE_GCODE_VIEWER m_label_view_type->Show(visible); m_choice_view_type->Show(visible); } @@ -676,72 +670,36 @@ void Preview::on_combochecklist_features(wxCommandEvent& evt) refresh_print(); } +#if ENABLE_GCODE_VIEWER +void Preview::on_combochecklist_options(wxCommandEvent& evt) +{ + m_canvas->set_gcode_options_visibility_from_flags(static_cast(Slic3r::GUI::combochecklist_get_flags(m_combochecklist_options))); + refresh_print(); +} +#else void Preview::on_checkbox_travel(wxCommandEvent& evt) { -#if ENABLE_GCODE_VIEWER - m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel, m_checkbox_travel->IsChecked()); - refresh_print(); -#else m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked(); m_gcode_preview_data->ranges.feedrate.set_mode(GCodePreviewData::FeedrateKind::TRAVEL, m_gcode_preview_data->travel.is_visible); // Rather than refresh, reload print so that speed color ranges get recomputed (affected by travel visibility) reload_print(); -#endif // ENABLE_GCODE_VIEWER } void Preview::on_checkbox_retractions(wxCommandEvent& evt) { -#if ENABLE_GCODE_VIEWER - m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract, m_checkbox_retractions->IsChecked()); -#else m_gcode_preview_data->retraction.is_visible = m_checkbox_retractions->IsChecked(); -#endif // ENABLE_GCODE_VIEWER refresh_print(); } void Preview::on_checkbox_unretractions(wxCommandEvent& evt) { -#if ENABLE_GCODE_VIEWER - m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract, m_checkbox_unretractions->IsChecked()); -#else m_gcode_preview_data->unretraction.is_visible = m_checkbox_unretractions->IsChecked(); -#endif // ENABLE_GCODE_VIEWER refresh_print(); } -#if ENABLE_GCODE_VIEWER -void Preview::on_checkbox_tool_changes(wxCommandEvent& evt) -{ - m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change, m_checkbox_tool_changes->IsChecked()); - refresh_print(); -} - -void Preview::on_checkbox_color_changes(wxCommandEvent& evt) -{ - m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change, m_checkbox_color_changes->IsChecked()); - refresh_print(); -} - -void Preview::on_checkbox_pause_prints(wxCommandEvent& evt) -{ - m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print, m_checkbox_pause_prints->IsChecked()); - refresh_print(); -} - -void Preview::on_checkbox_custom_gcodes(wxCommandEvent& evt) -{ - m_canvas->set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, m_checkbox_custom_gcodes->IsChecked()); - refresh_print(); -} -#endif // ENABLE_GCODE_VIEWER - void Preview::on_checkbox_shells(wxCommandEvent& evt) { -#if ENABLE_GCODE_VIEWER - m_canvas->set_shells_visible(m_checkbox_shells->IsChecked()); -#else m_gcode_preview_data->shell.is_visible = m_checkbox_shells->IsChecked(); -#endif // ENABLE_GCODE_VIEWER refresh_print(); } @@ -750,6 +708,7 @@ void Preview::on_checkbox_legend(wxCommandEvent& evt) m_canvas->enable_legend_texture(m_checkbox_legend->IsChecked()); m_canvas_widget->Refresh(); } +#endif // ENABLE_GCODE_VIEWER void Preview::update_view_type(bool slice_completed) { @@ -969,11 +928,13 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event) m_slider->SetHigherValue(new_pos); if (event.ShiftDown() || m_slider->is_one_layer()) m_slider->SetLowerValue(m_slider->GetHigherValue()); } +#if !ENABLE_GCODE_VIEWER else if (key == 'L') { m_checkbox_legend->SetValue(!m_checkbox_legend->GetValue()); auto evt = wxCommandEvent(); on_checkbox_legend(evt); } +#endif // !ENABLE_GCODE_VIEWER else if (key == 'S') m_slider->ChangeOneLayerLock(); else if (key == WXK_SHIFT) @@ -1078,6 +1039,7 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER m_canvas->load_gcode_preview(*m_gcode_result); m_canvas->refresh_gcode_preview(*m_gcode_result, colors); + show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); #else m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); #endif // ENABLE_GCODE_VIEWER @@ -1085,12 +1047,14 @@ void Preview::load_print_as_fff(bool keep_z_range) } else { // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); +#if ENABLE_GCODE_VIEWER + show_hide_ui_elements("none"); +#endif // ENABLE_GCODE_VIEWER } - show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); - // recalculates zs and update sliders accordingly #if ENABLE_GCODE_VIEWER const std::vector& zs = m_canvas->get_layers_zs(); #else + show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); std::vector zs = m_canvas->get_current_print_zs(true); #endif // ENABLE_GCODE_VIEWER if (zs.empty()) { diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index bdbbe79daa..c4ad4eb79c 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -93,19 +93,17 @@ class Preview : public wxPanel wxBoxSizer* m_double_slider_sizer; wxStaticText* m_label_view_type; wxChoice* m_choice_view_type; - wxStaticText* m_label_show_features; + wxStaticText* m_label_show; wxComboCtrl* m_combochecklist_features; +#if ENABLE_GCODE_VIEWER + wxComboCtrl* m_combochecklist_options; +#else wxCheckBox* m_checkbox_travel; wxCheckBox* m_checkbox_retractions; wxCheckBox* m_checkbox_unretractions; -#if ENABLE_GCODE_VIEWER - wxCheckBox* m_checkbox_tool_changes; - wxCheckBox* m_checkbox_color_changes; - wxCheckBox* m_checkbox_pause_prints; - wxCheckBox* m_checkbox_custom_gcodes; -#endif // ENABLE_GCODE_VIEWER wxCheckBox* m_checkbox_shells; wxCheckBox* m_checkbox_legend; +#endif // ENABLE_GCODE_VIEWER DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; @@ -192,17 +190,15 @@ private: void on_size(wxSizeEvent& evt); void on_choice_view_type(wxCommandEvent& evt); void on_combochecklist_features(wxCommandEvent& evt); +#if ENABLE_GCODE_VIEWER + void on_combochecklist_options(wxCommandEvent& evt); +#else void on_checkbox_travel(wxCommandEvent& evt); void on_checkbox_retractions(wxCommandEvent& evt); void on_checkbox_unretractions(wxCommandEvent& evt); -#if ENABLE_GCODE_VIEWER - void on_checkbox_tool_changes(wxCommandEvent& evt); - void on_checkbox_color_changes(wxCommandEvent& evt); - void on_checkbox_pause_prints(wxCommandEvent& evt); - void on_checkbox_custom_gcodes(wxCommandEvent& evt); -#endif // ENABLE_GCODE_VIEWER void on_checkbox_shells(wxCommandEvent& evt); void on_checkbox_legend(wxCommandEvent& evt); +#endif // ENABLE_GCODE_VIEWER // Create/Update/Reset double slider on 3dPreview void create_double_slider(); From 85676af17131eebebd61ed747d7827d2411bb9e0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 25 Apr 2020 10:36:51 +0200 Subject: [PATCH 039/826] Modified wxCheckListBoxComboPopup::GetAdjustedSize() and create_combochecklist() to size the combo control taking in account the items width --- src/slic3r/GUI/GUI.cpp | 6 ++++++ src/slic3r/GUI/wxExtensions.cpp | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index dd9ddcab8f..a66396b277 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -271,9 +271,12 @@ void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, cons // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. comboCtrl->UseAltPopupWindow(); + int max_width = 0; + comboCtrl->EnablePopupAnimation(false); comboCtrl->SetPopupControl(popup); wxString title = from_u8(text); + max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x); popup->SetStringValue(title); popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); }); @@ -289,9 +292,12 @@ void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, cons for (size_t i = 0; i < items_str.size(); i += 2) { wxString label = from_u8(items_str[i]); + max_width = std::max(max_width, 60 + popup->GetTextExtent(label).x); popup->Append(label); popup->Check(i / 2, items_str[i + 1] == "1"); } + + comboCtrl->SetMinClientSize(wxSize(max_width, -1)); } } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index b3f2791961..3e55f6ae30 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -197,8 +197,8 @@ wxString wxCheckListBoxComboPopup::GetStringValue() const wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight) { - // matches owner wxComboCtrl's width - // and sets height dinamically in dependence of contained items count + // set width dinamically in dependence of items text + // and set height dinamically in dependence of items count wxComboCtrl* cmb = GetComboCtrl(); if (cmb != nullptr) @@ -207,7 +207,15 @@ wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, i unsigned int count = GetCount(); if (count > 0) - size.SetHeight(count * DefaultItemHeight); + { + int max_width = size.x; + for (unsigned int i = 0; i < count; ++i) + { + max_width = std::max(max_width, 60 + GetTextExtent(GetString(i)).x); + } + size.SetWidth(max_width); + size.SetHeight(count * GetTextExtent(GetString(0)).y); + } else size.SetHeight(DefaultHeight); From eadad6c1d1c37a5afd8a7beb059fb2005621d3c1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 25 Apr 2020 11:16:28 +0200 Subject: [PATCH 040/826] GCodeViewer -> Add travel paths to legend --- src/slic3r/GUI/GCodeViewer.cpp | 41 ++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e6ddba8d11..73f41cb85e 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -177,10 +177,11 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: if (m_vertices.vertices_count == 0) return; + // update tool colors m_tool_colors = decode_colors(str_tool_colors); + // update ranges m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) { // skip first vertex @@ -201,9 +202,9 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: } case GCodeProcessor::EMoveType::Travel: { - if (m_buffers[buffer_id(curr.type)].visible) { + if (m_buffers[buffer_id(curr.type)].visible) m_extrusions.ranges.feedrate.update_from(curr.feedrate); - } + break; } default: { break; } @@ -689,6 +690,7 @@ void GCodeViewer::render_overlay() const } }; + // extrusion paths -> title ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); switch (m_view_type) { @@ -703,9 +705,9 @@ void GCodeViewer::render_overlay() const default: { break; } } ImGui::PopStyleColor(); - ImGui::Separator(); + // extrusion paths -> items switch (m_view_type) { case EViewType::FeatureType: @@ -810,6 +812,37 @@ void GCodeViewer::render_overlay() const default: { break; } } + // travel paths + if (m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)].visible) + { + switch (m_view_type) + { + case EViewType::Feedrate: + case EViewType::Tool: + case EViewType::ColorPrint: + { + break; + } + default: + { + // title + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(I18N::translate_utf8(L("Travel"))); + ImGui::PopStyleColor(); + ImGui::Separator(); + + // items + add_item(Travel_Colors[0], I18N::translate_utf8(L("Movement"))); + add_item(Travel_Colors[1], I18N::translate_utf8(L("Extrusion"))); + add_item(Travel_Colors[2], I18N::translate_utf8(L("Retraction"))); + + break; + } + } + } + imgui.end(); ImGui::PopStyleVar(); } From 4ea96340d8817df9df627d9a54adefb65d07739d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 25 Apr 2020 12:24:56 +0200 Subject: [PATCH 041/826] GCodeViewer -> Draw alphed extrusion paths into legend when set as not visible --- src/slic3r/GUI/GCodeViewer.cpp | 19 ++++++++++++------- src/slic3r/GUI/GCodeViewer.hpp | 6 ++++++ src/slic3r/GUI/GUI_Preview.cpp | 4 ++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 73f41cb85e..e6878c6191 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -33,8 +33,7 @@ static GCodeProcessor::EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); } -std::vector> decode_colors(const std::vector& colors) -{ +std::vector> decode_colors(const std::vector& colors) { static const float INV_255 = 1.0f / 255.0f; std::vector> output(colors.size(), {0.0f, 0.0f, 0.0f} ); @@ -67,6 +66,10 @@ void GCodeViewer::VBuffer::reset() vertices_count = 0; } +bool GCodeViewer::Path::is_path_visible(unsigned int flags, const Path& path) { + return Extrusions::is_role_visible(flags, path.role); +}; + void GCodeViewer::IBuffer::reset() { // release gpu memory @@ -509,10 +512,6 @@ void GCodeViewer::render_toolpaths() const BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; }; - auto is_path_visible = [](unsigned int flags, const Path& path) { - return Extrusions::is_role_visible(flags, path.role); - }; - glsafe(::glCullFace(GL_BACK)); glsafe(::glLineWidth(3.0f)); @@ -600,7 +599,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Extrude: { for (const Path& path : buffer.paths) { - if (!is_path_visible(m_extrusions.role_visibility_flags, path)) + if (!Path::is_path_visible(m_extrusions.role_visibility_flags, path)) continue; set_color(current_program_id, extrusion_color(path)); @@ -713,7 +712,13 @@ void GCodeViewer::render_overlay() const case EViewType::FeatureType: { for (ExtrusionRole role : m_roles) { + bool visible = m_extrusions.is_role_visible(role); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + add_item(Extrusion_Role_Colors[static_cast(role)], I18N::translate_utf8(ExtrusionEntity::role_to_string(role))); + if (!visible) + ImGui::PopStyleVar(); } break; } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index bfd61e8d4c..f644d3481e 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -54,6 +54,8 @@ class GCodeViewer feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; } + + static bool is_path_visible(unsigned int flags, const Path& path); }; // buffer containing indices data and shader for a specific toolpath type @@ -130,6 +132,10 @@ class GCodeViewer void reset_ranges() { ranges.reset(); } + bool is_role_visible(ExtrusionRole role) const { + return role < erCount && (role_visibility_flags & (1 << role)) != 0; + } + static bool is_role_visible(unsigned int flags, ExtrusionRole role) { return role < erCount && (flags & (1 << role)) != 0; } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 876d212fb4..65adfe8def 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -332,7 +332,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view #if ENABLE_GCODE_VIEWER m_combochecklist_options = new wxComboCtrl(); - m_combochecklist_options->Create(this, wxID_ANY, _(L("Options")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); + m_combochecklist_options->Create(this, wxID_ANY, _(L("Others")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); std::string options_items = GUI::into_u8( _(L("Travel")) + "|0|" + _(L("Retractions")) + "|0|" + @@ -344,7 +344,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view _(L("Shells")) + "|0|" + _(L("Legend")) + "|1" ); - Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_(L("Options"))), options_items); + Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_(L("Others"))), options_items); #else m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions"))); From c76bf934f73bf5c1097bf558ff1606a8b0769c44 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Apr 2020 08:19:48 +0200 Subject: [PATCH 042/826] GCodeViewer -> Shortcut to show/hide legend --- src/slic3r/GUI/GCodeViewer.cpp | 1 - src/slic3r/GUI/GLCanvas3D.cpp | 11 +++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e6878c6191..2baaedbc2f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -10,7 +10,6 @@ #if ENABLE_GCODE_VIEWER #include "GUI_Utils.hpp" #include "DoubleSlider.hpp" -#include "GLToolbar.hpp" #include "GLCanvas3D.hpp" #include "libslic3r/Model.hpp" #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ab2dee6938..3e4487c70c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3202,6 +3202,17 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #else case 'k': { m_camera.select_next_type(); m_dirty = true; break; } #endif // ENABLE_NON_STATIC_CANVAS_MANAGER +#if ENABLE_GCODE_VIEWER + case 'L': + case 'l': { + if (!m_main_toolbar.is_enabled()) + { + m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); + m_dirty = true; + } + break; + } +#endif // ENABLE_GCODE_VIEWER case 'O': case 'o': { _update_camera_zoom(-1.0); break; } #if ENABLE_RENDER_PICKING_PASS From a6ed1d817a0327745aed6a907af652ee2ad9a441 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Apr 2020 11:44:29 +0200 Subject: [PATCH 043/826] GCodeViewer -> Layers z slider wip --- resources/shaders/customs.vs | 2 +- resources/shaders/pauses.vs | 2 +- resources/shaders/retractions.vs | 2 +- resources/shaders/toolchanges.vs | 2 +- resources/shaders/unretractions.vs | 2 +- src/slic3r/GUI/3DScene.cpp | 2 + src/slic3r/GUI/3DScene.hpp | 4 ++ src/slic3r/GUI/GCodeViewer.cpp | 97 +++++++++++++++++++++++------- src/slic3r/GUI/GCodeViewer.hpp | 7 ++- src/slic3r/GUI/GLCanvas3D.cpp | 7 ++- src/slic3r/GUI/GLCanvas3D.hpp | 2 + src/slic3r/GUI/GUI_Preview.cpp | 5 ++ 12 files changed, 104 insertions(+), 30 deletions(-) diff --git a/resources/shaders/customs.vs b/resources/shaders/customs.vs index 45fa543f45..3b78a59700 100644 --- a/resources/shaders/customs.vs +++ b/resources/shaders/customs.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 10.0; + gl_PointSize = 15.0; } diff --git a/resources/shaders/pauses.vs b/resources/shaders/pauses.vs index 45fa543f45..3b78a59700 100644 --- a/resources/shaders/pauses.vs +++ b/resources/shaders/pauses.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 10.0; + gl_PointSize = 15.0; } diff --git a/resources/shaders/retractions.vs b/resources/shaders/retractions.vs index 2cf5ca2dd0..3b78a59700 100644 --- a/resources/shaders/retractions.vs +++ b/resources/shaders/retractions.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 5.0; + gl_PointSize = 15.0; } diff --git a/resources/shaders/toolchanges.vs b/resources/shaders/toolchanges.vs index 2cf5ca2dd0..3b78a59700 100644 --- a/resources/shaders/toolchanges.vs +++ b/resources/shaders/toolchanges.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 5.0; + gl_PointSize = 15.0; } diff --git a/resources/shaders/unretractions.vs b/resources/shaders/unretractions.vs index 2cf5ca2dd0..3b78a59700 100644 --- a/resources/shaders/unretractions.vs +++ b/resources/shaders/unretractions.vs @@ -13,5 +13,5 @@ void main() // world_normal_z = gl_Normal.z; gl_Position = ftransform(); - gl_PointSize = 5.0; + gl_PointSize = 15.0; } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index e60676ca31..63cacdd457 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -341,6 +341,7 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d & } +#if !ENABLE_GCODE_VIEWER void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; @@ -375,6 +376,7 @@ void GLVolume::set_range(double min_z, double max_z) } } } +#endif // !ENABLE_GCODE_VIEWER void GLVolume::render() const { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 70d6fb016a..28295a35f7 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -442,7 +442,9 @@ public: bool empty() const { return this->indexed_vertex_array.empty(); } +#if !ENABLE_GCODE_VIEWER void set_range(double low, double high); +#endif // !ENABLE_GCODE_VIEWER void render() const; #if !ENABLE_SLOPE_RENDERING @@ -560,7 +562,9 @@ public: void clear() { for (auto *v : volumes) delete v; volumes.clear(); } bool empty() const { return volumes.empty(); } +#if !ENABLE_GCODE_VIEWER void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } +#endif // !ENABLE_GCODE_VIEWER void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) { m_print_box_min[0] = min_x; m_print_box_min[1] = min_y; m_print_box_min[2] = min_z; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2baaedbc2f..045f6ed570 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -65,10 +65,19 @@ void GCodeViewer::VBuffer::reset() vertices_count = 0; } -bool GCodeViewer::Path::is_path_visible(unsigned int flags, const Path& path) { +bool GCodeViewer::Path::is_path_visible(const Path& path, unsigned int flags) { return Extrusions::is_role_visible(flags, path.role); }; +bool GCodeViewer::Path::is_path_in_z_range(const Path& path, const std::array& z_range) +{ + auto in_z_range = [z_range](double z) { + return z > z_range[0] - EPSILON && z < z_range[1] + EPSILON; + }; + + return in_z_range(path.first_z) || in_z_range(path.last_z); +} + void GCodeViewer::IBuffer::reset() { // release gpu memory @@ -96,7 +105,8 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { unsigned int id = static_cast(data.size()); - paths.push_back({ move.type, move.extrusion_role, id, id, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); + double z = static_cast(move.position[2]); + paths.push_back({ move.type, move.extrusion_role, id, id, z, z, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value) const @@ -182,7 +192,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update tool colors m_tool_colors = decode_colors(str_tool_colors); - // update ranges + // update ranges for coloring / legend m_extrusions.reset_ranges(); for (size_t i = 0; i < m_vertices.vertices_count; ++i) { @@ -229,6 +239,7 @@ void GCodeViewer::reset() m_extrusions.reset_ranges(); m_shells.volumes.clear(); m_layers_zs = std::vector(); + m_layers_z_range = { 0.0, 0.0 }; m_roles = std::vector(); } @@ -394,7 +405,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // layers zs / roles / extruder ids / cp color ids -> extract from result for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { if (move.type == GCodeProcessor::EMoveType::Extrude) - m_layers_zs.emplace_back(move.position[2]); + m_layers_zs.emplace_back(static_cast(move.position[2])); m_roles.emplace_back(move.extrusion_role); m_extruder_ids.emplace_back(move.extruder_id); @@ -414,6 +425,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (k < n) m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); + // set layers z range + m_layers_z_range = { m_layers_zs.front(), m_layers_zs.back() }; + // roles -> remove duplicates std::sort(m_roles.begin(), m_roles.end()); m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); @@ -545,60 +559,90 @@ void GCodeViewer::render_toolpaths() const { std::array color = { 1.0f, 1.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + for (const Path& path : buffer.paths) { + if (!Path::is_path_in_z_range(path, m_layers_z_range)) + continue; + + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + } break; } case GCodeProcessor::EMoveType::Color_change: { std::array color = { 1.0f, 0.0f, 0.0f }; set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + for (const Path& path : buffer.paths) { + if (!Path::is_path_in_z_range(path, m_layers_z_range)) + continue; + + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + } break; } case GCodeProcessor::EMoveType::Pause_Print: { std::array color = { 0.0f, 1.0f, 0.0f }; set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + for (const Path& path : buffer.paths) { + if (!Path::is_path_in_z_range(path, m_layers_z_range)) + continue; + + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + } break; } case GCodeProcessor::EMoveType::Custom_GCode: { std::array color = { 0.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + for (const Path& path : buffer.paths) { + if (!Path::is_path_in_z_range(path, m_layers_z_range)) + continue; + + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + } break; } case GCodeProcessor::EMoveType::Retract: { std::array color = { 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + for (const Path& path : buffer.paths) { + if (!Path::is_path_in_z_range(path, m_layers_z_range)) + continue; + + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + } break; } case GCodeProcessor::EMoveType::Unretract: { std::array color = { 0.0f, 1.0f, 1.0f }; set_color(current_program_id, color); - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, (GLsizei)buffer.data_size, GL_UNSIGNED_INT, nullptr)); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + for (const Path& path : buffer.paths) { + if (!Path::is_path_in_z_range(path, m_layers_z_range)) + continue; + + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + } break; } case GCodeProcessor::EMoveType::Extrude: { for (const Path& path : buffer.paths) { - if (!Path::is_path_visible(m_extrusions.role_visibility_flags, path)) + if (!Path::is_path_visible(path, m_extrusions.role_visibility_flags) || !Path::is_path_in_z_range(path, m_layers_z_range)) continue; set_color(current_program_id, extrusion_color(path)); @@ -609,6 +653,9 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Travel: { for (const Path& path : buffer.paths) { + if (!Path::is_path_in_z_range(path, m_layers_z_range)) + continue; + set_color(current_program_id, (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path)); glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); } @@ -655,6 +702,10 @@ void GCodeViewer::render_overlay() const ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + if (ImGui::IsWindowAppearing()) + // force an extra farme + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index f644d3481e..3c73cee5a2 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -40,6 +40,8 @@ class GCodeViewer ExtrusionRole role{ erNone }; unsigned int first{ 0 }; unsigned int last{ 0 }; + double first_z{ 0.0f }; + double last_z{ 0.0f }; float delta_extruder{ 0.0f }; float height{ 0.0f }; float width{ 0.0f }; @@ -55,7 +57,8 @@ class GCodeViewer extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; } - static bool is_path_visible(unsigned int flags, const Path& path); + static bool is_path_visible(const Path& path, unsigned int flags); + static bool is_path_in_z_range(const Path& path, const std::array& z_range); }; // buffer containing indices data and shader for a specific toolpath type @@ -162,6 +165,7 @@ private: BoundingBoxf3 m_bounding_box; std::vector> m_tool_colors; std::vector m_layers_zs; + std::array m_layers_z_range; std::vector m_roles; std::vector m_extruder_ids; Extrusions m_extrusions; @@ -201,6 +205,7 @@ public: void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } void set_options_visibility_from_flags(unsigned int flags); + void set_layers_z_range(const std::array& layers_z_range) { m_layers_z_range = layers_z_range; } bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3e4487c70c..6e7b53f689 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2346,17 +2346,22 @@ void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) { m_gcode_viewer.set_view_type(type); } + +void GLCanvas3D::set_toolpaths_z_range(const std::array& range) +{ + m_gcode_viewer.set_layers_z_range(range); +} #else std::vector GLCanvas3D::get_current_print_zs(bool active_only) const { return m_volumes.get_current_print_zs(active_only); } -#endif // ENABLE_GCODE_VIEWER void GLCanvas3D::set_toolpaths_range(double low, double high) { m_volumes.set_range(low, high); } +#endif // ENABLE_GCODE_VIEWER std::vector GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector instance_idxs) { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 7a295b900d..152658b13a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -650,8 +650,10 @@ public: void set_gcode_options_visibility_from_flags(unsigned int flags); void set_toolpath_role_visibility_flags(unsigned int flags); void set_toolpath_view_type(GCodeViewer::EViewType type); + void set_toolpaths_z_range(const std::array& range); #else std::vector get_current_print_zs(bool active_only) const; + void set_toolpaths_range(double low, double high); #endif // ENABLE_GCODE_VIEWER void set_toolpaths_range(double low, double high); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 65adfe8def..3e0d5cd5a4 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1113,9 +1113,14 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) PrinterTechnology tech = m_process->current_printer_technology(); if (tech == ptFFF) { +#if ENABLE_GCODE_VIEWER + m_canvas->set_toolpaths_z_range({ m_slider->GetLowerValueD(), m_slider->GetHigherValueD() }); + m_canvas->set_as_dirty(); +#else m_canvas->set_toolpaths_range(m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6); m_canvas->render(); m_canvas->set_use_clipping_planes(false); +#endif // ENABLE_GCODE_VIEWER } else if (tech == ptSLA) { From c1246f86eb7cd1c92f8a3c57ba924bc2f9b1ce9a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Apr 2020 12:43:51 +0200 Subject: [PATCH 044/826] GCodeViewer -> Refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 53 ++++++++++++++-------------------- src/slic3r/GUI/GCodeViewer.hpp | 22 +++++++------- src/slic3r/GUI/GLCanvas3D.cpp | 2 ++ 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 045f6ed570..268b998fa4 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -65,19 +65,6 @@ void GCodeViewer::VBuffer::reset() vertices_count = 0; } -bool GCodeViewer::Path::is_path_visible(const Path& path, unsigned int flags) { - return Extrusions::is_role_visible(flags, path.role); -}; - -bool GCodeViewer::Path::is_path_in_z_range(const Path& path, const std::array& z_range) -{ - auto in_z_range = [z_range](double z) { - return z > z_range[0] - EPSILON && z < z_range[1] + EPSILON; - }; - - return in_z_range(path.first_z) || in_z_range(path.last_z); -} - void GCodeViewer::IBuffer::reset() { // release gpu memory @@ -186,6 +173,8 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { + auto start_time = std::chrono::high_resolution_clock::now(); + if (m_vertices.vertices_count == 0) return; @@ -222,6 +211,9 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: default: { break; } } } + + auto end_time = std::chrono::high_resolution_clock::now(); + std::cout << "refresh: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; } void GCodeViewer::reset() @@ -560,7 +552,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 1.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { - if (!Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_in_z_range(path)) continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -574,7 +566,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 1.0f, 0.0f, 0.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { - if (!Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_in_z_range(path)) continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -588,7 +580,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 0.0f, 1.0f, 0.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { - if (!Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_in_z_range(path)) continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -602,7 +594,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 0.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { - if (!Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_in_z_range(path)) continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -616,7 +608,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { - if (!Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_in_z_range(path)) continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -630,7 +622,7 @@ void GCodeViewer::render_toolpaths() const std::array color = { 0.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { - if (!Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_in_z_range(path)) continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -642,7 +634,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Extrude: { for (const Path& path : buffer.paths) { - if (!Path::is_path_visible(path, m_extrusions.role_visibility_flags) || !Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_visible(path) || !is_in_z_range(path)) continue; set_color(current_program_id, extrusion_color(path)); @@ -653,7 +645,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Travel: { for (const Path& path : buffer.paths) { - if (!Path::is_path_in_z_range(path, m_layers_z_range)) + if (!is_in_z_range(path)) continue; set_color(current_program_id, (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path)); @@ -689,9 +681,7 @@ void GCodeViewer::render_shells() const void GCodeViewer::render_overlay() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - static const float ICON_BORDER_SIZE = 25.0f; static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - static const float GAP_ICON_TEXT = 7.5f; if (!m_legend_enabled || m_roles.empty()) return; @@ -709,14 +699,15 @@ void GCodeViewer::render_overlay() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label) { - // draw icon - ImVec2 pos(ImGui::GetCursorPosX() + 2.0f, ImGui::GetCursorPosY() + 2.0f); - draw_list->AddRect({ pos.x, pos.y }, { pos.x + ICON_BORDER_SIZE, pos.y + ICON_BORDER_SIZE }, ICON_BORDER_COLOR, 0.0f, 0); - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, - { pos.x + ICON_BORDER_SIZE - 1.0f, pos.y + ICON_BORDER_SIZE - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + float icon_size = ImGui::GetTextLineHeight(); + ImVec2 pos = ImGui::GetCursorPos(); + draw_list->AddRect({ pos.x, pos.y }, { pos.x + icon_size, pos.y + icon_size }, ICON_BORDER_COLOR, 0.0f, 0); + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + // draw text - ImGui::SetCursorPos({ pos.x + ICON_BORDER_SIZE + GAP_ICON_TEXT, pos.y + 0.5f * (ICON_BORDER_SIZE - ImGui::GetTextLineHeight()) }); + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); imgui.text(label); }; @@ -762,7 +753,7 @@ void GCodeViewer::render_overlay() const case EViewType::FeatureType: { for (ExtrusionRole role : m_roles) { - bool visible = m_extrusions.is_role_visible(role); + bool visible = is_visible(role); if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 3c73cee5a2..d2eb103f65 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -56,9 +56,6 @@ class GCodeViewer feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; } - - static bool is_path_visible(const Path& path, unsigned int flags); - static bool is_path_in_z_range(const Path& path, const std::array& z_range); }; // buffer containing indices data and shader for a specific toolpath type @@ -134,14 +131,6 @@ class GCodeViewer } void reset_ranges() { ranges.reset(); } - - bool is_role_visible(ExtrusionRole role) const { - return role < erCount && (role_visibility_flags & (1 << role)) != 0; - } - - static bool is_role_visible(unsigned int flags, ExtrusionRole role) { - return role < erCount && (flags & (1 << role)) != 0; - } }; public: @@ -217,6 +206,17 @@ private: void render_toolpaths() const; void render_shells() const; void render_overlay() const; + bool is_visible(ExtrusionRole role) const { + return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; + } + bool is_visible(const Path& path) const { return is_visible(path.role); } + bool is_in_z_range(const Path& path) const { + auto in_z_range = [this](double z) { + return z > m_layers_z_range[0] - EPSILON && z < m_layers_z_range[1] + EPSILON; + }; + + return in_z_range(path.first_z) || in_z_range(path.last_z); + } }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6e7b53f689..fa38aca470 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2858,6 +2858,8 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { m_gcode_viewer.refresh(gcode_result, str_tool_colors); + set_as_dirty(); + request_extra_frame(); } #endif // ENABLE_GCODE_VIEWER From eac4b3c15ac2866b59062f9e45988b3bccf2e849 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Apr 2020 14:10:18 +0200 Subject: [PATCH 045/826] GCodeViewer -> Added debug statistics imgui dialog --- src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 136 ++++++++++++++++++++++++++++++--- src/slic3r/GUI/GCodeViewer.hpp | 42 +++++++++- src/slic3r/GUI/GLCanvas3D.cpp | 4 + 4 files changed, 169 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 1ed939ba3e..d04bc97fa4 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_2_3_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 268b998fa4..7426eafc9e 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -7,12 +7,13 @@ #include "PresetBundle.hpp" #include "Camera.hpp" #include "I18N.hpp" -#if ENABLE_GCODE_VIEWER #include "GUI_Utils.hpp" #include "DoubleSlider.hpp" #include "GLCanvas3D.hpp" #include "libslic3r/Model.hpp" -#endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_STATISTICS +#include +#endif // ENABLE_GCODE_VIEWER_STATISTICS #include #include @@ -173,7 +174,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { +#if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS if (m_vertices.vertices_count == 0) return; @@ -212,8 +215,9 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: } } - auto end_time = std::chrono::high_resolution_clock::now(); - std::cout << "refresh: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS } void GCodeViewer::reset() @@ -233,14 +237,25 @@ void GCodeViewer::reset() m_layers_zs = std::vector(); m_layers_z_range = { 0.0, 0.0 }; m_roles = std::vector(); + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.reset_all(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS } void GCodeViewer::render() const { +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.reset_opengl(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); render_shells(); - render_overlay(); + render_legend(); +#if ENABLE_GCODE_VIEWER_STATISTICS + render_statistics(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS } bool GCodeViewer::is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const @@ -310,7 +325,9 @@ bool GCodeViewer::init_shaders() void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { +#if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS // vertex data m_vertices.vertices_count = gcode_result.moves.size(); @@ -326,6 +343,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.vertices_size = vertices_data.size() * sizeof(float); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + // vertex data -> send to gpu glsafe(::glGenBuffers(1, &m_vertices.vbo_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); @@ -383,6 +404,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (IBuffer& buffer : m_buffers) { buffer.data_size = buffer.data.size(); +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.indices_size += buffer.data_size * sizeof(unsigned int); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + if (buffer.data_size > 0) { glsafe(::glGenBuffers(1, &buffer.ibo_id)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); @@ -428,8 +453,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); - auto end_time = std::chrono::high_resolution_clock::now(); - std::cout << "toolpaths generation time: " << std::chrono::duration_cast(end_time - start_time).count() << "ms \n"; +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS } void GCodeViewer::load_shells(const Print& print, bool initialized) @@ -558,6 +584,10 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -572,6 +602,10 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -586,6 +620,10 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -600,6 +638,10 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -614,6 +656,10 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -628,6 +674,10 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -639,6 +689,10 @@ void GCodeViewer::render_toolpaths() const set_color(current_program_id, extrusion_color(path)); glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_line_strip_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -650,6 +704,10 @@ void GCodeViewer::render_toolpaths() const set_color(current_program_id, (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path)); glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_line_strip_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS } break; } @@ -678,7 +736,7 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); } -void GCodeViewer::render_overlay() const +void GCodeViewer::render_legend() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); @@ -692,10 +750,6 @@ void GCodeViewer::render_overlay() const ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - if (ImGui::IsWindowAppearing()) - // force an extra farme - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label) { @@ -893,6 +947,64 @@ void GCodeViewer::render_overlay() const ImGui::PopStyleVar(); } +#if ENABLE_GCODE_VIEWER_STATISTICS +void GCodeViewer::render_statistics() const +{ + static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); + static const float offset = 250.0f; + + if (!m_legend_enabled || m_roles.empty()) + return; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + imgui.begin(std::string("Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Load time:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.load_time) + "ms"); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Resfresh time:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.refresh_time) + "ms"); + + ImGui::Separator(); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("GL_POINTS calls:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.gl_points_calls_count)); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("GL_LINE_STRIP calls:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.gl_line_strip_calls_count)); + + ImGui::Separator(); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Vertices:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.vertices_size) + " bytes"); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Indices:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.indices_size) + " bytes"); + + imgui.end(); +} +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index d2eb103f65..be239e20ce 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -2,7 +2,6 @@ #define slic3r_GCodeViewer_hpp_ #if ENABLE_GCODE_VIEWER - #include "GLShader.hpp" #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" @@ -133,6 +132,39 @@ class GCodeViewer void reset_ranges() { ranges.reset(); } }; +#if ENABLE_GCODE_VIEWER_STATISTICS + struct Statistics + { + long long load_time{ 0 }; + long long refresh_time{ 0 }; + long long gl_points_calls_count{ 0 }; + long long gl_line_strip_calls_count{ 0 }; + long long vertices_size{ 0 }; + long long indices_size{ 0 }; + + void reset_all() { + reset_times(); + reset_opengl(); + reset_sizes(); + } + + void reset_times() { + load_time = 0; + refresh_time = 0; + } + + void reset_opengl() { + gl_points_calls_count = 0; + gl_line_strip_calls_count = 0; + } + + void reset_sizes() { + vertices_size = 0; + indices_size = 0; + } + }; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + public: enum class EViewType : unsigned char { @@ -161,6 +193,9 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; +#if ENABLE_GCODE_VIEWER_STATISTICS + mutable Statistics m_statistics; +#endif // ENABLE_GCODE_VIEWER_STATISTICS public: GCodeViewer() = default; @@ -205,7 +240,10 @@ private: void load_shells(const Print& print, bool initialized); void render_toolpaths() const; void render_shells() const; - void render_overlay() const; + void render_legend() const; +#if ENABLE_GCODE_VIEWER_STATISTICS + void render_statistics() const; +#endif // ENABLE_GCODE_VIEWER_STATISTICS bool is_visible(ExtrusionRole role) const { return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fa38aca470..91b8e37929 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4050,9 +4050,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_selection.is_empty()) m_gizmos.reset_all_states(); +#if ENABLE_GCODE_VIEWER + m_dirty = true; +#else // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. if (m_picking_enabled) m_dirty = true; +#endif // ENABLE_GCODE_VIEWER } else evt.Skip(); From 2a4d011817912c4420a9c8458df9eb8451e12e2b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 28 Apr 2020 08:50:52 +0200 Subject: [PATCH 046/826] GCodeViewer -> Toggle extrusion role visibility by clicking on legend --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 41 ++++++++++++++++++++--- src/slic3r/GUI/GCodeViewer.hpp | 4 ++- src/slic3r/GUI/GLCanvas3D.hpp | 3 ++ src/slic3r/GUI/GUI.cpp | 36 ++++++++++++++------- src/slic3r/GUI/GUI.hpp | 8 +++-- src/slic3r/GUI/GUI_Preview.cpp | 59 +++++++++++++++++++++++++++------- src/slic3r/GUI/GUI_Preview.hpp | 9 ++++++ src/slic3r/GUI/Plater.cpp | 17 ++++++++++ src/slic3r/GUI/Plater.hpp | 4 +++ 10 files changed, 152 insertions(+), 31 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index d04bc97fa4..255afc631e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,7 +59,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_2_3_0_ALPHA1) +#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 7426eafc9e..71053819dc 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -271,10 +271,29 @@ void GCodeViewer::set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, m_buffers[id].visible = visible; } +unsigned int GCodeViewer::get_options_visibility_flags() const +{ + auto set_flag = [](unsigned int flags, unsigned int flag, bool active) { + return active ? (flags | (1 << flag)) : flags; + }; + + unsigned int flags = 0; + flags = set_flag(flags, 0, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel)); + flags = set_flag(flags, 1, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract)); + flags = set_flag(flags, 2, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract)); + flags = set_flag(flags, 3, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change)); + flags = set_flag(flags, 4, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change)); + flags = set_flag(flags, 5, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print)); + flags = set_flag(flags, 6, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode)); + flags = set_flag(flags, 7, m_shells.visible); + flags = set_flag(flags, 8, is_legend_enabled()); + return flags; +} + void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) { auto is_flag_set = [flags](unsigned int flag) { - return (flags& (1 << flag)) != 0; + return (flags & (1 << flag)) != 0; }; set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel, is_flag_set(0)); @@ -752,7 +771,7 @@ void GCodeViewer::render_legend() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); - auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label) { + auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label, std::function callback = nullptr) { float icon_size = ImGui::GetTextLineHeight(); ImVec2 pos = ImGui::GetCursorPos(); draw_list->AddRect({ pos.x, pos.y }, { pos.x + icon_size, pos.y + icon_size }, ICON_BORDER_COLOR, 0.0f, 0); @@ -762,7 +781,13 @@ void GCodeViewer::render_legend() const // draw text ImGui::Dummy({ icon_size, icon_size }); ImGui::SameLine(); - imgui.text(label); + if (callback != nullptr) + { + if (ImGui::MenuItem(label.c_str())) + callback(); + } + else + imgui.text(label); }; auto add_range = [this, draw_list, &imgui, add_item](const Extrusions::Range& range, unsigned int decimals) { @@ -811,7 +836,15 @@ void GCodeViewer::render_legend() const if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - add_item(Extrusion_Role_Colors[static_cast(role)], I18N::translate_utf8(ExtrusionEntity::role_to_string(role))); + add_item(Extrusion_Role_Colors[static_cast(role)], I18N::translate_utf8(ExtrusionEntity::role_to_string(role)), [this, role]() { + if (role < erCount) + { + m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->update_preview_bottom_toolbar(); + } + }); + if (!visible) ImGui::PopStyleVar(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index be239e20ce..a8816f7a16 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -189,7 +189,7 @@ private: std::array m_layers_z_range; std::vector m_roles; std::vector m_extruder_ids; - Extrusions m_extrusions; + mutable Extrusions m_extrusions; Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; @@ -227,7 +227,9 @@ public: bool is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const; void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); + unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; } void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } + unsigned int get_options_visibility_flags() const; void set_options_visibility_from_flags(unsigned int flags); void set_layers_z_range(const std::array& layers_z_range) { m_layers_z_range = layers_z_range; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 152658b13a..bbe53c5cd2 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -646,8 +646,11 @@ public: void ensure_on_bed(unsigned int object_idx); #if ENABLE_GCODE_VIEWER + GCodeViewer::EViewType get_gcode_view_type() const { return m_gcode_viewer.get_view_type(); } const std::vector& get_layers_zs() const; + unsigned int get_gcode_options_visibility_flags() const { return m_gcode_viewer.get_options_visibility_flags(); } void set_gcode_options_visibility_from_flags(unsigned int flags); + unsigned int get_toolpath_role_visibility_flags() const { return m_gcode_viewer.get_toolpath_role_visibility_flags(); } void set_toolpath_role_visibility_flags(unsigned int flags); void set_toolpath_view_type(GCodeViewer::EViewType type); void set_toolpaths_z_range(const std::array& range); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index a66396b277..ebdc51c6b4 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -301,21 +301,33 @@ void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, cons } } -int combochecklist_get_flags(wxComboCtrl* comboCtrl) +unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl) { - int flags = 0; + unsigned int flags = 0; - wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); - if (popup != nullptr) - { - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { - if (popup->IsChecked(i)) - flags |= 1 << i; - } - } + wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); + if (popup != nullptr) + { + for (unsigned int i = 0; i < popup->GetCount(); ++i) + { + if (popup->IsChecked(i)) + flags |= 1 << i; + } + } - return flags; + return flags; +} + +void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags) +{ + wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); + if (popup != nullptr) + { + for (unsigned int i = 0; i < popup->GetCount(); ++i) + { + popup->Check(i, (flags & (1 << i)) != 0); + } + } } AppConfig* get_app_config() diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index 6690b21207..cf133971e3 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -54,8 +54,12 @@ void warning_catcher(wxWindow* parent, const wxString& message); void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items); // Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl, -// encoded inside an int. -int combochecklist_get_flags(wxComboCtrl* comboCtrl); +// encoded inside an unsigned int. +unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl); + +// Sets the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl, +// with the flags encoded in the given unsigned int. +void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags); // wxString conversions: diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 3e0d5cd5a4..e84d1a9d56 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -222,6 +222,9 @@ Preview::Preview( : m_canvas_widget(nullptr) , m_canvas(nullptr) , m_double_slider_sizer(nullptr) +#if ENABLE_GCODE_VIEWER + , m_bottom_toolbar_sizer(nullptr) +#endif // ENABLE_GCODE_VIEWER , m_label_view_type(nullptr) , m_choice_view_type(nullptr) , m_label_show(nullptr) @@ -256,7 +259,9 @@ Preview::Preview( if (init(parent, bed, camera, view_toolbar, model)) #endif // ENABLE_NON_STATIC_CANVAS_MANAGER { +#if !ENABLE_GCODE_VIEWER show_hide_ui_elements("none"); +#endif // !ENABLE_GCODE_VIEWER load_print(); } } @@ -358,15 +363,21 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); +#if ENABLE_GCODE_VIEWER + m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); + m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); + m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); + m_bottom_toolbar_sizer->AddSpacer(10); + m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL, 5); + m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5); + m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxEXPAND | wxALL, 5); +#else wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); bottom_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL, 5); bottom_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5); -#if ENABLE_GCODE_VIEWER - bottom_sizer->Add(m_combochecklist_options, 0, wxEXPAND | wxALL, 5); -#else bottom_sizer->AddSpacer(20); bottom_sizer->Add(m_checkbox_travel, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); @@ -381,8 +392,12 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0); +#if ENABLE_GCODE_VIEWER + main_sizer->Add(m_bottom_toolbar_sizer, 0, wxALL | wxEXPAND, 0); + main_sizer->Hide(m_bottom_toolbar_sizer); +#else main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0); - +#endif // ENABLE_GCODE_VIEWER SetSizer(main_sizer); SetMinSize(GetSize()); GetSizer()->SetSizeHints(this); @@ -480,6 +495,9 @@ void Preview::load_print(bool keep_z_range) else if (tech == ptSLA) load_print_as_sla(); +#if ENABLE_GCODE_VIEWER + update_bottom_toolbar(); +#endif // ENABLE_GCODE_VIEWER Layout(); } @@ -581,6 +599,7 @@ void Preview::unbind_event_handlers() #endif // ENABLE_GCODE_VIEWER } +#if !ENABLE_GCODE_VIEWER void Preview::show_hide_ui_elements(const std::string& what) { bool enable = (what == "full"); @@ -615,6 +634,7 @@ void Preview::show_hide_ui_elements(const std::string& what) m_label_view_type->Show(visible); m_choice_view_type->Show(visible); } +#endif // !ENABLE_GCODE_VIEWER void Preview::reset_sliders(bool reset_all) { @@ -661,11 +681,11 @@ void Preview::on_choice_view_type(wxCommandEvent& evt) void Preview::on_combochecklist_features(wxCommandEvent& evt) { - int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features); + unsigned int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features); #if ENABLE_GCODE_VIEWER - m_canvas->set_toolpath_role_visibility_flags(static_cast(flags)); + m_canvas->set_toolpath_role_visibility_flags(flags); #else - m_gcode_preview_data->extrusion.role_flags = (unsigned int)flags; + m_gcode_preview_data->extrusion.role_flags = flags; #endif // ENABLE_GCODE_VIEWER refresh_print(); } @@ -673,7 +693,7 @@ void Preview::on_combochecklist_features(wxCommandEvent& evt) #if ENABLE_GCODE_VIEWER void Preview::on_combochecklist_options(wxCommandEvent& evt) { - m_canvas->set_gcode_options_visibility_from_flags(static_cast(Slic3r::GUI::combochecklist_get_flags(m_combochecklist_options))); + m_canvas->set_gcode_options_visibility_from_flags(Slic3r::GUI::combochecklist_get_flags(m_combochecklist_options)); refresh_print(); } #else @@ -730,6 +750,17 @@ void Preview::update_view_type(bool slice_completed) } } +#if ENABLE_GCODE_VIEWER +void Preview::update_bottom_toolbar() +{ + combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags()); + combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags()); + + m_bottom_toolbar_sizer->Show(m_combochecklist_features, m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType); + m_bottom_toolbar_sizer->Layout(); +} +#endif // ENABLE_GCODE_VIEWER + void Preview::create_double_slider() { m_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100); @@ -1039,7 +1070,8 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER m_canvas->load_gcode_preview(*m_gcode_result); m_canvas->refresh_gcode_preview(*m_gcode_result, colors); - show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); + GetSizer()->Show(m_bottom_toolbar_sizer); + GetSizer()->Layout(); #else m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); #endif // ENABLE_GCODE_VIEWER @@ -1048,7 +1080,8 @@ void Preview::load_print_as_fff(bool keep_z_range) // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); #if ENABLE_GCODE_VIEWER - show_hide_ui_elements("none"); + GetSizer()->Hide(m_bottom_toolbar_sizer); + GetSizer()->Layout(); #endif // ENABLE_GCODE_VIEWER } #if ENABLE_GCODE_VIEWER @@ -1097,7 +1130,12 @@ void Preview::load_print_as_sla() if (IsShown()) { m_canvas->load_sla_preview(); +#if ENABLE_GCODE_VIEWER + GetSizer()->Hide(m_bottom_toolbar_sizer); + GetSizer()->Layout(); +#else show_hide_ui_elements("none"); +#endif // ENABLE_GCODE_VIEWER if (n_layers > 0) update_sliders(zs); @@ -1132,6 +1170,5 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) } } - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index c4ad4eb79c..a7db054bc3 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -91,6 +91,9 @@ class Preview : public wxPanel wxGLCanvas* m_canvas_widget; GLCanvas3D* m_canvas; wxBoxSizer* m_double_slider_sizer; +#if ENABLE_GCODE_VIEWER + wxBoxSizer* m_bottom_toolbar_sizer; +#endif // ENABLE_GCODE_VIEWER wxStaticText* m_label_view_type; wxChoice* m_choice_view_type; wxStaticText* m_label_show; @@ -172,6 +175,10 @@ public: bool is_loaded() const { return m_loaded; } +#if ENABLE_GCODE_VIEWER + void update_bottom_toolbar(); +#endif // ENABLE_GCODE_VIEWER + private: #if ENABLE_NON_STATIC_CANVAS_MANAGER bool init(wxWindow* parent, Model* model); @@ -182,7 +189,9 @@ private: void bind_event_handlers(); void unbind_event_handlers(); +#if !ENABLE_GCODE_VIEWER void show_hide_ui_elements(const std::string& what); +#endif // !ENABLE_GCODE_VIEWER void reset_sliders(bool reset_all); void update_sliders(const std::vector& layers_z, bool keep_z_range = false); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b64f705dd0..861e0e55fe 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1570,6 +1570,9 @@ struct Plater::priv #endif // ENABLE_NON_STATIC_CANVAS_MANAGER bool init_view_toolbar(); +#if ENABLE_GCODE_VIEWER + void update_preview_bottom_toolbar(); +#endif // ENABLE_GCODE_VIEWER void reset_all_gizmos(); void update_ui_from_settings(); @@ -3765,6 +3768,13 @@ bool Plater::priv::init_view_toolbar() return true; } +#if ENABLE_GCODE_VIEWER +void Plater::priv::update_preview_bottom_toolbar() +{ + preview->update_bottom_toolbar(); +} +#endif // ENABLE_GCODE_VIEWER + bool Plater::priv::can_set_instance_to_object() const { const int obj_idx = get_selected_object_idx(); @@ -5313,6 +5323,13 @@ GLToolbar& Plater::get_view_toolbar() } #endif // ENABLE_NON_STATIC_CANVAS_MANAGER +#if ENABLE_GCODE_VIEWER +void Plater::update_preview_bottom_toolbar() +{ + p->update_preview_bottom_toolbar(); +} +#endif // ENABLE_GCODE_VIEWER + const Mouse3DController& Plater::get_mouse3d_controller() const { return p->mouse3d_controller; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 2ac4f23c18..f4ca22578f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -310,6 +310,10 @@ public: GLToolbar& get_view_toolbar(); #endif // ENABLE_NON_STATIC_CANVAS_MANAGER +#if ENABLE_GCODE_VIEWER + void update_preview_bottom_toolbar(); +#endif // ENABLE_GCODE_VIEWER + const Mouse3DController& get_mouse3d_controller() const; Mouse3DController& get_mouse3d_controller(); From a77461b467ba68d6650452d82979476cc4581f70 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 28 Apr 2020 09:09:24 +0200 Subject: [PATCH 047/826] GCodeViewer -> Fixed synchronization between legend and bottom toolbar --- src/slic3r/GUI/GLCanvas3D.cpp | 1 + src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_Preview.cpp | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 91b8e37929..080ba2045b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3216,6 +3216,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) { m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); m_dirty = true; + wxGetApp().plater()->update_preview_bottom_toolbar(); } break; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index bbe53c5cd2..c640108fdf 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -646,6 +646,7 @@ public: void ensure_on_bed(unsigned int object_idx); #if ENABLE_GCODE_VIEWER + bool is_gcode_legend_enabled() const { return m_gcode_viewer.is_legend_enabled(); } GCodeViewer::EViewType get_gcode_view_type() const { return m_gcode_viewer.get_view_type(); } const std::vector& get_layers_zs() const; unsigned int get_gcode_options_visibility_flags() const { return m_gcode_viewer.get_options_visibility_flags(); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index e84d1a9d56..28fd45b6c2 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -369,8 +369,8 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); m_bottom_toolbar_sizer->AddSpacer(10); m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL, 5); - m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5); m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxEXPAND | wxALL, 5); + m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5); #else wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); @@ -756,7 +756,8 @@ void Preview::update_bottom_toolbar() combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags()); combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags()); - m_bottom_toolbar_sizer->Show(m_combochecklist_features, m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType); + m_bottom_toolbar_sizer->Show(m_combochecklist_features, + !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType); m_bottom_toolbar_sizer->Layout(); } #endif // ENABLE_GCODE_VIEWER From 1cb0f044db9ec1430074e4de59a32ed299c28912 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 28 Apr 2020 10:29:25 +0200 Subject: [PATCH 048/826] GCodeProcessor::MoveVertex -> added placeholder for time --- src/libslic3r/GCode/GCodeProcessor.cpp | 1 + src/libslic3r/GCode/GCodeProcessor.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 7 +++++++ src/slic3r/GUI/GCodeViewer.hpp | 2 ++ 4 files changed, 11 insertions(+) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 9dc3964436..9fda79a136 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -591,6 +591,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) vertex.fan_speed = m_fan_speed; vertex.extruder_id = m_extruder_id; vertex.cp_color_id = m_cp_color.current; + vertex.time = static_cast(m_result.moves.size()); m_result.moves.emplace_back(vertex); } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 2212148367..4f6cf7430f 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -82,6 +82,7 @@ namespace Slic3r { float height{ 0.0f }; // mm float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage + float time{ 0.0f }; // s float volumetric_rate() const { return feedrate * mm3_per_mm; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 71053819dc..d60b5e9b89 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -346,6 +346,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); + m_statistics.results_size = gcode_result.moves.size() * sizeof(GCodeProcessor::MoveVertex); #endif // ENABLE_GCODE_VIEWER_STATISTICS // vertex data @@ -1022,6 +1023,12 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Results:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.results_size) + " bytes"); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Vertices:")); ImGui::PopStyleColor(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index a8816f7a16..be1c7e9986 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -139,6 +139,7 @@ class GCodeViewer long long refresh_time{ 0 }; long long gl_points_calls_count{ 0 }; long long gl_line_strip_calls_count{ 0 }; + long long results_size{ 0 }; long long vertices_size{ 0 }; long long indices_size{ 0 }; @@ -159,6 +160,7 @@ class GCodeViewer } void reset_sizes() { + results_size = 0; vertices_size = 0; indices_size = 0; } From d265c84b76241dae01ade0658cc645bddbc29893 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 28 Apr 2020 12:24:03 +0200 Subject: [PATCH 049/826] GCodeViewer -> Refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 25 ++++++++++++------------- src/slic3r/GUI/GCodeViewer.hpp | 14 +++++++++----- src/slic3r/GUI/GUI_Preview.cpp | 14 +++----------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index d60b5e9b89..db16354ebe 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -92,9 +92,8 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { - unsigned int id = static_cast(data.size()); - double z = static_cast(move.position[2]); - paths.push_back({ move.type, move.extrusion_role, id, id, z, z, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); + Path::Endpoint endpoint = { static_cast(data.size()), static_cast(move.position[2]) }; + paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); } std::array GCodeViewer::Extrusions::Range::get_color_at(float value) const @@ -409,13 +408,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer.data.push_back(static_cast(i - 1)); } - buffer.paths.back().last = static_cast(buffer.data.size()); + buffer.paths.back().last.id = static_cast(buffer.data.size()); buffer.data.push_back(static_cast(i)); break; } default: { - continue; + break; } } } @@ -602,7 +601,7 @@ void GCodeViewer::render_toolpaths() const continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -620,7 +619,7 @@ void GCodeViewer::render_toolpaths() const continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -638,7 +637,7 @@ void GCodeViewer::render_toolpaths() const continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -656,7 +655,7 @@ void GCodeViewer::render_toolpaths() const continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -674,7 +673,7 @@ void GCodeViewer::render_toolpaths() const continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -692,7 +691,7 @@ void GCodeViewer::render_toolpaths() const continue; glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -708,7 +707,7 @@ void GCodeViewer::render_toolpaths() const continue; set_color(current_program_id, extrusion_color(path)); - glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_line_strip_calls_count; @@ -723,7 +722,7 @@ void GCodeViewer::render_toolpaths() const continue; set_color(current_program_id, (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path)); - glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last - path.first + 1), GL_UNSIGNED_INT, (const void*)(path.first * sizeof(GLuint)))); + glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_line_strip_calls_count; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index be1c7e9986..9688c5c4b8 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -35,12 +35,16 @@ class GCodeViewer // Used to identify different toolpath sub-types inside a IBuffer struct Path { + struct Endpoint + { + unsigned int id{ 0u }; + double z{ 0.0 }; + }; + GCodeProcessor::EMoveType type{ GCodeProcessor::EMoveType::Noop }; ExtrusionRole role{ erNone }; - unsigned int first{ 0 }; - unsigned int last{ 0 }; - double first_z{ 0.0f }; - double last_z{ 0.0f }; + Endpoint first; + Endpoint last; float delta_extruder{ 0.0f }; float height{ 0.0f }; float width{ 0.0f }; @@ -257,7 +261,7 @@ private: return z > m_layers_z_range[0] - EPSILON && z < m_layers_z_range[1] + EPSILON; }; - return in_z_range(path.first_z) || in_z_range(path.last_z); + return in_z_range(path.first.z) || in_z_range(path.last.z); } }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 28fd45b6c2..72f40bb6b7 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -314,7 +314,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_label_show = new wxStaticText(this, wxID_ANY, _(L("Show"))); m_combochecklist_features = new wxComboCtrl(); - m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); + m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); std::string feature_items = GUI::into_u8( #if ENABLE_GCODE_VIEWER _L("Unknown") + "|1|" + @@ -337,7 +337,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view #if ENABLE_GCODE_VIEWER m_combochecklist_options = new wxComboCtrl(); - m_combochecklist_options->Create(this, wxID_ANY, _(L("Others")), wxDefaultPosition, wxSize(15 * wxGetApp().em_unit(), -1), wxCB_READONLY); + m_combochecklist_options->Create(this, wxID_ANY, _(L("Options")), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); std::string options_items = GUI::into_u8( _(L("Travel")) + "|0|" + _(L("Retractions")) + "|0|" + @@ -349,7 +349,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view _(L("Shells")) + "|0|" + _(L("Legend")) + "|1" ); - Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_(L("Others"))), options_items); + Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_(L("Options"))), options_items); #else m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions"))); @@ -605,15 +605,11 @@ void Preview::show_hide_ui_elements(const std::string& what) bool enable = (what == "full"); m_label_show->Enable(enable); m_combochecklist_features->Enable(enable); -#if ENABLE_GCODE_VIEWER - m_combochecklist_options->Enable(enable); -#else m_checkbox_travel->Enable(enable); m_checkbox_retractions->Enable(enable); m_checkbox_unretractions->Enable(enable); m_checkbox_shells->Enable(enable); m_checkbox_legend->Enable(enable); -#endif // ENABLE_GCODE_VIEWER enable = (what != "none"); m_label_view_type->Enable(enable); @@ -622,15 +618,11 @@ void Preview::show_hide_ui_elements(const std::string& what) bool visible = (what != "none"); m_label_show->Show(visible); m_combochecklist_features->Show(visible); -#if ENABLE_GCODE_VIEWER - m_combochecklist_options->Show(visible); -#else m_checkbox_travel->Show(visible); m_checkbox_retractions->Show(visible); m_checkbox_unretractions->Show(visible); m_checkbox_shells->Show(visible); m_checkbox_legend->Show(visible); -#endif // ENABLE_GCODE_VIEWER m_label_view_type->Show(visible); m_choice_view_type->Show(visible); } From 3267d3368f0d80921df5d045ed096afb248f141d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 28 Apr 2020 15:08:36 +0200 Subject: [PATCH 050/826] GCodeViewer -> Use glMultiDrawElements() in place of glDrawElements() to draw extrude and travel paths --- src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 135 +++++++++++++++++++++++++++++++-- src/slic3r/GUI/GCodeViewer.hpp | 28 +++++++ 3 files changed, 157 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 255afc631e..3f821ddce0 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_GL_OPTIMIZATION (1 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index db16354ebe..64c027a7b2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -214,6 +214,11 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: } } +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + // update buffers' render paths + refresh_render_paths(); +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -345,7 +350,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); - m_statistics.results_size = gcode_result.moves.size() * sizeof(GCodeProcessor::MoveVertex); + m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex); #endif // ENABLE_GCODE_VIEWER_STATISTICS // vertex data @@ -363,7 +368,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.vertices_size = vertices_data.size() * sizeof(float); + m_statistics.vertices_size = SLIC3R_STDVEC_MEMSIZE(vertices_data, float); + m_statistics.vertices_gpu_size = vertices_data.size() * sizeof(float); #endif // ENABLE_GCODE_VIEWER_STATISTICS // vertex data -> send to gpu @@ -424,7 +430,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { buffer.data_size = buffer.data.size(); #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.indices_size += buffer.data_size * sizeof(unsigned int); + m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer.data, unsigned int); + m_statistics.indices_gpu_size += buffer.data_size * sizeof(unsigned int); #endif // ENABLE_GCODE_VIEWER_STATISTICS if (buffer.data_size > 0) { @@ -526,7 +533,8 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } -void GCodeViewer::render_toolpaths() const +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION +void GCodeViewer::refresh_render_paths() const { auto extrusion_color = [this](const Path& path) { std::array color; @@ -551,6 +559,65 @@ void GCodeViewer::render_toolpaths() const Travel_Colors[0] /* Move */); }; + + for (IBuffer& buffer : m_buffers) { + buffer.render_paths = std::vector(); + for (const Path& path : buffer.paths) + { + if (!is_in_z_range(path)) + continue; + + if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) + continue; + + std::array color = { 0.0f, 0.0f, 0.0f }; + switch (path.type) + { + case GCodeProcessor::EMoveType::Extrude: { color = extrusion_color(path); break; } + case GCodeProcessor::EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } + } + + auto it = std::find_if(buffer.render_paths.begin(), buffer.render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); + if (it == buffer.render_paths.end()) + { + it = buffer.render_paths.insert(buffer.render_paths.end(), RenderPath()); + it->color = color; + } + + it->sizes.push_back(path.last.id - path.first.id + 1); + it->offsets.push_back(static_cast(path.first.id * sizeof(unsigned int))); + } + } +} +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + +void GCodeViewer::render_toolpaths() const +{ +#if !ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + auto extrusion_color = [this](const Path& path) { + std::array color; + switch (m_view_type) + { + case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } + case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } + case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } + case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } + case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } + case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } + case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } + case EViewType::ColorPrint: { color = m_tool_colors[path.cp_color_id]; break; } + default: { color = { 1.0f, 1.0f, 1.0f }; break; } + } + return color; + }; + + auto travel_color = [this](const Path& path) { + return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : + ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : + Travel_Colors[0] /* Move */); + }; +#endif // !ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + auto set_color = [](GLint current_program_id, const std::array& color) { if (current_program_id > 0) { GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; @@ -702,6 +769,17 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Extrude: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + for (const RenderPath& path : buffer.render_paths) + { + set_color(current_program_id, path.color); + glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_line_strip_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + } +#else for (const Path& path : buffer.paths) { if (!is_visible(path) || !is_in_z_range(path)) continue; @@ -713,10 +791,22 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_line_strip_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Travel: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + for (const RenderPath& path : buffer.render_paths) + { + set_color(current_program_id, path.color); + glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_line_strip_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + } +#else for (const Path& path : buffer.paths) { if (!is_in_z_range(path)) continue; @@ -728,6 +818,7 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_line_strip_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } } @@ -840,6 +931,10 @@ void GCodeViewer::render_legend() const if (role < erCount) { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + // update buffers' render paths + refresh_render_paths(); +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->update_preview_bottom_toolbar(); } @@ -986,7 +1081,7 @@ void GCodeViewer::render_statistics() const static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); static const float offset = 250.0f; - if (!m_legend_enabled || m_roles.empty()) + if (m_roles.empty()) return; ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -1020,6 +1115,20 @@ void GCodeViewer::render_statistics() const ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.gl_line_strip_calls_count)); +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Multi GL_POINTS calls:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count)); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Multi GL_LINE_STRIP calls:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.gl_multi_line_strip_calls_count)); +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); @@ -1029,17 +1138,29 @@ void GCodeViewer::render_statistics() const imgui.text(std::to_string(m_statistics.results_size) + " bytes"); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("Vertices:")); + imgui.text(std::string("Vertices CPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.vertices_size) + " bytes"); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("Indices:")); + imgui.text(std::string("Vertices GPU:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Indices CPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.indices_size) + " bytes"); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Indices GPU:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); + imgui.end(); } #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 9688c5c4b8..ec3bfc9a63 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -61,6 +61,16 @@ class GCodeViewer } }; +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + // Used to batch the indices needed to render paths + struct RenderPath + { + std::array color; + std::vector sizes; + std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) + }; +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + // buffer containing indices data and shader for a specific toolpath type struct IBuffer { @@ -69,6 +79,9 @@ class GCodeViewer std::vector data; size_t data_size{ 0 }; std::vector paths; +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + std::vector render_paths; +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION bool visible{ false }; void reset(); @@ -143,9 +156,15 @@ class GCodeViewer long long refresh_time{ 0 }; long long gl_points_calls_count{ 0 }; long long gl_line_strip_calls_count{ 0 }; +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + long long gl_multi_points_calls_count{ 0 }; + long long gl_multi_line_strip_calls_count{ 0 }; +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION long long results_size{ 0 }; long long vertices_size{ 0 }; + long long vertices_gpu_size{ 0 }; long long indices_size{ 0 }; + long long indices_gpu_size{ 0 }; void reset_all() { reset_times(); @@ -161,12 +180,18 @@ class GCodeViewer void reset_opengl() { gl_points_calls_count = 0; gl_line_strip_calls_count = 0; +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + gl_multi_points_calls_count = 0; + gl_multi_line_strip_calls_count = 0; +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION } void reset_sizes() { results_size = 0; vertices_size = 0; + vertices_gpu_size = 0; indices_size = 0; + indices_gpu_size = 0; } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -246,6 +271,9 @@ private: bool init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + void refresh_render_paths() const; +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION void render_toolpaths() const; void render_shells() const; void render_legend() const; From d8f6a9179f096f4c495da269c015be34cd5bcfbb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 28 Apr 2020 15:49:01 +0200 Subject: [PATCH 051/826] GCodeViewer -> Use glMultiDrawElements() in place of glDrawElements() to draw all entities --- src/slic3r/GUI/GCodeViewer.cpp | 92 +++++++++++++++++++++++++++++++++- src/slic3r/GUI/GCodeViewer.hpp | 8 +++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 64c027a7b2..4b2784df98 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -661,6 +661,20 @@ void GCodeViewer::render_toolpaths() const { case GCodeProcessor::EMoveType::Tool_change: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + std::array color = { 1.0f, 1.0f, 1.0f }; + set_color(current_program_id, color); + for (const RenderPath& path : buffer.render_paths) + { + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } +#else std::array color = { 1.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { @@ -675,10 +689,25 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Color_change: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + std::array color = { 1.0f, 0.0f, 0.0f }; + set_color(current_program_id, color); + for (const RenderPath& path : buffer.render_paths) + { + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } +#else std::array color = { 1.0f, 0.0f, 0.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { @@ -693,10 +722,25 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Pause_Print: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + std::array color = { 0.0f, 1.0f, 0.0f }; + set_color(current_program_id, color); + for (const RenderPath& path : buffer.render_paths) + { + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } +#else std::array color = { 0.0f, 1.0f, 0.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { @@ -711,10 +755,25 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Custom_GCode: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + std::array color = { 0.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + for (const RenderPath& path : buffer.render_paths) + { + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } +#else std::array color = { 0.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { @@ -729,10 +788,25 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Retract: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + std::array color = { 1.0f, 0.0f, 1.0f }; + set_color(current_program_id, color); + for (const RenderPath& path : buffer.render_paths) + { + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } +#else std::array color = { 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { @@ -747,10 +821,25 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Unretract: { +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + std::array color = { 0.0f, 1.0f, 1.0f }; + set_color(current_program_id, color); + for (const RenderPath& path : buffer.render_paths) + { + glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); + +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } +#else std::array color = { 0.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const Path& path : buffer.paths) { @@ -765,6 +854,7 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Extrude: @@ -1132,7 +1222,7 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("Results:")); + imgui.text(std::string("GCodeProcessor results:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.results_size) + " bytes"); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index ec3bfc9a63..0f90be5d73 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -262,7 +262,15 @@ public: void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } unsigned int get_options_visibility_flags() const; void set_options_visibility_from_flags(unsigned int flags); +#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION + void set_layers_z_range(const std::array& layers_z_range) + { + m_layers_z_range = layers_z_range; + refresh_render_paths(); + } +#else void set_layers_z_range(const std::array& layers_z_range) { m_layers_z_range = layers_z_range; } +#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } From c9bd0840b3d3ad20e531bd10815b4d8fe4f92814 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Apr 2020 08:24:39 +0200 Subject: [PATCH 052/826] GCodeViewer -> Code cleanup --- src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/GCodeViewer.cpp | 175 --------------------------------- src/slic3r/GUI/GCodeViewer.hpp | 18 ---- 3 files changed, 194 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 3f821ddce0..255afc631e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,7 +59,6 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_GL_OPTIMIZATION (1 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 4b2784df98..579ef50a54 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -214,10 +214,8 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: } } -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION // update buffers' render paths refresh_render_paths(); -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); @@ -533,7 +531,6 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION void GCodeViewer::refresh_render_paths() const { auto extrusion_color = [this](const Path& path) { @@ -589,35 +586,9 @@ void GCodeViewer::refresh_render_paths() const } } } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION void GCodeViewer::render_toolpaths() const { -#if !ENABLE_GCODE_VIEWER_GL_OPTIMIZATION - auto extrusion_color = [this](const Path& path) { - std::array color; - switch (m_view_type) - { - case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } - case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } - case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } - case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } - case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } - case EViewType::ColorPrint: { color = m_tool_colors[path.cp_color_id]; break; } - default: { color = { 1.0f, 1.0f, 1.0f }; break; } - } - return color; - }; - - auto travel_color = [this](const Path& path) { - return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : - Travel_Colors[0] /* Move */); - }; -#endif // !ENABLE_GCODE_VIEWER_GL_OPTIMIZATION - auto set_color = [](GLint current_program_id, const std::array& color) { if (current_program_id > 0) { GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; @@ -661,7 +632,6 @@ void GCodeViewer::render_toolpaths() const { case GCodeProcessor::EMoveType::Tool_change: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION std::array color = { 1.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) @@ -674,27 +644,10 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - std::array color = { 1.0f, 1.0f, 1.0f }; - set_color(current_program_id, color); - for (const Path& path : buffer.paths) { - if (!is_in_z_range(path)) - continue; - - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Color_change: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION std::array color = { 1.0f, 0.0f, 0.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) @@ -707,27 +660,10 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - std::array color = { 1.0f, 0.0f, 0.0f }; - set_color(current_program_id, color); - for (const Path& path : buffer.paths) { - if (!is_in_z_range(path)) - continue; - - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Pause_Print: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION std::array color = { 0.0f, 1.0f, 0.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) @@ -740,27 +676,10 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - std::array color = { 0.0f, 1.0f, 0.0f }; - set_color(current_program_id, color); - for (const Path& path : buffer.paths) { - if (!is_in_z_range(path)) - continue; - - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Custom_GCode: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION std::array color = { 0.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) @@ -773,27 +692,10 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - std::array color = { 0.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); - for (const Path& path : buffer.paths) { - if (!is_in_z_range(path)) - continue; - - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Retract: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION std::array color = { 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) @@ -806,27 +708,10 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - std::array color = { 1.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); - for (const Path& path : buffer.paths) { - if (!is_in_z_range(path)) - continue; - - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Unretract: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION std::array color = { 0.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) @@ -839,27 +724,10 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - std::array color = { 0.0f, 1.0f, 1.0f }; - set_color(current_program_id, color); - for (const Path& path : buffer.paths) { - if (!is_in_z_range(path)) - continue; - - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); - glsafe(::glDrawElements(GL_POINTS, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Extrude: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION for (const RenderPath& path : buffer.render_paths) { set_color(current_program_id, path.color); @@ -869,24 +737,10 @@ void GCodeViewer::render_toolpaths() const #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - for (const Path& path : buffer.paths) { - if (!is_visible(path) || !is_in_z_range(path)) - continue; - - set_color(current_program_id, extrusion_color(path)); - glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_line_strip_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } case GCodeProcessor::EMoveType::Travel: { -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION for (const RenderPath& path : buffer.render_paths) { set_color(current_program_id, path.color); @@ -896,19 +750,6 @@ void GCodeViewer::render_toolpaths() const #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else - for (const Path& path : buffer.paths) { - if (!is_in_z_range(path)) - continue; - - set_color(current_program_id, (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path)); - glsafe(::glDrawElements(GL_LINE_STRIP, GLsizei(path.last.id - path.first.id + 1), GL_UNSIGNED_INT, (const void*)(path.first.id * sizeof(GLuint)))); - -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_line_strip_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION break; } } @@ -1021,10 +862,8 @@ void GCodeViewer::render_legend() const if (role < erCount) { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION // update buffers' render paths refresh_render_paths(); -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->update_preview_bottom_toolbar(); } @@ -1193,19 +1032,6 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("GL_POINTS calls:")); - ImGui::PopStyleColor(); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_points_calls_count)); - - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("GL_LINE_STRIP calls:")); - ImGui::PopStyleColor(); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_line_strip_calls_count)); - -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Multi GL_POINTS calls:")); ImGui::PopStyleColor(); @@ -1217,7 +1043,6 @@ void GCodeViewer::render_statistics() const ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.gl_multi_line_strip_calls_count)); -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION ImGui::Separator(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 0f90be5d73..9af7e7cc48 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -61,7 +61,6 @@ class GCodeViewer } }; -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION // Used to batch the indices needed to render paths struct RenderPath { @@ -69,7 +68,6 @@ class GCodeViewer std::vector sizes; std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) }; -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION // buffer containing indices data and shader for a specific toolpath type struct IBuffer @@ -79,9 +77,7 @@ class GCodeViewer std::vector data; size_t data_size{ 0 }; std::vector paths; -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION std::vector render_paths; -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION bool visible{ false }; void reset(); @@ -154,12 +150,8 @@ class GCodeViewer { long long load_time{ 0 }; long long refresh_time{ 0 }; - long long gl_points_calls_count{ 0 }; - long long gl_line_strip_calls_count{ 0 }; -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION long long gl_multi_points_calls_count{ 0 }; long long gl_multi_line_strip_calls_count{ 0 }; -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION long long results_size{ 0 }; long long vertices_size{ 0 }; long long vertices_gpu_size{ 0 }; @@ -178,12 +170,8 @@ class GCodeViewer } void reset_opengl() { - gl_points_calls_count = 0; - gl_line_strip_calls_count = 0; -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION gl_multi_points_calls_count = 0; gl_multi_line_strip_calls_count = 0; -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION } void reset_sizes() { @@ -262,15 +250,11 @@ public: void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } unsigned int get_options_visibility_flags() const; void set_options_visibility_from_flags(unsigned int flags); -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION void set_layers_z_range(const std::array& layers_z_range) { m_layers_z_range = layers_z_range; refresh_render_paths(); } -#else - void set_layers_z_range(const std::array& layers_z_range) { m_layers_z_range = layers_z_range; } -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } @@ -279,9 +263,7 @@ private: bool init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); -#if ENABLE_GCODE_VIEWER_GL_OPTIMIZATION void refresh_render_paths() const; -#endif // ENABLE_GCODE_VIEWER_GL_OPTIMIZATION void render_toolpaths() const; void render_shells() const; void render_legend() const; From cd5d70d5e14561375f29fccc821fcaa55381d37b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Apr 2020 10:18:29 +0200 Subject: [PATCH 053/826] GCodeViewer -> Fixed z slider in initial preview --- src/slic3r/GUI/3DScene.cpp | 3 --- src/slic3r/GUI/3DScene.hpp | 4 ---- src/slic3r/GUI/GLCanvas3D.cpp | 12 +++++++----- src/slic3r/GUI/GLCanvas3D.hpp | 3 ++- src/slic3r/GUI/GUI_Preview.cpp | 12 ++++++++---- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 63cacdd457..cac0910e23 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -340,8 +340,6 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d & bounding_box().transformed(trafo); } - -#if !ENABLE_GCODE_VIEWER void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; @@ -376,7 +374,6 @@ void GLVolume::set_range(double min_z, double max_z) } } } -#endif // !ENABLE_GCODE_VIEWER void GLVolume::render() const { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 28295a35f7..70d6fb016a 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -442,9 +442,7 @@ public: bool empty() const { return this->indexed_vertex_array.empty(); } -#if !ENABLE_GCODE_VIEWER void set_range(double low, double high); -#endif // !ENABLE_GCODE_VIEWER void render() const; #if !ENABLE_SLOPE_RENDERING @@ -562,9 +560,7 @@ public: void clear() { for (auto *v : volumes) delete v; volumes.clear(); } bool empty() const { return volumes.empty(); } -#if !ENABLE_GCODE_VIEWER void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } -#endif // !ENABLE_GCODE_VIEWER void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) { m_print_box_min[0] = min_x; m_print_box_min[1] = min_y; m_print_box_min[2] = min_z; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f8c0889fd6..6479947175 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -932,11 +932,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D std::vector> cp_values; cp_values.reserve(custom_gcode_per_print_z.size()); -#if ENABLE_GCODE_VIEWER - const std::vector& print_zs = canvas.get_layers_zs(); -#else std::vector print_zs = canvas.get_current_print_zs(true); -#endif // ENABLE_GCODE_VIEWER for (auto custom_code : custom_gcode_per_print_z) { if (custom_code.gcode != ColorChangeCode) @@ -2327,11 +2323,16 @@ void GLCanvas3D::ensure_on_bed(unsigned int object_idx) #if ENABLE_GCODE_VIEWER -const std::vector& GLCanvas3D::get_layers_zs() const +const std::vector& GLCanvas3D::get_gcode_layers_zs() const { return m_gcode_viewer.get_layers_zs(); } +std::vector GLCanvas3D::get_volumes_print_zs(bool active_only) const +{ + return m_volumes.get_current_print_zs(active_only); +} + void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags) { m_gcode_viewer.set_options_visibility_from_flags(flags); @@ -2350,6 +2351,7 @@ void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) void GLCanvas3D::set_toolpaths_z_range(const std::array& range) { m_gcode_viewer.set_layers_z_range(range); + m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); } #else std::vector GLCanvas3D::get_current_print_zs(bool active_only) const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a5066d923d..f057ea7d6f 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -648,7 +648,8 @@ public: #if ENABLE_GCODE_VIEWER bool is_gcode_legend_enabled() const { return m_gcode_viewer.is_legend_enabled(); } GCodeViewer::EViewType get_gcode_view_type() const { return m_gcode_viewer.get_view_type(); } - const std::vector& get_layers_zs() const; + const std::vector& get_gcode_layers_zs() const; + std::vector get_volumes_print_zs(bool active_only) const; unsigned int get_gcode_options_visibility_flags() const { return m_gcode_viewer.get_options_visibility_flags(); } void set_gcode_options_visibility_from_flags(unsigned int flags); unsigned int get_toolpath_role_visibility_flags() const { return m_gcode_viewer.get_toolpath_role_visibility_flags(); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 72f40bb6b7..7771484f5f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1057,6 +1057,10 @@ void Preview::load_print_as_fff(bool keep_z_range) if (IsShown()) { +#if ENABLE_GCODE_VIEWER + std::vector zs; +#endif // ENABLE_GCODE_VIEWER + m_canvas->set_selected_extruder(0); if (gcode_preview_data_valid) { // Load the real G-code preview. @@ -1065,6 +1069,7 @@ void Preview::load_print_as_fff(bool keep_z_range) m_canvas->refresh_gcode_preview(*m_gcode_result, colors); GetSizer()->Show(m_bottom_toolbar_sizer); GetSizer()->Layout(); + zs = m_canvas->get_gcode_layers_zs(); #else m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); #endif // ENABLE_GCODE_VIEWER @@ -1075,14 +1080,13 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER GetSizer()->Hide(m_bottom_toolbar_sizer); GetSizer()->Layout(); + zs = m_canvas->get_volumes_print_zs(true); #endif // ENABLE_GCODE_VIEWER } -#if ENABLE_GCODE_VIEWER - const std::vector& zs = m_canvas->get_layers_zs(); -#else +#if !ENABLE_GCODE_VIEWER show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); std::vector zs = m_canvas->get_current_print_zs(true); -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER if (zs.empty()) { // all layers filtered out reset_sliders(true); From 9f2f798ea29b8efe00065c4621cd3eb41fe93652 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Apr 2020 13:51:20 +0200 Subject: [PATCH 054/826] GCodeViewer -> Added ironing extrusion role --- src/slic3r/GUI/GCodeViewer.cpp | 1 + src/slic3r/GUI/GUI_Preview.cpp | 73 +++++++++++++++++----------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 579ef50a54..df8e816460 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -127,6 +127,7 @@ const std::vector> GCodeViewer::Extrusion_Role_Colors {{ { 0.69f, 0.19f, 0.16f }, // erInternalInfill { 0.84f, 0.20f, 0.84f }, // erSolidInfill { 1.00f, 0.10f, 0.10f }, // erTopSolidInfill + { 0.00f, 1.00f, 1.00f }, // erIroning { 0.60f, 0.60f, 1.00f }, // erBridgeInfill { 1.00f, 1.00f, 1.00f }, // erGapFill { 0.52f, 0.48f, 0.13f }, // erSkirt diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 7771484f5f..ea7d2dc1bc 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -298,58 +298,59 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); create_double_slider(); - m_label_view_type = new wxStaticText(this, wxID_ANY, _(L("View"))); + m_label_view_type = new wxStaticText(this, wxID_ANY, _L("View")); m_choice_view_type = new wxChoice(this, wxID_ANY); - m_choice_view_type->Append(_(L("Feature type"))); - m_choice_view_type->Append(_(L("Height"))); - m_choice_view_type->Append(_(L("Width"))); - m_choice_view_type->Append(_(L("Speed"))); - m_choice_view_type->Append(_(L("Fan speed"))); - m_choice_view_type->Append(_(L("Volumetric flow rate"))); - m_choice_view_type->Append(_(L("Tool"))); - m_choice_view_type->Append(_(L("Color Print"))); + m_choice_view_type->Append(_L("Feature type")); + m_choice_view_type->Append(_L("Height")); + m_choice_view_type->Append(_L("Width")); + m_choice_view_type->Append(_L("Speed")); + m_choice_view_type->Append(_L("Fan speed")); + m_choice_view_type->Append(_L("Volumetric flow rate")); + m_choice_view_type->Append(_L("Tool")); + m_choice_view_type->Append(_L("Color Print")); m_choice_view_type->SetSelection(0); - m_label_show = new wxStaticText(this, wxID_ANY, _(L("Show"))); + m_label_show = new wxStaticText(this, wxID_ANY, _L("Show")); m_combochecklist_features = new wxComboCtrl(); - m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); + m_combochecklist_features->Create(this, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); std::string feature_items = GUI::into_u8( #if ENABLE_GCODE_VIEWER _L("Unknown") + "|1|" + #endif // ENABLE_GCODE_VIEWER - _(L("Perimeter")) + "|1|" + - _(L("External perimeter")) + "|1|" + - _(L("Overhang perimeter")) + "|1|" + - _(L("Internal infill")) + "|1|" + - _(L("Solid infill")) + "|1|" + - _(L("Top solid infill")) + "|1|" + - _(L("Bridge infill")) + "|1|" + - _(L("Gap fill")) + "|1|" + - _(L("Skirt")) + "|1|" + - _(L("Support material")) + "|1|" + - _(L("Support material interface")) + "|1|" + - _(L("Wipe tower")) + "|1|" + - _(L("Custom")) + "|1" + _L("Perimeter") + "|1|" + + _L("External perimeter") + "|1|" + + _L("Overhang perimeter") + "|1|" + + _L("Internal infill") + "|1|" + + _L("Solid infill") + "|1|" + + _L("Top solid infill") + "|1|" + + _L("Ironing") + "|1|" + + _L("Bridge infill") + "|1|" + + _L("Gap fill") + "|1|" + + _L("Skirt") + "|1|" + + _L("Support material") + "|1|" + + _L("Support material interface") + "|1|" + + _L("Wipe tower") + "|1|" + + _L("Custom") + "|1" ); - Slic3r::GUI::create_combochecklist(m_combochecklist_features, GUI::into_u8(_(L("Feature types"))), feature_items); + Slic3r::GUI::create_combochecklist(m_combochecklist_features, GUI::into_u8(_L("Feature types")), feature_items); #if ENABLE_GCODE_VIEWER m_combochecklist_options = new wxComboCtrl(); - m_combochecklist_options->Create(this, wxID_ANY, _(L("Options")), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); + m_combochecklist_options->Create(this, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); std::string options_items = GUI::into_u8( - _(L("Travel")) + "|0|" + - _(L("Retractions")) + "|0|" + - _(L("Unretractions")) + "|0|" + - _(L("Tool changes")) + "|0|" + - _(L("Color changes")) + "|0|" + - _(L("Pause prints")) + "|0|" + - _(L("Custom GCodes")) + "|0|" + - _(L("Shells")) + "|0|" + - _(L("Legend")) + "|1" + _L("Travel") + "|0|" + + _L("Retractions") + "|0|" + + _L("Unretractions") + "|0|" + + _L("Tool changes") + "|0|" + + _L("Color changes") + "|0|" + + _L("Pause prints") + "|0|" + + _L("Custom GCodes") + "|0|" + + _L("Shells") + "|0|" + + _L("Legend") + "|1" ); - Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_(L("Options"))), options_items); + Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); #else m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions"))); From c7806dd021ca523a5d0e8354450a2c0d8aee1d92 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 4 May 2020 09:37:06 +0200 Subject: [PATCH 055/826] GCodeViewer -> Fixed visualization of travel paths --- src/slic3r/GUI/GCodeViewer.cpp | 114 ++++++++++++++++++++++++++++----- src/slic3r/GUI/GCodeViewer.hpp | 13 ++-- src/slic3r/GUI/GUI_Preview.cpp | 2 + 3 files changed, 107 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index df8e816460..0c631904aa 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -66,6 +66,30 @@ void GCodeViewer::VBuffer::reset() vertices_count = 0; } +bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const +{ + switch (move.type) + { + case GCodeProcessor::EMoveType::Tool_change: + case GCodeProcessor::EMoveType::Color_change: + case GCodeProcessor::EMoveType::Pause_Print: + case GCodeProcessor::EMoveType::Custom_GCode: + case GCodeProcessor::EMoveType::Retract: + case GCodeProcessor::EMoveType::Unretract: + case GCodeProcessor::EMoveType::Extrude: + { + return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && + feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && + extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; + } + case GCodeProcessor::EMoveType::Travel: + { + return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; + } + default: { return false; } + } +} + void GCodeViewer::IBuffer::reset() { // release gpu memory @@ -92,7 +116,7 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) { - Path::Endpoint endpoint = { static_cast(data.size()), static_cast(move.position[2]) }; + Path::Endpoint endpoint = { static_cast(data.size()), move.position }; paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); } @@ -410,10 +434,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr); + buffer.paths.back().first.position = prev.position; buffer.data.push_back(static_cast(i - 1)); } - buffer.paths.back().last.id = static_cast(buffer.data.size()); + buffer.paths.back().last = { static_cast(buffer.data.size()), curr.position }; buffer.data.push_back(static_cast(i)); break; } @@ -424,6 +449,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } +#if ENABLE_GCODE_VIEWER_STATISTICS + for (IBuffer& buffer : m_buffers) + { + m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + } +#endif // ENABLE_GCODE_VIEWER_STATISTICS + // indices data -> send data to gpu for (IBuffer& buffer : m_buffers) { @@ -445,12 +477,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } // layers zs / roles / extruder ids / cp color ids -> extract from result - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + for (size_t i = 0; i < m_vertices.vertices_count; ++i) + { + const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (move.type == GCodeProcessor::EMoveType::Extrude) m_layers_zs.emplace_back(static_cast(move.position[2])); - m_roles.emplace_back(move.extrusion_role); m_extruder_ids.emplace_back(move.extruder_id); + + if (i > 0) + m_roles.emplace_back(move.extrusion_role); } // layers zs -> replace intervals of layers with similar top positions with their average value. @@ -560,9 +596,13 @@ void GCodeViewer::refresh_render_paths() const for (IBuffer& buffer : m_buffers) { buffer.render_paths = std::vector(); - for (const Path& path : buffer.paths) - { - if (!is_in_z_range(path)) + for (size_t i = 0; i < buffer.paths.size(); ++i) { + const Path& path = buffer.paths[i]; + if (path.type == GCodeProcessor::EMoveType::Travel) { + if (!is_travel_in_z_range(i)) + continue; + } + else if (!is_in_z_range(path)) continue; if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) @@ -576,8 +616,7 @@ void GCodeViewer::refresh_render_paths() const } auto it = std::find_if(buffer.render_paths.begin(), buffer.render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); - if (it == buffer.render_paths.end()) - { + if (it == buffer.render_paths.end()) { it = buffer.render_paths.insert(buffer.render_paths.end(), RenderPath()); it->color = color; } @@ -1053,34 +1092,79 @@ void GCodeViewer::render_statistics() const ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.results_size) + " bytes"); + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Vertices CPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.vertices_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("Vertices GPU:")); - ImGui::PopStyleColor(); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Indices CPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.indices_size) + " bytes"); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Paths CPU:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.paths_size) + " bytes"); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("TOTAL CPU:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.vertices_size + m_statistics.indices_size + m_statistics.paths_size) + " bytes"); + + ImGui::Separator(); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Vertices GPU:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Indices GPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("TOTAL GPU:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.vertices_gpu_size + m_statistics.indices_gpu_size) + " bytes"); + imgui.end(); } #endif // ENABLE_GCODE_VIEWER_STATISTICS +bool GCodeViewer::is_travel_in_z_range(size_t id) const +{ + const IBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)]; + if (id >= buffer.paths.size()) + return false; + + Path path = buffer.paths[id]; + int first = static_cast(id); + unsigned int last = static_cast(id); + + // check adjacent paths + while (first > 0 && path.first.position.isApprox(buffer.paths[first - 1].last.position)) { + --first; + path.first = buffer.paths[first].first; + } + while (last < static_cast(buffer.paths.size() - 1) && path.last.position.isApprox(buffer.paths[last + 1].first.position)) { + ++last; + path.last = buffer.paths[last].last; + } + + return is_in_z_range(path); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 9af7e7cc48..aa024a742c 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -38,7 +38,7 @@ class GCodeViewer struct Endpoint { unsigned int id{ 0u }; - double z{ 0.0 }; + Vec3f position{ Vec3f::Zero() }; }; GCodeProcessor::EMoveType type{ GCodeProcessor::EMoveType::Noop }; @@ -54,11 +54,7 @@ class GCodeViewer unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; - bool matches(const GCodeProcessor::MoveVertex& move) const { - return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && - feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && - extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; - } + bool matches(const GCodeProcessor::MoveVertex& move) const; }; // Used to batch the indices needed to render paths @@ -157,6 +153,7 @@ class GCodeViewer long long vertices_gpu_size{ 0 }; long long indices_size{ 0 }; long long indices_gpu_size{ 0 }; + long long paths_size{ 0 }; void reset_all() { reset_times(); @@ -180,6 +177,7 @@ class GCodeViewer vertices_gpu_size = 0; indices_size = 0; indices_gpu_size = 0; + paths_size = 0; } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -279,8 +277,9 @@ private: return z > m_layers_z_range[0] - EPSILON && z < m_layers_z_range[1] + EPSILON; }; - return in_z_range(path.first.z) || in_z_range(path.last.z); + return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]); } + bool is_travel_in_z_range(size_t id) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ea7d2dc1bc..22b3d2c253 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -405,6 +405,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bind_event_handlers(); +#if !ENABLE_GCODE_VIEWER // sets colors for gcode preview extrusion roles std::vector extrusion_roles_colors = { "Perimeter", "FFFF66", @@ -422,6 +423,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view "Custom", "28CC94" }; m_gcode_preview_data->set_extrusion_paths_colors(extrusion_roles_colors); +#endif // !ENABLE_GCODE_VIEWER return true; } From 170f91b3353d495c9d602fa5f4dce7f94ecde6f5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 5 May 2020 12:09:11 +0200 Subject: [PATCH 056/826] GCodeViewer -> Prototype for sequential view --- src/slic3r/GUI/GCodeViewer.cpp | 208 +++++++++++++++++++++++++-------- src/slic3r/GUI/GCodeViewer.hpp | 43 +++++-- src/slic3r/GUI/GLCanvas3D.cpp | 3 +- 3 files changed, 197 insertions(+), 57 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 0c631904aa..dc7c0d9731 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -10,6 +10,7 @@ #include "GUI_Utils.hpp" #include "DoubleSlider.hpp" #include "GLCanvas3D.hpp" +#include "GLToolbar.hpp" #include "libslic3r/Model.hpp" #if ENABLE_GCODE_VIEWER_STATISTICS #include @@ -33,10 +34,10 @@ static GCodeProcessor::EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); } -std::vector> decode_colors(const std::vector& colors) { +std::vector> decode_colors(const std::vector & colors) { static const float INV_255 = 1.0f / 255.0f; - std::vector> output(colors.size(), {0.0f, 0.0f, 0.0f} ); + std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f }); for (size_t i = 0; i < colors.size(); ++i) { const std::string& color = colors[i]; @@ -102,6 +103,7 @@ void GCodeViewer::IBuffer::reset() data = std::vector(); data_size = 0; paths = std::vector(); + render_paths = std::vector(); } bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src) @@ -114,13 +116,13 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con return true; } -void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move) +void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int v_id) { - Path::Endpoint endpoint = { static_cast(data.size()), move.position }; + Path::Endpoint endpoint = { static_cast(data.size()), v_id, move.position }; paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); } -std::array GCodeViewer::Extrusions::Range::get_color_at(float value) const +GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const { // Input value scaled to the colors range const float step = step_size(); @@ -136,14 +138,14 @@ std::array GCodeViewer::Extrusions::Range::get_color_at(float value) c const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); // Interpolate between the low and high colors to find exactly which color the input value should get - std::array ret; + Color ret; for (unsigned int i = 0; i < 3; ++i) { ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); } return ret; } -const std::vector> GCodeViewer::Extrusion_Role_Colors {{ +const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.50f, 0.50f, 0.50f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter @@ -162,13 +164,13 @@ const std::vector> GCodeViewer::Extrusion_Role_Colors {{ { 0.00f, 0.00f, 0.00f } // erMixed }}; -const std::vector> GCodeViewer::Travel_Colors {{ +const std::vector GCodeViewer::Travel_Colors{ { { 0.0f, 0.0f, 0.5f }, // Move { 0.0f, 0.5f, 0.0f }, // Extrude { 0.5f, 0.0f, 0.0f } // Retract }}; -const std::vector> GCodeViewer::Range_Colors {{ +const std::vector GCodeViewer::Range_Colors{ { { 0.043f, 0.173f, 0.478f }, // bluish { 0.075f, 0.349f, 0.522f }, { 0.110f, 0.533f, 0.569f }, @@ -240,7 +242,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: } // update buffers' render paths - refresh_render_paths(); + refresh_render_paths(false); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); @@ -256,7 +258,7 @@ void GCodeViewer::reset() } m_bounding_box = BoundingBoxf3(); - m_tool_colors = std::vector>(); + m_tool_colors = std::vector(); m_extruder_ids = std::vector(); m_extrusions.reset_role_visibility_flags(); m_extrusions.reset_ranges(); @@ -280,6 +282,7 @@ void GCodeViewer::render() const render_toolpaths(); render_shells(); render_legend(); + render_sequential_dlg(); #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -425,7 +428,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { - buffer.add_path(curr); + buffer.add_path(curr, static_cast(i)); buffer.data.push_back(static_cast(i)); break; } @@ -433,12 +436,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Travel: { if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr); - buffer.paths.back().first.position = prev.position; + buffer.add_path(curr, static_cast(i)); + Path& last_path = buffer.paths.back(); + last_path.first.position = prev.position; + last_path.first.s_id = static_cast(i - 1); buffer.data.push_back(static_cast(i - 1)); } - buffer.paths.back().last = { static_cast(buffer.data.size()), curr.position }; + buffer.paths.back().last = { static_cast(buffer.data.size()), static_cast(i), curr.position }; buffer.data.push_back(static_cast(i)); break; } @@ -568,10 +573,10 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } -void GCodeViewer::refresh_render_paths() const +void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const { auto extrusion_color = [this](const Path& path) { - std::array color; + Color color; switch (m_view_type) { case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } @@ -593,26 +598,86 @@ void GCodeViewer::refresh_render_paths() const Travel_Colors[0] /* Move */); }; + auto is_valid_path = [this](const Path& path, size_t id) { + if (path.type == GCodeProcessor::EMoveType::Travel) { + if (!is_travel_in_z_range(id)) + return false; + } + else if (!is_in_z_range(path)) + return false; + + if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) + return false; + + return true; + }; + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.render_paths_size = 0; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + m_sequential_view.first = m_vertices.vertices_count; + m_sequential_view.last = 0; + if (!keep_sequential_current) + m_sequential_view.current = m_vertices.vertices_count; + + // first, detect current values for the sequential view + // to be used later to filter the paths + for (IBuffer& buffer : m_buffers) { + if (!buffer.visible) + continue; + + for (size_t i = 0; i < buffer.paths.size(); ++i) { + const Path& path = buffer.paths[i]; + if (!is_valid_path(path, i)) + continue; +// if (path.type == GCodeProcessor::EMoveType::Travel) { +// if (!is_travel_in_z_range(i)) +// continue; +// } +// else if (!is_in_z_range(path)) +// continue; +// +// if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) +// continue; + + m_sequential_view.first = std::min(m_sequential_view.first, path.first.s_id); + m_sequential_view.last = std::max(m_sequential_view.last, path.last.s_id); + } + } + + if (keep_sequential_current) + m_sequential_view.clamp_current(); + else + m_sequential_view.current = m_sequential_view.last; for (IBuffer& buffer : m_buffers) { buffer.render_paths = std::vector(); + if (!buffer.visible) + continue; for (size_t i = 0; i < buffer.paths.size(); ++i) { const Path& path = buffer.paths[i]; - if (path.type == GCodeProcessor::EMoveType::Travel) { - if (!is_travel_in_z_range(i)) - continue; - } - else if (!is_in_z_range(path)) + if (!is_valid_path(path, i)) + continue; +// if (path.type == GCodeProcessor::EMoveType::Travel) { +// if (!is_travel_in_z_range(i)) +// continue; +// } +// else if (!is_in_z_range(path)) +// continue; +// +// if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) +// continue; + + if ((m_sequential_view.current < path.first.s_id) || (path.last.s_id < m_sequential_view.first)) continue; - if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) - continue; - - std::array color = { 0.0f, 0.0f, 0.0f }; + Color color; switch (path.type) { case GCodeProcessor::EMoveType::Extrude: { color = extrusion_color(path); break; } case GCodeProcessor::EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } + default: { color = { 0.0f, 0.0f, 0.0f }; break; } } auto it = std::find_if(buffer.render_paths.begin(), buffer.render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); @@ -621,15 +686,25 @@ void GCodeViewer::refresh_render_paths() const it->color = color; } - it->sizes.push_back(path.last.id - path.first.id + 1); - it->offsets.push_back(static_cast(path.first.id * sizeof(unsigned int))); + it->sizes.push_back(std::min(m_sequential_view.current, path.last.s_id) - path.first.s_id + 1); + it->offsets.push_back(static_cast(path.first.i_id * sizeof(unsigned int))); } } + +#if ENABLE_GCODE_VIEWER_STATISTICS + for (const IBuffer& buffer : m_buffers) { + m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath); + for (const RenderPath& path : buffer.render_paths) { + m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); + } + } +#endif // ENABLE_GCODE_VIEWER_STATISTICS } void GCodeViewer::render_toolpaths() const { - auto set_color = [](GLint current_program_id, const std::array& color) { + auto set_color = [](GLint current_program_id, const Color& color) { if (current_program_id > 0) { GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; if (color_id >= 0) { @@ -672,7 +747,7 @@ void GCodeViewer::render_toolpaths() const { case GCodeProcessor::EMoveType::Tool_change: { - std::array color = { 1.0f, 1.0f, 1.0f }; + Color color = { 1.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) { @@ -688,7 +763,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Color_change: { - std::array color = { 1.0f, 0.0f, 0.0f }; + Color color = { 1.0f, 0.0f, 0.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) { @@ -704,7 +779,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Pause_Print: { - std::array color = { 0.0f, 1.0f, 0.0f }; + Color color = { 0.0f, 1.0f, 0.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) { @@ -720,7 +795,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Custom_GCode: { - std::array color = { 0.0f, 0.0f, 1.0f }; + Color color = { 0.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) { @@ -736,7 +811,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Retract: { - std::array color = { 1.0f, 0.0f, 1.0f }; + Color color = { 1.0f, 0.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) { @@ -752,7 +827,7 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Unretract: { - std::array color = { 0.0f, 1.0f, 1.0f }; + Color color = { 0.0f, 1.0f, 1.0f }; set_color(current_program_id, color); for (const RenderPath& path : buffer.render_paths) { @@ -827,13 +902,14 @@ void GCodeViewer::render_legend() const ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(0, 0, ImGuiCond_Always); + imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - auto add_item = [draw_list, &imgui](const std::array& color, const std::string& label, std::function callback = nullptr) { + auto add_item = [draw_list, &imgui](const Color& color, const std::string& label, std::function callback = nullptr) { float icon_size = ImGui::GetTextLineHeight(); ImVec2 pos = ImGui::GetCursorPos(); draw_list->AddRect({ pos.x, pos.y }, { pos.x + icon_size, pos.y + icon_size }, ICON_BORDER_COLOR, 0.0f, 0); @@ -903,7 +979,7 @@ void GCodeViewer::render_legend() const { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths - refresh_render_paths(); + refresh_render_paths(false); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->update_preview_bottom_toolbar(); } @@ -1044,6 +1120,51 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } +void GCodeViewer::render_sequential_dlg() const +{ + static const float margin = 125.0f; + + if (m_roles.empty()) + return; + + if (m_sequential_view.last <= m_sequential_view.first) + return; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + const ImGuiStyle& style = ImGui::GetStyle(); + + const GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); + Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + + float left = view_toolbar.get_width(); + float width = static_cast(cnv_size.get_width()) - left; + + ImGui::SetNextWindowBgAlpha(0.5f); + imgui.set_next_window_pos(left, static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.0f, 1.0f); + ImGui::SetNextWindowSize({ width, -1.0f }, ImGuiCond_Always); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + imgui.begin(std::string("Sequential"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + + std::string low_str = std::to_string(m_sequential_view.first); + ImGui::SetCursorPosX(margin - style.ItemSpacing.x - ImGui::CalcTextSize(low_str.c_str()).x); + ImGui::AlignTextToFramePadding(); + imgui.text(low_str); + ImGui::SameLine(margin); + ImGui::PushItemWidth(-margin); + int index = static_cast(m_sequential_view.current); + if (ImGui::SliderInt("##slider int", &index, static_cast(m_sequential_view.first), static_cast(m_sequential_view.last))) + { + m_sequential_view.current = static_cast(index); + refresh_render_paths(true); + } + ImGui::PopItemWidth(); + ImGui::SameLine(); + imgui.text(std::to_string(m_sequential_view.last)); + + imgui.end(); + ImGui::PopStyleVar(); +} + #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const { @@ -1055,6 +1176,7 @@ void GCodeViewer::render_statistics() const ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); imgui.begin(std::string("Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); @@ -1113,10 +1235,10 @@ void GCodeViewer::render_statistics() const imgui.text(std::to_string(m_statistics.paths_size) + " bytes"); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("TOTAL CPU:")); + imgui.text(std::string("Render paths CPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.vertices_size + m_statistics.indices_size + m_statistics.paths_size) + " bytes"); + imgui.text(std::to_string(m_statistics.render_paths_size) + " bytes"); ImGui::Separator(); @@ -1132,12 +1254,6 @@ void GCodeViewer::render_statistics() const ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("TOTAL GPU:")); - ImGui::PopStyleColor(); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.vertices_gpu_size + m_statistics.indices_gpu_size) + " bytes"); - imgui.end(); } #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index aa024a742c..5e4ea53964 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -14,9 +14,10 @@ namespace GUI { class GCodeViewer { - static const std::vector> Extrusion_Role_Colors; - static const std::vector> Travel_Colors; - static const std::vector> Range_Colors; + using Color = std::array; + static const std::vector Extrusion_Role_Colors; + static const std::vector Travel_Colors; + static const std::vector Range_Colors; // buffer containing vertices data struct VBuffer @@ -37,7 +38,10 @@ class GCodeViewer { struct Endpoint { - unsigned int id{ 0u }; + // index into the ibo + unsigned int i_id{ 0u }; + // sequential id + unsigned int s_id{ 0u }; Vec3f position{ Vec3f::Zero() }; }; @@ -55,12 +59,14 @@ class GCodeViewer unsigned char cp_color_id{ 0 }; bool matches(const GCodeProcessor::MoveVertex& move) const; + + unsigned int size() const { return last.i_id - first.i_id + 1; } }; // Used to batch the indices needed to render paths struct RenderPath { - std::array color; + Color color; std::vector sizes; std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) }; @@ -78,9 +84,10 @@ class GCodeViewer void reset(); bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); - void add_path(const GCodeProcessor::MoveVertex& move); + void add_path(const GCodeProcessor::MoveVertex& move, unsigned int s_id); }; + struct Shells { GLVolumeCollection volumes; @@ -102,7 +109,7 @@ class GCodeViewer void reset() { min = FLT_MAX; max = -FLT_MAX; } float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } - std::array get_color_at(float value) const; + Color get_color_at(float value) const; }; struct Ranges @@ -141,6 +148,15 @@ class GCodeViewer void reset_ranges() { ranges.reset(); } }; + struct SequentialView + { + unsigned int first{ 0 }; + unsigned int last{ 0 }; + unsigned int current{ 0 }; + + void clamp_current() { current = std::clamp(current, first, last); } + }; + #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics { @@ -154,6 +170,7 @@ class GCodeViewer long long indices_size{ 0 }; long long indices_gpu_size{ 0 }; long long paths_size{ 0 }; + long long render_paths_size{ 0 }; void reset_all() { reset_times(); @@ -178,6 +195,7 @@ class GCodeViewer indices_size = 0; indices_gpu_size = 0; paths_size = 0; + render_paths_size = 0; } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -201,12 +219,13 @@ private: VBuffer m_vertices; mutable std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; BoundingBoxf3 m_bounding_box; - std::vector> m_tool_colors; + std::vector m_tool_colors; std::vector m_layers_zs; std::array m_layers_z_range; std::vector m_roles; std::vector m_extruder_ids; mutable Extrusions m_extrusions; + mutable SequentialView m_sequential_view; Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; @@ -231,6 +250,8 @@ public: void reset(); void render() const; + bool has_data() const { return !m_roles.empty(); } + const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::vector& get_layers_zs() const { return m_layers_zs; }; @@ -250,8 +271,9 @@ public: void set_options_visibility_from_flags(unsigned int flags); void set_layers_z_range(const std::array& layers_z_range) { + bool keep_sequential_current = layers_z_range[1] <= m_layers_z_range[1]; m_layers_z_range = layers_z_range; - refresh_render_paths(); + refresh_render_paths(keep_sequential_current); } bool is_legend_enabled() const { return m_legend_enabled; } @@ -261,10 +283,11 @@ private: bool init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); - void refresh_render_paths() const; + void refresh_render_paths(bool keep_sequential_current) const; void render_toolpaths() const; void render_shells() const; void render_legend() const; + void render_sequential_dlg() const; #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6881e86fd5..875e4fd525 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2363,8 +2363,9 @@ void GLCanvas3D::set_toolpath_view_type(GCodeViewer::EViewType type) void GLCanvas3D::set_toolpaths_z_range(const std::array& range) { - m_gcode_viewer.set_layers_z_range(range); m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6); + if (m_gcode_viewer.has_data()) + m_gcode_viewer.set_layers_z_range(range); } #else std::vector GLCanvas3D::get_current_print_zs(bool active_only) const From a84c4347872eb009d88296e053e930975973b91e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 5 May 2020 13:57:51 +0200 Subject: [PATCH 057/826] GCodeViewer -> Refactoring/Optimization --- src/slic3r/GUI/GCodeViewer.cpp | 125 ++++++++++++++------------------- src/slic3r/GUI/GCodeViewer.hpp | 10 ++- src/slic3r/GUI/GUI_Preview.cpp | 1 + 3 files changed, 59 insertions(+), 77 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index dc7c0d9731..3323e784cd 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -385,12 +385,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) return; // vertex data / bounding box -> extract from result - std::vector vertices_data; - for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { - for (int j = 0; j < 3; ++j) { - vertices_data.insert(vertices_data.end(), move.position[j]); - m_bounding_box.merge(move.position.cast()); - } + std::vector vertices_data(m_vertices.vertices_count * 3); + for (size_t i = 0; i < m_vertices.vertices_count; ++i) { + const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; + m_bounding_box.merge(move.position.cast()); + ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); } #if ENABLE_GCODE_VIEWER_STATISTICS @@ -575,6 +574,10 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + auto extrusion_color = [this](const Path& path) { Color color; switch (m_view_type) @@ -598,20 +601,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const Travel_Colors[0] /* Move */); }; - auto is_valid_path = [this](const Path& path, size_t id) { - if (path.type == GCodeProcessor::EMoveType::Travel) { - if (!is_travel_in_z_range(id)) - return false; - } - else if (!is_in_z_range(path)) - return false; - - if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) - return false; - - return true; - }; - #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.render_paths_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -621,74 +610,60 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const if (!keep_sequential_current) m_sequential_view.current = m_vertices.vertices_count; - // first, detect current values for the sequential view - // to be used later to filter the paths + // first pass: collect visible paths and update sequential view data + std::vector> paths; for (IBuffer& buffer : m_buffers) { + // reset render paths + buffer.render_paths = std::vector(); + if (!buffer.visible) continue; for (size_t i = 0; i < buffer.paths.size(); ++i) { const Path& path = buffer.paths[i]; - if (!is_valid_path(path, i)) + if (path.type == GCodeProcessor::EMoveType::Travel) { + if (!is_travel_in_z_range(i)) + continue; + } + else if (!is_in_z_range(path)) continue; -// if (path.type == GCodeProcessor::EMoveType::Travel) { -// if (!is_travel_in_z_range(i)) -// continue; -// } -// else if (!is_in_z_range(path)) -// continue; -// -// if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) -// continue; + + if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) + continue; + + // store valid path + paths.push_back({ &buffer, i }); m_sequential_view.first = std::min(m_sequential_view.first, path.first.s_id); m_sequential_view.last = std::max(m_sequential_view.last, path.last.s_id); } } - if (keep_sequential_current) - m_sequential_view.clamp_current(); - else - m_sequential_view.current = m_sequential_view.last; + // update current sequential position + m_sequential_view.current = keep_sequential_current ? std::clamp(m_sequential_view.current, m_sequential_view.first, m_sequential_view.last) : m_sequential_view.last; - for (IBuffer& buffer : m_buffers) { - buffer.render_paths = std::vector(); - if (!buffer.visible) + // second pass: filter paths by sequential data + for (auto&& [buffer, id] : paths) { + const Path& path = buffer->paths[id]; + if ((m_sequential_view.current < path.first.s_id) || (path.last.s_id < m_sequential_view.first)) continue; - for (size_t i = 0; i < buffer.paths.size(); ++i) { - const Path& path = buffer.paths[i]; - if (!is_valid_path(path, i)) - continue; -// if (path.type == GCodeProcessor::EMoveType::Travel) { -// if (!is_travel_in_z_range(i)) -// continue; -// } -// else if (!is_in_z_range(path)) -// continue; -// -// if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) -// continue; - if ((m_sequential_view.current < path.first.s_id) || (path.last.s_id < m_sequential_view.first)) - continue; - - Color color; - switch (path.type) - { - case GCodeProcessor::EMoveType::Extrude: { color = extrusion_color(path); break; } - case GCodeProcessor::EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } - default: { color = { 0.0f, 0.0f, 0.0f }; break; } - } - - auto it = std::find_if(buffer.render_paths.begin(), buffer.render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); - if (it == buffer.render_paths.end()) { - it = buffer.render_paths.insert(buffer.render_paths.end(), RenderPath()); - it->color = color; - } - - it->sizes.push_back(std::min(m_sequential_view.current, path.last.s_id) - path.first.s_id + 1); - it->offsets.push_back(static_cast(path.first.i_id * sizeof(unsigned int))); + Color color; + switch (path.type) + { + case GCodeProcessor::EMoveType::Extrude: { color = extrusion_color(path); break; } + case GCodeProcessor::EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } + default: { color = { 0.0f, 0.0f, 0.0f }; break; } } + + auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); + if (it == buffer->render_paths.end()) { + it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath()); + it->color = color; + } + + it->sizes.push_back(std::min(m_sequential_view.current, path.last.s_id) - path.first.s_id + 1); + it->offsets.push_back(static_cast(path.first.i_id * sizeof(unsigned int))); } #if ENABLE_GCODE_VIEWER_STATISTICS @@ -699,6 +674,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); } } + + m_statistics.refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } @@ -1192,6 +1169,12 @@ void GCodeViewer::render_statistics() const ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.refresh_time) + "ms"); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Resfresh paths time:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.refresh_paths_time) + "ms"); + ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5e4ea53964..3e73cb5d68 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -38,9 +38,9 @@ class GCodeViewer { struct Endpoint { - // index into the ibo + // index into the buffer indices ibo unsigned int i_id{ 0u }; - // sequential id + // sequential id (same as index into the vertices vbo) unsigned int s_id{ 0u }; Vec3f position{ Vec3f::Zero() }; }; @@ -59,8 +59,6 @@ class GCodeViewer unsigned char cp_color_id{ 0 }; bool matches(const GCodeProcessor::MoveVertex& move) const; - - unsigned int size() const { return last.i_id - first.i_id + 1; } }; // Used to batch the indices needed to render paths @@ -153,8 +151,6 @@ class GCodeViewer unsigned int first{ 0 }; unsigned int last{ 0 }; unsigned int current{ 0 }; - - void clamp_current() { current = std::clamp(current, first, last); } }; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -162,6 +158,7 @@ class GCodeViewer { long long load_time{ 0 }; long long refresh_time{ 0 }; + long long refresh_paths_time{ 0 }; long long gl_multi_points_calls_count{ 0 }; long long gl_multi_line_strip_calls_count{ 0 }; long long results_size{ 0 }; @@ -181,6 +178,7 @@ class GCodeViewer void reset_times() { load_time = 0; refresh_time = 0; + refresh_paths_time = 0; } void reset_opengl() { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ce79a4bcd4..b4cbe44616 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -368,6 +368,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view #if ENABLE_GCODE_VIEWER m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); + m_bottom_toolbar_sizer->AddSpacer(10); m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); m_bottom_toolbar_sizer->AddSpacer(10); From 1c4ffa9b16eb85bfed2e6fadf68c21937bf68ac8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 5 May 2020 16:29:07 +0200 Subject: [PATCH 058/826] GCodeViewer -> Added buttons for forward/backward movements of 1, 10 and 100 moves to sequential view bar --- src/slic3r/GUI/GCodeViewer.cpp | 67 ++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3323e784cd..44fe361c21 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1099,7 +1099,13 @@ void GCodeViewer::render_legend() const void GCodeViewer::render_sequential_dlg() const { - static const float margin = 125.0f; + static const float MARGIN = 125.0f; + static const float BUTTON_W = 50.0f; + + auto apply_button_action = [this](unsigned int value) { + m_sequential_view.current = std::clamp(value, m_sequential_view.first, m_sequential_view.last); + refresh_render_paths(true); + }; if (m_roles.empty()) return; @@ -1110,10 +1116,9 @@ void GCodeViewer::render_sequential_dlg() const ImGuiWrapper& imgui = *wxGetApp().imgui(); const ImGuiStyle& style = ImGui::GetStyle(); - const GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar(); Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - float left = view_toolbar.get_width(); + float left = wxGetApp().plater()->get_view_toolbar().get_width(); float width = static_cast(cnv_size.get_width()) - left; ImGui::SetNextWindowBgAlpha(0.5f); @@ -1122,21 +1127,59 @@ void GCodeViewer::render_sequential_dlg() const ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); imgui.begin(std::string("Sequential"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - std::string low_str = std::to_string(m_sequential_view.first); - ImGui::SetCursorPosX(margin - style.ItemSpacing.x - ImGui::CalcTextSize(low_str.c_str()).x); + ImGui::SetCursorPosX(MARGIN); + imgui.disabled_begin(m_sequential_view.first == m_sequential_view.current); + if (ImGui::Button("< 1", { BUTTON_W, 0.0f })) + apply_button_action(m_sequential_view.current - 1); + imgui.disabled_end(); + + ImGui::SameLine(); + imgui.disabled_begin(m_sequential_view.current - m_sequential_view.first < 10); + if (ImGui::Button("< 10", { BUTTON_W, 0.0f })) + apply_button_action(m_sequential_view.current - 10); + imgui.disabled_end(); + + ImGui::SameLine(); + imgui.disabled_begin(m_sequential_view.current - m_sequential_view.first < 100); + if (ImGui::Button("< 100", { BUTTON_W, 0.0f })) + apply_button_action(m_sequential_view.current - 100); + imgui.disabled_end(); + + ImGui::SameLine(width - MARGIN - 3 * BUTTON_W - 2 * style.ItemSpacing.x - style.WindowPadding.x); + imgui.disabled_begin(m_sequential_view.last - m_sequential_view.current < 100); + if (ImGui::Button("> 100", { BUTTON_W, 0.0f })) + apply_button_action(m_sequential_view.current + 100); + imgui.disabled_end(); + + ImGui::SameLine(); + imgui.disabled_begin(m_sequential_view.last - m_sequential_view.current < 10); + if (ImGui::Button("> 10", { BUTTON_W, 0.0f })) + apply_button_action(m_sequential_view.current + 10); + imgui.disabled_end(); + + ImGui::SameLine(); + imgui.disabled_begin(m_sequential_view.last == m_sequential_view.current); + if (ImGui::Button("> 1", { BUTTON_W, 0.0f })) + apply_button_action(m_sequential_view.current + 1); + imgui.disabled_end(); + + int index = 1 + static_cast(m_sequential_view.current); + int i_min = 1 + static_cast(m_sequential_view.first); + int i_max = 1 + static_cast(m_sequential_view.last); + + std::string low_str = std::to_string(i_min); + ImGui::SetCursorPosX(MARGIN - style.ItemSpacing.x - ImGui::CalcTextSize(low_str.c_str()).x); ImGui::AlignTextToFramePadding(); imgui.text(low_str); - ImGui::SameLine(margin); - ImGui::PushItemWidth(-margin); - int index = static_cast(m_sequential_view.current); - if (ImGui::SliderInt("##slider int", &index, static_cast(m_sequential_view.first), static_cast(m_sequential_view.last))) - { - m_sequential_view.current = static_cast(index); + ImGui::SameLine(MARGIN); + ImGui::PushItemWidth(-MARGIN); + if (ImGui::SliderInt("##slider int", &index, i_min, i_max)) { + m_sequential_view.current = static_cast(index - 1); refresh_render_paths(true); } ImGui::PopItemWidth(); ImGui::SameLine(); - imgui.text(std::to_string(m_sequential_view.last)); + imgui.text(std::to_string(i_max)); imgui.end(); ImGui::PopStyleVar(); From 82b75112bd0be3b9e5eccea447605369a6f12033 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 6 May 2020 11:18:37 +0200 Subject: [PATCH 059/826] GCodeViewer -> Sequential view marker wip + refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 71 +++++++++++++++++++++++----------- src/slic3r/GUI/GCodeViewer.hpp | 27 ++++++++++--- 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 44fe361c21..d4cd840884 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -100,8 +100,7 @@ void GCodeViewer::IBuffer::reset() } // release cpu memory - data = std::vector(); - data_size = 0; + indices_count = 0; paths = std::vector(); render_paths = std::vector(); } @@ -116,9 +115,9 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con return true; } -void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int v_id) +void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) { - Path::Endpoint endpoint = { static_cast(data.size()), v_id, move.position }; + Path::Endpoint endpoint = { i_id, s_id, move.position }; paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); } @@ -145,6 +144,21 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con return ret; } +void GCodeViewer::SequentialView::Marker::init() +{ + if (m_initialized) + return; + +} + +void GCodeViewer::SequentialView::Marker::render() const +{ + if (!m_initialized) + return; + + +} + const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.50f, 0.50f, 0.50f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter @@ -280,9 +294,11 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); + if (m_sequential_view.marker.visible) + m_sequential_view.marker.render(); render_shells(); render_legend(); - render_sequential_dlg(); + render_sequential_bar(); #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -403,10 +419,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices_data.size() * sizeof(float), vertices_data.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - // vertex data -> free ram + // vertex data -> no more needed, free ram vertices_data = std::vector(); // indices data -> extract from result + std::vector> indices(m_buffers.size()); for (size_t i = 0; i < m_vertices.vertices_count; ++i) { // skip first vertex @@ -416,7 +433,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; - IBuffer& buffer = m_buffers[buffer_id(curr.type)]; + unsigned char id = buffer_id(curr.type); + IBuffer& buffer = m_buffers[id]; + std::vector& buffer_indices = indices[id]; switch (curr.type) { @@ -427,23 +446,23 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { - buffer.add_path(curr, static_cast(i)); - buffer.data.push_back(static_cast(i)); + buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(i)); + buffer_indices.push_back(static_cast(i)); break; } case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, static_cast(i)); + buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(i)); Path& last_path = buffer.paths.back(); last_path.first.position = prev.position; last_path.first.s_id = static_cast(i - 1); - buffer.data.push_back(static_cast(i - 1)); + buffer_indices.push_back(static_cast(i - 1)); } - buffer.paths.back().last = { static_cast(buffer.data.size()), static_cast(i), curr.position }; - buffer.data.push_back(static_cast(i)); + buffer.paths.back().last = { static_cast(buffer_indices.size()), static_cast(i), curr.position }; + buffer_indices.push_back(static_cast(i)); break; } default: @@ -461,22 +480,21 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #endif // ENABLE_GCODE_VIEWER_STATISTICS // indices data -> send data to gpu - for (IBuffer& buffer : m_buffers) + for (size_t i = 0; i < m_buffers.size(); ++i) { - buffer.data_size = buffer.data.size(); + IBuffer& buffer = m_buffers[i]; + std::vector& buffer_indices = indices[i]; + buffer.indices_count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer.data, unsigned int); - m_statistics.indices_gpu_size += buffer.data_size * sizeof(unsigned int); + m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer_indices, unsigned int); + m_statistics.indices_gpu_size += buffer.indices_count * sizeof(unsigned int); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (buffer.data_size > 0) { + if (buffer.indices_count > 0) { glsafe(::glGenBuffers(1, &buffer.ibo_id)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.data_size * sizeof(unsigned int), buffer.data.data(), GL_STATIC_DRAW)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.indices_count * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // indices data -> free ram - buffer.data = std::vector(); } } @@ -641,6 +659,10 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const // update current sequential position m_sequential_view.current = keep_sequential_current ? std::clamp(m_sequential_view.current, m_sequential_view.first, m_sequential_view.last) : m_sequential_view.last; + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); + size_t v_size = VBuffer::vertex_size_bytes(); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(m_sequential_view.current * v_size), static_cast(v_size), static_cast(m_sequential_view.current_position.data()))); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); // second pass: filter paths by sequential data for (auto&& [buffer, id] : paths) { @@ -1097,7 +1119,7 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } -void GCodeViewer::render_sequential_dlg() const +void GCodeViewer::render_sequential_bar() const { static const float MARGIN = 125.0f; static const float BUTTON_W = 50.0f; @@ -1181,6 +1203,9 @@ void GCodeViewer::render_sequential_dlg() const ImGui::SameLine(); imgui.text(std::to_string(i_max)); + ImGui::Separator(); + ImGui::Checkbox(I18N::translate_utf8(L("Show marker")).c_str(), &m_sequential_view.marker.visible); + imgui.end(); ImGui::PopStyleVar(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 3e73cb5d68..5354ae067f 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -74,18 +74,17 @@ class GCodeViewer { unsigned int ibo_id{ 0 }; Shader shader; - std::vector data; - size_t data_size{ 0 }; + size_t indices_count{ 0 }; std::vector paths; std::vector render_paths; bool visible{ false }; void reset(); bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); - void add_path(const GCodeProcessor::MoveVertex& move, unsigned int s_id); + void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); }; - + // helper to render shells struct Shells { GLVolumeCollection volumes; @@ -148,9 +147,26 @@ class GCodeViewer struct SequentialView { + struct Marker + { + private: + bool m_initialized{ false }; + + public: + unsigned int vbo_id{ 0 }; + unsigned int ibo_id{ 0 }; + bool visible{ false }; + Shader shader; + + void init(); + void render() const; + }; + unsigned int first{ 0 }; unsigned int last{ 0 }; unsigned int current{ 0 }; + Vec3f current_position{ Vec3f::Zero() }; + Marker marker; }; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -237,6 +253,7 @@ public: bool init() { set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); + m_sequential_view.marker.init(); return init_shaders(); } @@ -285,7 +302,7 @@ private: void render_toolpaths() const; void render_shells() const; void render_legend() const; - void render_sequential_dlg() const; + void render_sequential_bar() const; #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS From c29f0a4849a8ec10bbbb35bec72256565417e554 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 6 May 2020 15:17:53 +0200 Subject: [PATCH 060/826] GCodeViewer -> Increased size of wxCheckListBoxComboPopup --- src/slic3r/GUI/wxExtensions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 55468cde7b..7dadffb5b4 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -214,7 +214,7 @@ wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, i max_width = std::max(max_width, 60 + GetTextExtent(GetString(i)).x); } size.SetWidth(max_width); - size.SetHeight(count * GetTextExtent(GetString(0)).y); + size.SetHeight(4 + count * (2 + GetTextExtent(GetString(0)).y)); } else size.SetHeight(DefaultHeight); From 5c6a56ca29d0faf38cf1f01e12d5da175cd1857e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 7 May 2020 10:49:12 +0200 Subject: [PATCH 061/826] GCodeAnalyzer and GCodePreviewData removed from tech ENABLE_GCODE_VIEWER --- src/libslic3r/CustomGCode.cpp | 12 +- src/libslic3r/GCode.cpp | 128 +++++++++++++------- src/libslic3r/GCode.hpp | 37 +++++- src/libslic3r/GCode/Analyzer.cpp | 42 +------ src/libslic3r/GCode/Analyzer.hpp | 9 +- src/libslic3r/GCode/PreviewData.cpp | 4 + src/libslic3r/GCode/PreviewData.hpp | 4 + src/libslic3r/GCode/WipeTower.cpp | 25 ++-- src/libslic3r/Model.cpp | 2 + src/libslic3r/Print.cpp | 8 +- src/libslic3r/Print.hpp | 2 +- src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/3DScene.cpp | 4 + src/slic3r/GUI/BackgroundSlicingProcess.cpp | 8 +- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 17 +-- src/slic3r/GUI/DoubleSlider.cpp | 8 ++ src/slic3r/GUI/GCodeViewer.cpp | 5 +- src/slic3r/GUI/GLCanvas3D.cpp | 116 ++++++++++++++---- src/slic3r/GUI/GLCanvas3D.hpp | 2 + src/slic3r/GUI/GUI_Preview.cpp | 19 ++- src/slic3r/GUI/GUI_Preview.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 10 +- 22 files changed, 311 insertions(+), 156 deletions(-) diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp index 824bcdd93c..72c5c20de9 100644 --- a/src/libslic3r/CustomGCode.cpp +++ b/src/libslic3r/CustomGCode.cpp @@ -1,6 +1,10 @@ #include "CustomGCode.hpp" #include "Config.hpp" +#if ENABLE_GCODE_VIEWER +#include "GCode.hpp" +#else #include "GCode/PreviewData.hpp" +#endif // ENABLE_GCODE_VIEWER #include "GCodeWriter.hpp" namespace Slic3r { @@ -17,8 +21,12 @@ extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrint return; if (info.gcodes.empty() && ! colorprint_heights->values.empty()) { // Convert the old colorprint_heighs only if there is no equivalent data in a new format. - const std::vector& colors = GCodePreviewData::ColorPrintColors(); - const auto& colorprint_values = colorprint_heights->values; +#if ENABLE_GCODE_VIEWER + const std::vector& colors = ColorPrintColors::get(); +#else + const std::vector& colors = GCodePreviewData::ColorPrintColors(); +#endif // ENABLE_GCODE_VIEWER + const auto& colorprint_values = colorprint_heights->values; info.gcodes.clear(); info.gcodes.reserve(colorprint_values.size()); int i = 0; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 10636082bb..584d1a1e3e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -572,6 +572,10 @@ std::string WipeTowerIntegration::finalize(GCode &gcodegen) return gcode; } +#if ENABLE_GCODE_VIEWER +const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; +#endif // ENABLE_GCODE_VIEWER + #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) // Collect pairs of object_layer + support_layer sorted by print_z. @@ -699,7 +703,7 @@ std::vector>> GCode::collec } #if ENABLE_GCODE_VIEWER -void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) +void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) #else void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) #endif // ENABLE_GCODE_VIEWER @@ -724,7 +728,9 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ if (file == nullptr) throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); +#if !ENABLE_GCODE_VIEWER m_enable_analyzer = preview_data != nullptr; +#endif // !ENABLE_GCODE_VIEWER try { m_placeholder_parser_failed_templates.clear(); @@ -778,16 +784,14 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ m_silent_time_estimator.reset(); } +#if !ENABLE_GCODE_VIEWER // starts analyzer calculations if (m_enable_analyzer) { -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - m_analyzer.close_debug_output_file(); -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data" << log_memory_info(); m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); m_analyzer.reset(); } +#endif // !ENABLE_GCODE_VIEWER if (rename_file(path_tmp, path)) throw std::runtime_error( @@ -877,7 +881,14 @@ namespace DoExport { } } - static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer) +#if ENABLE_GCODE_VIEWER + static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor) + { + processor.reset(); + processor.apply_config(config); + } +#else + static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer) { // resets analyzer analyzer.reset(); @@ -901,17 +912,6 @@ namespace DoExport { // tell analyzer about the gcode flavor analyzer.set_gcode_flavor(config.gcode_flavor); - -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - analyzer.open_debug_output_file(); -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - } - -#if ENABLE_GCODE_VIEWER - static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor) - { - processor.reset(); - processor.apply_config(config); } #endif // ENABLE_GCODE_VIEWER @@ -1145,15 +1145,22 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu DoExport::init_time_estimators(print.config(), // modifies the following: m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled); - DoExport::init_gcode_analyzer(print.config(), m_analyzer); #if ENABLE_GCODE_VIEWER DoExport::init_gcode_processor(print.config(), m_processor); +#else + DoExport::init_gcode_analyzer(print.config(), m_analyzer); #endif // ENABLE_GCODE_VIEWER // resets analyzer's tracking data +#if ENABLE_GCODE_VIEWER + m_last_mm3_per_mm = 0.0f; + m_last_width = 0.0f; + m_last_height = 0.0f; +#else m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; m_last_width = GCodeAnalyzer::Default_Width; m_last_height = GCodeAnalyzer::Default_Height; +#endif // ENABLE_GCODE_VIEWER // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. @@ -1333,13 +1340,13 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Set extruder(s) temperature before and after start G-code. this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); - if (m_enable_analyzer) - // adds tag for analyzer - _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); - #if ENABLE_GCODE_VIEWER // adds tag for processor _write_format(file, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erCustom); +#else + if (m_enable_analyzer) + // adds tag for analyzer + _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); #endif // ENABLE_GCODE_VIEWER // Write the custom start G-code @@ -1491,13 +1498,13 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write(file, this->retract()); _write(file, m_writer.set_fan(false)); - if (m_enable_analyzer) - // adds tag for analyzer - _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); - #if ENABLE_GCODE_VIEWER // adds tag for processor _write_format(file, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erCustom); +#else + if (m_enable_analyzer) + // adds tag for analyzer + _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); #endif // ENABLE_GCODE_VIEWER // Process filament-specific gcode in extruder order. @@ -1821,11 +1828,12 @@ namespace ProcessLayer { assert(m600_extruder_before_layer >= 0); // Color Change or Tool Change as Color Change. - // add tag for analyzer - gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n"; #if ENABLE_GCODE_VIEWER // add tag for processor gcode += "; " + GCodeProcessor::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n"; +#else + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n"; #endif // ENABLE_GCODE_VIEWER // add tag for time estimator gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; @@ -1846,11 +1854,12 @@ namespace ProcessLayer { if (custom_code == PausePrintCode) // Pause print { - // add tag for analyzer - gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n"; #if ENABLE_GCODE_VIEWER // add tag for processor gcode += "; " + GCodeProcessor::Pause_Print_Tag + "\n"; +#else + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n"; #endif // ENABLE_GCODE_VIEWER //! FIXME_in_fw show message during print pause if (!pause_print_msg.empty()) @@ -1860,11 +1869,12 @@ namespace ProcessLayer } else // custom Gcode { - // add tag for analyzer - gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n"; #if ENABLE_GCODE_VIEWER // add tag for processor gcode += "; " + GCodeProcessor::Custom_Code_Tag + "\n"; +#else + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n"; #endif // ENABLE_GCODE_VIEWER // add tag for time estimator //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n"; @@ -2218,9 +2228,15 @@ void GCode::process_layer( m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : this->set_extruder(extruder_id, print_z); +#if ENABLE_GCODE_VIEWER + // let analyzer tag generator aware of a role type change + if (layer_tools.has_wipe_tower && m_wipe_tower) + m_last_processor_extrusion_role = erWipeTower; +#else // let analyzer tag generator aware of a role type change if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower) m_last_analyzer_extrusion_role = erWipeTower; +#endif // ENABLE_GCODE_VIEWER if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; @@ -2324,11 +2340,13 @@ void GCode::process_layer( if (m_cooling_buffer) gcode = m_cooling_buffer->process_layer(gcode, layer.id()); +#if !ENABLE_GCODE_VIEWER // add tag for analyzer if (gcode.find(GCodeAnalyzer::Pause_Print_Tag) != gcode.npos) gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n"; else if (gcode.find(GCodeAnalyzer::Custom_Code_Tag) != gcode.npos) gcode += "\n; " + GCodeAnalyzer::End_Pause_Print_Or_Custom_Code_Tag + "\n"; +#endif // !ENABLE_GCODE_VIEWER #ifdef HAS_PRESSURE_EQUALIZER // Apply pressure equalization if enabled; @@ -2342,9 +2360,11 @@ void GCode::process_layer( BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << ", time estimator memory: " << format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << - ", analyzer memory: " << +#if !ENABLE_GCODE_VIEWER + ", analyzer memory: " << format_memsize_MB(m_analyzer.memory_used()) << - log_memory_info(); +#endif // !ENABLE_GCODE_VIEWER + log_memory_info(); } void GCode::apply_print_config(const PrintConfig &print_config) @@ -2984,8 +3004,12 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill void GCode::_write(FILE* file, const char *what) { if (what != nullptr) { +#if ENABLE_GCODE_VIEWER + const char* gcode = what; +#else // apply analyzer, if enabled const char* gcode = m_enable_analyzer ? m_analyzer.process_gcode(what).c_str() : what; +#endif // !ENABLE_GCODE_VIEWER // writes string to file fwrite(gcode, 1, ::strlen(gcode), file); @@ -3132,57 +3156,73 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } // adds analyzer tags and updates analyzer's tracking data +#if !ENABLE_GCODE_VIEWER if (m_enable_analyzer) { +#endif // !ENABLE_GCODE_VIEWER // PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width // so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines +#if ENABLE_GCODE_VIEWER + bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); +#else bool last_was_wipe_tower = (m_last_analyzer_extrusion_role == erWipeTower); +#endif // ENABLE_GCODE_VIEWER char buf[64]; +#if ENABLE_GCODE_VIEWER + if (path.role() != m_last_processor_extrusion_role) + { + m_last_processor_extrusion_role = path.role(); + sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), int(m_last_processor_extrusion_role)); + gcode += buf; + } +#else if (path.role() != m_last_analyzer_extrusion_role) { m_last_analyzer_extrusion_role = path.role(); sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role)); -#if ENABLE_GCODE_VIEWER - gcode += buf; - sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role)); -#endif // ENABLE_GCODE_VIEWER gcode += buf; } +#endif // ENABLE_GCODE_VIEWER if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); - gcode += buf; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); gcode += buf; +#else + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); + gcode += buf; #endif // ENABLE_GCODE_VIEWER } if (last_was_wipe_tower || (m_last_width != path.width)) { m_last_width = path.width; - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); - gcode += buf; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); gcode += buf; +#else + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); + gcode += buf; #endif // ENABLE_GCODE_VIEWER } if (last_was_wipe_tower || (m_last_height != path.height)) { m_last_height = path.height; - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height); - gcode += buf; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_last_height); gcode += buf; +#else + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height); + gcode += buf; #endif // ENABLE_GCODE_VIEWER } +#if !ENABLE_GCODE_VIEWER } +#endif // !ENABLE_GCODE_VIEWER std::string comment; if (m_enable_cooling_markers) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index f6668ad3d6..546c425755 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -16,10 +16,11 @@ #include "GCode/WipeTower.hpp" #if ENABLE_GCODE_VIEWER #include "GCode/GCodeProcessor.hpp" +#else +#include "GCode/Analyzer.hpp" #endif // ENABLE_GCODE_VIEWER #include "GCodeTimeEstimator.hpp" #include "EdgeGrid.hpp" -#include "GCode/Analyzer.hpp" #include "GCode/ThumbnailData.hpp" #include @@ -33,7 +34,9 @@ namespace Slic3r { // Forward declarations. class GCode; +#if !ENABLE_GCODE_VIEWER class GCodePreviewData; +#endif // !ENABLE_GCODE_VIEWER class AvoidCrossingPerimeters { public: @@ -138,6 +141,15 @@ private: double m_last_wipe_tower_print_z = 0.f; }; +#if ENABLE_GCODE_VIEWER +class ColorPrintColors +{ + static const std::vector Colors; +public: + static const std::vector& get() { return Colors; } +}; +#endif // ENABLE_GCODE_VIEWER + class GCode { public: GCode() : @@ -145,17 +157,27 @@ public: m_enable_loop_clipping(true), m_enable_cooling_markers(false), m_enable_extrusion_role_markers(false), +#if ENABLE_GCODE_VIEWER + m_last_processor_extrusion_role(erNone), +#else m_enable_analyzer(false), m_last_analyzer_extrusion_role(erNone), +#endif // ENABLE_GCODE_VIEWER m_layer_count(0), m_layer_index(-1), m_layer(nullptr), m_volumetric_speed(0), m_last_pos_defined(false), m_last_extrusion_role(erNone), +#if ENABLE_GCODE_VIEWER + m_last_mm3_per_mm(0.0f), + m_last_width(0.0f), + m_last_height(0.0f), +#else m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm), m_last_width(GCodeAnalyzer::Default_Width), m_last_height(GCodeAnalyzer::Default_Height), +#endif // ENABLE_GCODE_VIEWER m_brim_done(false), m_second_layer_things_done(false), m_normal_time_estimator(GCodeTimeEstimator::Normal), @@ -168,7 +190,7 @@ public: // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). #if ENABLE_GCODE_VIEWER - void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, GCodeProcessor::Result* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); + void do_export(Print* print, const char* path, GCodeProcessor::Result* result = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); #else void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); #endif // ENABLE_GCODE_VIEWER @@ -331,11 +353,16 @@ private: // Markers for the Pressure Equalizer to recognize the extrusion type. // The Pressure Equalizer removes the markers from the final G-code. bool m_enable_extrusion_role_markers; +#if ENABLE_GCODE_VIEWER + // Keeps track of the last extrusion role passed to the processor + ExtrusionRole m_last_processor_extrusion_role; +#else // Enableds the G-code Analyzer. // Extended markers will be added during G-code generation. // The G-code Analyzer will remove these comments from the final G-code. bool m_enable_analyzer; ExtrusionRole m_last_analyzer_extrusion_role; +#endif // ENABLE_GCODE_VIEWER // How many times will change_layer() be called? // change_layer() will update the progress bar. unsigned int m_layer_count; @@ -377,12 +404,12 @@ private: GCodeTimeEstimator m_silent_time_estimator; bool m_silent_time_estimator_enabled; - // Analyzer - GCodeAnalyzer m_analyzer; - #if ENABLE_GCODE_VIEWER // Processor GCodeProcessor m_processor; +#else + // Analyzer + GCodeAnalyzer m_analyzer; #endif // ENABLE_GCODE_VIEWER // Write a string into a file. diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 974176dbd7..d022b37983 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -8,19 +8,11 @@ #include "Print.hpp" #include -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT -#include -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT #include "Analyzer.hpp" #include "PreviewData.hpp" -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT -#include - -// don't worry, this is just temporary -static boost::nowide::ofstream g_debug_output; -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT +#if !ENABLE_GCODE_VIEWER static const std::string AXIS_STR = "XYZE"; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; @@ -184,19 +176,6 @@ bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role) return ((erPerimeter <= role) && (role < erMixed)); } -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT -void GCodeAnalyzer::open_debug_output_file() -{ - boost::filesystem::path path("d:/analyzer.output"); - g_debug_output.open(path.string()); -} - -void GCodeAnalyzer::close_debug_output_file() -{ - g_debug_output.close(); -} -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) { // processes 'special' comments contained in line @@ -945,23 +924,6 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) Vec3f start_position = _get_start_position() + extruder_offset; Vec3f end_position = _get_end_position() + extruder_offset; it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_fan_speed(), _get_cp_color_id()); - -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - if (g_debug_output.good()) - { - g_debug_output << std::to_string(static_cast(type)); - g_debug_output << ", " << std::to_string(static_cast(_get_extrusion_role())); - g_debug_output << ", " << Slic3r::to_string(static_cast(end_position.cast())); - g_debug_output << ", " << std::to_string(extruder_id); - g_debug_output << ", " << std::to_string(_get_cp_color_id()); - g_debug_output << ", " << std::to_string(_get_feedrate()); - g_debug_output << ", " << std::to_string(_get_width()); - g_debug_output << ", " << std::to_string(_get_height()); - g_debug_output << ", " << std::to_string(_get_mm3_per_mm()); - g_debug_output << ", " << std::to_string(_get_fan_speed()); - g_debug_output << "\n"; - } -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT } bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const @@ -1231,3 +1193,5 @@ size_t GCodeAnalyzer::memory_used() const } } // namespace Slic3r + +#endif // !ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 9d16ab4944..37d9072592 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_GCode_Analyzer_hpp_ #define slic3r_GCode_Analyzer_hpp_ +#if !ENABLE_GCODE_VIEWER + #include "../libslic3r.h" #include "../PrintConfig.hpp" #include "../ExtrusionEntity.hpp" @@ -147,11 +149,6 @@ public: static bool is_valid_extrusion_role(ExtrusionRole role); -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - void open_debug_output_file(); - void close_debug_output_file(); -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - private: // Processes the given gcode line void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line); @@ -307,4 +304,6 @@ private: } // namespace Slic3r +#endif // !ENABLE_GCODE_VIEWER + #endif /* slic3r_GCode_Analyzer_hpp_ */ diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 3aae15748d..58b15e9a46 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -5,6 +5,8 @@ #include +#if !ENABLE_GCODE_VIEWER + //! macro used to mark string used at localization, #define L(s) (s) @@ -515,3 +517,5 @@ Color operator * (float f, const Color& color) } } // namespace Slic3r + +#endif // !ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index c0f768088e..930c1659e3 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_GCode_PreviewData_hpp_ #define slic3r_GCode_PreviewData_hpp_ +#if !ENABLE_GCODE_VIEWER + #include "../libslic3r.h" #include "../ExtrusionEntity.hpp" #include "../Point.hpp" @@ -391,4 +393,6 @@ public: } // namespace Slic3r +#endif // !ENABLE_GCODE_VIEWER + #endif /* slic3r_GCode_PreviewData_hpp_ */ diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index d5d060f773..3b5c2a1599 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -21,9 +21,10 @@ TODO LIST #include #include -#include "Analyzer.hpp" #if ENABLE_GCODE_VIEWER #include "GCodeProcessor.hpp" +#else +#include "Analyzer.hpp" #endif // ENABLE_GCODE_VIEWER #include "BoundingBox.hpp" @@ -56,16 +57,16 @@ public: { // adds tag for analyzer: char buf[64]; - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming - m_gcode += buf; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming - m_gcode += buf; +#else + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming #endif // ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); -#if ENABLE_GCODE_VIEWER m_gcode += buf; +#if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erWipeTower); +#else + sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); #endif // ENABLE_GCODE_VIEWER m_gcode += buf; change_analyzer_line_width(line_width); @@ -74,12 +75,12 @@ public: WipeTowerWriter& change_analyzer_line_width(float line_width) { // adds tag for analyzer: char buf[64]; - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); - m_gcode += buf; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); - m_gcode += buf; +#else + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); #endif // ENABLE_GCODE_VIEWER + m_gcode += buf; return *this; } @@ -88,12 +89,12 @@ public: float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); // adds tag for analyzer: char buf[64]; - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); - m_gcode += buf; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); - m_gcode += buf; +#else + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); #endif // ENABLE_GCODE_VIEWER + m_gcode += buf; return *this; } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 98f595d91e..90c9d03571 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -20,7 +20,9 @@ #include "SVG.hpp" #include #include "GCodeWriter.hpp" +#if !ENABLE_GCODE_VIEWER #include "GCode/PreviewData.hpp" +#endif // !ENABLE_GCODE_VIEWER namespace Slic3r { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6bc0494127..0ffb5472c6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1628,7 +1628,7 @@ void Print::process() // write error into the G-code, cannot execute post-processing scripts). // It is up to the caller to show an error message. #if ENABLE_GCODE_VIEWER -std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) +std::string Print::export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) #else std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) #endif // ENABLE_GCODE_VIEWER @@ -1637,7 +1637,11 @@ std::string Print::export_gcode(const std::string& path_template, GCodePreviewDa // The following call may die if the output_filename_format template substitution fails. std::string path = this->output_filepath(path_template); std::string message; +#if ENABLE_GCODE_VIEWER + if (!path.empty() && result == nullptr) { +#else if (! path.empty() && preview_data == nullptr) { +#endif // ENABLE_GCODE_VIEWER // Only show the path if preview_data is not set -> running from command line. message = L("Exporting G-code"); message += " to "; @@ -1649,7 +1653,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodePreviewDa // The following line may die for multiple reasons. GCode gcode; #if ENABLE_GCODE_VIEWER - gcode.do_export(this, path.c_str(), preview_data, result, thumbnail_cb); + gcode.do_export(this, path.c_str(), result, thumbnail_cb); #else gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb); #endif // ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9ae0f13945..c358cd9184 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -372,7 +372,7 @@ public: // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). #if ENABLE_GCODE_VIEWER - std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); + std::string export_gcode(const std::string& path_template, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); #else std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb = nullptr); #endif // ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a648bb244b..c22e504dff 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -55,7 +55,6 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -#define ENABLE_GCODE_VIEWER_DEBUG_OUTPUT (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ca2f0389da..6aaf0b500e 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -5,11 +5,15 @@ #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/Geometry.hpp" +#if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" +#endif // !ENABLE_GCODE_VIEWER #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Slicing.hpp" +#if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/Analyzer.hpp" +#endif // !ENABLE_GCODE_VIEWER #include "slic3r/GUI/BitmapCache.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Utils.hpp" diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index c8c344caaf..32b7b83653 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -18,7 +18,9 @@ #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/GCode/PostProcessor.hpp" +#if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" +#endif // !ENABLE_GCODE_VIEWER #include "libslic3r/Format/SL1.hpp" #include "libslic3r/libslic3r.h" @@ -89,7 +91,7 @@ void BackgroundSlicingProcess::process_fff() m_print->process(); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); #if ENABLE_GCODE_VIEWER - m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_gcode_result, m_thumbnail_cb); + m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb); #else m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); #endif // ENABLE_GCODE_VIEWER @@ -385,9 +387,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn // Some FFF status was invalidated, and the G-code was not exported yet. // Let the G-code preview UI know that the final G-code preview is not valid. // In addition, this early memory deallocation reduces memory footprint. - if (m_gcode_preview_data != nullptr) - m_gcode_preview_data->reset(); - else if (m_gcode_result != nullptr) + if (m_gcode_result != nullptr) m_gcode_result->reset(); } #else diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index d3fca88fc4..91ebc1372c 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -50,10 +50,11 @@ public: void set_fff_print(Print *print) { m_fff_print = print; } void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } - void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; } - void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } + void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } #if ENABLE_GCODE_VIEWER void set_gcode_result(GCodeProcessor::Result* result) { m_gcode_result = result; } +#else + void set_gcode_preview_data(GCodePreviewData* gpd) { m_gcode_preview_data = gpd; } #endif // ENABLE_GCODE_VIEWER // The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished @@ -156,15 +157,17 @@ private: // Non-owned pointers to Print instances. Print *m_fff_print = nullptr; SLAPrint *m_sla_print = nullptr; +#if ENABLE_GCODE_VIEWER + // Data structure, to which the G-code export writes its annotations. + GCodeProcessor::Result *m_gcode_result = nullptr; +#else // Data structure, to which the G-code export writes its annotations. GCodePreviewData *m_gcode_preview_data = nullptr; - // Callback function, used to write thumbnails into gcode. +#endif // ENABLE_GCODE_VIEWER + // Callback function, used to write thumbnails into gcode. ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; SL1Archive m_sla_archive; -#if ENABLE_GCODE_VIEWER - GCodeProcessor::Result* m_gcode_result = nullptr; -#endif // ENABLE_GCODE_VIEWER - // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. + // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, // but once set, it cannot be re-set. diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 4c4c1aa8dd..0a89333714 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1,5 +1,9 @@ #include "wxExtensions.hpp" +#if ENABLE_GCODE_VIEWER +#include "libslic3r/GCode.hpp" +#else #include "libslic3r/GCode/PreviewData.hpp" +#endif // ENABLE_GCODE_VIEWER #include "GUI.hpp" #include "GUI_App.hpp" #include "I18N.hpp" @@ -1945,7 +1949,11 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& c { if (mode == t_mode::SingleExtruder && code == ColorChangeCode && m_use_default_colors) { +#if ENABLE_GCODE_VIEWER + const std::vector& colors = ColorPrintColors::get(); +#else const std::vector& colors = GCodePreviewData::ColorPrintColors(); +#endif // ENABLE_GCODE_VIEWER if (ticks.empty()) return colors[0]; m_default_color_idx++; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index d4cd840884..0893a7d465 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -167,7 +167,7 @@ const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.69f, 0.19f, 0.16f }, // erInternalInfill { 0.84f, 0.20f, 0.84f }, // erSolidInfill { 1.00f, 0.10f, 0.10f }, // erTopSolidInfill - { 0.00f, 1.00f, 1.00f }, // erIroning + { 1.00f, 0.55f, 0.41f }, // erIroning { 0.60f, 0.60f, 1.00f }, // erBridgeInfill { 1.00f, 1.00f, 1.00f }, // erGapFill { 0.52f, 0.48f, 0.13f }, // erSkirt @@ -404,7 +404,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) std::vector vertices_data(m_vertices.vertices_count * 3); for (size_t i = 0; i < m_vertices.vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - m_bounding_box.merge(move.position.cast()); + if (move.type == GCodeProcessor::EMoveType::Extrude) + m_bounding_box.merge(move.position.cast()); ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8859a07463..8be8f8b378 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5,7 +5,9 @@ #include "polypartition.h" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/PrintConfig.hpp" +#if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" +#endif // !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/ExtrusionEntity.hpp" @@ -56,10 +58,6 @@ #include #include -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT -#include -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - #include #include #include @@ -2747,22 +2745,8 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio #if ENABLE_GCODE_VIEWER void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { -#if ENABLE_GCODE_VIEWER_DEBUG_OUTPUT - static unsigned int last_result_id = 0; - if (last_result_id != gcode_result.id) - { - last_result_id = gcode_result.id; - boost::filesystem::path path("d:/processor.output"); - boost::nowide::ofstream out; - out.open(path.string()); - for (const GCodeProcessor::MoveVertex& v : gcode_result.moves) - { - out << v.to_string() << "\n"; - } - out.close(); - } -#endif // ENABLE_GCODE_VIEWER_DEBUG_OUTPUT m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); + _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); } void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) @@ -2771,9 +2755,7 @@ void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_resul set_as_dirty(); request_extra_frame(); } -#endif // ENABLE_GCODE_VIEWER - -#if !ENABLE_GCODE_VIEWER +#else void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector& str_tool_colors) { const Print *print = this->fff_print(); @@ -2842,7 +2824,7 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const _generate_legend_texture(preview_data, tool_colors); } } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER void GLCanvas3D::load_sla_preview() { @@ -3144,7 +3126,24 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) } #endif // ENABLE_RENDER_PICKING_PASS case 'Z': +#if ENABLE_GCODE_VIEWER + case 'z': + { + if (!m_selection.is_empty()) + zoom_to_selection(); + else + { + if (!m_volumes.empty()) + zoom_to_volumes(); + else + _zoom_to_box(m_gcode_viewer.get_bounding_box()); + } + + break; + } +#else case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } +#endif // ENABLE_GCODE_VIEWER default: { evt.Skip(); break; } } } @@ -5467,8 +5466,39 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const _update_volumes_hover_state(); } +#if ENABLE_GCODE_VIEWER +static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) +{ + // tolerance to avoid false detection at bed edges + const double tolerance_x = 0.05; + const double tolerance_y = 0.05; + + BoundingBoxf3 ret; + const ConfigOptionPoints* opt = dynamic_cast(config.option("bed_shape")); + if (opt != nullptr) + { + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); + ret = BoundingBoxf3(Vec3d(unscale(bed_box_2D.min(0)) - tolerance_x, unscale(bed_box_2D.min(1)) - tolerance_y, 0.0), Vec3d(unscale(bed_box_2D.max(0)) + tolerance_x, unscale(bed_box_2D.max(1)) + tolerance_y, config.opt_float("max_print_height"))); + // Allow the objects to protrude below the print bed + ret.min(2) = -1e10; + } + return ret; +} +#endif // ENABLE_GCODE_VIEWER + void GLCanvas3D::_render_background() const { +#if ENABLE_GCODE_VIEWER + bool use_error_color = m_dynamic_background_enabled; + if (!m_volumes.empty()) + use_error_color &= _is_any_volume_outside(); + else + { + BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); + use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; + } +#endif // ENABLE_GCODE_VIEWER + glsafe(::glPushMatrix()); glsafe(::glLoadIdentity()); glsafe(::glMatrixMode(GL_PROJECTION)); @@ -5479,7 +5509,11 @@ void GLCanvas3D::_render_background() const glsafe(::glDisable(GL_DEPTH_TEST)); ::glBegin(GL_QUADS); +#if ENABLE_GCODE_VIEWER + if (use_error_color) +#else if (m_dynamic_background_enabled && _is_any_volume_outside()) +#endif // ENABLE_GCODE_VIEWER ::glColor3fv(ERROR_BG_DARK_COLOR); else ::glColor3fv(DEFAULT_BG_DARK_COLOR); @@ -5487,8 +5521,12 @@ void GLCanvas3D::_render_background() const ::glVertex2f(-1.0f, -1.0f); ::glVertex2f(1.0f, -1.0f); +#if ENABLE_GCODE_VIEWER + if (use_error_color) +#else if (m_dynamic_background_enabled && _is_any_volume_outside()) - ::glColor3fv(ERROR_BG_LIGHT_COLOR); +#endif // ENABLE_GCODE_VIEWER +::glColor3fv(ERROR_BG_LIGHT_COLOR); else ::glColor3fv(DEFAULT_BG_LIGHT_COLOR); @@ -6991,6 +7029,7 @@ void GLCanvas3D::_load_sla_shells() update_volumes_colors_by_extruder(); } +#if !ENABLE_GCODE_VIEWER void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) { unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size(); @@ -7048,9 +7087,13 @@ void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& previe } } } +#endif // !ENABLE_GCODE_VIEWER void GLCanvas3D::_update_toolpath_volumes_outside_state() { +#if ENABLE_GCODE_VIEWER + BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); +#else // tolerance to avoid false detection at bed edges static const double tolerance_x = 0.05; static const double tolerance_y = 0.05; @@ -7067,15 +7110,23 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() print_volume.min(2) = -1e10; } } +#endif // ENABLE_GCODE_VIEWER for (GLVolume* volume : m_volumes.volumes) { +#if ENABLE_GCODE_VIEWER + volume->is_outside = ((test_volume.radius() > 0.0) && volume->is_extrusion_path) ? !test_volume.contains(volume->bounding_box()) : false; +#else volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box()) : false; +#endif // ENABLE_GCODE_VIEWER } } void GLCanvas3D::_update_sla_shells_outside_state() { +#if ENABLE_GCODE_VIEWER + BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); +#else // tolerance to avoid false detection at bed edges static const double tolerance_x = 0.05; static const double tolerance_y = 0.05; @@ -7092,17 +7143,34 @@ void GLCanvas3D::_update_sla_shells_outside_state() print_volume.min(2) = -1e10; } } +#endif // ENABLE_GCODE_VIEWER for (GLVolume* volume : m_volumes.volumes) { +#if ENABLE_GCODE_VIEWER + volume->is_outside = ((test_volume.radius() > 0.0) && volume->shader_outside_printer_detection_enabled) ? !test_volume.contains(volume->transformed_convex_hull_bounding_box()) : false; +#else volume->is_outside = ((print_volume.radius() > 0.0) && volume->shader_outside_printer_detection_enabled) ? !print_volume.contains(volume->transformed_convex_hull_bounding_box()) : false; +#endif // ENABLE_GCODE_VIEWER } } void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning) { _set_current(); +#if ENABLE_GCODE_VIEWER + bool show = false; + if (!m_volumes.empty()) + show = _is_any_volume_outside(); + else + { + BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); + show = (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; + } + _set_warning_texture(warning, show); +#else _set_warning_texture(warning, _is_any_volume_outside()); +#endif // ENABLE_GCODE_VIEWER } std::vector GLCanvas3D::_parse_colors(const std::vector& colors) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ed034bd28c..5684901f3d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -846,8 +846,10 @@ private: #endif // !ENABLE_GCODE_VIEWER // Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished. void _load_sla_shells(); +#if !ENABLE_GCODE_VIEWER // sets gcode geometry visibility according to user selection void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); +#endif // !ENABLE_GCODE_VIEWER void _update_toolpath_volumes_outside_state(); void _update_sla_shells_outside_state(); void _show_warning_texture_if_needed(WarningTexture::Warning warning); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 05d6071099..4f47c2d18f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1,5 +1,7 @@ #include "libslic3r/libslic3r.h" +#if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" +#endif // !ENABLE_GCODE_VIEWER #include "GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI.hpp" @@ -172,8 +174,8 @@ void View3D::render() #if ENABLE_GCODE_VIEWER Preview::Preview( - wxWindow * parent, Model * model, DynamicPrintConfig * config, - BackgroundSlicingProcess * process, GCodePreviewData * gcode_preview_data, GCodeProcessor::Result * gcode_result, std::function schedule_background_process_func) + wxWindow* parent, Model* model, DynamicPrintConfig* config, + BackgroundSlicingProcess* process, GCodeProcessor::Result* gcode_result, std::function schedule_background_process_func) #else Preview::Preview( wxWindow* parent, Model* model, DynamicPrintConfig* config, @@ -200,9 +202,10 @@ Preview::Preview( #endif // ENABLE_GCODE_VIEWER , m_config(config) , m_process(process) - , m_gcode_preview_data(gcode_preview_data) #if ENABLE_GCODE_VIEWER , m_gcode_result(gcode_result) +#else + , m_gcode_preview_data(gcode_preview_data) #endif // ENABLE_GCODE_VIEWER , m_number_extruders(1) , m_preferred_color_mode("feature") @@ -401,8 +404,13 @@ void Preview::set_number_extruders(unsigned int number_extruders) int tool_idx = m_choice_view_type->FindString(_(L("Tool"))); int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type m_choice_view_type->SetSelection(type); +#if ENABLE_GCODE_VIEWER + if ((0 <= type) && (type < static_cast(GCodeViewer::EViewType::Count))) + m_canvas->set_gcode_view_preview_type(static_cast(type)); +#else if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; +#endif // ENABLE_GCODE_VIEWER m_preferred_color_mode = (type == tool_idx) ? "tool_or_feature" : "feature"; } @@ -679,8 +687,13 @@ void Preview::update_view_type(bool slice_completed) int type = m_choice_view_type->FindString(choice); if (m_choice_view_type->GetSelection() != type) { m_choice_view_type->SetSelection(type); +#if ENABLE_GCODE_VIEWER + if ((0 <= type) && (type < static_cast(GCodeViewer::EViewType::Count))) + m_canvas->set_gcode_view_preview_type(static_cast(type)); +#else if (0 <= type && type < (int)GCodePreviewData::Extrusion::Num_View_Types) m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; +#endif // ENABLE_GCODE_VIEWER m_preferred_color_mode = "feature"; } } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 90943de006..a11a474ccf 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -124,8 +124,8 @@ class Preview : public wxPanel public: #if ENABLE_GCODE_VIEWER -Preview(wxWindow * parent, Model * model, DynamicPrintConfig * config, - BackgroundSlicingProcess * process, GCodePreviewData * gcode_preview_data, GCodeProcessor::Result * gcode_result, std::function schedule_background_process = []() {}); +Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, + GCodeProcessor::Result* gcode_result, std::function schedule_background_process = []() {}); #else Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = []() {}); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7d6497731c..c4a43fbb00 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -33,7 +33,9 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" +#if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" +#endif // !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" @@ -1528,9 +1530,10 @@ struct Plater::priv Slic3r::SLAPrint sla_print; Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; - Slic3r::GCodePreviewData gcode_preview_data; #if ENABLE_GCODE_VIEWER Slic3r::GCodeProcessor::Result gcode_result; +#else + Slic3r::GCodePreviewData gcode_preview_data; #endif // ENABLE_GCODE_VIEWER // GUI elements @@ -1840,9 +1843,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); - background_process.set_gcode_preview_data(&gcode_preview_data); #if ENABLE_GCODE_VIEWER background_process.set_gcode_result(&gcode_result); +#else + background_process.set_gcode_preview_data(&gcode_preview_data); #endif // ENABLE_GCODE_VIEWER background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) { @@ -1868,7 +1872,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D = new View3D(q, &model, config, &background_process); #if ENABLE_GCODE_VIEWER - preview = new Preview(q, &model, config, &background_process, &gcode_preview_data, &gcode_result, [this]() { schedule_background_process(); }); + preview = new Preview(q, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); }); #else preview = new Preview(q, &model, config, &background_process, &gcode_preview_data, [this]() { schedule_background_process(); }); #endif // ENABLE_GCODE_VIEWER From 27b9f856077009be98fad60920ea31d335dabd25 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 7 May 2020 11:24:36 +0200 Subject: [PATCH 062/826] Fixed build when tech ENABLE_GCODE_VIEWER is disabled + fixed perl code --- src/slic3r/GUI/DoubleSlider.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.hpp | 1 - xs/src/perlglue.cpp | 4 +- xs/xsp/GCode.xsp | 66 +++++++++++++++++---------------- xs/xsp/Print.xsp | 2 +- xs/xsp/my.map | 8 ++-- xs/xsp/typemap.xspt | 8 ++-- 7 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 0a89333714..3475f50246 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1950,7 +1950,7 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& c if (mode == t_mode::SingleExtruder && code == ColorChangeCode && m_use_default_colors) { #if ENABLE_GCODE_VIEWER - const std::vector& colors = ColorPrintColors::get(); + const std::vector& colors = Slic3r::ColorPrintColors::get(); #else const std::vector& colors = GCodePreviewData::ColorPrintColors(); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index cc43e7d145..294dbabe17 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -640,7 +640,6 @@ public: void set_toolpaths_z_range(const std::array& range); #else std::vector get_current_print_zs(bool active_only) const; - void set_toolpaths_range(double low, double high); #endif // ENABLE_GCODE_VIEWER void set_toolpaths_range(double low, double high); diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index c3cd7e6165..64cf9378fc 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -15,7 +15,9 @@ REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); -REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData"); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData"); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // REGISTER_CLASS(GCodeSender, "GCode::Sender"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 9e04edd4c6..5799cafdbd 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -26,14 +26,16 @@ croak("%s\n", e.what()); } %}; - void do_export_w_preview(Print *print, const char *path, GCodePreviewData *preview_data) - %code%{ - try { - THIS->do_export(print, path, preview_data); - } catch (std::exception& e) { - croak("%s\n", e.what()); - } - %}; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// void do_export_w_preview(Print *print, const char *path, GCodePreviewData *preview_data) +// %code%{ +// try { +// THIS->do_export(print, path, preview_data); +// } catch (std::exception& e) { +// croak("%s\n", e.what()); +// } +// %}; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Ref origin() %code{% RETVAL = &(THIS->origin()); %}; @@ -60,26 +62,28 @@ %code{% RETVAL = const_cast(static_cast(static_cast(&THIS->config()))); %}; }; -%name{Slic3r::GCode::PreviewData} class GCodePreviewData { - GCodePreviewData(); - ~GCodePreviewData(); - void reset(); - bool empty() const; - void set_type(int type) - %code%{ - if ((0 <= type) && (type < GCodePreviewData::Extrusion::Num_View_Types)) - THIS->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; - %}; - int type() %code%{ RETVAL = (int)THIS->extrusion.view_type; %}; - void set_extrusion_flags(int flags) - %code%{ THIS->extrusion.role_flags = (unsigned int)flags; %}; - void set_travel_visible(bool visible) - %code%{ THIS->travel.is_visible = visible; %}; - void set_retractions_visible(bool visible) - %code%{ THIS->retraction.is_visible = visible; %}; - void set_unretractions_visible(bool visible) - %code%{ THIS->unretraction.is_visible = visible; %}; - void set_shells_visible(bool visible) - %code%{ THIS->shell.is_visible = visible; %}; - void set_extrusion_paths_colors(std::vector colors); -}; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//%name{Slic3r::GCode::PreviewData} class GCodePreviewData { +// GCodePreviewData(); +// ~GCodePreviewData(); +// void reset(); +// bool empty() const; +// void set_type(int type) +// %code%{ +// if ((0 <= type) && (type < GCodePreviewData::Extrusion::Num_View_Types)) +// THIS->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; +// %}; +// int type() %code%{ RETVAL = (int)THIS->extrusion.view_type; %}; +// void set_extrusion_flags(int flags) +// %code%{ THIS->extrusion.role_flags = (unsigned int)flags; %}; +// void set_travel_visible(bool visible) +// %code%{ THIS->travel.is_visible = visible; %}; +// void set_retractions_visible(bool visible) +// %code%{ THIS->retraction.is_visible = visible; %}; +// void set_unretractions_visible(bool visible) +// %code%{ THIS->unretraction.is_visible = visible; %}; +// void set_shells_visible(bool visible) +// %code%{ THIS->shell.is_visible = visible; %}; +// void set_extrusion_paths_colors(std::vector colors); +//}; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 160fd3e60c..0952513ca3 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -164,7 +164,7 @@ _constant() void export_gcode(char *path_template) %code%{ try { - THIS->export_gcode(path_template, nullptr, nullptr); + THIS->export_gcode(path_template, nullptr); } catch (std::exception& e) { croak("%s\n", e.what()); } diff --git a/xs/xsp/my.map b/xs/xsp/my.map index fd50d29751..44b9eaa893 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -191,9 +191,11 @@ GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -GCodePreviewData* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//GCodePreviewData* O_OBJECT_SLIC3R +//Ref O_OBJECT_SLIC3R_T +//Clone O_OBJECT_SLIC3R_T +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 7e277703b8..5ee142029b 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -155,9 +155,11 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{GCodePreviewData*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//%typemap{GCodePreviewData*}; +//%typemap{Ref}{simple}; +//%typemap{Clone}{simple}; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ %typemap{Points}; %typemap{Pointfs}; From 277b340481617936f4c5c4ca82f3e8ca8e268826 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 7 May 2020 12:03:17 +0200 Subject: [PATCH 063/826] Attempt to fix build on OsX --- src/slic3r/GUI/DoubleSlider.cpp | 5 ++++- xs/src/perlglue.cpp | 2 -- xs/xsp/GCode.xsp | 4 ---- xs/xsp/my.map | 2 -- xs/xsp/typemap.xspt | 2 -- 5 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 3475f50246..f0940fbfdb 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1,3 +1,6 @@ +#if ENABLE_GCODE_VIEWER +#include "libslic3r/libslic3r.h" +#endif // ENABLE_GCODE_VIEWER #include "wxExtensions.hpp" #if ENABLE_GCODE_VIEWER #include "libslic3r/GCode.hpp" @@ -1950,7 +1953,7 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& c if (mode == t_mode::SingleExtruder && code == ColorChangeCode && m_use_default_colors) { #if ENABLE_GCODE_VIEWER - const std::vector& colors = Slic3r::ColorPrintColors::get(); + const std::vector& colors = ColorPrintColors::get(); #else const std::vector& colors = GCodePreviewData::ColorPrintColors(); #endif // ENABLE_GCODE_VIEWER diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 64cf9378fc..47961c6231 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -15,9 +15,7 @@ REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData"); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // REGISTER_CLASS(GCodeSender, "GCode::Sender"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 5799cafdbd..1536c874b5 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -26,7 +26,6 @@ croak("%s\n", e.what()); } %}; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // void do_export_w_preview(Print *print, const char *path, GCodePreviewData *preview_data) // %code%{ // try { @@ -35,7 +34,6 @@ // croak("%s\n", e.what()); // } // %}; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Ref origin() %code{% RETVAL = &(THIS->origin()); %}; @@ -62,7 +60,6 @@ %code{% RETVAL = const_cast(static_cast(static_cast(&THIS->config()))); %}; }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //%name{Slic3r::GCode::PreviewData} class GCodePreviewData { // GCodePreviewData(); // ~GCodePreviewData(); @@ -86,4 +83,3 @@ // %code%{ THIS->shell.is_visible = visible; %}; // void set_extrusion_paths_colors(std::vector colors); //}; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 44b9eaa893..7e51b237c0 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -191,11 +191,9 @@ GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //GCodePreviewData* O_OBJECT_SLIC3R //Ref O_OBJECT_SLIC3R_T //Clone O_OBJECT_SLIC3R_T -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 5ee142029b..385b50f1aa 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -155,11 +155,9 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //%typemap{GCodePreviewData*}; //%typemap{Ref}{simple}; //%typemap{Clone}{simple}; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ %typemap{Points}; %typemap{Pointfs}; From 383d7f2d73066c1efdca893897b999f2d013fd06 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 7 May 2020 13:07:56 +0200 Subject: [PATCH 064/826] 2nd attempt to fix build on OsX --- src/slic3r/GUI/DoubleSlider.cpp | 5 ++--- src/slic3r/GUI/DoubleSlider.hpp | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index f0940fbfdb..4732ff2613 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1,10 +1,9 @@ -#if ENABLE_GCODE_VIEWER #include "libslic3r/libslic3r.h" -#endif // ENABLE_GCODE_VIEWER -#include "wxExtensions.hpp" #if ENABLE_GCODE_VIEWER +#include "DoubleSlider.hpp" #include "libslic3r/GCode.hpp" #else +#include "wxExtensions.hpp" #include "libslic3r/GCode/PreviewData.hpp" #endif // ENABLE_GCODE_VIEWER #include "GUI.hpp" diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index bf8f54d6c9..36bff17e94 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -4,7 +4,9 @@ #include "libslic3r/CustomGCode.hpp" #include "wxExtensions.hpp" +#if !ENABLE_GCODE_VIEWER #include +#endif // !ENABLE_GCODE_VIEWER #include #include #include From cb73dd2ca6a11fd1e98b5f5ffe282590bb145520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=BD?= Date: Thu, 7 May 2020 20:33:15 +0200 Subject: [PATCH 065/826] Use https in config update URLs http://files.prusa3d.com/* always redirects to HTTPS so use https scheme directly. --- resources/profiles/BIBO.ini | 2 +- resources/profiles/Creality.ini | 4 ++-- resources/profiles/LulzBot.ini | 2 +- resources/profiles/PrusaResearch.ini | 4 ++-- src/slic3r/GUI/UpdateDialogs.cpp | 2 +- src/slic3r/Utils/PresetUpdater.cpp | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/profiles/BIBO.ini b/resources/profiles/BIBO.ini index 4a10de3cbc..b084d47b34 100644 --- a/resources/profiles/BIBO.ini +++ b/resources/profiles/BIBO.ini @@ -7,7 +7,7 @@ name = BIBO # This means, the server may force the PrusaSlicer configuration to be downgraded. config_version = 0.0.1 # Where to get the updates from? -config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/BIBO/ +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/BIBO/ # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index c8d8d9bfbe..a05728da64 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -7,8 +7,8 @@ name = Creality # This means, the server may force the PrusaSlicer configuration to be downgraded. config_version = 0.0.2 # Where to get the updates from? -config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ -# changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ +# changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. diff --git a/resources/profiles/LulzBot.ini b/resources/profiles/LulzBot.ini index 53151d8195..fdd673d256 100644 --- a/resources/profiles/LulzBot.ini +++ b/resources/profiles/LulzBot.ini @@ -4,7 +4,7 @@ # Vendor name will be shown by the Config Wizard. name = LulzBot config_version = 0.0.1 -config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/LulzBot/ +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/LulzBot/ [printer_model:MINI_AERO] name = Mini Aero diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index cc22d50939..d279e7dda3 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -7,8 +7,8 @@ name = Prusa Research # This means, the server may force the PrusaSlicer configuration to be downgraded. config_version = 1.1.2 # Where to get the updates from? -config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ -changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ +changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% # The printer models will be shown by the Configuration Wizard in this order, # also the first model installed & the first nozzle installed will be activated after install. diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index b3b35d68d7..97a3e38805 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -25,7 +25,7 @@ namespace Slic3r { namespace GUI { -static const char* URL_CHANGELOG = "http://files.prusa3d.com/?latest=slicer-stable&lng=%1%"; +static const char* URL_CHANGELOG = "https://files.prusa3d.com/?latest=slicer-stable&lng=%1%"; static const char* URL_DOWNLOAD = "https://www.prusa3d.com/downloads&lng=%1%"; static const char* URL_DEV = "https://github.com/prusa3d/PrusaSlicer/releases/tag/version_%1%"; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index b86775c88c..94e4a4c901 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -303,7 +303,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path_temp = idx_path + "-update"; //check if idx_url is leading to our site - if (! boost::starts_with(idx_url, "http://files.prusa3d.com/wp-content/uploads/repository/")) + if (! boost::starts_with(idx_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) { BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url; continue; From c02a77d94215348eb89f4143269a8cd59160c1ca Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 11 May 2020 13:09:26 +0200 Subject: [PATCH 066/826] GCodeViewer -> Prototype for tool marker --- resources/shaders/gouraud_light.fs | 11 ++ resources/shaders/gouraud_light.vs | 38 ++++++ src/slic3r/GUI/GCodeViewer.cpp | 201 ++++++++++++++++++++++++----- src/slic3r/GUI/GCodeViewer.hpp | 33 +++-- src/slic3r/GUI/GUI_Preview.cpp | 3 +- 5 files changed, 244 insertions(+), 42 deletions(-) create mode 100644 resources/shaders/gouraud_light.fs create mode 100644 resources/shaders/gouraud_light.vs diff --git a/resources/shaders/gouraud_light.fs b/resources/shaders/gouraud_light.fs new file mode 100644 index 0000000000..1a58abc852 --- /dev/null +++ b/resources/shaders/gouraud_light.fs @@ -0,0 +1,11 @@ +#version 110 + +uniform vec4 uniform_color; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); +} diff --git a/resources/shaders/gouraud_light.vs b/resources/shaders/gouraud_light.vs new file mode 100644 index 0000000000..d4f71938a9 --- /dev/null +++ b/resources/shaders/gouraud_light.vs @@ -0,0 +1,38 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(gl_NormalMatrix * gl_Normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = ftransform(); +} diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 0893a7d465..269780d950 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3,6 +3,8 @@ #if ENABLE_GCODE_VIEWER #include "libslic3r/Print.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Geometry.hpp" #include "GUI_App.hpp" #include "PresetBundle.hpp" #include "Camera.hpp" @@ -146,20 +148,160 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con void GCodeViewer::SequentialView::Marker::init() { - if (m_initialized) - return; + Pointf3s vertices; + std::vector triangles; + // arrow tip + vertices.emplace_back(0.0, 0.0, 0.0); + vertices.emplace_back(0.5, -0.5, 1.0); + vertices.emplace_back(0.5, 0.5, 1.0); + vertices.emplace_back(-0.5, 0.5, 1.0); + vertices.emplace_back(-0.5, -0.5, 1.0); + + triangles.emplace_back(0, 1, 4); + triangles.emplace_back(0, 2, 1); + triangles.emplace_back(0, 3, 2); + triangles.emplace_back(0, 4, 3); + triangles.emplace_back(1, 2, 4); + triangles.emplace_back(2, 3, 4); + + // arrow stem + vertices.emplace_back(0.25, -0.25, 1.0); + vertices.emplace_back(0.25, 0.25, 1.0); + vertices.emplace_back(-0.25, 0.25, 1.0); + vertices.emplace_back(-0.25, -0.25, 1.0); + vertices.emplace_back(0.25, -0.25, 3.0); + vertices.emplace_back(0.25, 0.25, 3.0); + vertices.emplace_back(-0.25, 0.25, 3.0); + vertices.emplace_back(-0.25, -0.25, 3.0); + + triangles.emplace_back(5, 9, 8); + triangles.emplace_back(8, 9, 12); + triangles.emplace_back(6, 10, 5); + triangles.emplace_back(5, 10, 9); + triangles.emplace_back(7, 11, 6); + triangles.emplace_back(6, 11, 10); + triangles.emplace_back(8, 12, 7); + triangles.emplace_back(7, 12, 11); + triangles.emplace_back(9, 10, 12); + triangles.emplace_back(12, 10, 11); + + TriangleMesh mesh(vertices, triangles); + mesh.require_shared_vertices(); + + init_from_mesh(mesh); +} + +bool GCodeViewer::SequentialView::Marker::init_from_mesh(const TriangleMesh& mesh) +{ + auto get_normal = [](const std::array& triangle) { + return (triangle[1] - triangle[0]).cross(triangle[2] - triangle[0]).normalized(); + }; + + reset(); + + // vertex data -> load from mesh + std::vector vertices(6 * mesh.its.vertices.size()); + for (size_t i = 0; i < mesh.its.vertices.size(); ++i) { + ::memcpy(static_cast(&vertices[i * 6]), static_cast(mesh.its.vertices[i].data()), 3 * sizeof(float)); + } + + // indices/normals data -> load from mesh + std::vector indices(3 * mesh.its.indices.size()); + for (size_t i = 0; i < mesh.its.indices.size(); ++i) { + const stl_triangle_vertex_indices& triangle = mesh.its.indices[i]; + for (size_t j = 0; j < 3; ++j) { + indices[i * 3 + j] = static_cast(triangle[j]); + } + Vec3f normal = get_normal({ mesh.its.vertices[triangle[0]], mesh.its.vertices[triangle[1]], mesh.its.vertices[triangle[2]] }); + ::memcpy(static_cast(&vertices[3 + static_cast(triangle[0]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); + ::memcpy(static_cast(&vertices[3 + static_cast(triangle[1]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); + ::memcpy(static_cast(&vertices[3 + static_cast(triangle[2]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); + } + + m_indices_count = static_cast(indices.size()); + + // vertex data -> send to gpu + glsafe(::glGenBuffers(1, &m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + // indices data -> send to gpu + glsafe(::glGenBuffers(1, &m_ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + return init_shader(); } void GCodeViewer::SequentialView::Marker::render() const { - if (!m_initialized) + if (!m_visible || !m_shader.is_initialized()) return; + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + m_shader.start_using(); + GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_color.data())); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)0)); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixf(m_world_transform.data())); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); + glsafe(::glDrawElements(GL_TRIANGLES, static_cast(m_indices_count), GL_UNSIGNED_INT, (const void*)0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + glsafe(::glPopMatrix()); + + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + m_shader.stop_using(); + + glsafe(::glDisable(GL_BLEND)); } -const std::vector GCodeViewer::Extrusion_Role_Colors{ { +void GCodeViewer::SequentialView::Marker::reset() +{ + // release gpu memory + if (m_ibo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_ibo_id)); + m_ibo_id = 0; + } + + if (m_vbo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_vbo_id)); + m_vbo_id = 0; + } + + m_indices_count = 0; +} + +bool GCodeViewer::SequentialView::Marker::init_shader() +{ + if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) { + BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; + return false; + } + + return true; +} + +const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.50f, 0.50f, 0.50f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter @@ -178,13 +320,13 @@ const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.00f, 0.00f, 0.00f } // erMixed }}; -const std::vector GCodeViewer::Travel_Colors{ { +const std::vector GCodeViewer::Travel_Colors {{ { 0.0f, 0.0f, 0.5f }, // Move { 0.0f, 0.5f, 0.0f }, // Extrude { 0.5f, 0.0f, 0.0f } // Retract }}; -const std::vector GCodeViewer::Range_Colors{ { +const std::vector GCodeViewer::Range_Colors {{ { 0.043f, 0.173f, 0.478f }, // bluish { 0.075f, 0.349f, 0.522f }, { 0.110f, 0.533f, 0.569f }, @@ -292,10 +434,13 @@ void GCodeViewer::render() const m_statistics.reset_opengl(); #endif // ENABLE_GCODE_VIEWER_STATISTICS + if (m_roles.empty()) + return; + glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); - if (m_sequential_view.marker.visible) - m_sequential_view.marker.render(); + m_sequential_view.marker.set_world_transform(Geometry::assemble_transform(m_sequential_view.current_position.cast() + 0.5 * Vec3d::UnitZ(), { 0.0, 0.0, 0.0 }, { 4.0, 4.0, 4.0 }, { 1.0, 1.0, 1.0 }).cast()); + m_sequential_view.marker.render(); render_shells(); render_legend(); render_sequential_bar(); @@ -332,7 +477,8 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, 5, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print)); flags = set_flag(flags, 6, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode)); flags = set_flag(flags, 7, m_shells.visible); - flags = set_flag(flags, 8, is_legend_enabled()); + flags = set_flag(flags, 8, m_sequential_view.marker.is_visible()); + flags = set_flag(flags, 9, is_legend_enabled()); return flags; } @@ -350,7 +496,8 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print, is_flag_set(5)); set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, is_flag_set(6)); m_shells.visible = is_flag_set(7); - enable_legend(is_flag_set(8)); + m_sequential_view.marker.set_visible(is_flag_set(8)); + enable_legend(is_flag_set(9)); } bool GCodeViewer::init_shaders() @@ -706,7 +853,7 @@ void GCodeViewer::render_toolpaths() const { auto set_color = [](GLint current_program_id, const Color& color) { if (current_program_id > 0) { - GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; + GLint color_id = ::glGetUniformLocation(current_program_id, "uniform_color"); if (color_id >= 0) { glsafe(::glUniform3fv(color_id, 1, (const GLfloat*)color.data())); return; @@ -737,10 +884,7 @@ void GCodeViewer::render_toolpaths() const GCodeProcessor::EMoveType type = buffer_type(i); buffer.shader.start_using(); - - GLint current_program_id; - glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); - + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); switch (type) @@ -748,7 +892,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Tool_change: { Color color = { 1.0f, 1.0f, 1.0f }; - set_color(current_program_id, color); + set_color(static_cast(buffer.shader.get_shader_program_id()), color); for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -764,7 +908,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Color_change: { Color color = { 1.0f, 0.0f, 0.0f }; - set_color(current_program_id, color); + set_color(static_cast(buffer.shader.get_shader_program_id()), color); for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -780,7 +924,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Pause_Print: { Color color = { 0.0f, 1.0f, 0.0f }; - set_color(current_program_id, color); + set_color(static_cast(buffer.shader.get_shader_program_id()), color); for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -796,7 +940,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Custom_GCode: { Color color = { 0.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); + set_color(static_cast(buffer.shader.get_shader_program_id()), color); for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -812,7 +956,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Retract: { Color color = { 1.0f, 0.0f, 1.0f }; - set_color(current_program_id, color); + set_color(static_cast(buffer.shader.get_shader_program_id()), color); for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -828,7 +972,7 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Unretract: { Color color = { 0.0f, 1.0f, 1.0f }; - set_color(current_program_id, color); + set_color(static_cast(buffer.shader.get_shader_program_id()), color); for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -845,7 +989,7 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { - set_color(current_program_id, path.color); + set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -858,7 +1002,7 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { - set_color(current_program_id, path.color); + set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -897,7 +1041,7 @@ void GCodeViewer::render_legend() const static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - if (!m_legend_enabled || m_roles.empty()) + if (!m_legend_enabled) return; ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -1130,9 +1274,6 @@ void GCodeViewer::render_sequential_bar() const refresh_render_paths(true); }; - if (m_roles.empty()) - return; - if (m_sequential_view.last <= m_sequential_view.first) return; @@ -1204,9 +1345,6 @@ void GCodeViewer::render_sequential_bar() const ImGui::SameLine(); imgui.text(std::to_string(i_max)); - ImGui::Separator(); - ImGui::Checkbox(I18N::translate_utf8(L("Show marker")).c_str(), &m_sequential_view.marker.visible); - imgui.end(); ImGui::PopStyleVar(); } @@ -1217,9 +1355,6 @@ void GCodeViewer::render_statistics() const static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); static const float offset = 250.0f; - if (m_roles.empty()) - return; - ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5354ae067f..d34e8ee4bd 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -9,7 +9,10 @@ #include namespace Slic3r { + class Print; +class TriangleMesh; + namespace GUI { class GCodeViewer @@ -73,8 +76,8 @@ class GCodeViewer struct IBuffer { unsigned int ibo_id{ 0 }; - Shader shader; size_t indices_count{ 0 }; + Shader shader; std::vector paths; std::vector render_paths; bool visible{ false }; @@ -147,19 +150,33 @@ class GCodeViewer struct SequentialView { - struct Marker + class Marker { - private: - bool m_initialized{ false }; + unsigned int m_vbo_id{ 0 }; + unsigned int m_ibo_id{ 0 }; + size_t m_indices_count{ 0 }; + Transform3f m_world_transform; + std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; + bool m_visible{ false }; + Shader m_shader; public: - unsigned int vbo_id{ 0 }; - unsigned int ibo_id{ 0 }; - bool visible{ false }; - Shader shader; + ~Marker() { reset(); } void init(); + bool init_from_mesh(const TriangleMesh& mesh); + + void set_world_transform(const Transform3f& transform) { m_world_transform = transform; } + + void set_color(const std::array& color) { m_color = color; } + + bool is_visible() const { return m_visible; } + void set_visible(bool visible) { m_visible = visible; } void render() const; + void reset(); + + private: + bool init_shader(); }; unsigned int first{ 0 }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 4f47c2d18f..fc97796749 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -298,8 +298,9 @@ bool Preview::init(wxWindow* parent, Model* model) _L("Pause prints") + "|0|" + _L("Custom GCodes") + "|0|" + _L("Shells") + "|0|" + + _L("Tool marker") + "|1|" + _L("Legend") + "|1" - ); +); Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); #else m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); From 769cca4b25410a2cdaf43af032ff301e443bbe43 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 11 May 2020 16:26:35 +0200 Subject: [PATCH 067/826] GCodeViewer -> Enhanced tool marker + refactoring (added new base class for OpenGL models) --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GCodeViewer.cpp | 129 +------------------ src/slic3r/GUI/GCodeViewer.hpp | 13 +- src/slic3r/GUI/GLModel.cpp | 229 +++++++++++++++++++++++++++++++++ src/slic3r/GUI/GLModel.hpp | 46 +++++++ 5 files changed, 286 insertions(+), 133 deletions(-) create mode 100644 src/slic3r/GUI/GLModel.cpp create mode 100644 src/slic3r/GUI/GLModel.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index fefc12ba87..b085fad456 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -55,6 +55,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoHollow.hpp GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.hpp + GUI/GLModel.hpp + GUI/GLModel.cpp GUI/GLTexture.hpp GUI/GLTexture.cpp GUI/GLToolbar.hpp diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 269780d950..ad3985c625 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -3,7 +3,6 @@ #if ENABLE_GCODE_VIEWER #include "libslic3r/Print.hpp" -#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Geometry.hpp" #include "GUI_App.hpp" #include "PresetBundle.hpp" @@ -148,92 +147,8 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con void GCodeViewer::SequentialView::Marker::init() { - Pointf3s vertices; - std::vector triangles; - - // arrow tip - vertices.emplace_back(0.0, 0.0, 0.0); - vertices.emplace_back(0.5, -0.5, 1.0); - vertices.emplace_back(0.5, 0.5, 1.0); - vertices.emplace_back(-0.5, 0.5, 1.0); - vertices.emplace_back(-0.5, -0.5, 1.0); - - triangles.emplace_back(0, 1, 4); - triangles.emplace_back(0, 2, 1); - triangles.emplace_back(0, 3, 2); - triangles.emplace_back(0, 4, 3); - triangles.emplace_back(1, 2, 4); - triangles.emplace_back(2, 3, 4); - - // arrow stem - vertices.emplace_back(0.25, -0.25, 1.0); - vertices.emplace_back(0.25, 0.25, 1.0); - vertices.emplace_back(-0.25, 0.25, 1.0); - vertices.emplace_back(-0.25, -0.25, 1.0); - vertices.emplace_back(0.25, -0.25, 3.0); - vertices.emplace_back(0.25, 0.25, 3.0); - vertices.emplace_back(-0.25, 0.25, 3.0); - vertices.emplace_back(-0.25, -0.25, 3.0); - - triangles.emplace_back(5, 9, 8); - triangles.emplace_back(8, 9, 12); - triangles.emplace_back(6, 10, 5); - triangles.emplace_back(5, 10, 9); - triangles.emplace_back(7, 11, 6); - triangles.emplace_back(6, 11, 10); - triangles.emplace_back(8, 12, 7); - triangles.emplace_back(7, 12, 11); - triangles.emplace_back(9, 10, 12); - triangles.emplace_back(12, 10, 11); - - TriangleMesh mesh(vertices, triangles); - mesh.require_shared_vertices(); - - init_from_mesh(mesh); -} - -bool GCodeViewer::SequentialView::Marker::init_from_mesh(const TriangleMesh& mesh) -{ - auto get_normal = [](const std::array& triangle) { - return (triangle[1] - triangle[0]).cross(triangle[2] - triangle[0]).normalized(); - }; - - reset(); - - // vertex data -> load from mesh - std::vector vertices(6 * mesh.its.vertices.size()); - for (size_t i = 0; i < mesh.its.vertices.size(); ++i) { - ::memcpy(static_cast(&vertices[i * 6]), static_cast(mesh.its.vertices[i].data()), 3 * sizeof(float)); - } - - // indices/normals data -> load from mesh - std::vector indices(3 * mesh.its.indices.size()); - for (size_t i = 0; i < mesh.its.indices.size(); ++i) { - const stl_triangle_vertex_indices& triangle = mesh.its.indices[i]; - for (size_t j = 0; j < 3; ++j) { - indices[i * 3 + j] = static_cast(triangle[j]); - } - Vec3f normal = get_normal({ mesh.its.vertices[triangle[0]], mesh.its.vertices[triangle[1]], mesh.its.vertices[triangle[2]] }); - ::memcpy(static_cast(&vertices[3 + static_cast(triangle[0]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); - ::memcpy(static_cast(&vertices[3 + static_cast(triangle[1]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); - ::memcpy(static_cast(&vertices[3 + static_cast(triangle[2]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); - } - - m_indices_count = static_cast(indices.size()); - - // vertex data -> send to gpu - glsafe(::glGenBuffers(1, &m_vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - // indices data -> send to gpu - glsafe(::glGenBuffers(1, &m_ibo_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - return init_shader(); + m_model.init_from(stilized_arrow(16, 0.5f, 1.0f, 0.25f, 2.0f)); + init_shader(); } void GCodeViewer::SequentialView::Marker::render() const @@ -249,56 +164,22 @@ void GCodeViewer::SequentialView::Marker::render() const if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_color.data())); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)0)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(m_world_transform.data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); - glsafe(::glDrawElements(GL_TRIANGLES, static_cast(m_indices_count), GL_UNSIGNED_INT, (const void*)0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + m_model.render(); glsafe(::glPopMatrix()); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - m_shader.stop_using(); glsafe(::glDisable(GL_BLEND)); } -void GCodeViewer::SequentialView::Marker::reset() +void GCodeViewer::SequentialView::Marker::init_shader() { - // release gpu memory - if (m_ibo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_ibo_id)); - m_ibo_id = 0; - } - - if (m_vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &m_vbo_id)); - m_vbo_id = 0; - } - - m_indices_count = 0; -} - -bool GCodeViewer::SequentialView::Marker::init_shader() -{ - if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) { + if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; - return false; - } - - return true; } const std::vector GCodeViewer::Extrusion_Role_Colors {{ diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index d34e8ee4bd..44f23b2852 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -5,6 +5,7 @@ #include "GLShader.hpp" #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" +#include "GLModel.hpp" #include @@ -152,31 +153,25 @@ class GCodeViewer { class Marker { - unsigned int m_vbo_id{ 0 }; - unsigned int m_ibo_id{ 0 }; - size_t m_indices_count{ 0 }; + GL_Model m_model; Transform3f m_world_transform; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; bool m_visible{ false }; Shader m_shader; public: - ~Marker() { reset(); } - void init(); - bool init_from_mesh(const TriangleMesh& mesh); void set_world_transform(const Transform3f& transform) { m_world_transform = transform; } - void set_color(const std::array& color) { m_color = color; } bool is_visible() const { return m_visible; } void set_visible(bool visible) { m_visible = visible; } + void render() const; - void reset(); private: - bool init_shader(); + void init_shader(); }; unsigned int first{ 0 }; diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp new file mode 100644 index 0000000000..336c699371 --- /dev/null +++ b/src/slic3r/GUI/GLModel.cpp @@ -0,0 +1,229 @@ +#include "libslic3r/libslic3r.h" +#include "GLModel.hpp" + +#include "3DScene.hpp" +#include "libslic3r/TriangleMesh.hpp" + +#include + +namespace Slic3r { +namespace GUI { + +bool GL_Model::init_from(const GLModelInitializationData& data) +{ + assert(!data.positions.empty() && !data.triangles.empty()); + assert(data.positions.size() == data.normals.size()); + + reset(); + + // vertices/normals data + std::vector vertices(6 * data.positions.size()); + for (size_t i = 0; i < data.positions.size(); ++i) { + ::memcpy(static_cast(&vertices[i * 6]), static_cast(data.positions[i].data()), 3 * sizeof(float)); + ::memcpy(static_cast(&vertices[3 + i * 6]), static_cast(data.normals[i].data()), 3 * sizeof(float)); + } + + // indices data + std::vector indices(3 * data.triangles.size()); + for (size_t i = 0; i < data.triangles.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + indices[i * 3 + j] = static_cast(data.triangles[i][j]); + } + } + + m_indices_count = static_cast(indices.size()); + + send_to_gpu(vertices, indices); + + return true; +} + +bool GL_Model::init_from(const TriangleMesh& mesh) +{ + auto get_normal = [](const std::array& triangle) { + return (triangle[1] - triangle[0]).cross(triangle[2] - triangle[0]).normalized(); + }; + + reset(); + + assert(!mesh.its.vertices.empty() && !mesh.its.indices.empty()); // call require_shared_vertices() before to pass the mesh to this method + + // vertices data -> load from mesh + std::vector vertices(6 * mesh.its.vertices.size()); + for (size_t i = 0; i < mesh.its.vertices.size(); ++i) { + ::memcpy(static_cast(&vertices[i * 6]), static_cast(mesh.its.vertices[i].data()), 3 * sizeof(float)); + } + + // indices/normals data -> load from mesh + std::vector indices(3 * mesh.its.indices.size()); + for (size_t i = 0; i < mesh.its.indices.size(); ++i) { + const stl_triangle_vertex_indices& triangle = mesh.its.indices[i]; + for (size_t j = 0; j < 3; ++j) { + indices[i * 3 + j] = static_cast(triangle[j]); + } + Vec3f normal = get_normal({ mesh.its.vertices[triangle[0]], mesh.its.vertices[triangle[1]], mesh.its.vertices[triangle[2]] }); + ::memcpy(static_cast(&vertices[3 + static_cast(triangle[0]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); + ::memcpy(static_cast(&vertices[3 + static_cast(triangle[1]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); + ::memcpy(static_cast(&vertices[3 + static_cast(triangle[2]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); + } + + m_indices_count = static_cast(indices.size()); + + send_to_gpu(vertices, indices); + + return true; +} + +void GL_Model::reset() +{ + // release gpu memory + if (m_ibo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_ibo_id)); + m_ibo_id = 0; + } + + if (m_vbo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_vbo_id)); + m_vbo_id = 0; + } + + m_indices_count = 0; +} + +void GL_Model::render() const +{ + if (m_vbo_id == 0 || m_ibo_id == 0) + return; + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)0)); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); + glsafe(::glDrawElements(GL_TRIANGLES, static_cast(m_indices_count), GL_UNSIGNED_INT, (const void*)0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GL_Model::send_to_gpu(const std::vector& vertices, const std::vector& indices) +{ + // vertex data -> send to gpu + glsafe(::glGenBuffers(1, &m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + // indices data -> send to gpu + glsafe(::glGenBuffers(1, &m_ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); +} + +GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) +{ + GLModelInitializationData data; + + float angle_step = 2.0f * M_PI / static_cast(resolution); + std::vector cosines(resolution); + std::vector sines(resolution); + + for (int i = 0; i < resolution; ++i) + { + float angle = angle_step * static_cast(i); + cosines[i] = ::cos(angle); + sines[i] = -::sin(angle); + } + + // tip vertices/normals + data.positions.emplace_back(0.0f, 0.0f, 0.0f); + data.normals.emplace_back(-Vec3f::UnitZ()); + for (int i = 0; i < resolution; ++i) + { + data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], tip_height); + data.normals.emplace_back(sines[i], cosines[i], 0.0f); + } + + // tip triangles + for (int i = 0; i < resolution; ++i) + { + int v3 = (i < resolution - 1) ? i + 2 : 1; + data.triangles.emplace_back(0, v3, i + 1); + } + + // tip cap outer perimeter vertices + for (int i = 0; i < resolution; ++i) + { + data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], tip_height); + data.normals.emplace_back(Vec3f::UnitZ()); + } + + // tip cap inner perimeter vertices + for (int i = 0; i < resolution; ++i) + { + data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], tip_height); + data.normals.emplace_back(Vec3f::UnitZ()); + } + + // tip cap triangles + for (int i = 0; i < resolution; ++i) + { + int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1; + int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1; + data.triangles.emplace_back(i + resolution + 1, v2, v3); + data.triangles.emplace_back(i + resolution + 1, v3, i + 2 * resolution + 1); + } + + // stem bottom vertices + for (int i = 0; i < resolution; ++i) + { + data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], tip_height); + data.normals.emplace_back(sines[i], cosines[i], 0.0f); + } + + float total_height = tip_height + stem_height; + + // stem top vertices + for (int i = 0; i < resolution; ++i) + { + data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], total_height); + data.normals.emplace_back(sines[i], cosines[i], 0.0f); + } + + // stem triangles + for (int i = 0; i < resolution; ++i) + { + int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1; + int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1; + data.triangles.emplace_back(i + 3 * resolution + 1, v2, v3); + data.triangles.emplace_back(i + 3 * resolution + 1, v3, i + 4 * resolution + 1); + } + + // stem cap vertices + data.positions.emplace_back(0.0f, 0.0f, total_height); + data.normals.emplace_back(Vec3f::UnitZ()); + for (int i = 0; i < resolution; ++i) + { + data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], total_height); + data.normals.emplace_back(Vec3f::UnitZ()); + } + + // stem cap triangles + for (int i = 0; i < resolution; ++i) + { + int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2; + data.triangles.emplace_back(5 * resolution + 1, i + 5 * resolution + 2, v3); + } + + return data; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp new file mode 100644 index 0000000000..f294531abb --- /dev/null +++ b/src/slic3r/GUI/GLModel.hpp @@ -0,0 +1,46 @@ +#ifndef slic3r_GLModel_hpp_ +#define slic3r_GLModel_hpp_ + +namespace Slic3r { + +class TriangleMesh; + +namespace GUI { + + struct GLModelInitializationData + { + std::vector positions; + std::vector normals; + std::vector triangles; + }; + + class GL_Model + { + unsigned int m_vbo_id{ 0 }; + unsigned int m_ibo_id{ 0 }; + size_t m_indices_count{ 0 }; + + public: + virtual ~GL_Model() { reset(); } + + bool init_from(const GLModelInitializationData& data); + bool init_from(const TriangleMesh& mesh); + void reset(); + void render() const; + + private: + void send_to_gpu(const std::vector& vertices, const std::vector& indices); + }; + + + // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution + // the arrow tip is at 0,0,0 + // the arrow has its axis of symmetry along the Z axis and is pointing downward + GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, + float stem_radius, float stem_height); + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLModel_hpp_ + From b2f8f2bca6a3b2907870743180af05130a0352fc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 11 May 2020 16:37:04 +0200 Subject: [PATCH 068/826] Added missing includes --- src/slic3r/GUI/GLModel.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index f294531abb..18e8bafbdd 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -1,6 +1,9 @@ #ifndef slic3r_GLModel_hpp_ #define slic3r_GLModel_hpp_ +#include "libslic3r/Point.hpp" +#include + namespace Slic3r { class TriangleMesh; From 8d5cea82f4e2035663812547d65a5bb1a63ea73a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 May 2020 11:33:50 +0200 Subject: [PATCH 069/826] Tech ENABLE_GCODE_VIEWER -> Bed axes rendered using the new OpenGL model class --- src/slic3r/GUI/3DBed.cpp | 74 +++++++++++++++++++++++++++++++++- src/slic3r/GUI/3DBed.hpp | 33 +++++++++++++++ src/slic3r/GUI/GCodeViewer.cpp | 6 ++- src/slic3r/GUI/GCodeViewer.hpp | 2 + src/slic3r/GUI/GLModel.cpp | 63 ++++++++++++++++------------- src/slic3r/GUI/GLModel.hpp | 13 ++++-- 6 files changed, 154 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 6c070ca99a..4ef8679603 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -5,15 +5,24 @@ #include "libslic3r/Polygon.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/BoundingBox.hpp" +#if ENABLE_GCODE_VIEWER +#include "libslic3r/Geometry.hpp" +#endif // ENABLE_GCODE_VIEWER #include "GUI_App.hpp" #include "PresetBundle.hpp" #include "GLCanvas3D.hpp" +#if ENABLE_GCODE_VIEWER +#include "3DScene.hpp" +#endif // ENABLE_GCODE_VIEWER #include #include #include +#if ENABLE_GCODE_VIEWER +#include +#endif // ENABLE_GCODE_VIEWER static const float GROUND_Z = -0.02f; @@ -119,13 +128,25 @@ const float* GeometryBuffer::get_vertices_data() const return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; } +#if ENABLE_GCODE_VIEWER +const float Bed3D::Axes::DefaultStemRadius = 0.5f; +const float Bed3D::Axes::DefaultStemLength = 25.0f; +const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; +const float Bed3D::Axes::DefaultTipLength = 5.0f; +#else const double Bed3D::Axes::Radius = 0.5; const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius; const double Bed3D::Axes::ArrowLength = 5.0; +#endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER +void Bed3D::Axes::set_stem_length(float length) +{ + m_stem_length = length; + m_arrow.reset(); +} +#else Bed3D::Axes::Axes() -: origin(Vec3d::Zero()) -, length(25.0 * Vec3d::Ones()) { m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) @@ -137,9 +158,46 @@ Bed3D::Axes::~Axes() if (m_quadric != nullptr) ::gluDeleteQuadric(m_quadric); } +#endif // ENABLE_GCODE_VIEWER void Bed3D::Axes::render() const { +#if ENABLE_GCODE_VIEWER + auto render_axis = [this](const Transform3f& transform, GLint color_id, const std::array& color) { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixf(transform.data())); + m_arrow.render(); + glsafe(::glPopMatrix()); + }; + + m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); + if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) + BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; + + if (!m_shader.is_initialized()) + return; + + glsafe(::glEnable(GL_DEPTH_TEST)); + + m_shader.start_using(); + GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); + + // x axis + render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0f }).cast(), color_id, { 0.75f, 0.0f, 0.0f, 1.0f }); + + // y axis + render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0f }).cast(), color_id, { 0.0f, 0.75f, 0.0f, 1.0f }); + + // z axis + render_axis(Geometry::assemble_transform(m_origin).cast(), color_id, { 0.0f, 0.0f, 0.75f, 1.0f }); + + m_shader.stop_using(); + + glsafe(::glDisable(GL_DEPTH_TEST)); +#else if (m_quadric == nullptr) return; @@ -171,8 +229,10 @@ void Bed3D::Axes::render() const glsafe(::glDisable(GL_LIGHTING)); glsafe(::glDisable(GL_DEPTH_TEST)); +#endif // !ENABLE_GCODE_VIEWER } +#if !ENABLE_GCODE_VIEWER void Bed3D::Axes::render_axis(double length) const { ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); @@ -185,6 +245,7 @@ void Bed3D::Axes::render_axis(double length) const ::gluQuadricOrientation(m_quadric, GLU_INSIDE); ::gluDisk(m_quadric, 0.0, ArrowBaseRadius, 32, 1); } +#endif // !ENABLE_GCODE_VIEWER Bed3D::Bed3D() : m_type(Custom) @@ -242,8 +303,13 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c m_model.reset(); // Set the origin and size for rendering the coordinate system axes. +#if ENABLE_GCODE_VIEWER + m_axes.set_origin({ 0.0, 0.0, static_cast(GROUND_Z) }); + m_axes.set_stem_length(0.1f * static_cast(m_bounding_box.max_size())); +#else m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones(); +#endif // ENABLE_GCODE_VIEWER // Let the calee to update the UI. return true; @@ -290,7 +356,11 @@ void Bed3D::calc_bounding_boxes() const m_extended_bounding_box = m_bounding_box; // extend to contain axes +#if ENABLE_GCODE_VIEWER + m_extended_bounding_box.merge(m_axes.get_total_length() * Vec3d::Ones()); +#else m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones()); +#endif // ENABLE_GCODE_VIEWER // extend to contain model, if any if (!m_model.get_filename().empty()) diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index abdfca1fe0..440468233c 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -4,11 +4,16 @@ #include "GLTexture.hpp" #include "3DScene.hpp" #include "GLShader.hpp" +#if ENABLE_GCODE_VIEWER +#include "GLModel.hpp" +#endif // ENABLE_GCODE_VIEWER #include +#if !ENABLE_GCODE_VIEWER class GLUquadric; typedef class GLUquadric GLUquadricObj; +#endif // !ENABLE_GCODE_VIEWER namespace Slic3r { namespace GUI { @@ -45,22 +50,50 @@ public: class Bed3D { +#if ENABLE_GCODE_VIEWER + class Axes + { + static const float DefaultStemRadius; + static const float DefaultStemLength; + static const float DefaultTipRadius; + static const float DefaultTipLength; +#else struct Axes { static const double Radius; static const double ArrowBaseRadius; static const double ArrowLength; +#endif // ENABLE_GCODE_VIEWER + +#if ENABLE_GCODE_VIEWER + Vec3d m_origin{ Vec3d::Zero() }; + float m_stem_length{ DefaultStemLength }; + mutable GL_Model m_arrow; + mutable Shader m_shader; + + public: +#else Vec3d origin; Vec3d length; GLUquadricObj* m_quadric; +#endif // ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER Axes(); ~Axes(); +#endif // !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + void set_origin(const Vec3d& origin) { m_origin = origin; } + void set_stem_length(float length); + float get_total_length() const { return m_stem_length + DefaultTipLength; } +#endif // ENABLE_GCODE_VIEWER void render() const; +#if !ENABLE_GCODE_VIEWER private: void render_axis(double length) const; +#endif // !ENABLE_GCODE_VIEWER }; public: diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ad3985c625..525a2fd5a1 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -147,7 +147,7 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con void GCodeViewer::SequentialView::Marker::init() { - m_model.init_from(stilized_arrow(16, 0.5f, 1.0f, 0.25f, 2.0f)); + m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); init_shader(); } @@ -320,7 +320,7 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); - m_sequential_view.marker.set_world_transform(Geometry::assemble_transform(m_sequential_view.current_position.cast() + 0.5 * Vec3d::UnitZ(), { 0.0, 0.0, 0.0 }, { 4.0, 4.0, 4.0 }, { 1.0, 1.0, 1.0 }).cast()); + m_sequential_view.marker.set_world_transform(Geometry::assemble_transform(m_sequential_view.current_position.cast() + (0.5 + 12.0) * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 }).cast()); m_sequential_view.marker.render(); render_shells(); render_legend(); @@ -437,6 +437,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); } + m_bounding_box.merge(m_bounding_box.max + m_sequential_view.marker.get_bounding_box().max[2] * Vec3d::UnitZ()); + #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.vertices_size = SLIC3R_STDVEC_MEMSIZE(vertices_data, float); m_statistics.vertices_gpu_size = vertices_data.size() * sizeof(float); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 44f23b2852..6f3ca47dba 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -162,6 +162,8 @@ class GCodeViewer public: void init(); + const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } + void set_world_transform(const Transform3f& transform) { m_world_transform = transform; } void set_color(const std::array& color) { m_color = color; } diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 336c699371..4b2ce4e9e9 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -9,12 +9,14 @@ namespace Slic3r { namespace GUI { -bool GL_Model::init_from(const GLModelInitializationData& data) +void GL_Model::init_from(const GLModelInitializationData& data) { + assert(!data.positions.empty() && !data.triangles.empty()); assert(data.positions.size() == data.normals.size()); - reset(); + if (m_vbo_id > 0) // call reset() if you want to reuse this model + return; // vertices/normals data std::vector vertices(6 * data.positions.size()); @@ -32,19 +34,22 @@ bool GL_Model::init_from(const GLModelInitializationData& data) } m_indices_count = static_cast(indices.size()); + m_bounding_box = BoundingBoxf3(); + for (size_t i = 0; i < data.positions.size(); ++i) { + m_bounding_box.merge(data.positions[i].cast()); + } send_to_gpu(vertices, indices); - - return true; } -bool GL_Model::init_from(const TriangleMesh& mesh) +void GL_Model::init_from(const TriangleMesh& mesh) { auto get_normal = [](const std::array& triangle) { return (triangle[1] - triangle[0]).cross(triangle[2] - triangle[0]).normalized(); }; - reset(); + if (m_vbo_id > 0) // call reset() if you want to reuse this model + return; assert(!mesh.its.vertices.empty() && !mesh.its.indices.empty()); // call require_shared_vertices() before to pass the mesh to this method @@ -68,10 +73,9 @@ bool GL_Model::init_from(const TriangleMesh& mesh) } m_indices_count = static_cast(indices.size()); + m_bounding_box = mesh.bounding_box(); send_to_gpu(vertices, indices); - - return true; } void GL_Model::reset() @@ -88,6 +92,7 @@ void GL_Model::reset() } m_indices_count = 0; + m_bounding_box = BoundingBoxf3(); } void GL_Model::render() const @@ -142,12 +147,14 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float sines[i] = -::sin(angle); } + float total_height = tip_height + stem_height; + // tip vertices/normals - data.positions.emplace_back(0.0f, 0.0f, 0.0f); - data.normals.emplace_back(-Vec3f::UnitZ()); + data.positions.emplace_back(0.0f, 0.0f, total_height); + data.normals.emplace_back(Vec3f::UnitZ()); for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], tip_height); + data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], stem_height); data.normals.emplace_back(sines[i], cosines[i], 0.0f); } @@ -155,21 +162,21 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float for (int i = 0; i < resolution; ++i) { int v3 = (i < resolution - 1) ? i + 2 : 1; - data.triangles.emplace_back(0, v3, i + 1); + data.triangles.emplace_back(0, i + 1, v3); } // tip cap outer perimeter vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], tip_height); - data.normals.emplace_back(Vec3f::UnitZ()); + data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], stem_height); + data.normals.emplace_back(-Vec3f::UnitZ()); } // tip cap inner perimeter vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], tip_height); - data.normals.emplace_back(Vec3f::UnitZ()); + data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], stem_height); + data.normals.emplace_back(-Vec3f::UnitZ()); } // tip cap triangles @@ -177,23 +184,21 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float { int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1; int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1; - data.triangles.emplace_back(i + resolution + 1, v2, v3); - data.triangles.emplace_back(i + resolution + 1, v3, i + 2 * resolution + 1); + data.triangles.emplace_back(i + resolution + 1, v3, v2); + data.triangles.emplace_back(i + resolution + 1, i + 2 * resolution + 1, v3); } // stem bottom vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], tip_height); + data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], stem_height); data.normals.emplace_back(sines[i], cosines[i], 0.0f); } - float total_height = tip_height + stem_height; - // stem top vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], total_height); + data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], 0.0f); data.normals.emplace_back(sines[i], cosines[i], 0.0f); } @@ -202,24 +207,24 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float { int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1; int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1; - data.triangles.emplace_back(i + 3 * resolution + 1, v2, v3); - data.triangles.emplace_back(i + 3 * resolution + 1, v3, i + 4 * resolution + 1); + data.triangles.emplace_back(i + 3 * resolution + 1, v3, v2); + data.triangles.emplace_back(i + 3 * resolution + 1, i + 4 * resolution + 1, v3); } // stem cap vertices - data.positions.emplace_back(0.0f, 0.0f, total_height); - data.normals.emplace_back(Vec3f::UnitZ()); + data.positions.emplace_back(0.0f, 0.0f, 0.0f); + data.normals.emplace_back(-Vec3f::UnitZ()); for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], total_height); - data.normals.emplace_back(Vec3f::UnitZ()); + data.positions.emplace_back(stem_radius* sines[i], stem_radius* cosines[i], 0.0f); + data.normals.emplace_back(-Vec3f::UnitZ()); } // stem cap triangles for (int i = 0; i < resolution; ++i) { int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2; - data.triangles.emplace_back(5 * resolution + 1, i + 5 * resolution + 2, v3); + data.triangles.emplace_back(5 * resolution + 1, v3, i + 5 * resolution + 2); } return data; diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 18e8bafbdd..7b135ada6b 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -2,6 +2,7 @@ #define slic3r_GLModel_hpp_ #include "libslic3r/Point.hpp" +#include "libslic3r/BoundingBox.hpp" #include namespace Slic3r { @@ -23,22 +24,26 @@ namespace GUI { unsigned int m_ibo_id{ 0 }; size_t m_indices_count{ 0 }; + BoundingBoxf3 m_bounding_box; + public: virtual ~GL_Model() { reset(); } - bool init_from(const GLModelInitializationData& data); - bool init_from(const TriangleMesh& mesh); + void init_from(const GLModelInitializationData& data); + void init_from(const TriangleMesh& mesh); void reset(); void render() const; + const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } + private: void send_to_gpu(const std::vector& vertices, const std::vector& indices); }; // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution - // the arrow tip is at 0,0,0 - // the arrow has its axis of symmetry along the Z axis and is pointing downward + // the origin of the arrow is in the center of the stem cap + // the arrow has its axis of symmetry along the Z axis and is pointing upward GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); From 58258df113249505ebe872bd1608fb08b6051884 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 May 2020 16:15:43 +0200 Subject: [PATCH 070/826] Tech ENABLE_GCODE_VIEWER -> Selection curved arrows rendered using the new OpenGL model class --- src/slic3r/GUI/3DScene.cpp | 2 + src/slic3r/GUI/3DScene.hpp | 2 + src/slic3r/GUI/GLModel.cpp | 184 +++++++++++++++++++++++++++++++---- src/slic3r/GUI/GLModel.hpp | 5 + src/slic3r/GUI/Selection.cpp | 54 +++++++++- src/slic3r/GUI/Selection.hpp | 9 ++ 6 files changed, 236 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6aaf0b500e..ef5e22c823 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -2002,6 +2002,7 @@ bool GLArrow::on_init() return true; } +#if !ENABLE_GCODE_VIEWER GLCurvedArrow::GLCurvedArrow(unsigned int resolution) : GLModel() , m_resolution(resolution) @@ -2115,6 +2116,7 @@ bool GLCurvedArrow::on_init() m_volume.indexed_vertex_array.finalize_geometry(true); return true; } +#endif // !ENABLE_GCODE_VIEWER bool GLBed::on_init_from_file(const std::string& filename) { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 07c5cd53e3..d6ee72bdc1 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -648,6 +648,7 @@ protected: bool on_init() override; }; +#if !ENABLE_GCODE_VIEWER class GLCurvedArrow : public GLModel { unsigned int m_resolution; @@ -658,6 +659,7 @@ public: protected: bool on_init() override; }; +#endif // !ENABLE_GCODE_VIEWER class GLBed : public GLModel { diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 4b2ce4e9e9..6590da1409 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -11,7 +11,6 @@ namespace GUI { void GL_Model::init_from(const GLModelInitializationData& data) { - assert(!data.positions.empty() && !data.triangles.empty()); assert(data.positions.size() == data.normals.size()); @@ -134,9 +133,16 @@ void GL_Model::send_to_gpu(const std::vector& vertices, const std::vector GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) { + auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) { + data.positions.emplace_back(position); + data.normals.emplace_back(normal); + }; + + resolution = std::max(4, resolution); + GLModelInitializationData data; - float angle_step = 2.0f * M_PI / static_cast(resolution); + const float angle_step = 2.0f * M_PI / static_cast(resolution); std::vector cosines(resolution); std::vector sines(resolution); @@ -147,15 +153,13 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float sines[i] = -::sin(angle); } - float total_height = tip_height + stem_height; + const float total_height = tip_height + stem_height; // tip vertices/normals - data.positions.emplace_back(0.0f, 0.0f, total_height); - data.normals.emplace_back(Vec3f::UnitZ()); + append_vertex(data, { 0.0f, 0.0f, total_height }, Vec3f::UnitZ()); for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], stem_height); - data.normals.emplace_back(sines[i], cosines[i], 0.0f); + append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); } // tip triangles @@ -168,15 +172,13 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float // tip cap outer perimeter vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(tip_radius * sines[i], tip_radius * cosines[i], stem_height); - data.normals.emplace_back(-Vec3f::UnitZ()); + append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); } // tip cap inner perimeter vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], stem_height); - data.normals.emplace_back(-Vec3f::UnitZ()); + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); } // tip cap triangles @@ -191,15 +193,13 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float // stem bottom vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], stem_height); - data.normals.emplace_back(sines[i], cosines[i], 0.0f); + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); } // stem top vertices for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius * sines[i], stem_radius * cosines[i], 0.0f); - data.normals.emplace_back(sines[i], cosines[i], 0.0f); + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, { sines[i], cosines[i], 0.0f }); } // stem triangles @@ -212,12 +212,10 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float } // stem cap vertices - data.positions.emplace_back(0.0f, 0.0f, 0.0f); - data.normals.emplace_back(-Vec3f::UnitZ()); + append_vertex(data, Vec3f::Zero(), -Vec3f::UnitZ()); for (int i = 0; i < resolution; ++i) { - data.positions.emplace_back(stem_radius* sines[i], stem_radius* cosines[i], 0.0f); - data.normals.emplace_back(-Vec3f::UnitZ()); + append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, -Vec3f::UnitZ()); } // stem cap triangles @@ -230,5 +228,153 @@ GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float return data; } +GLModelInitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness) +{ + auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) { + data.positions.emplace_back(position); + data.normals.emplace_back(normal); + }; + + resolution = std::max(2, resolution); + + GLModelInitializationData data; + + const float half_thickness = 0.5f * thickness; + const float half_stem_width = 0.5f * stem_width; + const float half_tip_width = 0.5f * tip_width; + + const float outer_radius = radius + half_stem_width; + const float inner_radius = radius - half_stem_width; + const float step_angle = 0.5f * PI / static_cast(resolution); + + // tip + // top face vertices + append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -tip_height, radius, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitZ()); + + // top face triangles + data.triangles.emplace_back(0, 1, 2); + data.triangles.emplace_back(0, 2, 4); + data.triangles.emplace_back(4, 2, 3); + + // bottom face vertices + append_vertex(data, { 0.0f, outer_radius, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -tip_height, radius, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0f, inner_radius, -half_thickness }, -Vec3f::UnitZ()); + + // bottom face triangles + data.triangles.emplace_back(5, 7, 6); + data.triangles.emplace_back(5, 9, 7); + data.triangles.emplace_back(9, 8, 7); + + // side faces vertices + append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitY()); + append_vertex(data, { -tip_height, radius, half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitX()); + + append_vertex(data, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitY()); + append_vertex(data, { -tip_height, radius, -half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX()); + + // side faces triangles + for (int i = 0; i < 4; ++i) + { + data.triangles.emplace_back(15 + i, 11 + i, 10 + i); + data.triangles.emplace_back(15 + i, 16 + i, 11 + i); + } + + // stem + // top face vertices + for (int i = 0; i <= resolution; ++i) + { + float angle = static_cast(i) * step_angle; + append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + } + + for (int i = 0; i <= resolution; ++i) + { + float angle = static_cast(i) * step_angle; + append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + } + + // top face triangles + for (int i = 0; i < resolution; ++i) + { + data.triangles.emplace_back(20 + i, 21 + i, 21 + resolution + i); + data.triangles.emplace_back(21 + i, 22 + resolution + i, 21 + resolution + i); + } + + // bottom face vertices + for (int i = 0; i <= resolution; ++i) + { + float angle = static_cast(i) * step_angle; + append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + } + + for (int i = 0; i <= resolution; ++i) + { + float angle = static_cast(i) * step_angle; + append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + } + + // bottom face triangles + for (int i = 0; i < resolution; ++i) + { + data.triangles.emplace_back(22 + 2 * resolution + i, 23 + 3 * resolution + i, 23 + 2 * resolution + i); + data.triangles.emplace_back(23 + 2 * resolution + i, 23 + 3 * resolution + i, 24 + 3 * resolution + i); + } + + // side faces vertices + for (int i = 0; i <= resolution; ++i) + { + float angle = static_cast(i) * step_angle; + float c = ::cos(angle); + float s = ::sin(angle); + append_vertex(data, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f}); + } + + for (int i = resolution; i >= 0; --i) + { + float angle = static_cast(i) * step_angle; + float c = ::cos(angle); + float s = ::sin(angle); + append_vertex(data, { outer_radius * s, outer_radius * c, half_thickness }, { s, c, 0.0f }); + } + + for (int i = 0; i <= resolution; ++i) + { + float angle = static_cast(i) * step_angle; + float c = ::cos(angle); + float s = ::sin(angle); + append_vertex(data, { inner_radius * s, inner_radius * c, -half_thickness }, { -s, -c, 0.0f }); + } + + for (int i = resolution; i >= 0; --i) + { + float angle = static_cast(i) * step_angle; + float c = ::cos(angle); + float s = ::sin(angle); + append_vertex(data, { outer_radius * s, outer_radius * c, -half_thickness }, { s, c, 0.0f }); + } + + // side faces triangles + for (int i = 0; i < 2 * resolution + 1; ++i) + { + data.triangles.emplace_back(20 + 6 * (resolution + 1) + i, 21 + 6 * (resolution + 1) + i, 21 + 4 * (resolution + 1) + i); + data.triangles.emplace_back(20 + 6 * (resolution + 1) + i, 21 + 4 * (resolution + 1) + i, 20 + 4 * (resolution + 1) + i); + } + + return data; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index 7b135ada6b..c55931ef10 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -47,6 +47,11 @@ namespace GUI { GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); + // create an arrow whose stem is a quarter of circle, with the given dimensions and resolution + // the origin of the arrow is in the center of the circle + // the arrow is contained in the 1st quadrant and is pointing counterclockwise + GLModelInitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 87f8b60ede..62a046a251 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -13,6 +13,9 @@ #include #include +#if ENABLE_GCODE_VIEWER +#include +#endif // ENABLE_GCODE_VIEWER static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f }; @@ -76,7 +79,9 @@ Selection::Selection() , m_mode(Instance) , m_type(Empty) , m_valid(false) +#if !ENABLE_GCODE_VIEWER , m_curved_arrow(16) +#endif // !ENABLE_GCODE_VIEWER , m_scale_factor(1.0f) { this->set_bounding_boxes_dirty(); @@ -109,10 +114,21 @@ bool Selection::init() m_arrow.set_scale(5.0 * Vec3d::Ones()); +#if ENABLE_GCODE_VIEWER + m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); + + if (!m_arrows_shader.init("gouraud_light.vs", "gouraud_light.fs")) + { + BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; + return false; + } +#else if (!m_curved_arrow.init()) return false; m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); +#endif //ENABLE_GCODE_VIEWER + return true; } @@ -1927,6 +1943,40 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const { +#if ENABLE_GCODE_VIEWER + if (!m_arrows_shader.is_initialized()) + return; + + m_arrows_shader.start_using(); + GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); + + if (boost::ends_with(sidebar_field, "x")) + { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); + + glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); + render_sidebar_rotation_hint(X); + } + else if (boost::ends_with(sidebar_field, "y")) + { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); + + glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); + render_sidebar_rotation_hint(Y); + } + else if (boost::ends_with(sidebar_field, "z")) + { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); + + render_sidebar_rotation_hint(Z); + } + + m_arrows_shader.stop_using(); + +#else if (boost::ends_with(sidebar_field, "x")) { glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); @@ -1939,6 +1989,7 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) } else if (boost::ends_with(sidebar_field, "z")) render_sidebar_rotation_hint(Z); +#endif // ENABLE_GCODE_VIEWER } void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const @@ -2054,9 +2105,10 @@ void Selection::render_sidebar_position_hint(Axis axis) const void Selection::render_sidebar_rotation_hint(Axis axis) const { +#if !ENABLE_GCODE_VIEWER m_curved_arrow.set_color(AXES_COLOR[axis], 3); +#endif // !ENABLE_GCODE_VIEWER m_curved_arrow.render(); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); m_curved_arrow.render(); } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c27b4cc29d..321cb70e04 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -4,6 +4,10 @@ #include #include "libslic3r/Geometry.hpp" #include "3DScene.hpp" +#if ENABLE_GCODE_VIEWER +#include "GLModel.hpp" +#include "GLShader.hpp" +#endif // ENABLE_GCODE_VIEWER #if ENABLE_RENDER_SELECTION_CENTER class GLUquadric; @@ -201,7 +205,12 @@ private: GLUquadricObj* m_quadric; #endif // ENABLE_RENDER_SELECTION_CENTER mutable GLArrow m_arrow; +#if ENABLE_GCODE_VIEWER + GL_Model m_curved_arrow; + Shader m_arrows_shader; +#else mutable GLCurvedArrow m_curved_arrow; +#endif // ENABLE_GCODE_VIEWER mutable float m_scale_factor; From b59fc1e57d7e6f66cb580196c984f5d18cec7027 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 May 2020 09:07:06 +0200 Subject: [PATCH 071/826] Tech ENABLE_GCODE_VIEWER -> Selection straight arrows rendered using the new OpenGL model class --- src/slic3r/GUI/GLCanvas3D.cpp | 4 ++ src/slic3r/GUI/GLModel.cpp | 97 ++++++++++++++++++++++++++++++++++ src/slic3r/GUI/GLModel.hpp | 7 ++- src/slic3r/GUI/Selection.cpp | 98 ++++++++++++++++++++++++++++++----- src/slic3r/GUI/Selection.hpp | 11 +++- 5 files changed, 203 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ba8d3b1ba6..1cbfac4e48 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5976,7 +5976,11 @@ void GLCanvas3D::_render_sla_slices() const void GLCanvas3D::_render_selection_sidebar_hints() const { +#if ENABLE_GCODE_VIEWER + m_selection.render_sidebar_hints(m_sidebar_field); +#else m_selection.render_sidebar_hints(m_sidebar_field, m_shader); +#endif // ENABLE_GCODE_VIEWER } void GLCanvas3D::_update_volumes_hover_state() const diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 6590da1409..ee98ce678a 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -376,5 +376,102 @@ GLModelInitializationData circular_arrow(int resolution, float radius, float tip return data; } +GLModelInitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness) +{ + auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) { + data.positions.emplace_back(position); + data.normals.emplace_back(normal); + }; + + GLModelInitializationData data; + + const float half_thickness = 0.5f * thickness; + const float half_stem_width = 0.5f * stem_width; + const float half_tip_width = 0.5f * tip_width; + const float total_height = tip_height + stem_height; + + // top face vertices + append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { 0.0, total_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); + + // top face triangles + data.triangles.emplace_back(0, 1, 6); + data.triangles.emplace_back(6, 1, 5); + data.triangles.emplace_back(4, 5, 3); + data.triangles.emplace_back(5, 1, 3); + data.triangles.emplace_back(1, 2, 3); + + // bottom face vertices + append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { 0.0, total_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); + append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); + + // bottom face triangles + data.triangles.emplace_back(7, 13, 8); + data.triangles.emplace_back(13, 12, 8); + data.triangles.emplace_back(12, 11, 10); + data.triangles.emplace_back(8, 12, 10); + data.triangles.emplace_back(9, 8, 10); + + // side faces vertices + append_vertex(data, { half_stem_width, 0.0, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { half_stem_width, stem_height, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitX()); + append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitX()); + + append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); + + Vec3f normal(tip_height, half_tip_width, 0.0f); + normal.normalize(); + append_vertex(data, { half_tip_width, stem_height, -half_thickness }, normal); + append_vertex(data, { 0.0, total_height, -half_thickness }, normal); + append_vertex(data, { half_tip_width, stem_height, half_thickness }, normal); + append_vertex(data, { 0.0, total_height, half_thickness }, normal); + + normal = Vec3f(-tip_height, half_tip_width, 0.0f); + normal.normalize(); + append_vertex(data, { 0.0, total_height, -half_thickness }, normal); + append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, normal); + append_vertex(data, { 0.0, total_height, half_thickness }, normal); + append_vertex(data, { -half_tip_width, stem_height, half_thickness }, normal); + + append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); + + append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitX()); + append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitX()); + + append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); + + // side face triangles + for (int i = 0; i < 7; ++i) + { + int ii = i * 4; + data.triangles.emplace_back(14 + ii, 15 + ii, 17 + ii); + data.triangles.emplace_back(14 + ii, 17 + ii, 16 + ii); + } + + return data; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index c55931ef10..a11073b191 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -49,9 +49,14 @@ namespace GUI { // create an arrow whose stem is a quarter of circle, with the given dimensions and resolution // the origin of the arrow is in the center of the circle - // the arrow is contained in the 1st quadrant and is pointing counterclockwise + // the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise GLModelInitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); + // create an arrow with the given dimensions + // the origin of the arrow is in the center of the stem cap + // the arrow is contained in XY plane and has its main axis along the Y axis + GLModelInitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 62a046a251..3bab06b4d5 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -109,12 +109,8 @@ void Selection::set_volumes(GLVolumePtrs* volumes) // Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! bool Selection::init() { - if (!m_arrow.init()) - return false; - - m_arrow.set_scale(5.0 * Vec3d::Ones()); - #if ENABLE_GCODE_VIEWER + m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); if (!m_arrows_shader.init("gouraud_light.vs", "gouraud_light.fs")) @@ -123,6 +119,11 @@ bool Selection::init() return false; } #else + if (!m_arrow.init()) + return false; + + m_arrow.set_scale(5.0 * Vec3d::Ones()); + if (!m_curved_arrow.init()) return false; @@ -1243,16 +1244,28 @@ void Selection::render_center(bool gizmo_is_dragging) const } #endif // ENABLE_RENDER_SELECTION_CENTER +#if ENABLE_GCODE_VIEWER +void Selection::render_sidebar_hints(const std::string& sidebar_field) const +#else void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const +#endif // ENABLE_GCODE_VIEWER { if (sidebar_field.empty()) return; if (!boost::starts_with(sidebar_field, "layer")) { +#if ENABLE_GCODE_VIEWER + if (!m_arrows_shader.is_initialized()) + return; + + m_arrows_shader.start_using(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); +#else shader.start_using(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_LIGHTING)); +#endif // ENABLE_GCODE_VIEWER } glsafe(::glEnable(GL_DEPTH_TEST)); @@ -1323,8 +1336,12 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha if (!boost::starts_with(sidebar_field, "layer")) { +#if ENABLE_GCODE_VIEWER + m_arrows_shader.stop_using(); +#else glsafe(::glDisable(GL_LIGHTING)); shader.stop_using(); +#endif // ENABLE_GCODE_VIEWER } } @@ -1927,6 +1944,33 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const { +#if ENABLE_GCODE_VIEWER + GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); + + if (boost::ends_with(sidebar_field, "x")) + { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); + + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "y")) + { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); + + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "z")) + { + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); + + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); + m_arrow.render(); + } +#else if (boost::ends_with(sidebar_field, "x")) { glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); @@ -1939,15 +1983,12 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); render_sidebar_position_hint(Z); } +#endif // ENABLE_GCODE_VIEWER } void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const { #if ENABLE_GCODE_VIEWER - if (!m_arrows_shader.is_initialized()) - return; - - m_arrows_shader.start_using(); GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); if (boost::ends_with(sidebar_field, "x")) @@ -1973,9 +2014,6 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) render_sidebar_rotation_hint(Z); } - - m_arrows_shader.stop_using(); - #else if (boost::ends_with(sidebar_field, "x")) { @@ -1996,6 +2034,7 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con { bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); +#if ENABLE_GCODE_VIEWER if (boost::ends_with(sidebar_field, "x") || uniform_scale) { glsafe(::glPushMatrix()); @@ -2018,6 +2057,30 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con render_sidebar_scale_hint(Z); glsafe(::glPopMatrix()); } +#else + if (boost::ends_with(sidebar_field, "x") || uniform_scale) + { + glsafe(::glPushMatrix()); + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); + render_sidebar_scale_hint(X); + glsafe(::glPopMatrix()); + } + + if (boost::ends_with(sidebar_field, "y") || uniform_scale) + { + glsafe(::glPushMatrix()); + render_sidebar_scale_hint(Y); + glsafe(::glPopMatrix()); + } + + if (boost::ends_with(sidebar_field, "z") || uniform_scale) + { + glsafe(::glPushMatrix()); + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); + render_sidebar_scale_hint(Z); + glsafe(::glPopMatrix()); + } +#endif // ENABLE_GCODE_VIEWER } void Selection::render_sidebar_size_hints(const std::string& sidebar_field) const @@ -2097,11 +2160,13 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co glsafe(::glDisable(GL_BLEND)); } +#if !ENABLE_GCODE_VIEWER void Selection::render_sidebar_position_hint(Axis axis) const { m_arrow.set_color(AXES_COLOR[axis], 3); m_arrow.render(); } +#endif // !ENABLE_GCODE_VIEWER void Selection::render_sidebar_rotation_hint(Axis axis) const { @@ -2115,7 +2180,14 @@ void Selection::render_sidebar_rotation_hint(Axis axis) const void Selection::render_sidebar_scale_hint(Axis axis) const { +#if ENABLE_GCODE_VIEWER + GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); + + if (color_id >= 0) + glsafe(::glUniform4fv(color_id, 1, (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? (const GLfloat*)UNIFORM_SCALE_COLOR : (const GLfloat*)AXES_COLOR[axis])); +#else m_arrow.set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); +#endif // ENABLE_GCODE_VIEWER glsafe(::glTranslated(0.0, 5.0, 0.0)); m_arrow.render(); @@ -2125,9 +2197,11 @@ void Selection::render_sidebar_scale_hint(Axis axis) const m_arrow.render(); } +#if !ENABLE_GCODE_VIEWER void Selection::render_sidebar_size_hint(Axis axis, double length) const { } +#endif // !ENABLE_GCODE_VIEWER #ifndef NDEBUG static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 321cb70e04..53caf11060 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -204,11 +204,12 @@ private: #if ENABLE_RENDER_SELECTION_CENTER GLUquadricObj* m_quadric; #endif // ENABLE_RENDER_SELECTION_CENTER - mutable GLArrow m_arrow; #if ENABLE_GCODE_VIEWER + GL_Model m_arrow; GL_Model m_curved_arrow; Shader m_arrows_shader; #else + mutable GLArrow m_arrow; mutable GLCurvedArrow m_curved_arrow; #endif // ENABLE_GCODE_VIEWER @@ -325,7 +326,11 @@ public: #if ENABLE_RENDER_SELECTION_CENTER void render_center(bool gizmo_is_dragging) const; #endif // ENABLE_RENDER_SELECTION_CENTER +#if ENABLE_GCODE_VIEWER + void render_sidebar_hints(const std::string& sidebar_field) const; +#else void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const; +#endif // ENABLE_GCODE_VIEWER bool requires_local_axes() const; @@ -368,10 +373,14 @@ private: void render_sidebar_scale_hints(const std::string& sidebar_field) const; void render_sidebar_size_hints(const std::string& sidebar_field) const; void render_sidebar_layers_hints(const std::string& sidebar_field) const; +#if !ENABLE_GCODE_VIEWER void render_sidebar_position_hint(Axis axis) const; +#endif // !ENABLE_GCODE_VIEWER void render_sidebar_rotation_hint(Axis axis) const; void render_sidebar_scale_hint(Axis axis) const; +#if !ENABLE_GCODE_VIEWER void render_sidebar_size_hint(Axis axis, double length) const; +#endif // !ENABLE_GCODE_VIEWER public: enum SyncRotationType { From 800a6c5e574ca9b35b4a1128860cb9b12d38ac13 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 May 2020 11:48:29 +0200 Subject: [PATCH 072/826] Tech ENABLE_GCODE_VIEWER -> Fixed normals in curved arrows model --- src/slic3r/GUI/GLModel.cpp | 105 ++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index ee98ce678a..e5b6cbdcb6 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -273,23 +273,36 @@ GLModelInitializationData circular_arrow(int resolution, float radius, float tip data.triangles.emplace_back(9, 8, 7); // side faces vertices + append_vertex(data, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitX()); append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitX()); - append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitY()); - append_vertex(data, { -tip_height, radius, half_thickness }, -Vec3f::UnitX()); - append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitX()); + + Vec3f normal(-half_tip_width, tip_height, 0.0f); + normal.normalize(); + append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, normal); + append_vertex(data, { -tip_height, radius, -half_thickness }, normal); + append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, normal); + append_vertex(data, { -tip_height, radius, half_thickness }, normal); + + normal = Vec3f(-half_tip_width, -tip_height, 0.0f); + normal.normalize(); + append_vertex(data, { -tip_height, radius, -half_thickness }, normal); + append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, normal); + append_vertex(data, { -tip_height, radius, half_thickness }, normal); + append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, normal); + + append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX()); + append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitX()); append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitX()); - append_vertex(data, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX()); - append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitY()); - append_vertex(data, { -tip_height, radius, -half_thickness }, -Vec3f::UnitX()); - append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitY()); - append_vertex(data, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX()); - - // side faces triangles + // side face triangles for (int i = 0; i < 4; ++i) { - data.triangles.emplace_back(15 + i, 11 + i, 10 + i); - data.triangles.emplace_back(15 + i, 16 + i, 11 + i); + int ii = i * 4; + data.triangles.emplace_back(10 + ii, 11 + ii, 13 + ii); + data.triangles.emplace_back(10 + ii, 13 + ii, 12 + ii); } // stem @@ -309,8 +322,8 @@ GLModelInitializationData circular_arrow(int resolution, float radius, float tip // top face triangles for (int i = 0; i < resolution; ++i) { - data.triangles.emplace_back(20 + i, 21 + i, 21 + resolution + i); - data.triangles.emplace_back(21 + i, 22 + resolution + i, 21 + resolution + i); + data.triangles.emplace_back(26 + i, 27 + i, 27 + resolution + i); + data.triangles.emplace_back(27 + i, 28 + resolution + i, 27 + resolution + i); } // bottom face vertices @@ -329,27 +342,11 @@ GLModelInitializationData circular_arrow(int resolution, float radius, float tip // bottom face triangles for (int i = 0; i < resolution; ++i) { - data.triangles.emplace_back(22 + 2 * resolution + i, 23 + 3 * resolution + i, 23 + 2 * resolution + i); - data.triangles.emplace_back(23 + 2 * resolution + i, 23 + 3 * resolution + i, 24 + 3 * resolution + i); - } - - // side faces vertices - for (int i = 0; i <= resolution; ++i) - { - float angle = static_cast(i) * step_angle; - float c = ::cos(angle); - float s = ::sin(angle); - append_vertex(data, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f}); - } - - for (int i = resolution; i >= 0; --i) - { - float angle = static_cast(i) * step_angle; - float c = ::cos(angle); - float s = ::sin(angle); - append_vertex(data, { outer_radius * s, outer_radius * c, half_thickness }, { s, c, 0.0f }); + data.triangles.emplace_back(28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i); + data.triangles.emplace_back(29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i); } + // side faces vertices and triangles for (int i = 0; i <= resolution; ++i) { float angle = static_cast(i) * step_angle; @@ -358,6 +355,31 @@ GLModelInitializationData circular_arrow(int resolution, float radius, float tip append_vertex(data, { inner_radius * s, inner_radius * c, -half_thickness }, { -s, -c, 0.0f }); } + for (int i = 0; i <= resolution; ++i) + { + float angle = static_cast(i) * step_angle; + float c = ::cos(angle); + float s = ::sin(angle); + append_vertex(data, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f }); + } + + int first_id = 26 + 4 * (resolution + 1); + for (int i = 0; i < resolution; ++i) + { + int ii = first_id + i; + data.triangles.emplace_back(ii, ii + 1, ii + resolution + 2); + data.triangles.emplace_back(ii, ii + resolution + 2, ii + resolution + 1); + } + + append_vertex(data, { inner_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { outer_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { inner_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); + append_vertex(data, { outer_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); + + first_id = 26 + 6 * (resolution + 1); + data.triangles.emplace_back(first_id, first_id + 1, first_id + 3); + data.triangles.emplace_back(first_id, first_id + 3, first_id + 2); + for (int i = resolution; i >= 0; --i) { float angle = static_cast(i) * step_angle; @@ -366,11 +388,20 @@ GLModelInitializationData circular_arrow(int resolution, float radius, float tip append_vertex(data, { outer_radius * s, outer_radius * c, -half_thickness }, { s, c, 0.0f }); } - // side faces triangles - for (int i = 0; i < 2 * resolution + 1; ++i) + for (int i = resolution; i >= 0; --i) { - data.triangles.emplace_back(20 + 6 * (resolution + 1) + i, 21 + 6 * (resolution + 1) + i, 21 + 4 * (resolution + 1) + i); - data.triangles.emplace_back(20 + 6 * (resolution + 1) + i, 21 + 4 * (resolution + 1) + i, 20 + 4 * (resolution + 1) + i); + float angle = static_cast(i) * step_angle; + float c = ::cos(angle); + float s = ::sin(angle); + append_vertex(data, { outer_radius * s, outer_radius * c, +half_thickness }, { s, c, 0.0f }); + } + + first_id = 30 + 6 * (resolution + 1); + for (int i = 0; i < resolution; ++i) + { + int ii = first_id + i; + data.triangles.emplace_back(ii, ii + 1, ii + resolution + 2); + data.triangles.emplace_back(ii, ii + resolution + 2, ii + resolution + 1); } return data; From cd2e4002ed2b432ecd898db99d3306aa319bd94f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 May 2020 11:59:48 +0200 Subject: [PATCH 073/826] Tech ENABLE_GCODE_VIEWER -> Removed obsolete class GLArrow --- src/slic3r/GUI/3DScene.cpp | 7 ++++++- src/slic3r/GUI/3DScene.hpp | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ef5e22c823..dc5376553f 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1944,6 +1944,9 @@ void GLModel::render() const glsafe(::glDisable(GL_BLEND)); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GLArrow::on_init() { Pointf3s vertices; @@ -2002,7 +2005,9 @@ bool GLArrow::on_init() return true; } -#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLCurvedArrow::GLCurvedArrow(unsigned int resolution) : GLModel() , m_resolution(resolution) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index d6ee72bdc1..d357fba6cf 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -642,13 +642,18 @@ protected: virtual bool on_init_from_file(const std::string& filename) { return false; } }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class GLArrow : public GLModel { protected: bool on_init() override; }; -#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class GLCurvedArrow : public GLModel { unsigned int m_resolution; From 32529b66ac1945e406f4b7f4b795ee388a418e75 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 May 2020 13:55:00 +0200 Subject: [PATCH 074/826] Tech ENABLE_GCODE_VIEWER -> Small refactoring --- src/slic3r/GUI/3DScene.cpp | 5 ---- src/slic3r/GUI/3DScene.hpp | 5 ---- src/slic3r/GUI/GCodeViewer.cpp | 42 +++++++++++++++++----------------- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index dc5376553f..29a4b84a30 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1944,9 +1944,7 @@ void GLModel::render() const glsafe(::glDisable(GL_BLEND)); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GLArrow::on_init() { Pointf3s vertices; @@ -2005,9 +2003,6 @@ bool GLArrow::on_init() return true; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLCurvedArrow::GLCurvedArrow(unsigned int resolution) : GLModel() , m_resolution(resolution) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index d357fba6cf..86072754d7 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -642,18 +642,13 @@ protected: virtual bool on_init_from_file(const std::string& filename) { return false; } }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class GLArrow : public GLModel { protected: bool on_init() override; }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class GLCurvedArrow : public GLModel { unsigned int m_resolution; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 525a2fd5a1..aa188cae3f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -978,14 +978,14 @@ void GCodeViewer::render_legend() const ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); switch (m_view_type) { - case EViewType::FeatureType: { imgui.text(I18N::translate_utf8(L("Feature type"))); break; } - case EViewType::Height: { imgui.text(I18N::translate_utf8(L("Height (mm)"))); break; } - case EViewType::Width: { imgui.text(I18N::translate_utf8(L("Width (mm)"))); break; } - case EViewType::Feedrate: { imgui.text(I18N::translate_utf8(L("Speed (mm/s)"))); break; } - case EViewType::FanSpeed: { imgui.text(I18N::translate_utf8(L("Fan Speed (%%)"))); break; } - case EViewType::VolumetricRate: { imgui.text(I18N::translate_utf8(L("Volumetric flow rate (mm³/s)"))); break; } - case EViewType::Tool: { imgui.text(I18N::translate_utf8(L("Tool"))); break; } - case EViewType::ColorPrint: { imgui.text(I18N::translate_utf8(L("Color Print"))); break; } + case EViewType::FeatureType: { imgui.text(_u8L("Feature type")); break; } + case EViewType::Height: { imgui.text(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.text(_u8L("Width (mm)")); break; } + case EViewType::Feedrate: { imgui.text(_u8L("Speed (mm/s)")); break; } + case EViewType::FanSpeed: { imgui.text(_u8L("Fan Speed (%%)")); break; } + case EViewType::VolumetricRate: { imgui.text(_u8L("Volumetric flow rate (mm³/s)")); break; } + case EViewType::Tool: { imgui.text(_u8L("Tool")); break; } + case EViewType::ColorPrint: { imgui.text(_u8L("Color Print")); break; } default: { break; } } ImGui::PopStyleColor(); @@ -1001,7 +1001,7 @@ void GCodeViewer::render_legend() const if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - add_item(Extrusion_Role_Colors[static_cast(role)], I18N::translate_utf8(ExtrusionEntity::role_to_string(role)), [this, role]() { + add_item(Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { if (role < erCount) { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); @@ -1031,7 +1031,7 @@ void GCodeViewer::render_legend() const if (it == m_extruder_ids.end()) continue; - add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("Extruder %d"))) % (i + 1)).str()); + add_item(m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); } break; } @@ -1042,7 +1042,7 @@ void GCodeViewer::render_legend() const if (extruders_count == 1) { // single extruder use case if (custom_gcode_per_print_z.empty()) // no data to show - add_item(m_tool_colors.front(), I18N::translate_utf8(L("Default print color"))); + add_item(m_tool_colors.front(), _u8L("Default print color")); else { std::vector> cp_values; cp_values.reserve(custom_gcode_per_print_z.size()); @@ -1066,7 +1066,7 @@ void GCodeViewer::render_legend() const const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There is no one color change, but there are some pause print or custom Gcode - add_item(m_tool_colors.front(), I18N::translate_utf8(L("Default print color"))); + add_item(m_tool_colors.front(), _u8L("Default print color")); } else { for (int i = items_cnt; i >= 0; --i) { @@ -1074,14 +1074,14 @@ void GCodeViewer::render_legend() const std::string id_str = " (" + std::to_string(i + 1) + ")"; if (i == 0) { - add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("up to %.2f mm"))) % cp_values.front().first).str() + id_str); + add_item(m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str() + id_str); break; } else if (i == items_cnt) { - add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("above %.2f mm"))) % cp_values[i - 1].second).str() + id_str); + add_item(m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str() + id_str); continue; } - add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("%.2f - %.2f mm"))) % cp_values[i - 1].second % cp_values[i].first).str() + id_str); + add_item(m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second% cp_values[i].first).str() + id_str); } } } @@ -1090,7 +1090,7 @@ void GCodeViewer::render_legend() const { // extruders for (unsigned int i = 0; i < (unsigned int)extruders_count; ++i) { - add_item(m_tool_colors[i], (boost::format(I18N::translate_utf8(L("Extruder %d"))) % (i + 1)).str()); + add_item(m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); } // color changes @@ -1102,7 +1102,7 @@ void GCodeViewer::render_legend() const std::string id_str = " (" + std::to_string(color_change_idx--) + ")"; add_item(m_tool_colors[last_color_id--], - (boost::format(I18N::translate_utf8(L("Color change for Extruder %d at %.2f mm"))) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str() + id_str); + (boost::format(_u8L("Color change for Extruder %d at %.2f mm")) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str() + id_str); } } } @@ -1129,14 +1129,14 @@ void GCodeViewer::render_legend() const ImGui::Spacing(); ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(I18N::translate_utf8(L("Travel"))); + imgui.text(_u8L("Travel")); ImGui::PopStyleColor(); ImGui::Separator(); // items - add_item(Travel_Colors[0], I18N::translate_utf8(L("Movement"))); - add_item(Travel_Colors[1], I18N::translate_utf8(L("Extrusion"))); - add_item(Travel_Colors[2], I18N::translate_utf8(L("Retraction"))); + add_item(Travel_Colors[0], _u8L("Movement")); + add_item(Travel_Colors[1], _u8L("Extrusion")); + add_item(Travel_Colors[2], _u8L("Retraction")); break; } From 5be901547e1c9cd641681f3c40c8aeabdbbe7332 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 15 May 2020 09:22:51 +0200 Subject: [PATCH 075/826] GCodeViewer -> Imgui slider for sequential view replaced by DoubleSlider::Control (wip) --- src/libslic3r/Print.hpp | 2 + src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/BackgroundSlicingProcess.hpp | 2 + src/slic3r/GUI/DoubleSlider.cpp | 76 ++++-- src/slic3r/GUI/DoubleSlider.hpp | 8 +- src/slic3r/GUI/GCodeViewer.cpp | 14 + src/slic3r/GUI/GCodeViewer.hpp | 49 ++++ src/slic3r/GUI/GLCanvas3D.hpp | 6 + src/slic3r/GUI/GUI_Preview.cpp | 285 ++++++++++++++++++-- src/slic3r/GUI/GUI_Preview.hpp | 48 +++- src/slic3r/GUI/Plater.cpp | 21 ++ src/slic3r/GUI/Plater.hpp | 3 + 12 files changed, 463 insertions(+), 52 deletions(-) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c358cd9184..36ba68c45e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -24,7 +24,9 @@ class Print; class PrintObject; class ModelObject; class GCode; +#if !ENABLE_GCODE_VIEWER class GCodePreviewData; +#endif // !ENABLE_GCODE_VIEWER enum class SlicingMode : uint32_t; // Print step IDs for keeping track of the print state. diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c22e504dff..8ce01a3530 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -55,6 +55,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) +#define ENABLE_GCODE_USE_WXWIDGETS_SLIDER (1 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 91ebc1372c..22a99951ff 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -17,7 +17,9 @@ namespace Slic3r { class DynamicPrintConfig; +#if !ENABLE_GCODE_VIEWER class GCodePreviewData; +#endif // !ENABLE_GCODE_VIEWER class Model; class SLAPrint; class SL1Archive; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 4732ff2613..021d73882e 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -20,7 +20,9 @@ #include #include #include +#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #include +#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #include #include @@ -408,22 +410,22 @@ void Control::render() // draw line draw_scroll_line(dc, lower_pos, higher_pos); - //draw color print ticks + // draw color print ticks draw_ticks(dc); // draw both sliders draw_thumbs(dc, lower_pos, higher_pos); - //draw lock/unlock + // draw lock/unlock draw_one_layer_icon(dc); - //draw revert bitmap (if it's shown) + // draw revert bitmap (if it's shown) draw_revert_icon(dc); - //draw cog bitmap (if it's shown) + // draw cog bitmap (if it's shown) draw_cog_icon(dc); - //draw mouse position + // draw mouse position draw_tick_on_mouse_position(dc); } @@ -535,10 +537,21 @@ wxString Control::get_label(int tick) const if (value >= m_values.size()) return "ErrVal"; - const wxString str = m_values.empty() ? - wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) : - wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None); - return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value+1); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (m_draw_mode == dmSequentialGCodeView) + return wxString::Format("%d", static_cast(m_values[value])); + else { + const wxString str = m_values.empty() ? + wxString::Format("%.*f", 2, m_label_koef * value) : + wxString::Format("%.*f", 2, m_values[value]); + return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1); + } +#else + const wxString str = m_values.empty() ? + wxNumberFormatter::ToString(m_label_koef * value, 2, wxNumberFormatter::Style_None) : + wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None); + return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER } void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_side/*=true*/) const @@ -779,6 +792,11 @@ void Control::draw_colored_band(wxDC& dc) void Control::draw_one_layer_icon(wxDC& dc) { +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (m_draw_mode == dmSequentialGCodeView) + return; +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + const wxBitmap& icon = m_is_one_layer ? m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() : m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp(); @@ -1284,7 +1302,11 @@ void Control::OnWheel(wxMouseEvent& event) ssLower : ssHigher; } +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + move_current_thumb((m_draw_mode == dmSequentialGCodeView) ? event.GetWheelRotation() < 0 : event.GetWheelRotation() > 0); +#else move_current_thumb(event.GetWheelRotation() > 0); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER } void Control::OnKeyDown(wxKeyEvent &event) @@ -1306,20 +1328,34 @@ void Control::OnKeyDown(wxKeyEvent &event) UseDefaultColors(false); else if (is_horizontal()) { - if (key == WXK_LEFT || key == WXK_RIGHT) - move_current_thumb(key == WXK_LEFT); - else if (key == WXK_UP || key == WXK_DOWN) { - m_selection = key == WXK_UP ? ssHigher : ssLower; - Refresh(); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (m_is_focused) + { +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (key == WXK_LEFT || key == WXK_RIGHT) + move_current_thumb(key == WXK_LEFT); + else if (key == WXK_UP || key == WXK_DOWN) { + m_selection = key == WXK_UP ? ssHigher : ssLower; + Refresh(); + } +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + } +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER } - } else { - if (key == WXK_LEFT || key == WXK_RIGHT) { - m_selection = key == WXK_LEFT ? ssHigher : ssLower; - Refresh(); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (m_is_focused) + { +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (key == WXK_LEFT || key == WXK_RIGHT) { + m_selection = key == WXK_LEFT ? ssHigher : ssLower; + Refresh(); + } + else if (key == WXK_UP || key == WXK_DOWN) + move_current_thumb(key == WXK_UP); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER } - else if (key == WXK_UP || key == WXK_DOWN) - move_current_thumb(key == WXK_UP); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER } event.Skip(); // !Needed to have EVT_CHAR generated as well diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 36bff17e94..a17fb2b6fa 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -73,6 +73,9 @@ enum DrawMode dmRegular, dmSlaPrint, dmSequentialFffPrint, +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + dmSequentialGCodeView, +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER }; using t_mode = CustomGCode::Mode; @@ -211,6 +214,9 @@ public: void SetTicksValues(const Slic3r::CustomGCode::Info &custom_gcode_per_print_z); void SetDrawMode(bool is_sla_print, bool is_sequential_print); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void SetDrawMode(DrawMode mode) { m_draw_mode = mode; } +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER void SetManipulationMode(t_mode mode) { m_mode = mode; } t_mode GetManipulationMode() const { return m_mode; } @@ -223,7 +229,7 @@ public: bool is_higher_at_max() const { return m_higher_value == m_max_value; } bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); } - void OnPaint(wxPaintEvent& ) { render();} + void OnPaint(wxPaintEvent& ) { render(); } void OnLeftDown(wxMouseEvent& event); void OnMotion(wxMouseEvent& event); void OnLeftUp(wxMouseEvent& event); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index aa188cae3f..acf1da4c71 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -324,7 +324,9 @@ void GCodeViewer::render() const m_sequential_view.marker.render(); render_shells(); render_legend(); +#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER render_sequential_bar(); +#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -381,6 +383,16 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) enable_legend(is_flag_set(9)); } +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) +{ + bool keep_sequential_current = layers_z_range[1] <= m_layers_z_range[1]; + m_layers_z_range = layers_z_range; + refresh_render_paths(keep_sequential_current); + wxGetApp().plater()->update_preview_horz_slider(); +} +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + bool GCodeViewer::init_shaders() { unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); @@ -1147,6 +1159,7 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } +#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER void GCodeViewer::render_sequential_bar() const { static const float MARGIN = 125.0f; @@ -1231,6 +1244,7 @@ void GCodeViewer::render_sequential_bar() const imgui.end(); ImGui::PopStyleVar(); } +#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 6f3ca47dba..a4ac3358d2 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -149,6 +149,7 @@ class GCodeViewer void reset_ranges() { ranges.reset(); } }; +#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER struct SequentialView { class Marker @@ -182,6 +183,7 @@ class GCodeViewer Vec3f current_position{ Vec3f::Zero() }; Marker marker; }; +#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics @@ -229,6 +231,42 @@ class GCodeViewer #endif // ENABLE_GCODE_VIEWER_STATISTICS public: +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + struct SequentialView + { + class Marker + { + GL_Model m_model; + Transform3f m_world_transform; + std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; + bool m_visible{ false }; + Shader m_shader; + + public: + void init(); + + const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } + + void set_world_transform(const Transform3f& transform) { m_world_transform = transform; } + void set_color(const std::array& color) { m_color = color; } + + bool is_visible() const { return m_visible; } + void set_visible(bool visible) { m_visible = visible; } + + void render() const; + + private: + void init_shader(); + }; + + unsigned int first{ 0 }; + unsigned int last{ 0 }; + unsigned int current{ 0 }; + Vec3f current_position{ Vec3f::Zero() }; + Marker marker; + }; +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + enum class EViewType : unsigned char { FeatureType, @@ -284,6 +322,11 @@ public: const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::vector& get_layers_zs() const { return m_layers_zs; }; +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + const SequentialView& get_sequential_view() const { return m_sequential_view; } + void update_sequential_view_current(unsigned int low, unsigned int high) { m_sequential_view.current = high; refresh_render_paths(true); } +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + EViewType get_view_type() const { return m_view_type; } void set_view_type(EViewType type) { if (type == EViewType::Count) @@ -298,12 +341,16 @@ public: void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } unsigned int get_options_visibility_flags() const; void set_options_visibility_from_flags(unsigned int flags); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void set_layers_z_range(const std::array& layers_z_range); +#else void set_layers_z_range(const std::array& layers_z_range) { bool keep_sequential_current = layers_z_range[1] <= m_layers_z_range[1]; m_layers_z_range = layers_z_range; refresh_render_paths(keep_sequential_current); } +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } @@ -316,7 +363,9 @@ private: void render_toolpaths() const; void render_shells() const; void render_legend() const; +#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER void render_sequential_bar() const; +#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 3e7296e139..d4df60ad2a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -42,7 +42,9 @@ namespace Slic3r { class Bed3D; struct Camera; class BackgroundSlicingProcess; +#if !ENABLE_GCODE_VIEWER class GCodePreviewData; +#endif // !ENABLE_GCODE_VIEWER struct ThumbnailData; struct SlicingParameters; enum LayerHeightEditActionType : unsigned int; @@ -551,6 +553,10 @@ public: #if ENABLE_GCODE_VIEWER void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } + void update_gcode_sequential_view_current(unsigned int low, unsigned int high) { m_gcode_viewer.update_sequential_view_current(low, high); } +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index fc97796749..9547e0bf89 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -210,7 +210,9 @@ Preview::Preview( , m_number_extruders(1) , m_preferred_color_mode("feature") , m_loaded(false) +#if !ENABLE_GCODE_VIEWER , m_enabled(false) +#endif // !ENABLE_GCODE_VIEWER , m_schedule_background_process(schedule_background_process_func) #ifdef __linux__ , m_volumes_cleanup_required(false) @@ -245,8 +247,12 @@ bool Preview::init(wxWindow* parent, Model* model) m_canvas->enable_dynamic_background(true); m_canvas->enable_collapse_toolbar(true); +#if ENABLE_GCODE_VIEWER + m_double_slider_sizer = create_vert_slider_sizer(); +#else m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); create_double_slider(); +#endif // ENABLE_GCODE_VIEWER m_label_view_type = new wxStaticText(this, wxID_ANY, _L("View")); @@ -315,15 +321,25 @@ bool Preview::init(wxWindow* parent, Model* model) top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + m_horz_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + m_horz_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); + m_horz_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_horz_slider_scroll_changed, this); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + #if ENABLE_GCODE_VIEWER m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); m_bottom_toolbar_sizer->AddSpacer(10); - m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); - m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); + m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); m_bottom_toolbar_sizer->AddSpacer(10); - m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL, 5); - m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxEXPAND | wxALL, 5); - m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5); + m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5); + m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 5); + m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + m_bottom_toolbar_sizer->AddSpacer(10); + m_bottom_toolbar_sizer->Add(m_horz_slider, 1, wxALL | wxEXPAND, 5); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #else wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); @@ -417,10 +433,12 @@ void Preview::set_number_extruders(unsigned int number_extruders) } } +#if !ENABLE_GCODE_VIEWER void Preview::set_enabled(bool enabled) { m_enabled = enabled; } +#endif // !ENABLE_GCODE_VIEWER void Preview::bed_shape_changed() { @@ -498,7 +516,14 @@ void Preview::refresh_print() void Preview::msw_rescale() { // rescale slider +#if ENABLE_GCODE_VIEWER + if (m_vert_slider != nullptr) m_vert_slider->msw_rescale(); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (m_horz_slider != nullptr) m_horz_slider->msw_rescale(); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#else if (m_slider) m_slider->msw_rescale(); +#endif // ENABLE_GCODE_VIEWER // rescale warning legend on the canvas get_canvas3d()->msw_rescale(); @@ -509,14 +534,22 @@ void Preview::msw_rescale() void Preview::move_double_slider(wxKeyEvent& evt) { - if (m_slider) +#if ENABLE_GCODE_VIEWER + if (m_vert_slider != nullptr) m_vert_slider->OnKeyDown(evt); +#else + if (m_slider) m_slider->OnKeyDown(evt); +#endif // ENABLE_GCODE_VIEWER } void Preview::edit_double_slider(wxKeyEvent& evt) { - if (m_slider) +#if ENABLE_GCODE_VIEWER + if (m_vert_slider != nullptr) m_vert_slider->OnChar(evt); +#else + if (m_slider) m_slider->OnChar(evt); +#endif // ENABLE_GCODE_VIEWER } void Preview::bind_event_handlers() @@ -580,25 +613,35 @@ void Preview::show_hide_ui_elements(const std::string& what) } #endif // !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER +void Preview::hide_vert_slider() +{ + m_double_slider_sizer->Hide((size_t)0); + Layout(); +} +#else void Preview::reset_sliders(bool reset_all) { m_enabled = false; -// reset_double_slider(); + // reset_double_slider(); if (reset_all) m_double_slider_sizer->Hide((size_t)0); else m_double_slider_sizer->GetItem(size_t(0))->GetSizer()->Hide(1); } +#endif // ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER void Preview::update_sliders(const std::vector& layers_z, bool keep_z_range) { m_enabled = true; - update_double_slider(layers_z, keep_z_range); + m_double_slider_sizer->Show((size_t)0); Layout(); } +#endif // !ENABLE_GCODE_VIEWER void Preview::on_size(wxSizeEvent& evt) { @@ -705,32 +748,68 @@ void Preview::update_bottom_toolbar() combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags()); combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags()); - m_bottom_toolbar_sizer->Show(m_combochecklist_features, + m_bottom_toolbar_sizer->Show(m_combochecklist_features, !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType); m_bottom_toolbar_sizer->Layout(); + Refresh(); } #endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER +wxBoxSizer* Preview::create_vert_slider_sizer() +{ + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + m_vert_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100); + + m_vert_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA, + wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects")); + + sizer->Add(m_vert_slider, 0, wxEXPAND, 0); + + // sizer, m_canvas_widget + m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_vert_slider_from_canvas, this); + m_canvas_widget->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) { + if (event.GetKeyCode() == WXK_SHIFT) + m_vert_slider->UseDefaultColors(true); + event.Skip(); + }); + + m_vert_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_vert_slider_scroll_changed, this); + + Bind(DoubleSlider::wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { + Model& model = wxGetApp().plater()->model(); + model.custom_gcode_per_print_z = m_vert_slider->GetTicksValues(); + m_schedule_background_process(); + + update_view_type(false); + + reload_print(); + }); + + return sizer; +} +#else void Preview::create_double_slider() { m_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100); + bool sla_print_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects"); m_slider->SetDrawMode(sla_print_technology, sequential_print); m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0); + // sizer, m_canvas_widget m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_double_slider_from_canvas, this); m_canvas_widget->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) { if (event.GetKeyCode() == WXK_SHIFT) m_slider->UseDefaultColors(true); event.Skip(); - }); + }); m_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_sliders_scroll_changed, this); - Bind(DoubleSlider::wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { Model& model = wxGetApp().plater()->model(); model.custom_gcode_per_print_z = m_slider->GetTicksValues(); @@ -739,8 +818,9 @@ void Preview::create_double_slider() update_view_type(false); reload_print(); - }); + }); } +#endif // ENABLE_GCODE_VIEWER // Find an index of a value in a sorted vector, which is in . // Returns -1 if there is no such member. @@ -769,8 +849,13 @@ static int find_close_layer_idx(const std::vector& zs, double &z, double return -1; } +#if ENABLE_GCODE_VIEWER +void Preview::check_vert_slider_values(std::vector& ticks_from_model, + const std::vector& layers_z) +#else void Preview::check_slider_values(std::vector& ticks_from_model, const std::vector& layers_z) +#endif // ENABLE_GCODE_VIEWER { // All ticks that would end up outside the slider range should be erased. // TODO: this should be placed into more appropriate part of code, @@ -787,12 +872,68 @@ void Preview::check_slider_values(std::vector& ticks_from_mod m_schedule_background_process(); } -void Preview::update_double_slider(const std::vector& layers_z, bool keep_z_range) +#if ENABLE_GCODE_VIEWER +void Preview::update_vert_slider(const std::vector& layers_z, bool keep_z_range) +{ + // Save the initial slider span. + double z_low = m_vert_slider->GetLowerValueD(); + double z_high = m_vert_slider->GetHigherValueD(); + bool was_empty = m_vert_slider->GetMaxValue() == 0; + + bool force_sliders_full_range = was_empty; + if (!keep_z_range) + { + bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_vert_slider->GetMaxValueD()) > DoubleSlider::epsilon()/*1e-6*/; + force_sliders_full_range |= span_changed; +} + bool snap_to_min = force_sliders_full_range || m_vert_slider->is_lower_at_min(); + bool snap_to_max = force_sliders_full_range || m_vert_slider->is_higher_at_max(); + + // Detect and set manipulation mode for double slider + update_vert_slider_mode(); + + CustomGCode::Info& ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z; + check_vert_slider_values(ticks_info_from_model.gcodes, layers_z); + + m_vert_slider->SetSliderValues(layers_z); + assert(m_vert_slider->GetMinValue() == 0); + m_vert_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1); + + int idx_low = 0; + int idx_high = m_vert_slider->GetMaxValue(); + if (!layers_z.empty()) { + if (!snap_to_min) { + int idx_new = find_close_layer_idx(layers_z, z_low, DoubleSlider::epsilon()/*1e-6*/); + if (idx_new != -1) + idx_low = idx_new; + } + if (!snap_to_max) { + int idx_new = find_close_layer_idx(layers_z, z_high, DoubleSlider::epsilon()/*1e-6*/); + if (idx_new != -1) + idx_high = idx_new; + } + } + m_vert_slider->SetSelectionSpan(idx_low, idx_high); + + m_vert_slider->SetTicksValues(ticks_info_from_model); + + bool sla_print_technology = wxGetApp().plater()->printer_technology() == ptSLA; + bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects"); + m_vert_slider->SetDrawMode(sla_print_technology, sequential_print); + + m_vert_slider->SetExtruderColors(wxGetApp().plater()->get_extruder_colors_from_plater_config()); + + m_double_slider_sizer->Show((size_t)0); + Layout(); +} +#else +void Preview::update_double_slider(const std::vector & layers_z, bool keep_z_range) { // Save the initial slider span. double z_low = m_slider->GetLowerValueD(); double z_high = m_slider->GetHigherValueD(); bool was_empty = m_slider->GetMaxValue() == 0; + bool force_sliders_full_range = was_empty; if (!keep_z_range) { @@ -800,27 +941,27 @@ void Preview::update_double_slider(const std::vector& layers_z, bool kee force_sliders_full_range |= span_changed; } bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); - bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max(); + bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max(); // Detect and set manipulation mode for double slider update_double_slider_mode(); - CustomGCode::Info &ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z; + CustomGCode::Info& ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z; check_slider_values(ticks_info_from_model.gcodes, layers_z); m_slider->SetSliderValues(layers_z); assert(m_slider->GetMinValue() == 0); m_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1); - int idx_low = 0; + int idx_low = 0; int idx_high = m_slider->GetMaxValue(); - if (! layers_z.empty()) { - if (! snap_to_min) { + if (!layers_z.empty()) { + if (!snap_to_min) { int idx_new = find_close_layer_idx(layers_z, z_low, DoubleSlider::epsilon()/*1e-6*/); if (idx_new != -1) idx_low = idx_new; } - if (! snap_to_max) { + if (!snap_to_max) { int idx_new = find_close_layer_idx(layers_z, z_high, DoubleSlider::epsilon()/*1e-6*/); if (idx_new != -1) idx_high = idx_new; @@ -836,8 +977,13 @@ void Preview::update_double_slider(const std::vector& layers_z, bool kee m_slider->SetExtruderColors(wxGetApp().plater()->get_extruder_colors_from_plater_config()); } +#endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER +void Preview::update_vert_slider_mode() +#else void Preview::update_double_slider_mode() +#endif // ENABLE_GCODE_VIEWER { // true -> single-extruder printer profile OR // multi-extruder printer profile , but whole model is printed by only one extruder @@ -886,16 +1032,68 @@ void Preview::update_double_slider_mode() } } +#if ENABLE_GCODE_VIEWER + m_vert_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); +#else m_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); +#endif // ENABLE_GCODE_VIEWER } +#if ENABLE_GCODE_VIEWER +void Preview::reset_vert_slider() +{ + m_vert_slider->SetHigherValue(0); + m_vert_slider->SetLowerValue(0); +} +#else void Preview::reset_double_slider() { m_slider->SetHigherValue(0); m_slider->SetLowerValue(0); } +#endif // ENABLE_GCODE_VIEWER -void Preview::update_double_slider_from_canvas(wxKeyEvent& event) +#if ENABLE_GCODE_VIEWER +void Preview::update_vert_slider_from_canvas(wxKeyEvent& event) +{ + if (event.HasModifiers()) { + event.Skip(); + return; + } + + const auto key = event.GetKeyCode(); + + if (key == 'U' || key == 'D') { + const int new_pos = key == 'U' ? m_vert_slider->GetHigherValue() + 1 : m_vert_slider->GetHigherValue() - 1; + m_vert_slider->SetHigherValue(new_pos); + if (event.ShiftDown() || m_vert_slider->is_one_layer()) m_vert_slider->SetLowerValue(m_vert_slider->GetHigherValue()); + } + else if (key == 'S') + m_vert_slider->ChangeOneLayerLock(); + else if (key == WXK_SHIFT) + m_vert_slider->UseDefaultColors(false); + else + event.Skip(); +} + +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +void Preview::update_horz_slider() +{ + const GCodeViewer::SequentialView& view = m_canvas->get_gcode_sequential_view(); + std::vector values(view.last - view.first + 1); + unsigned int count = 0; + for (unsigned int i = view.first; i <= view.last; ++i) + { + values[count++] = static_cast(i + 1); + } + + m_horz_slider->SetSliderValues(values); + m_horz_slider->SetMaxValue(view.last - view.first); + m_horz_slider->SetSelectionSpan(0, view.current); +} +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#else +void Preview::update_double_slider_from_canvas(wxKeyEvent & event) { if (event.HasModifiers()) { event.Skip(); @@ -909,13 +1107,11 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event) m_slider->SetHigherValue(new_pos); if (event.ShiftDown() || m_slider->is_one_layer()) m_slider->SetLowerValue(m_slider->GetHigherValue()); } -#if !ENABLE_GCODE_VIEWER else if (key == 'L') { m_checkbox_legend->SetValue(!m_checkbox_legend->GetValue()); auto evt = wxCommandEvent(); on_checkbox_legend(evt); } -#endif // !ENABLE_GCODE_VIEWER else if (key == 'S') m_slider->ChangeOneLayerLock(); else if (key == WXK_SHIFT) @@ -923,6 +1119,7 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event) else event.Skip(); } +#endif // ENABLE_GCODE_VIEWER void Preview::load_print_as_fff(bool keep_z_range) { @@ -951,10 +1148,12 @@ void Preview::load_print_as_fff(bool keep_z_range) if (! has_layers) { +#if ENABLE_GCODE_VIEWER + hide_vert_slider(); +#else reset_sliders(true); -#if !ENABLE_GCODE_VIEWER m_canvas->reset_legend_texture(); -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER m_canvas_widget->Refresh(); return; } @@ -1046,10 +1245,18 @@ void Preview::load_print_as_fff(bool keep_z_range) #endif // !ENABLE_GCODE_VIEWER if (zs.empty()) { // all layers filtered out +#if ENABLE_GCODE_VIEWER + hide_vert_slider(); +#else reset_sliders(true); +#endif // ENABLE_GCODE_VIEWER m_canvas_widget->Refresh(); } else +#if ENABLE_GCODE_VIEWER + update_vert_slider(zs, keep_z_range); +#else update_sliders(zs, keep_z_range); +#endif // ENABLE_GCODE_VIEWER } } @@ -1077,7 +1284,11 @@ void Preview::load_print_as_sla() n_layers = (unsigned int)zs.size(); if (n_layers == 0) { +#if ENABLE_GCODE_VIEWER + hide_vert_slider(); +#else reset_sliders(true); +#endif // ENABLE_GCODE_VIEWER m_canvas_widget->Refresh(); } @@ -1092,13 +1303,21 @@ void Preview::load_print_as_sla() #endif // ENABLE_GCODE_VIEWER if (n_layers > 0) +#if ENABLE_GCODE_VIEWER + update_vert_slider(zs); +#else update_sliders(zs); +#endif // ENABLE_GCODE_VIEWER m_loaded = true; } } +#if ENABLE_GCODE_VIEWER +void Preview::on_vert_slider_scroll_changed(wxCommandEvent& event) +#else void Preview::on_sliders_scroll_changed(wxCommandEvent& event) +#endif // ENABLE_GCODE_VIEWER { if (IsShown()) { @@ -1106,7 +1325,7 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) if (tech == ptFFF) { #if ENABLE_GCODE_VIEWER - m_canvas->set_toolpaths_z_range({ m_slider->GetLowerValueD(), m_slider->GetHigherValueD() }); + m_canvas->set_toolpaths_z_range({ m_vert_slider->GetLowerValueD(), m_vert_slider->GetHigherValueD() }); m_canvas->set_as_dirty(); #else m_canvas->set_toolpaths_range(m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6); @@ -1116,13 +1335,27 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) } else if (tech == ptSLA) { +#if ENABLE_GCODE_VIEWER + m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_vert_slider->GetLowerValueD())); + m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_vert_slider->GetHigherValueD())); + m_canvas->set_use_clipping_planes(m_vert_slider->GetHigherValue() != 0); +#else m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD())); m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD())); m_canvas->set_use_clipping_planes(m_slider->GetHigherValue() != 0); +#endif // ENABLE_GCODE_VIEWER m_canvas->render(); } } } +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +void Preview::on_horz_slider_scroll_changed(wxCommandEvent& event) +{ + m_canvas->update_gcode_sequential_view_current(static_cast(m_horz_slider->GetLowerValueD()), static_cast(m_horz_slider->GetHigherValueD())); + m_canvas->render(); +} +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index a11a474ccf..4df48a153e 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -24,7 +24,9 @@ namespace Slic3r { class DynamicPrintConfig; class Print; class BackgroundSlicingProcess; +#if !ENABLE_GCODE_VIEWER class GCodePreviewData; +#endif // !ENABLE_GCODE_VIEWER class Model; namespace DoubleSlider { @@ -100,9 +102,10 @@ class Preview : public wxPanel DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; - GCodePreviewData* m_gcode_preview_data; #if ENABLE_GCODE_VIEWER GCodeProcessor::Result* m_gcode_result; +#else + GCodePreviewData* m_gcode_preview_data; #endif // ENABLE_GCODE_VIEWER #ifdef __linux__ @@ -118,9 +121,18 @@ class Preview : public wxPanel std::string m_preferred_color_mode; bool m_loaded; +#if !ENABLE_GCODE_VIEWER bool m_enabled; +#endif // !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + DoubleSlider::Control* m_vert_slider{ nullptr }; +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + DoubleSlider::Control* m_horz_slider{ nullptr }; +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#else DoubleSlider::Control* m_slider {nullptr}; +#endif // ENABLE_GCODE_VIEWER public: #if ENABLE_GCODE_VIEWER @@ -138,7 +150,9 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, void set_as_dirty(); void set_number_extruders(unsigned int number_extruders); +#if !ENABLE_GCODE_VIEWER void set_enabled(bool enabled); +#endif // !ENABLE_GCODE_VIEWER void bed_shape_changed(); void select_view(const std::string& direction); void set_drop_target(wxDropTarget* target); @@ -157,6 +171,9 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, #if ENABLE_GCODE_VIEWER void update_bottom_toolbar(); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void update_horz_slider(); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER private: @@ -165,12 +182,14 @@ private: void bind_event_handlers(); void unbind_event_handlers(); -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + void hide_vert_slider(); +#else void show_hide_ui_elements(const std::string& what); -#endif // !ENABLE_GCODE_VIEWER void reset_sliders(bool reset_all); void update_sliders(const std::vector& layers_z, bool keep_z_range = false); +#endif // ENABLE_GCODE_VIEWER void on_size(wxSizeEvent& evt); void on_choice_view_type(wxCommandEvent& evt); @@ -185,20 +204,39 @@ private: void on_checkbox_legend(wxCommandEvent& evt); #endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + // Create/Update/Reset double slider on 3dPreview + wxBoxSizer* create_vert_slider_sizer(); + void check_vert_slider_values(std::vector& ticks_from_model, + const std::vector& layers_z); + void reset_vert_slider(); + void update_vert_slider(const std::vector& layers_z, bool keep_z_range = false); + void update_vert_slider_mode(); + // update vertical DoubleSlider after keyDown in canvas + void update_vert_slider_from_canvas(wxKeyEvent& event); +#else // Create/Update/Reset double slider on 3dPreview void create_double_slider(); - void check_slider_values(std::vector &ticks_from_model, - const std::vector &layers_z); + void check_slider_values(std::vector& ticks_from_model, + const std::vector& layers_z); void reset_double_slider(); void update_double_slider(const std::vector& layers_z, bool keep_z_range = false); void update_double_slider_mode(); // update DoubleSlider after keyDown in canvas void update_double_slider_from_canvas(wxKeyEvent& event); +#endif // ENABLE_GCODE_VIEWER void load_print_as_fff(bool keep_z_range = false); void load_print_as_sla(); +#if ENABLE_GCODE_VIEWER + void on_vert_slider_scroll_changed(wxCommandEvent& event); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void on_horz_slider_scroll_changed(wxCommandEvent& event); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#else void on_sliders_scroll_changed(wxCommandEvent& event); +#endif // ENABLE_GCODE_VIEWER }; } // namespace GUI diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8d505f2b65..4243b6ee84 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1641,6 +1641,9 @@ struct Plater::priv bool init_view_toolbar(); #if ENABLE_GCODE_VIEWER void update_preview_bottom_toolbar(); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void update_preview_horz_slider(); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER void reset_all_gizmos(); @@ -2591,8 +2594,10 @@ void Plater::priv::deselect_all() void Plater::priv::remove(size_t obj_idx) { +#if !ENABLE_GCODE_VIEWER // Prevent toolpaths preview from rendering while we modify the Print object preview->set_enabled(false); +#endif // !ENABLE_GCODE_VIEWER if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); @@ -2622,8 +2627,10 @@ void Plater::priv::reset() set_project_filename(wxEmptyString); +#if !ENABLE_GCODE_VIEWER // Prevent toolpaths preview from rendering while we modify the Print object preview->set_enabled(false); +#endif // !ENABLE_GCODE_VIEWER if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); @@ -3867,6 +3874,13 @@ void Plater::priv::update_preview_bottom_toolbar() { preview->update_bottom_toolbar(); } + +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +void Plater::priv::update_preview_horz_slider() +{ + preview->update_horz_slider(); +} +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER bool Plater::priv::can_set_instance_to_object() const @@ -5463,6 +5477,13 @@ void Plater::update_preview_bottom_toolbar() { p->update_preview_bottom_toolbar(); } + +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +void Plater::update_preview_horz_slider() +{ + p->update_preview_horz_slider(); +} +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER const Mouse3DController& Plater::get_mouse3d_controller() const diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 7a08c04efe..82c8bbe076 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -320,6 +320,9 @@ public: #if ENABLE_GCODE_VIEWER void update_preview_bottom_toolbar(); +#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void update_preview_horz_slider(); +#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER const Mouse3DController& get_mouse3d_controller() const; From a68eefbe4abc366dc5c80b315503b05fc321df64 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 15 May 2020 12:25:38 +0200 Subject: [PATCH 076/826] Tech ENABLE_GCODE_VIEWER -> Refactoring and code cleanup --- src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/DoubleSlider.cpp | 32 +++--- src/slic3r/GUI/DoubleSlider.hpp | 8 +- src/slic3r/GUI/GCodeViewer.cpp | 94 +---------------- src/slic3r/GUI/GCodeViewer.hpp | 52 ---------- src/slic3r/GUI/GLCanvas3D.cpp | 8 ++ src/slic3r/GUI/GLCanvas3D.hpp | 6 +- src/slic3r/GUI/GUI_Preview.cpp | 175 ++++++++++++++++---------------- src/slic3r/GUI/GUI_Preview.hpp | 39 +++---- src/slic3r/GUI/Plater.cpp | 21 ++-- src/slic3r/GUI/Plater.hpp | 4 +- 11 files changed, 152 insertions(+), 288 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 8ce01a3530..c22e504dff 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -55,7 +55,6 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -#define ENABLE_GCODE_USE_WXWIDGETS_SLIDER (1 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 021d73882e..597b9ad607 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -20,9 +20,9 @@ #include #include #include -#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if !ENABLE_GCODE_VIEWER #include -#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // !ENABLE_GCODE_VIEWER #include #include @@ -537,7 +537,7 @@ wxString Control::get_label(int tick) const if (value >= m_values.size()) return "ErrVal"; -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER if (m_draw_mode == dmSequentialGCodeView) return wxString::Format("%d", static_cast(m_values[value])); else { @@ -551,7 +551,7 @@ wxString Control::get_label(int tick) const wxNumberFormatter::ToString(m_label_koef * value, 2, wxNumberFormatter::Style_None) : wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None); return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER } void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_side/*=true*/) const @@ -792,10 +792,10 @@ void Control::draw_colored_band(wxDC& dc) void Control::draw_one_layer_icon(wxDC& dc) { -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER if (m_draw_mode == dmSequentialGCodeView) return; -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER const wxBitmap& icon = m_is_one_layer ? m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() : @@ -1302,11 +1302,11 @@ void Control::OnWheel(wxMouseEvent& event) ssLower : ssHigher; } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER move_current_thumb((m_draw_mode == dmSequentialGCodeView) ? event.GetWheelRotation() < 0 : event.GetWheelRotation() > 0); #else move_current_thumb(event.GetWheelRotation() > 0); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER } void Control::OnKeyDown(wxKeyEvent &event) @@ -1328,34 +1328,34 @@ void Control::OnKeyDown(wxKeyEvent &event) UseDefaultColors(false); else if (is_horizontal()) { -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER if (m_is_focused) { -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER if (key == WXK_LEFT || key == WXK_RIGHT) move_current_thumb(key == WXK_LEFT); else if (key == WXK_UP || key == WXK_DOWN) { m_selection = key == WXK_UP ? ssHigher : ssLower; Refresh(); } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER } else { -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER if (m_is_focused) { -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER if (key == WXK_LEFT || key == WXK_RIGHT) { m_selection = key == WXK_LEFT ? ssHigher : ssLower; Refresh(); } else if (key == WXK_UP || key == WXK_DOWN) move_current_thumb(key == WXK_UP); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER } event.Skip(); // !Needed to have EVT_CHAR generated as well diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index a17fb2b6fa..fea1ba172e 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -73,9 +73,9 @@ enum DrawMode dmRegular, dmSlaPrint, dmSequentialFffPrint, -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER dmSequentialGCodeView, -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER }; using t_mode = CustomGCode::Mode; @@ -214,9 +214,9 @@ public: void SetTicksValues(const Slic3r::CustomGCode::Info &custom_gcode_per_print_z); void SetDrawMode(bool is_sla_print, bool is_sequential_print); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#if ENABLE_GCODE_VIEWER void SetDrawMode(DrawMode mode) { m_draw_mode = mode; } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER void SetManipulationMode(t_mode mode) { m_mode = mode; } t_mode GetManipulationMode() const { return m_mode; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index acf1da4c71..c4bc391d18 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -324,9 +324,6 @@ void GCodeViewer::render() const m_sequential_view.marker.render(); render_shells(); render_legend(); -#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER - render_sequential_bar(); -#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -383,15 +380,13 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) enable_legend(is_flag_set(9)); } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) { bool keep_sequential_current = layers_z_range[1] <= m_layers_z_range[1]; m_layers_z_range = layers_z_range; refresh_render_paths(keep_sequential_current); - wxGetApp().plater()->update_preview_horz_slider(); + wxGetApp().plater()->update_preview_moves_slider(); } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER bool GCodeViewer::init_shaders() { @@ -1159,93 +1154,6 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } -#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER -void GCodeViewer::render_sequential_bar() const -{ - static const float MARGIN = 125.0f; - static const float BUTTON_W = 50.0f; - - auto apply_button_action = [this](unsigned int value) { - m_sequential_view.current = std::clamp(value, m_sequential_view.first, m_sequential_view.last); - refresh_render_paths(true); - }; - - if (m_sequential_view.last <= m_sequential_view.first) - return; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - const ImGuiStyle& style = ImGui::GetStyle(); - - Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - - float left = wxGetApp().plater()->get_view_toolbar().get_width(); - float width = static_cast(cnv_size.get_width()) - left; - - ImGui::SetNextWindowBgAlpha(0.5f); - imgui.set_next_window_pos(left, static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.0f, 1.0f); - ImGui::SetNextWindowSize({ width, -1.0f }, ImGuiCond_Always); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - imgui.begin(std::string("Sequential"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - - ImGui::SetCursorPosX(MARGIN); - imgui.disabled_begin(m_sequential_view.first == m_sequential_view.current); - if (ImGui::Button("< 1", { BUTTON_W, 0.0f })) - apply_button_action(m_sequential_view.current - 1); - imgui.disabled_end(); - - ImGui::SameLine(); - imgui.disabled_begin(m_sequential_view.current - m_sequential_view.first < 10); - if (ImGui::Button("< 10", { BUTTON_W, 0.0f })) - apply_button_action(m_sequential_view.current - 10); - imgui.disabled_end(); - - ImGui::SameLine(); - imgui.disabled_begin(m_sequential_view.current - m_sequential_view.first < 100); - if (ImGui::Button("< 100", { BUTTON_W, 0.0f })) - apply_button_action(m_sequential_view.current - 100); - imgui.disabled_end(); - - ImGui::SameLine(width - MARGIN - 3 * BUTTON_W - 2 * style.ItemSpacing.x - style.WindowPadding.x); - imgui.disabled_begin(m_sequential_view.last - m_sequential_view.current < 100); - if (ImGui::Button("> 100", { BUTTON_W, 0.0f })) - apply_button_action(m_sequential_view.current + 100); - imgui.disabled_end(); - - ImGui::SameLine(); - imgui.disabled_begin(m_sequential_view.last - m_sequential_view.current < 10); - if (ImGui::Button("> 10", { BUTTON_W, 0.0f })) - apply_button_action(m_sequential_view.current + 10); - imgui.disabled_end(); - - ImGui::SameLine(); - imgui.disabled_begin(m_sequential_view.last == m_sequential_view.current); - if (ImGui::Button("> 1", { BUTTON_W, 0.0f })) - apply_button_action(m_sequential_view.current + 1); - imgui.disabled_end(); - - int index = 1 + static_cast(m_sequential_view.current); - int i_min = 1 + static_cast(m_sequential_view.first); - int i_max = 1 + static_cast(m_sequential_view.last); - - std::string low_str = std::to_string(i_min); - ImGui::SetCursorPosX(MARGIN - style.ItemSpacing.x - ImGui::CalcTextSize(low_str.c_str()).x); - ImGui::AlignTextToFramePadding(); - imgui.text(low_str); - ImGui::SameLine(MARGIN); - ImGui::PushItemWidth(-MARGIN); - if (ImGui::SliderInt("##slider int", &index, i_min, i_max)) { - m_sequential_view.current = static_cast(index - 1); - refresh_render_paths(true); - } - ImGui::PopItemWidth(); - ImGui::SameLine(); - imgui.text(std::to_string(i_max)); - - imgui.end(); - ImGui::PopStyleVar(); -} -#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER - #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index a4ac3358d2..e53bcb0c62 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -149,42 +149,6 @@ class GCodeViewer void reset_ranges() { ranges.reset(); } }; -#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER - struct SequentialView - { - class Marker - { - GL_Model m_model; - Transform3f m_world_transform; - std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; - bool m_visible{ false }; - Shader m_shader; - - public: - void init(); - - const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } - - void set_world_transform(const Transform3f& transform) { m_world_transform = transform; } - void set_color(const std::array& color) { m_color = color; } - - bool is_visible() const { return m_visible; } - void set_visible(bool visible) { m_visible = visible; } - - void render() const; - - private: - void init_shader(); - }; - - unsigned int first{ 0 }; - unsigned int last{ 0 }; - unsigned int current{ 0 }; - Vec3f current_position{ Vec3f::Zero() }; - Marker marker; - }; -#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER - #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics { @@ -231,7 +195,6 @@ class GCodeViewer #endif // ENABLE_GCODE_VIEWER_STATISTICS public: -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER struct SequentialView { class Marker @@ -265,7 +228,6 @@ public: Vec3f current_position{ Vec3f::Zero() }; Marker marker; }; -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER enum class EViewType : unsigned char { @@ -322,10 +284,8 @@ public: const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::vector& get_layers_zs() const { return m_layers_zs; }; -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER const SequentialView& get_sequential_view() const { return m_sequential_view; } void update_sequential_view_current(unsigned int low, unsigned int high) { m_sequential_view.current = high; refresh_render_paths(true); } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER EViewType get_view_type() const { return m_view_type; } void set_view_type(EViewType type) { @@ -341,16 +301,7 @@ public: void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } unsigned int get_options_visibility_flags() const; void set_options_visibility_from_flags(unsigned int flags); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER void set_layers_z_range(const std::array& layers_z_range); -#else - void set_layers_z_range(const std::array& layers_z_range) - { - bool keep_sequential_current = layers_z_range[1] <= m_layers_z_range[1]; - m_layers_z_range = layers_z_range; - refresh_render_paths(keep_sequential_current); - } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } @@ -363,9 +314,6 @@ private: void render_toolpaths() const; void render_shells() const; void render_legend() const; -#if !ENABLE_GCODE_USE_WXWIDGETS_SLIDER - void render_sequential_bar() const; -#endif // !ENABLE_GCODE_USE_WXWIDGETS_SLIDER #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 073a9cd35e..7629bc969b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1523,7 +1523,11 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +#if ENABLE_GCODE_VIEWER +wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, wxKeyEvent); +#else wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); +#endif // ENABLE_GCODE_VIEWER wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); @@ -3405,7 +3409,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) keyCode == WXK_DOWN) { if (dynamic_cast(m_canvas->GetParent()) != nullptr) +#if ENABLE_GCODE_VIEWER + post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, evt)); +#else post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt)); +#endif // ENABLE_GCODE_VIEWER } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d4df60ad2a..43d37607c5 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -107,7 +107,11 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +#if ENABLE_GCODE_VIEWER +wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, wxKeyEvent); +#else wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); +#endif // ENABLE_GCODE_VIEWER wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); @@ -553,10 +557,8 @@ public: #if ENABLE_GCODE_VIEWER void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } void update_gcode_sequential_view_current(unsigned int low, unsigned int high) { m_gcode_viewer.update_sequential_view_current(low, high); } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9547e0bf89..714ed3c9ef 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -183,9 +183,11 @@ Preview::Preview( #endif // ENABLE_GCODE_VIEWER : m_canvas_widget(nullptr) , m_canvas(nullptr) - , m_double_slider_sizer(nullptr) #if ENABLE_GCODE_VIEWER , m_bottom_toolbar_sizer(nullptr) + , m_layers_slider_sizer(nullptr) +#else + , m_double_slider_sizer(nullptr) #endif // ENABLE_GCODE_VIEWER , m_label_view_type(nullptr) , m_choice_view_type(nullptr) @@ -248,7 +250,7 @@ bool Preview::init(wxWindow* parent, Model* model) m_canvas->enable_collapse_toolbar(true); #if ENABLE_GCODE_VIEWER - m_double_slider_sizer = create_vert_slider_sizer(); + m_layers_slider_sizer = create_layers_slider_sizer(); #else m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); create_double_slider(); @@ -319,15 +321,17 @@ bool Preview::init(wxWindow* parent, Model* model) wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); +#if ENABLE_GCODE_VIEWER + top_sizer->Add(m_layers_slider_sizer, 0, wxEXPAND, 0); +#else top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); - -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER - m_horz_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); - m_horz_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); - m_horz_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_horz_slider_scroll_changed, this); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER + m_moves_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); + m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); + m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); m_bottom_toolbar_sizer->AddSpacer(10); m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); @@ -336,10 +340,8 @@ bool Preview::init(wxWindow* parent, Model* model) m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5); m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 5); m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER m_bottom_toolbar_sizer->AddSpacer(10); - m_bottom_toolbar_sizer->Add(m_horz_slider, 1, wxALL | wxEXPAND, 5); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + m_bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 5); #else wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); @@ -517,10 +519,8 @@ void Preview::msw_rescale() { // rescale slider #if ENABLE_GCODE_VIEWER - if (m_vert_slider != nullptr) m_vert_slider->msw_rescale(); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER - if (m_horz_slider != nullptr) m_horz_slider->msw_rescale(); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + if (m_layers_slider != nullptr) m_layers_slider->msw_rescale(); + if (m_moves_slider != nullptr) m_moves_slider->msw_rescale(); #else if (m_slider) m_slider->msw_rescale(); #endif // ENABLE_GCODE_VIEWER @@ -532,25 +532,31 @@ void Preview::msw_rescale() refresh_print(); } +#if ENABLE_GCODE_VIEWER +void Preview::move_layers_slider(wxKeyEvent& evt) +{ + if (m_layers_slider != nullptr) m_layers_slider->OnKeyDown(evt); +} +#else void Preview::move_double_slider(wxKeyEvent& evt) { -#if ENABLE_GCODE_VIEWER - if (m_vert_slider != nullptr) m_vert_slider->OnKeyDown(evt); -#else if (m_slider) m_slider->OnKeyDown(evt); -#endif // ENABLE_GCODE_VIEWER } +#endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER +void Preview::edit_layers_slider(wxKeyEvent& evt) +{ + if (m_layers_slider != nullptr) m_layers_slider->OnChar(evt); +} +#else void Preview::edit_double_slider(wxKeyEvent& evt) { -#if ENABLE_GCODE_VIEWER - if (m_vert_slider != nullptr) m_vert_slider->OnChar(evt); -#else if (m_slider) m_slider->OnChar(evt); -#endif // ENABLE_GCODE_VIEWER } +#endif // ENABLE_GCODE_VIEWER void Preview::bind_event_handlers() { @@ -614,9 +620,9 @@ void Preview::show_hide_ui_elements(const std::string& what) #endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -void Preview::hide_vert_slider() +void Preview::hide_layers_slider() { - m_double_slider_sizer->Hide((size_t)0); + m_layers_slider_sizer->Hide((size_t)0); Layout(); } #else @@ -756,29 +762,29 @@ void Preview::update_bottom_toolbar() #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -wxBoxSizer* Preview::create_vert_slider_sizer() +wxBoxSizer* Preview::create_layers_slider_sizer() { wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); - m_vert_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100); + m_layers_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100); - m_vert_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA, + m_layers_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA, wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects")); - sizer->Add(m_vert_slider, 0, wxEXPAND, 0); + sizer->Add(m_layers_slider, 0, wxEXPAND, 0); // sizer, m_canvas_widget - m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_vert_slider_from_canvas, this); + m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_layers_slider_from_canvas, this); m_canvas_widget->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) { if (event.GetKeyCode() == WXK_SHIFT) - m_vert_slider->UseDefaultColors(true); + m_layers_slider->UseDefaultColors(true); event.Skip(); }); - m_vert_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_vert_slider_scroll_changed, this); + m_layers_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_layers_slider_scroll_changed, this); Bind(DoubleSlider::wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { Model& model = wxGetApp().plater()->model(); - model.custom_gcode_per_print_z = m_vert_slider->GetTicksValues(); + model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues(); m_schedule_background_process(); update_view_type(false); @@ -850,8 +856,7 @@ static int find_close_layer_idx(const std::vector& zs, double &z, double } #if ENABLE_GCODE_VIEWER -void Preview::check_vert_slider_values(std::vector& ticks_from_model, - const std::vector& layers_z) +void Preview::check_layers_slider_values(std::vector& ticks_from_model, const std::vector& layers_z) #else void Preview::check_slider_values(std::vector& ticks_from_model, const std::vector& layers_z) @@ -873,34 +878,34 @@ void Preview::check_slider_values(std::vector& ticks_from_mod } #if ENABLE_GCODE_VIEWER -void Preview::update_vert_slider(const std::vector& layers_z, bool keep_z_range) +void Preview::update_layers_slider(const std::vector& layers_z, bool keep_z_range) { // Save the initial slider span. - double z_low = m_vert_slider->GetLowerValueD(); - double z_high = m_vert_slider->GetHigherValueD(); - bool was_empty = m_vert_slider->GetMaxValue() == 0; + double z_low = m_layers_slider->GetLowerValueD(); + double z_high = m_layers_slider->GetHigherValueD(); + bool was_empty = m_layers_slider->GetMaxValue() == 0; bool force_sliders_full_range = was_empty; if (!keep_z_range) { - bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_vert_slider->GetMaxValueD()) > DoubleSlider::epsilon()/*1e-6*/; + bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_layers_slider->GetMaxValueD()) > DoubleSlider::epsilon()/*1e-6*/; force_sliders_full_range |= span_changed; -} - bool snap_to_min = force_sliders_full_range || m_vert_slider->is_lower_at_min(); - bool snap_to_max = force_sliders_full_range || m_vert_slider->is_higher_at_max(); + } + bool snap_to_min = force_sliders_full_range || m_layers_slider->is_lower_at_min(); + bool snap_to_max = force_sliders_full_range || m_layers_slider->is_higher_at_max(); // Detect and set manipulation mode for double slider - update_vert_slider_mode(); + update_layers_slider_mode(); CustomGCode::Info& ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z; - check_vert_slider_values(ticks_info_from_model.gcodes, layers_z); + check_layers_slider_values(ticks_info_from_model.gcodes, layers_z); - m_vert_slider->SetSliderValues(layers_z); - assert(m_vert_slider->GetMinValue() == 0); - m_vert_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1); + m_layers_slider->SetSliderValues(layers_z); + assert(m_layers_slider->GetMinValue() == 0); + m_layers_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1); int idx_low = 0; - int idx_high = m_vert_slider->GetMaxValue(); + int idx_high = m_layers_slider->GetMaxValue(); if (!layers_z.empty()) { if (!snap_to_min) { int idx_new = find_close_layer_idx(layers_z, z_low, DoubleSlider::epsilon()/*1e-6*/); @@ -913,17 +918,15 @@ void Preview::update_vert_slider(const std::vector& layers_z, bool keep_ idx_high = idx_new; } } - m_vert_slider->SetSelectionSpan(idx_low, idx_high); - - m_vert_slider->SetTicksValues(ticks_info_from_model); + m_layers_slider->SetSelectionSpan(idx_low, idx_high); + m_layers_slider->SetTicksValues(ticks_info_from_model); bool sla_print_technology = wxGetApp().plater()->printer_technology() == ptSLA; bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects"); - m_vert_slider->SetDrawMode(sla_print_technology, sequential_print); + m_layers_slider->SetDrawMode(sla_print_technology, sequential_print); + m_layers_slider->SetExtruderColors(wxGetApp().plater()->get_extruder_colors_from_plater_config()); - m_vert_slider->SetExtruderColors(wxGetApp().plater()->get_extruder_colors_from_plater_config()); - - m_double_slider_sizer->Show((size_t)0); + m_layers_slider_sizer->Show((size_t)0); Layout(); } #else @@ -980,7 +983,7 @@ void Preview::update_double_slider(const std::vector & layers_z, bool ke #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -void Preview::update_vert_slider_mode() +void Preview::update_layers_slider_mode() #else void Preview::update_double_slider_mode() #endif // ENABLE_GCODE_VIEWER @@ -1033,17 +1036,17 @@ void Preview::update_double_slider_mode() } #if ENABLE_GCODE_VIEWER - m_vert_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); + m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); #else m_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); #endif // ENABLE_GCODE_VIEWER } #if ENABLE_GCODE_VIEWER -void Preview::reset_vert_slider() +void Preview::reset_layers_slider() { - m_vert_slider->SetHigherValue(0); - m_vert_slider->SetLowerValue(0); + m_layers_slider->SetHigherValue(0); + m_layers_slider->SetLowerValue(0); } #else void Preview::reset_double_slider() @@ -1054,7 +1057,7 @@ void Preview::reset_double_slider() #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -void Preview::update_vert_slider_from_canvas(wxKeyEvent& event) +void Preview::update_layers_slider_from_canvas(wxKeyEvent& event) { if (event.HasModifiers()) { event.Skip(); @@ -1064,20 +1067,19 @@ void Preview::update_vert_slider_from_canvas(wxKeyEvent& event) const auto key = event.GetKeyCode(); if (key == 'U' || key == 'D') { - const int new_pos = key == 'U' ? m_vert_slider->GetHigherValue() + 1 : m_vert_slider->GetHigherValue() - 1; - m_vert_slider->SetHigherValue(new_pos); - if (event.ShiftDown() || m_vert_slider->is_one_layer()) m_vert_slider->SetLowerValue(m_vert_slider->GetHigherValue()); + const int new_pos = key == 'U' ? m_layers_slider->GetHigherValue() + 1 : m_layers_slider->GetHigherValue() - 1; + m_layers_slider->SetHigherValue(new_pos); + if (event.ShiftDown() || m_layers_slider->is_one_layer()) m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue()); } else if (key == 'S') - m_vert_slider->ChangeOneLayerLock(); + m_layers_slider->ChangeOneLayerLock(); else if (key == WXK_SHIFT) - m_vert_slider->UseDefaultColors(false); + m_layers_slider->UseDefaultColors(false); else event.Skip(); } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER -void Preview::update_horz_slider() +void Preview::update_moves_slider() { const GCodeViewer::SequentialView& view = m_canvas->get_gcode_sequential_view(); std::vector values(view.last - view.first + 1); @@ -1087,11 +1089,10 @@ void Preview::update_horz_slider() values[count++] = static_cast(i + 1); } - m_horz_slider->SetSliderValues(values); - m_horz_slider->SetMaxValue(view.last - view.first); - m_horz_slider->SetSelectionSpan(0, view.current); + m_moves_slider->SetSliderValues(values); + m_moves_slider->SetMaxValue(view.last - view.first); + m_moves_slider->SetSelectionSpan(0, view.current); } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #else void Preview::update_double_slider_from_canvas(wxKeyEvent & event) { @@ -1149,7 +1150,7 @@ void Preview::load_print_as_fff(bool keep_z_range) if (! has_layers) { #if ENABLE_GCODE_VIEWER - hide_vert_slider(); + hide_layers_slider(); #else reset_sliders(true); m_canvas->reset_legend_texture(); @@ -1246,14 +1247,14 @@ void Preview::load_print_as_fff(bool keep_z_range) if (zs.empty()) { // all layers filtered out #if ENABLE_GCODE_VIEWER - hide_vert_slider(); + hide_layers_slider(); #else reset_sliders(true); #endif // ENABLE_GCODE_VIEWER m_canvas_widget->Refresh(); } else #if ENABLE_GCODE_VIEWER - update_vert_slider(zs, keep_z_range); + update_layers_slider(zs, keep_z_range); #else update_sliders(zs, keep_z_range); #endif // ENABLE_GCODE_VIEWER @@ -1285,7 +1286,7 @@ void Preview::load_print_as_sla() if (n_layers == 0) { #if ENABLE_GCODE_VIEWER - hide_vert_slider(); + hide_layers_slider(); #else reset_sliders(true); #endif // ENABLE_GCODE_VIEWER @@ -1304,7 +1305,7 @@ void Preview::load_print_as_sla() if (n_layers > 0) #if ENABLE_GCODE_VIEWER - update_vert_slider(zs); + update_layers_slider(zs); #else update_sliders(zs); #endif // ENABLE_GCODE_VIEWER @@ -1314,7 +1315,7 @@ void Preview::load_print_as_sla() } #if ENABLE_GCODE_VIEWER -void Preview::on_vert_slider_scroll_changed(wxCommandEvent& event) +void Preview::on_layers_slider_scroll_changed(wxCommandEvent& event) #else void Preview::on_sliders_scroll_changed(wxCommandEvent& event) #endif // ENABLE_GCODE_VIEWER @@ -1325,7 +1326,7 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) if (tech == ptFFF) { #if ENABLE_GCODE_VIEWER - m_canvas->set_toolpaths_z_range({ m_vert_slider->GetLowerValueD(), m_vert_slider->GetHigherValueD() }); + m_canvas->set_toolpaths_z_range({ m_layers_slider->GetLowerValueD(), m_layers_slider->GetHigherValueD() }); m_canvas->set_as_dirty(); #else m_canvas->set_toolpaths_range(m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6); @@ -1336,9 +1337,9 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) else if (tech == ptSLA) { #if ENABLE_GCODE_VIEWER - m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_vert_slider->GetLowerValueD())); - m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_vert_slider->GetHigherValueD())); - m_canvas->set_use_clipping_planes(m_vert_slider->GetHigherValue() != 0); + m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_layers_slider->GetLowerValueD())); + m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_layers_slider->GetHigherValueD())); + m_canvas->set_use_clipping_planes(m_layers_slider->GetHigherValue() != 0); #else m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD())); m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD())); @@ -1349,13 +1350,13 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) } } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER -void Preview::on_horz_slider_scroll_changed(wxCommandEvent& event) +#if ENABLE_GCODE_VIEWER +void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event) { - m_canvas->update_gcode_sequential_view_current(static_cast(m_horz_slider->GetLowerValueD()), static_cast(m_horz_slider->GetHigherValueD())); + m_canvas->update_gcode_sequential_view_current(static_cast(m_moves_slider->GetLowerValueD()), static_cast(m_moves_slider->GetHigherValueD())); m_canvas->render(); } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER +#endif // ENABLE_GCODE_VIEWER } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 4df48a153e..3f0d4b4e68 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -82,9 +82,11 @@ class Preview : public wxPanel { wxGLCanvas* m_canvas_widget; GLCanvas3D* m_canvas; - wxBoxSizer* m_double_slider_sizer; #if ENABLE_GCODE_VIEWER + wxBoxSizer* m_layers_slider_sizer; wxBoxSizer* m_bottom_toolbar_sizer; +#else + wxBoxSizer* m_double_slider_sizer; #endif // ENABLE_GCODE_VIEWER wxStaticText* m_label_view_type; wxChoice* m_choice_view_type; @@ -126,10 +128,8 @@ class Preview : public wxPanel #endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - DoubleSlider::Control* m_vert_slider{ nullptr }; -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER - DoubleSlider::Control* m_horz_slider{ nullptr }; -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + DoubleSlider::Control* m_layers_slider{ nullptr }; + DoubleSlider::Control* m_moves_slider{ nullptr }; #else DoubleSlider::Control* m_slider {nullptr}; #endif // ENABLE_GCODE_VIEWER @@ -162,8 +162,13 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, void refresh_print(); void msw_rescale(); +#if ENABLE_GCODE_VIEWER + void move_layers_slider(wxKeyEvent& evt); + void edit_layers_slider(wxKeyEvent& evt); +#else void move_double_slider(wxKeyEvent& evt); void edit_double_slider(wxKeyEvent& evt); +#endif // ENABLE_GCODE_VIEWER void update_view_type(bool slice_completed); @@ -171,9 +176,7 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, #if ENABLE_GCODE_VIEWER void update_bottom_toolbar(); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER - void update_horz_slider(); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void update_moves_slider(); #endif // ENABLE_GCODE_VIEWER private: @@ -183,7 +186,7 @@ private: void unbind_event_handlers(); #if ENABLE_GCODE_VIEWER - void hide_vert_slider(); + void hide_layers_slider(); #else void show_hide_ui_elements(const std::string& what); @@ -206,14 +209,14 @@ private: #if ENABLE_GCODE_VIEWER // Create/Update/Reset double slider on 3dPreview - wxBoxSizer* create_vert_slider_sizer(); - void check_vert_slider_values(std::vector& ticks_from_model, + wxBoxSizer* create_layers_slider_sizer(); + void check_layers_slider_values(std::vector& ticks_from_model, const std::vector& layers_z); - void reset_vert_slider(); - void update_vert_slider(const std::vector& layers_z, bool keep_z_range = false); - void update_vert_slider_mode(); + void reset_layers_slider(); + void update_layers_slider(const std::vector& layers_z, bool keep_z_range = false); + void update_layers_slider_mode(); // update vertical DoubleSlider after keyDown in canvas - void update_vert_slider_from_canvas(wxKeyEvent& event); + void update_layers_slider_from_canvas(wxKeyEvent& event); #else // Create/Update/Reset double slider on 3dPreview void create_double_slider(); @@ -230,10 +233,8 @@ private: void load_print_as_sla(); #if ENABLE_GCODE_VIEWER - void on_vert_slider_scroll_changed(wxCommandEvent& event); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER - void on_horz_slider_scroll_changed(wxCommandEvent& event); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void on_layers_slider_scroll_changed(wxCommandEvent& event); + void on_moves_slider_scroll_changed(wxCommandEvent& event); #else void on_sliders_scroll_changed(wxCommandEvent& event); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index be5b5b3ab4..35455fa9c0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1642,9 +1642,7 @@ struct Plater::priv bool init_view_toolbar(); #if ENABLE_GCODE_VIEWER void update_preview_bottom_toolbar(); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER - void update_preview_horz_slider(); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void update_preview_moves_slider(); #endif // ENABLE_GCODE_VIEWER void reset_all_gizmos(); @@ -1975,8 +1973,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) config->option("bed_custom_model")->value); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); +#if ENABLE_GCODE_VIEWER + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, [this](wxKeyEvent& evt) { preview->move_layers_slider(evt); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_layers_slider(evt); }); +#else preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); +#endif // ENABLE_GCODE_VIEWER q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); @@ -3879,12 +3882,10 @@ void Plater::priv::update_preview_bottom_toolbar() preview->update_bottom_toolbar(); } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER -void Plater::priv::update_preview_horz_slider() +void Plater::priv::update_preview_moves_slider() { - preview->update_horz_slider(); + preview->update_moves_slider(); } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER bool Plater::priv::can_set_instance_to_object() const @@ -5482,12 +5483,10 @@ void Plater::update_preview_bottom_toolbar() p->update_preview_bottom_toolbar(); } -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER -void Plater::update_preview_horz_slider() +void Plater::update_preview_moves_slider() { - p->update_preview_horz_slider(); + p->update_preview_moves_slider(); } -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER #endif // ENABLE_GCODE_VIEWER const Mouse3DController& Plater::get_mouse3d_controller() const diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 82c8bbe076..21eba8ad13 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -320,9 +320,7 @@ public: #if ENABLE_GCODE_VIEWER void update_preview_bottom_toolbar(); -#if ENABLE_GCODE_USE_WXWIDGETS_SLIDER - void update_preview_horz_slider(); -#endif // ENABLE_GCODE_USE_WXWIDGETS_SLIDER + void update_preview_moves_slider(); #endif // ENABLE_GCODE_VIEWER const Mouse3DController& get_mouse3d_controller() const; From 2b536137d2830847b42732b294f37698a4484519 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 15 May 2020 17:57:47 +0200 Subject: [PATCH 077/826] Tech ENABLE_GCODE_VIEWER -> Adapting DoubleSlider::Control for sequential view --- src/slic3r/GUI/DoubleSlider.cpp | 75 ++++++++++++++++++++++++++++++--- src/slic3r/GUI/DoubleSlider.hpp | 4 ++ src/slic3r/GUI/GLCanvas3D.hpp | 2 +- 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 597b9ad607..a6c9a5c772 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -838,8 +838,20 @@ void Control::draw_cog_icon(wxDC& dc) get_size(&width, &height); wxCoord x_draw, y_draw; - is_horizontal() ? x_draw = width-2 : x_draw = width - m_cog_icon_dim - 2; - is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height-2; +#if ENABLE_GCODE_VIEWER + if (m_draw_mode == dmSequentialGCodeView) + { + is_horizontal() ? x_draw = width - 2 : x_draw = 0.5 * width - 0.5 * m_cog_icon_dim; + is_horizontal() ? y_draw = 0.5 * height - 0.5 * m_cog_icon_dim : y_draw = height - 2; + } + else + { +#endif // ENABLE_GCODE_VIEWER + is_horizontal() ? x_draw = width - 2 : x_draw = width - m_cog_icon_dim - 2; + is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2; +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw); @@ -977,10 +989,19 @@ wxString Control::get_tooltip(int tick/*=-1*/) if (m_focus == fiRevertIcon) return _(L("Discard all custom changes")); if (m_focus == fiCogIcon) - return m_mode == t_mode::MultiAsSingle ? - GUI::from_u8((boost::format(_utf8(L("Jump to height %s or " - "Set extruder sequence for the entire print"))) % " (Shift + G)\n").str()) : - _(L("Jump to height")) + " (Shift + G)"; +#if ENABLE_GCODE_VIEWER + { + if (m_draw_mode == dmSequentialGCodeView) + return _L("Jump to move") + " (Shift + G)"; + else +#endif // ENABLE_GCODE_VIEWER + return m_mode == t_mode::MultiAsSingle ? + GUI::from_u8((boost::format(_utf8(L("Jump to height %s or " + "Set extruder sequence for the entire print"))) % " (Shift + G)\n").str()) : + _(L("Jump to height")) + " (Shift + G)"; +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER if (m_focus == fiColorBand) return m_mode != t_mode::SingleExtruder ? "" : _(L("Edit current color - Right click the colored slider segment")); @@ -1230,7 +1251,11 @@ void Control::OnLeftUp(wxMouseEvent& event) if (m_mode == t_mode::MultiAsSingle && m_draw_mode == dmRegular) show_cog_icon_context_menu(); else +#if ENABLE_GCODE_VIEWER + jump_to_value(); +#else jump_to_print_z(); +#endif // ENABLE_GCODE_VIEWER break; case maOneLayerIconClick: switch_one_layer_mode(); @@ -1385,7 +1410,11 @@ void Control::OnChar(wxKeyEvent& event) m_ticks.suppress_minus(false); } if (key == 'G') +#if ENABLE_GCODE_VIEWER + jump_to_value(); +#else jump_to_print_z(); +#endif // ENABLE_GCODE_VIEWER } void Control::OnRightDown(wxMouseEvent& event) @@ -1571,7 +1600,11 @@ void Control::show_cog_icon_context_menu() wxMenu menu; append_menu_item(&menu, wxID_ANY, _(L("Jump to height")) + " (Shift+G)", "", - [this](wxCommandEvent&) { jump_to_print_z(); }, "", &menu); +#if ENABLE_GCODE_VIEWER + [this](wxCommandEvent&) { jump_to_value(); }, "", & menu); +#else + [this](wxCommandEvent&) { jump_to_print_z(); }, "", &menu); +#endif // ENABLE_GCODE_VIEWER append_menu_item(&menu, wxID_ANY, _(L("Set extruder sequence for the entire print")), "", [this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu); @@ -1689,11 +1722,21 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height) return into_u8(dlg.GetValue()); } +#if ENABLE_GCODE_VIEWER +static double get_value_to_jump(double active_value, double min_z, double max_z, DrawMode mode) +#else static double get_print_z_to_jump(double active_print_z, double min_z, double max_z) +#endif // ENABLE_GCODE_VIEWER { +#if ENABLE_GCODE_VIEWER + wxString msg_text = (mode == dmSequentialGCodeView) ? _L("Enter the move you want to jump to") + ":" : _L("Enter the height you want to jump to") + ":"; + wxString msg_header = (mode == dmSequentialGCodeView) ? _L("Jump to move") : _L("Jump to height"); + wxString msg_in = GUI::double_to_string(active_value); +#else wxString msg_text = _(L("Enter the height you want to jump to")) + ":"; wxString msg_header = _(L("Jump to height")); wxString msg_in = GUI::double_to_string(active_print_z); +#endif // ENABLE_GCODE_VIEWER // get custom gcode wxTextEntryDialog dlg(nullptr, msg_text, msg_header, msg_in, wxTextEntryDialogStyle); @@ -1902,6 +1945,23 @@ void Control::edit_extruder_sequence() post_ticks_changed_event(ToolChangeCode); } +#if ENABLE_GCODE_VIEWER +void Control::jump_to_value() +{ + double value = get_value_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value], + m_values[m_min_value], m_values[m_max_value], m_draw_mode); + if (value < 0.0) + return; + + auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon()); + int tick_value = it - m_values.begin(); + + if (m_selection == ssLower) + SetLowerValue(tick_value); + else + SetHigherValue(tick_value); +} +#else void Control::jump_to_print_z() { double print_z = get_print_z_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value], @@ -1917,6 +1977,7 @@ void Control::jump_to_print_z() else SetHigherValue(tick_value); } +#endif // ENABLE_GCODE_VIEWER void Control::post_ticks_changed_event(const std::string& gcode /*= ""*/) { diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index fea1ba172e..71949f7f3e 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -253,7 +253,11 @@ public: void discard_all_thicks(); void move_current_thumb_to_pos(wxPoint pos); void edit_extruder_sequence(); +#if ENABLE_GCODE_VIEWER + void jump_to_value(); +#else void jump_to_print_z(); +#endif // ENABLE_GCODE_VIEWER void show_add_context_menu(); void show_edit_context_menu(); void show_cog_icon_context_menu(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 43d37607c5..2c059c648f 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -558,7 +558,7 @@ public: #if ENABLE_GCODE_VIEWER void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } - void update_gcode_sequential_view_current(unsigned int low, unsigned int high) { m_gcode_viewer.update_sequential_view_current(low, high); } + void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); } #endif // ENABLE_GCODE_VIEWER void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); From 163fbec8c84f6d314bd79a54a4741e050d52b3f3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 18 May 2020 13:24:07 +0200 Subject: [PATCH 078/826] GCodeViewer -> Completed implementation of slider for sequential view --- src/slic3r/GUI/GCodeViewer.cpp | 81 +++++++++++++++++--------------- src/slic3r/GUI/GCodeViewer.hpp | 20 ++++++-- src/slic3r/GUI/GUI_Preview.cpp | 84 ++++++++++++++++++++++++---------- src/slic3r/GUI/GUI_Preview.hpp | 15 ++++++ 4 files changed, 136 insertions(+), 64 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c4bc391d18..dcb320766a 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -12,6 +12,7 @@ #include "DoubleSlider.hpp" #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" +#include "GUI_Preview.hpp" #include "libslic3r/Model.hpp" #if ENABLE_GCODE_VIEWER_STATISTICS #include @@ -279,7 +280,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: } // update buffers' render paths - refresh_render_paths(false); + refresh_render_paths(false, false); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); @@ -349,16 +350,16 @@ unsigned int GCodeViewer::get_options_visibility_flags() const }; unsigned int flags = 0; - flags = set_flag(flags, 0, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel)); - flags = set_flag(flags, 1, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract)); - flags = set_flag(flags, 2, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract)); - flags = set_flag(flags, 3, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change)); - flags = set_flag(flags, 4, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change)); - flags = set_flag(flags, 5, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print)); - flags = set_flag(flags, 6, is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode)); - flags = set_flag(flags, 7, m_shells.visible); - flags = set_flag(flags, 8, m_sequential_view.marker.is_visible()); - flags = set_flag(flags, 9, is_legend_enabled()); + flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel)); + flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract)); + flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract)); + flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change)); + flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change)); + flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print)); + flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode)); + flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); + flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); + flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); return flags; } @@ -368,23 +369,24 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) return (flags & (1 << flag)) != 0; }; - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel, is_flag_set(0)); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract, is_flag_set(1)); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract, is_flag_set(2)); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change, is_flag_set(3)); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change, is_flag_set(4)); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print, is_flag_set(5)); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, is_flag_set(6)); - m_shells.visible = is_flag_set(7); - m_sequential_view.marker.set_visible(is_flag_set(8)); - enable_legend(is_flag_set(9)); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); + m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); + m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); + enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) { - bool keep_sequential_current = layers_z_range[1] <= m_layers_z_range[1]; + bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0]; + bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1]; m_layers_z_range = layers_z_range; - refresh_render_paths(keep_sequential_current); + refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last); wxGetApp().plater()->update_preview_moves_slider(); } @@ -628,7 +630,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } -void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const +void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const { #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); @@ -661,10 +663,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const m_statistics.render_paths_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS - m_sequential_view.first = m_vertices.vertices_count; - m_sequential_view.last = 0; - if (!keep_sequential_current) - m_sequential_view.current = m_vertices.vertices_count; + m_sequential_view.endpoints.first = m_vertices.vertices_count; + m_sequential_view.endpoints.last = 0; + if (!keep_sequential_current_first) + m_sequential_view.current.first = 0; + if (!keep_sequential_current_last) + m_sequential_view.current.last = m_vertices.vertices_count; // first pass: collect visible paths and update sequential view data std::vector> paths; @@ -690,22 +694,23 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const // store valid path paths.push_back({ &buffer, i }); - m_sequential_view.first = std::min(m_sequential_view.first, path.first.s_id); - m_sequential_view.last = std::max(m_sequential_view.last, path.last.s_id); + m_sequential_view.endpoints.first = std::min(m_sequential_view.endpoints.first, path.first.s_id); + m_sequential_view.endpoints.last = std::max(m_sequential_view.endpoints.last, path.last.s_id); } } // update current sequential position - m_sequential_view.current = keep_sequential_current ? std::clamp(m_sequential_view.current, m_sequential_view.first, m_sequential_view.last) : m_sequential_view.last; + m_sequential_view.current.first = keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.first; + m_sequential_view.current.last = keep_sequential_current_last ? std::clamp(m_sequential_view.current.last, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.last; glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); size_t v_size = VBuffer::vertex_size_bytes(); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(m_sequential_view.current * v_size), static_cast(v_size), static_cast(m_sequential_view.current_position.data()))); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(m_sequential_view.current.last * v_size), static_cast(v_size), static_cast(m_sequential_view.current_position.data()))); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); // second pass: filter paths by sequential data for (auto&& [buffer, id] : paths) { const Path& path = buffer->paths[id]; - if ((m_sequential_view.current < path.first.s_id) || (path.last.s_id < m_sequential_view.first)) + if ((m_sequential_view.current.last <= path.first.s_id) || (path.last.s_id <= m_sequential_view.current.first)) continue; Color color; @@ -722,8 +727,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current) const it->color = color; } - it->sizes.push_back(std::min(m_sequential_view.current, path.last.s_id) - path.first.s_id + 1); - it->offsets.push_back(static_cast(path.first.i_id * sizeof(unsigned int))); + it->sizes.push_back(std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1); + unsigned int delta_1st = 0; + if ((path.first.s_id < m_sequential_view.current.first) && (m_sequential_view.current.first <= path.last.s_id)) + delta_1st = m_sequential_view.current.first - path.first.s_id; + + it->offsets.push_back(static_cast((path.first.i_id + delta_1st) * sizeof(unsigned int))); } #if ENABLE_GCODE_VIEWER_STATISTICS @@ -1013,7 +1022,7 @@ void GCodeViewer::render_legend() const { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths - refresh_render_paths(false); + refresh_render_paths(false, false); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->update_preview_bottom_toolbar(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index e53bcb0c62..688f1266b5 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -222,9 +222,14 @@ public: void init_shader(); }; - unsigned int first{ 0 }; - unsigned int last{ 0 }; - unsigned int current{ 0 }; + struct Endpoints + { + unsigned int first{ 0 }; + unsigned int last{ 0 }; + }; + + Endpoints endpoints; + Endpoints current; Vec3f current_position{ Vec3f::Zero() }; Marker marker; }; @@ -285,7 +290,12 @@ public: const std::vector& get_layers_zs() const { return m_layers_zs; }; const SequentialView& get_sequential_view() const { return m_sequential_view; } - void update_sequential_view_current(unsigned int low, unsigned int high) { m_sequential_view.current = high; refresh_render_paths(true); } + void update_sequential_view_current(unsigned int first, unsigned int last) + { + m_sequential_view.current.first = first; + m_sequential_view.current.last = last; + refresh_render_paths(true, true); + } EViewType get_view_type() const { return m_view_type; } void set_view_type(EViewType type) { @@ -310,7 +320,7 @@ private: bool init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); - void refresh_render_paths(bool keep_sequential_current) const; + void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; void render_toolpaths() const; void render_shells() const; void render_legend() const; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 714ed3c9ef..65e1e50589 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -298,17 +298,17 @@ bool Preview::init(wxWindow* parent, Model* model) m_combochecklist_options = new wxComboCtrl(); m_combochecklist_options->Create(this, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); std::string options_items = GUI::into_u8( - _L("Travel") + "|0|" + - _L("Retractions") + "|0|" + - _L("Unretractions") + "|0|" + - _L("Tool changes") + "|0|" + - _L("Color changes") + "|0|" + - _L("Pause prints") + "|0|" + - _L("Custom GCodes") + "|0|" + - _L("Shells") + "|0|" + - _L("Tool marker") + "|1|" + - _L("Legend") + "|1" -); + get_option_type_string(OptionType::Travel) + "|0|" + + get_option_type_string(OptionType::Retractions) + "|0|" + + get_option_type_string(OptionType::Unretractions) + "|0|" + + get_option_type_string(OptionType::ToolChanges) + "|0|" + + get_option_type_string(OptionType::ColorChanges) + "|0|" + + get_option_type_string(OptionType::PausePrints) + "|0|" + + get_option_type_string(OptionType::CustomGCodes) + "|0|" + + get_option_type_string(OptionType::Shells) + "|0|" + + get_option_type_string(OptionType::ToolMarker) + "|0|" + + get_option_type_string(OptionType::Legend) + "|1" + ); Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); #else m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); @@ -328,19 +328,19 @@ bool Preview::init(wxWindow* parent, Model* model) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - m_moves_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL); + m_moves_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); - m_bottom_toolbar_sizer->AddSpacer(10); + m_bottom_toolbar_sizer->AddSpacer(5); m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); - m_bottom_toolbar_sizer->AddSpacer(10); + m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0); + m_bottom_toolbar_sizer->AddSpacer(5); m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5); - m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 5); + m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0); m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); - m_bottom_toolbar_sizer->AddSpacer(10); + m_bottom_toolbar_sizer->AddSpacer(5); m_bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 5); #else wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); @@ -686,8 +686,27 @@ void Preview::on_combochecklist_features(wxCommandEvent& evt) #if ENABLE_GCODE_VIEWER void Preview::on_combochecklist_options(wxCommandEvent& evt) { - m_canvas->set_gcode_options_visibility_from_flags(Slic3r::GUI::combochecklist_get_flags(m_combochecklist_options)); - refresh_print(); + auto xor = [](unsigned int flags1, unsigned int flags2, unsigned int flag) { + auto is_flag_set = [](unsigned int flags, unsigned int flag) { + return (flags & (1 << flag)) != 0; + }; + return !is_flag_set(flags1, flag) != !is_flag_set(flags2, flag); + }; + + unsigned int curr_flags = m_canvas->get_gcode_options_visibility_flags(); + unsigned int new_flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_options); + if (curr_flags == new_flags) + return; + + m_canvas->set_gcode_options_visibility_from_flags(new_flags); + + bool skip_refresh = xor(curr_flags, new_flags, static_cast(OptionType::Shells)) || + xor(curr_flags, new_flags, static_cast(OptionType::ToolMarker)); + + if (!skip_refresh) + refresh_print(); + else + m_canvas->set_as_dirty(); } #else void Preview::on_checkbox_travel(wxCommandEvent& evt) @@ -1082,16 +1101,16 @@ void Preview::update_layers_slider_from_canvas(wxKeyEvent& event) void Preview::update_moves_slider() { const GCodeViewer::SequentialView& view = m_canvas->get_gcode_sequential_view(); - std::vector values(view.last - view.first + 1); + std::vector values(view.endpoints.last - view.endpoints.first + 1); unsigned int count = 0; - for (unsigned int i = view.first; i <= view.last; ++i) + for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i) { values[count++] = static_cast(i + 1); } m_moves_slider->SetSliderValues(values); - m_moves_slider->SetMaxValue(view.last - view.first); - m_moves_slider->SetSelectionSpan(0, view.current); + m_moves_slider->SetMaxValue(view.endpoints.last - view.endpoints.first); + m_moves_slider->SetSelectionSpan(view.current.first - view.endpoints.first, view.current.last - view.endpoints.first); } #else void Preview::update_double_slider_from_canvas(wxKeyEvent & event) @@ -1356,6 +1375,25 @@ void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event) m_canvas->update_gcode_sequential_view_current(static_cast(m_moves_slider->GetLowerValueD()), static_cast(m_moves_slider->GetHigherValueD())); m_canvas->render(); } + +wxString Preview::get_option_type_string(OptionType type) const +{ + switch (type) + { + case OptionType::Travel: { return _L("Travel"); } + case OptionType::Retractions: { return _L("Retractions"); } + case OptionType::Unretractions: { return _L("Unretractions"); } + case OptionType::ToolChanges: { return _L("Tool changes"); } + case OptionType::ColorChanges: { return _L("Color changes"); } + case OptionType::PausePrints: { return _L("Pause prints"); } + case OptionType::CustomGCodes: { return _L("Custom GCodes"); } + case OptionType::Shells: { return _L("Shells"); } + case OptionType::ToolMarker: { return _L("Tool marker"); } + case OptionType::Legend: { return _L("Legend"); } + default: { return ""; } + } +} + #endif // ENABLE_GCODE_VIEWER } // namespace GUI diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 3f0d4b4e68..9cf694ece3 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -136,6 +136,20 @@ class Preview : public wxPanel public: #if ENABLE_GCODE_VIEWER + enum class OptionType : unsigned int + { + Travel, + Retractions, + Unretractions, + ToolChanges, + ColorChanges, + PausePrints, + CustomGCodes, + Shells, + ToolMarker, + Legend + }; + Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodeProcessor::Result* gcode_result, std::function schedule_background_process = []() {}); #else @@ -235,6 +249,7 @@ private: #if ENABLE_GCODE_VIEWER void on_layers_slider_scroll_changed(wxCommandEvent& event); void on_moves_slider_scroll_changed(wxCommandEvent& event); + wxString get_option_type_string(OptionType type) const; #else void on_sliders_scroll_changed(wxCommandEvent& event); #endif // ENABLE_GCODE_VIEWER From f4303fc419d8ed2a0ded1c1cec795284c49c51fe Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 18 May 2020 13:32:07 +0200 Subject: [PATCH 079/826] Attempt to fix build on OsX --- src/slic3r/GUI/GUI_Preview.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 65e1e50589..bf694b445f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -686,7 +686,7 @@ void Preview::on_combochecklist_features(wxCommandEvent& evt) #if ENABLE_GCODE_VIEWER void Preview::on_combochecklist_options(wxCommandEvent& evt) { - auto xor = [](unsigned int flags1, unsigned int flags2, unsigned int flag) { + auto xored = [](unsigned int flags1, unsigned int flags2, unsigned int flag) { auto is_flag_set = [](unsigned int flags, unsigned int flag) { return (flags & (1 << flag)) != 0; }; @@ -700,8 +700,8 @@ void Preview::on_combochecklist_options(wxCommandEvent& evt) m_canvas->set_gcode_options_visibility_from_flags(new_flags); - bool skip_refresh = xor(curr_flags, new_flags, static_cast(OptionType::Shells)) || - xor(curr_flags, new_flags, static_cast(OptionType::ToolMarker)); + bool skip_refresh = xored(curr_flags, new_flags, static_cast(OptionType::Shells)) || + xored(curr_flags, new_flags, static_cast(OptionType::ToolMarker)); if (!skip_refresh) refresh_print(); From 053f509437693938283df052b2376893091bbb8d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 19 May 2020 10:04:14 +0200 Subject: [PATCH 080/826] GCodeViewer -> Fixed visibility of bottom toolbar --- src/slic3r/GUI/GUI_Preview.cpp | 94 +++++++++++++++++++++++++--------- src/slic3r/GUI/GUI_Preview.hpp | 3 +- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index bf694b445f..fdbd396e26 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -184,8 +184,8 @@ Preview::Preview( : m_canvas_widget(nullptr) , m_canvas(nullptr) #if ENABLE_GCODE_VIEWER - , m_bottom_toolbar_sizer(nullptr) , m_layers_slider_sizer(nullptr) + , m_bottom_toolbar_panel(nullptr) #else , m_double_slider_sizer(nullptr) #endif // ENABLE_GCODE_VIEWER @@ -194,6 +194,7 @@ Preview::Preview( , m_label_show(nullptr) , m_combochecklist_features(nullptr) #if ENABLE_GCODE_VIEWER + , m_combochecklist_features_pos(0) , m_combochecklist_options(nullptr) #else , m_checkbox_travel(nullptr) @@ -251,14 +252,20 @@ bool Preview::init(wxWindow* parent, Model* model) #if ENABLE_GCODE_VIEWER m_layers_slider_sizer = create_layers_slider_sizer(); + + m_bottom_toolbar_panel = new wxPanel(this); + + m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View")); + + m_choice_view_type = new wxChoice(m_bottom_toolbar_panel, wxID_ANY); #else m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); create_double_slider(); -#endif // ENABLE_GCODE_VIEWER m_label_view_type = new wxStaticText(this, wxID_ANY, _L("View")); m_choice_view_type = new wxChoice(this, wxID_ANY); +#endif // ENABLE_GCODE_VIEWER m_choice_view_type->Append(_L("Feature type")); m_choice_view_type->Append(_L("Height")); m_choice_view_type->Append(_L("Width")); @@ -269,10 +276,18 @@ bool Preview::init(wxWindow* parent, Model* model) m_choice_view_type->Append(_L("Color Print")); m_choice_view_type->SetSelection(0); +#if ENABLE_GCODE_VIEWER + m_label_show = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("Show")); +#else m_label_show = new wxStaticText(this, wxID_ANY, _L("Show")); +#endif // ENABLE_GCODE_VIEWER m_combochecklist_features = new wxComboCtrl(); +#if ENABLE_GCODE_VIEWER + m_combochecklist_features->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); +#else m_combochecklist_features->Create(this, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); +#endif // ENABLE_GCODE_VIEWER std::string feature_items = GUI::into_u8( #if ENABLE_GCODE_VIEWER _L("Unknown") + "|1|" + @@ -296,7 +311,7 @@ bool Preview::init(wxWindow* parent, Model* model) #if ENABLE_GCODE_VIEWER m_combochecklist_options = new wxComboCtrl(); - m_combochecklist_options->Create(this, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); + m_combochecklist_options->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY); std::string options_items = GUI::into_u8( get_option_type_string(OptionType::Travel) + "|0|" + get_option_type_string(OptionType::Retractions) + "|0|" + @@ -328,20 +343,23 @@ bool Preview::init(wxWindow* parent, Model* model) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - m_moves_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); + m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); - m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); - m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); - m_bottom_toolbar_sizer->AddSpacer(5); - m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); - m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0); - m_bottom_toolbar_sizer->AddSpacer(5); - m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5); - m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0); - m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); - m_bottom_toolbar_sizer->AddSpacer(5); - m_bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 5); + wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); + bottom_toolbar_sizer->AddSpacer(5); + bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0); + bottom_toolbar_sizer->AddSpacer(5); + bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5); + bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0); + // change the following number if editing the layout of the bottom toolbar sizer. It is used into update_bottom_toolbar() + m_combochecklist_features_pos = 6; + bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); + bottom_toolbar_sizer->Hide(m_combochecklist_features); + bottom_toolbar_sizer->AddSpacer(5); + bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0); + m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer); #else wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); @@ -364,8 +382,8 @@ bool Preview::init(wxWindow* parent, Model* model) wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0); #if ENABLE_GCODE_VIEWER - main_sizer->Add(m_bottom_toolbar_sizer, 0, wxALL | wxEXPAND, 0); - main_sizer->Hide(m_bottom_toolbar_sizer); + main_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0); + main_sizer->Hide(m_bottom_toolbar_panel); #else main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0); #endif // ENABLE_GCODE_VIEWER @@ -565,6 +583,7 @@ void Preview::bind_event_handlers() m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); #if ENABLE_GCODE_VIEWER m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); + m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); #else m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); @@ -581,6 +600,7 @@ void Preview::unbind_event_handlers() m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this); #if ENABLE_GCODE_VIEWER m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this); + m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this); #else m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this); m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); @@ -773,10 +793,33 @@ void Preview::update_bottom_toolbar() combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags()); combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags()); - m_bottom_toolbar_sizer->Show(m_combochecklist_features, - !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType); - m_bottom_toolbar_sizer->Layout(); - Refresh(); + // updates visibility of features combobox + if (m_bottom_toolbar_panel->IsShown()) + { + wxSizer* sizer = m_bottom_toolbar_panel->GetSizer(); + bool show = !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType; + + if (show) + { + if (sizer->GetItem(m_combochecklist_features) == nullptr) + { + sizer->Insert(m_combochecklist_features_pos, m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5); + sizer->Show(m_combochecklist_features); + sizer->Layout(); + Refresh(); + } + } + else + { + if (sizer->GetItem(m_combochecklist_features) != nullptr) + { + sizer->Hide(m_combochecklist_features); + sizer->Detach(m_combochecklist_features); + sizer->Layout(); + Refresh(); + } + } + } } #endif // ENABLE_GCODE_VIEWER @@ -1243,8 +1286,9 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER m_canvas->load_gcode_preview(*m_gcode_result); m_canvas->refresh_gcode_preview(*m_gcode_result, colors); - GetSizer()->Show(m_bottom_toolbar_sizer); + GetSizer()->Show(m_bottom_toolbar_panel); GetSizer()->Layout(); + Refresh(); zs = m_canvas->get_gcode_layers_zs(); #else m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); @@ -1254,8 +1298,9 @@ void Preview::load_print_as_fff(bool keep_z_range) // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); #if ENABLE_GCODE_VIEWER - GetSizer()->Hide(m_bottom_toolbar_sizer); + GetSizer()->Hide(m_bottom_toolbar_panel); GetSizer()->Layout(); + Refresh(); zs = m_canvas->get_volumes_print_zs(true); #endif // ENABLE_GCODE_VIEWER } @@ -1316,8 +1361,9 @@ void Preview::load_print_as_sla() { m_canvas->load_sla_preview(); #if ENABLE_GCODE_VIEWER - GetSizer()->Hide(m_bottom_toolbar_sizer); + GetSizer()->Hide(m_bottom_toolbar_panel); GetSizer()->Layout(); + Refresh(); #else show_hide_ui_elements("none"); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 9cf694ece3..64aa8f7e35 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -84,7 +84,7 @@ class Preview : public wxPanel GLCanvas3D* m_canvas; #if ENABLE_GCODE_VIEWER wxBoxSizer* m_layers_slider_sizer; - wxBoxSizer* m_bottom_toolbar_sizer; + wxPanel* m_bottom_toolbar_panel; #else wxBoxSizer* m_double_slider_sizer; #endif // ENABLE_GCODE_VIEWER @@ -93,6 +93,7 @@ class Preview : public wxPanel wxStaticText* m_label_show; wxComboCtrl* m_combochecklist_features; #if ENABLE_GCODE_VIEWER + size_t m_combochecklist_features_pos; wxComboCtrl* m_combochecklist_options; #else wxCheckBox* m_checkbox_travel; From 98c2e3c7b12b45bba6369330ad980bbe1c825077 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 19 May 2020 11:17:47 +0200 Subject: [PATCH 081/826] GCodeViewer -> New icons for thumbs of horizontal DoubleSlider::Control --- resources/icons/thumb_left.svg | 54 +++++++++++++++++++++++++++++++++ resources/icons/thumb_right.svg | 54 +++++++++++++++++++++++++++++++++ src/slic3r/GUI/DoubleSlider.cpp | 12 +++++++- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 resources/icons/thumb_left.svg create mode 100644 resources/icons/thumb_right.svg diff --git a/resources/icons/thumb_left.svg b/resources/icons/thumb_left.svg new file mode 100644 index 0000000000..ef78bd1410 --- /dev/null +++ b/resources/icons/thumb_left.svg @@ -0,0 +1,54 @@ + +image/svg+xml + + + + + + diff --git a/resources/icons/thumb_right.svg b/resources/icons/thumb_right.svg new file mode 100644 index 0000000000..f3748525d2 --- /dev/null +++ b/resources/icons/thumb_right.svg @@ -0,0 +1,54 @@ + +image/svg+xml + + + + + + diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index a6c9a5c772..6173680817 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -64,8 +64,13 @@ Control::Control( wxWindow *parent, if (!is_osx) SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX +#if ENABLE_GCODE_VIEWER + m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up")); + m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left") : ScalableBitmap(this, "thumb_down")); +#else m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up")); m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down")); +#endif // ENABLE_GCODE_VIEWER m_thumb_size = m_bmp_thumb_lower.GetBmpSize(); m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add"); @@ -576,6 +581,10 @@ void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) { +#if ENABLE_GCODE_VIEWER + wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x); + wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y); +#else wxCoord x_draw, y_draw; if (selection == ssLower) { if (is_horizontal()) { @@ -587,7 +596,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider y_draw = pos.y - int(0.5*m_thumb_size.y); } } - else{ + else { if (is_horizontal()) { x_draw = pos.x; y_draw = pos.y - int(0.5*m_thumb_size.y); @@ -597,6 +606,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider y_draw = pos.y - int(0.5*m_thumb_size.y); } } +#endif // ENABLE_GCODE_VIEWER dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw); // Update thumb rect diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index fdbd396e26..1784dbccc1 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -343,7 +343,7 @@ bool Preview::init(wxWindow* parent, Model* model) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); + m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 4 * GetTextExtent("m").y), wxSL_HORIZONTAL); m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); From c7c87973b723a306b95558a476a5b27d8232800d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 20 May 2020 14:11:22 +0200 Subject: [PATCH 082/826] First installment of tech ENABLE_SHADERS_MANAGER, using class GLShadersManager as a central point to manage OpenGL shaders --- src/libslic3r/GCode/ToolOrdering.cpp | 4 +- src/libslic3r/Technologies.hpp | 3 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/3DBed.cpp | 75 +++++++ src/slic3r/GUI/3DBed.hpp | 18 ++ src/slic3r/GUI/GCodeViewer.cpp | 197 +++++++++++++++++- src/slic3r/GUI/GCodeViewer.hpp | 55 +++++ src/slic3r/GUI/GLCanvas3D.cpp | 192 +++++++++++++++++- src/slic3r/GUI/GLCanvas3D.hpp | 34 +++- src/slic3r/GUI/GLShader.cpp | 289 ++++++++++++++++++++++++++- src/slic3r/GUI/GLShader.hpp | 61 ++++++ src/slic3r/GUI/GLShadersManager.cpp | 76 +++++++ src/slic3r/GUI/GLShadersManager.hpp | 31 +++ src/slic3r/GUI/GUI_App.hpp | 10 + src/slic3r/GUI/OpenGLManager.cpp | 86 +++++++- src/slic3r/GUI/OpenGLManager.hpp | 22 +- src/slic3r/GUI/Selection.cpp | 263 +++++++++++++++++++++--- src/slic3r/GUI/Selection.hpp | 44 ++++ 18 files changed, 1415 insertions(+), 47 deletions(-) create mode 100644 src/slic3r/GUI/GLShadersManager.cpp create mode 100644 src/slic3r/GUI/GLShadersManager.hpp diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index db398f06c8..9c1a1900fd 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -400,7 +400,9 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. - assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) { for (size_t i = 0; i + 1 < m_layer_tools.size();) { const LayerTools < = m_layer_tools[i]; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 3df9da961d..c274c1e841 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -44,6 +44,9 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#define ENABLE_SHADERS_MANAGER (1 && ENABLE_GCODE_VIEWER) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index b085fad456..5d47f97589 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -23,6 +23,8 @@ set(SLIC3R_GUI_SOURCES GUI/3DScene.cpp GUI/3DScene.hpp GUI/format.hpp + GUI/GLShadersManager.hpp + GUI/GLShadersManager.cpp GUI/GLShader.cpp GUI/GLShader.hpp GUI/GLCanvas3D.hpp diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 4ef8679603..4bf8ce900e 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -15,6 +15,11 @@ #if ENABLE_GCODE_VIEWER #include "3DScene.hpp" #endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if ENABLE_SHADERS_MANAGER +//#include "GLShader.hpp" +//#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include @@ -163,9 +168,17 @@ Bed3D::Axes::~Axes() void Bed3D::Axes::render() const { #if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + auto render_axis = [this](const Transform3f& transform) { +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto render_axis = [this](const Transform3f& transform, GLint color_id, const std::array& color) { if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(transform.data())); @@ -174,14 +187,43 @@ void Bed3D::Axes::render() const }; m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; if (!m_shader.is_initialized()) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glEnable(GL_DEPTH_TEST)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->start_using(); + + // x axis + shader->set_uniform("uniform_color", { 0.75f, 0.0f, 0.0f, 1.0f }); + render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0f }).cast()); + + // y axis + shader->set_uniform("uniform_color", { 0.0f, 0.75f, 0.0f, 1.0f }); + render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0f }).cast()); + + // z axis + shader->set_uniform("uniform_color", { 0.0f, 0.0f, 0.75f, 1.0f }); + render_axis(Geometry::assemble_transform(m_origin).cast()); + + shader->stop_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.start_using(); GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); @@ -195,6 +237,9 @@ void Bed3D::Axes::render() const render_axis(Geometry::assemble_transform(m_origin).cast(), color_id, { 0.0f, 0.0f, 0.75f, 1.0f }); m_shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glDisable(GL_DEPTH_TEST)); #else @@ -540,6 +585,16 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const if (m_triangles.get_vertices_count() > 0) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("printbed"); + if (shader != nullptr) + { + shader->start_using(); + shader->set_uniform("transparent_background", bottom); + shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_shader.get_shader_program_id() == 0) m_shader.init("printbed.vs", "printbed.fs"); @@ -548,6 +603,9 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const m_shader.start_using(); m_shader.set_uniform("transparent_background", bottom); m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_vbo_id == 0) { @@ -568,8 +626,17 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const unsigned int stride = m_triangles.get_vertex_data_size(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLint position_id = shader->get_attrib_location("v_position"); + GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint position_id = m_shader.get_attrib_location("v_position"); GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // show the temporary texture while no compressed data is available GLuint tex_id = (GLuint)m_temp_texture.get_id(); @@ -607,7 +674,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const glsafe(::glDisable(GL_BLEND)); glsafe(::glDepthMask(GL_TRUE)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } } } diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 440468233c..9603015da8 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,7 +3,13 @@ #include "GLTexture.hpp" #include "3DScene.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER #include "GLModel.hpp" #endif // ENABLE_GCODE_VIEWER @@ -69,7 +75,13 @@ class Bed3D Vec3d m_origin{ Vec3d::Zero() }; float m_stem_length{ DefaultStemLength }; mutable GL_Model m_arrow; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ mutable Shader m_shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ public: #else @@ -118,7 +130,13 @@ private: mutable GLBed m_model; // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ mutable Shader m_shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ mutable unsigned int m_vbo_id; Axes m_axes; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index dcb320766a..55f603829b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -12,6 +12,11 @@ #include "DoubleSlider.hpp" #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if ENABLE_SHADERS_MANAGER +//#include "GLShader.hpp" +//#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GUI_Preview.hpp" #include "libslic3r/Model.hpp" #if ENABLE_GCODE_VIEWER_STATISTICS @@ -107,6 +112,9 @@ void GCodeViewer::IBuffer::reset() render_paths = std::vector(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src) { if (!shader.init(vertex_shader_src, fragment_shader_src)) { @@ -116,6 +124,9 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con return true; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) { @@ -149,11 +160,33 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con void GCodeViewer::SequentialView::Marker::init() { m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ init_shader(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GCodeViewer::SequentialView::Marker::render() const { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + if (!m_visible) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + shader->start_using(); + shader->set_uniform("uniform_color", m_color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_visible || !m_shader.is_initialized()) return; @@ -164,6 +197,9 @@ void GCodeViewer::SequentialView::Marker::render() const GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_color.data())); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(m_world_transform.data())); @@ -172,16 +208,30 @@ void GCodeViewer::SequentialView::Marker::render() const glsafe(::glPopMatrix()); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glDisable(GL_BLEND)); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void GCodeViewer::SequentialView::Marker::init_shader() { if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.50f, 0.50f, 0.50f }, // erNone @@ -390,6 +440,31 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range wxGetApp().plater()->update_preview_moves_slider(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +void GCodeViewer::init_shaders() +{ + unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); + unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + + for (unsigned char i = begin_id; i < end_id; ++i) + { + switch (buffer_type(i)) + { + case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = "toolchanges"; break; } + case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = "colorchanges"; break; } + case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = "pauses"; break; } + case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = "customs"; break; } + case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = "retractions"; break; } + case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = "unretractions"; break; } + case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "extrusions"; break; } + case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "travels"; break; } + default: { break; } + } + } +} +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GCodeViewer::init_shaders() { unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); @@ -424,6 +499,9 @@ bool GCodeViewer::init_shaders() return true; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { @@ -750,6 +828,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto set_color = [](GLint current_program_id, const Color& color) { if (current_program_id > 0) { GLint color_id = ::glGetUniformLocation(current_program_id, "uniform_color"); @@ -760,6 +841,9 @@ void GCodeViewer::render_toolpaths() const } BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glCullFace(GL_BACK)); glsafe(::glLineWidth(3.0f)); @@ -779,11 +863,29 @@ void GCodeViewer::render_toolpaths() const if (!buffer.visible) continue; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); + if (shader != nullptr) { +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (buffer.shader.is_initialized()) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + GCodeProcessor::EMoveType type = buffer_type(i); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->start_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ buffer.shader.start_using(); - +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); switch (type) @@ -791,7 +893,15 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Tool_change: { Color color = { 1.0f, 1.0f, 1.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -807,7 +917,15 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Color_change: { Color color = { 1.0f, 0.0f, 0.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -823,7 +941,15 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Pause_Print: { Color color = { 0.0f, 1.0f, 0.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -839,7 +965,15 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Custom_GCode: { Color color = { 0.0f, 0.0f, 1.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -855,7 +989,15 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Retract: { Color color = { 1.0f, 0.0f, 1.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -871,7 +1013,15 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Unretract: { Color color = { 0.0f, 1.0f, 1.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -888,7 +1038,15 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", path.color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -901,7 +1059,15 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", path.color); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -913,7 +1079,15 @@ void GCodeViewer::render_toolpaths() const } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ buffer.shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } } @@ -923,6 +1097,24 @@ void GCodeViewer::render_toolpaths() const void GCodeViewer::render_shells() const { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + if (!m_shells.visible || m_shells.volumes.empty()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("shells"); + if (shader == nullptr) + return; + +// glsafe(::glDepthMask(GL_FALSE)); + + shader->start_using(); + m_shells.volumes.render(GLVolumeCollection::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix()); + shader->stop_using(); + +// glsafe(::glDepthMask(GL_TRUE)); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_shells.visible || m_shells.volumes.empty() || !m_shells.shader.is_initialized()) return; @@ -933,6 +1125,9 @@ void GCodeViewer::render_shells() const m_shells.shader.stop_using(); // glsafe(::glDepthMask(GL_TRUE)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GCodeViewer::render_legend() const diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 688f1266b5..df28f4aa63 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -2,7 +2,13 @@ #define slic3r_GCodeViewer_hpp_ #if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GLModel.hpp" @@ -78,13 +84,27 @@ class GCodeViewer { unsigned int ibo_id{ 0 }; size_t indices_count{ 0 }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + std::string shader; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::vector paths; std::vector render_paths; bool visible{ false }; void reset(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); }; @@ -93,7 +113,13 @@ class GCodeViewer { GLVolumeCollection volumes; bool visible{ false }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ }; // helper to render extrusion paths @@ -203,7 +229,13 @@ public: Transform3f m_world_transform; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; bool m_visible{ false }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ public: void init(); @@ -218,8 +250,14 @@ public: void render() const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ private: void init_shader(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ }; struct Endpoints @@ -273,7 +311,16 @@ public: bool init() { set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); m_sequential_view.marker.init(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + init_shaders(); + return true; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return init_shaders(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } // extract rendering data from the given parameters @@ -317,7 +364,15 @@ public: void enable_legend(bool enable) { m_legend_enabled = enable; } private: +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + void init_shaders(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool init_shaders(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 82c7576a25..c1c310b34c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -156,10 +156,19 @@ GLCanvas3D::LayersEditing::~LayersEditing() const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +void GLCanvas3D::LayersEditing::init() +{ +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) { if (!m_shader.init(vertex_shader_filename, fragment_shader_filename)) return false; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); @@ -170,7 +179,13 @@ bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return true; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) @@ -203,7 +218,15 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) bool GLCanvas3D::LayersEditing::is_allowed() const { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } bool GLCanvas3D::LayersEditing::is_enabled() const @@ -361,7 +384,15 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) bool GLCanvas3D::LayersEditing::is_initialized() const { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + return wxGetApp().get_shader("variable_layer_height") != nullptr; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return m_shader.is_initialized(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const @@ -395,6 +426,21 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); + if (shader == nullptr) + return; + + shader->start_using(); + + shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); + shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); + shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); + shader->set_uniform("z_cursor_band_width", band_width); + shader->set_uniform("object_max_z", m_object_max_z); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.start_using(); m_shader.set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); @@ -402,6 +448,9 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 m_shader.set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); m_shader.set_uniform("z_cursor_band_width", band_width); m_shader.set_uniform("object_max_z", m_object_max_z); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); @@ -421,7 +470,15 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 glsafe(::glEnd()); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const @@ -455,6 +512,29 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G { assert(this->is_allowed()); assert(this->last_object_id != -1); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); + assert(shader != nullptr); + + GLint current_program_id = 0; + glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); + if (shader->get_id() != static_cast(current_program_id)) + // The layer editing shader is not yet active. Activate it. + shader->start_using(); + else + // The layer editing shader was already active. + current_program_id = 0; + + const_cast(this)->generate_layer_height_texture(); + + // Uniforms were resolved, go ahead using the layer editing shader. + shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z))); + shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height)); + shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); + shader->set_uniform("z_cursor_band_width", float(this->band_width)); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint shader_id = m_shader.get_shader()->shader_program_id; assert(shader_id > 0); @@ -467,15 +547,16 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G // The layer editing shader was already active. current_program_id = -1; - GLint z_to_texture_row_id = ::glGetUniformLocation(shader_id, "z_to_texture_row"); - GLint z_texture_row_to_normalized_id = ::glGetUniformLocation(shader_id, "z_texture_row_to_normalized"); - GLint z_cursor_id = ::glGetUniformLocation(shader_id, "z_cursor"); - GLint z_cursor_band_width_id = ::glGetUniformLocation(shader_id, "z_cursor_band_width"); - GLint world_matrix_id = ::glGetUniformLocation(shader_id, "volume_world_matrix"); - GLint object_max_z_id = ::glGetUniformLocation(shader_id, "object_max_z"); + GLint z_to_texture_row_id = ::glGetUniformLocation(shader_id, "z_to_texture_row"); + GLint z_texture_row_to_normalized_id = ::glGetUniformLocation(shader_id, "z_texture_row_to_normalized"); + GLint z_cursor_id = ::glGetUniformLocation(shader_id, "z_cursor"); + GLint z_cursor_band_width_id = ::glGetUniformLocation(shader_id, "z_cursor_band_width"); + GLint world_matrix_id = ::glGetUniformLocation(shader_id, "volume_world_matrix"); + GLint object_max_z_id = ::glGetUniformLocation(shader_id, "object_max_z"); glcheck(); - if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1) + + if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1) { const_cast(this)->generate_layer_height_texture(); @@ -484,6 +565,9 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glsafe(::glUniform1f(z_texture_row_to_normalized_id, GLfloat(1.0f / m_layers_texture.height))); glsafe(::glUniform1f(z_cursor_id, GLfloat(m_object_max_z) * GLfloat(this->get_cursor_z_relative(canvas)))); glsafe(::glUniform1f(z_cursor_band_width_id, GLfloat(this->band_width))); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Initialize the layer height texture mapping. GLsizei w = (GLsizei)m_layers_texture.width; GLsizei h = (GLsizei)m_layers_texture.height; @@ -499,17 +583,29 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G // Render the object using the layer editing shader and texture. if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); + shader->set_uniform("object_max_z", GLfloat(0)); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (world_matrix_id != -1) glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); if (object_max_z_id != -1) glsafe(::glUniform1f(object_max_z_id, GLfloat(0))); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glvolume->render(); } // Revert back to the previous shader. glBindTexture(GL_TEXTURE_2D, 0); if (current_program_id > 0) glsafe(::glUseProgram(current_program_id)); - } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + } else { // Something went wrong. Just render the object. @@ -522,6 +618,9 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glvolume->render(); } } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GLCanvas3D::LayersEditing::adjust_layer_height_profile() @@ -1645,6 +1744,12 @@ bool GLCanvas3D::init() if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + if (m_main_toolbar.is_enabled()) + m_layers_editing.init(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_shader.init("gouraud.vs", "gouraud.fs")) { std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl; @@ -1656,6 +1761,9 @@ bool GLCanvas3D::init() std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; return false; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER if (!m_main_toolbar.is_enabled()) @@ -4515,8 +4623,17 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool return ret; }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + static const std::array orange = { 0.923f, 0.504f, 0.264f, 1.0f }; + static const std::array gray = { 0.64f, 0.64f, 0.64f, 1.0f }; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const GLfloat orange[] = { 0.923f, 0.504f, 0.264f, 1.0f }; static const GLfloat gray[] = { 0.64f, 0.64f, 0.64f, 1.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLVolumePtrs visible_volumes; @@ -4560,6 +4677,22 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool camera.apply_projection(box, near_z, far_z); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); + if (shader == nullptr) + return; + + if (transparent_background) + glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); + + glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + shader->start_using(); + shader->set_uniform("print_box.volume_detection", 0); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (transparent_background) glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); @@ -4575,24 +4708,44 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool if (print_box_detection_id != -1) glsafe(::glUniform1i(print_box_detection_id, 0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const GLVolume* vol : visible_volumes) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (vol->printable && !vol->is_outside) ? orange : gray)); else glsafe(::glColor4fv((vol->printable && !vol->is_outside) ? orange : gray)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ vol->render(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glDisable(GL_DEPTH_TEST)); if (show_bed) _render_bed(!camera.is_looking_downward(), false); + // restore background color if (transparent_background) glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); } @@ -5531,7 +5684,18 @@ void GLCanvas3D::_render_objects() const m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); + if (shader != nullptr) + { + shader->start_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.start_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { int object_id = m_layers_editing.last_object_id; m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { @@ -5540,7 +5704,8 @@ void GLCanvas3D::_render_objects() const }); // Let LayersEditing handle rendering of the active object using the layer height profile shader. m_layers_editing.render_volumes(*this, this->m_volumes); - } else { + } + else { // do not cull backfaces to show broken geometry, if any m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); @@ -5548,7 +5713,16 @@ void GLCanvas3D::_render_objects() const } m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); + } +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_camera_clipping_plane = ClippingPlane::ClipsNothing(); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9d77790619..635d23e544 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -7,7 +7,13 @@ #include "3DScene.hpp" #include "GLToolbar.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "Event.hpp" #include "Selection.hpp" #include "Gizmos/GLGizmosManager.hpp" @@ -167,7 +173,13 @@ private: private: bool m_enabled; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ unsigned int m_z_texture_id; // Not owned by LayersEditing. const DynamicPrintConfig *m_config; @@ -214,8 +226,16 @@ private: LayersEditing(); ~LayersEditing(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + void init(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); - void set_config(const DynamicPrintConfig* config); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void set_config(const DynamicPrintConfig* config); void select_object(const Model &model, int object_id); bool is_allowed() const; @@ -457,7 +477,13 @@ private: WarningTexture m_warning_texture; wxTimer m_timer; LayersEditing m_layers_editing; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Mouse m_mouse; mutable GLGizmosManager m_gizmos; mutable GLToolbar m_main_toolbar; @@ -587,7 +613,13 @@ public: void set_color_by(const std::string& value); void refresh_camera_scene_box(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const Shader& get_shader() const { return m_shader; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ BoundingBoxf3 volumes_bounding_box() const; BoundingBoxf3 scene_bounding_box() const; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index c310760603..3b7c2abcc6 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -1,11 +1,289 @@ -#include - +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#include "libslic3r/libslic3r.h" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" -#include "libslic3r/Utils.hpp" #include "3DScene.hpp" -#include +#include "libslic3r/Utils.hpp" +#include +#include + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +#include + +namespace Slic3r { + +GLShaderProgram::~GLShaderProgram() +{ + if (m_id > 0) + glsafe(::glDeleteProgram(m_id)); +} + +bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilenames& filenames) +{ + auto load_from_file = [](const std::string& filename) { + std::string path = resources_dir() + "/shaders/" + filename; + boost::nowide::ifstream s(path, boost::nowide::ifstream::binary); + if (!s.good()) + { + BOOST_LOG_TRIVIAL(error) << "Couldn't open file: '" << path << "'"; + return std::string(); + } + + s.seekg(0, s.end); + int file_length = static_cast(s.tellg()); + s.seekg(0, s.beg); + std::string source(file_length, '\0'); + s.read(source.data(), file_length); + if (!s.good()) + { + BOOST_LOG_TRIVIAL(error) << "Error while loading file: '" << path << "'"; + return std::string(); + } + + s.close(); + return source; + }; + + ShaderSources sources = {}; + for (size_t i = 0; i < static_cast(EShaderType::Count); ++i) { + sources[i] = filenames[i].empty() ? std::string() : load_from_file(filenames[i]); + } + + bool valid = (!sources[static_cast(EShaderType::Vertex)].empty() && !sources[static_cast(EShaderType::Fragment)].empty()) || + !sources[static_cast(EShaderType::Compute)].empty(); + + return valid ? init_from_texts(name, sources) : false; +} + +bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSources& sources) +{ + auto shader_type_as_string = [](EShaderType type) { + switch (type) + { + case EShaderType::Vertex: { return "vertex"; } + case EShaderType::Fragment: { return "fragment"; } + case EShaderType::Geometry: { return "geometry"; } + case EShaderType::TessEvaluation: { return "tesselation evaluation"; } + case EShaderType::TessControl: { return "tesselation control"; } + case EShaderType::Compute: { return "compute"; } + default: { return "unknown"; } + } + }; + + auto create_shader = [](EShaderType type) { + GLuint id = 0; + switch (type) + { + case EShaderType::Vertex: { id = ::glCreateShader(GL_VERTEX_SHADER); glcheck(); break; } + case EShaderType::Fragment: { id = ::glCreateShader(GL_FRAGMENT_SHADER); glcheck(); break; } + case EShaderType::Geometry: { id = ::glCreateShader(GL_GEOMETRY_SHADER); glcheck(); break; } + case EShaderType::TessEvaluation: { id = ::glCreateShader(GL_TESS_EVALUATION_SHADER); glcheck(); break; } + case EShaderType::TessControl: { id = ::glCreateShader(GL_TESS_CONTROL_SHADER); glcheck(); break; } + case EShaderType::Compute: { id = ::glCreateShader(GL_COMPUTE_SHADER); glcheck(); break; } + default: { break; } + } + + return (id == 0) ? std::make_pair(false, GLuint(0)) : std::make_pair(true, id); + }; + + auto release_shaders = [](const std::array(EShaderType::Count)>& shader_ids) { + for (size_t i = 0; i < static_cast(EShaderType::Count); ++i) { + if (shader_ids[i] > 0) + glsafe(::glDeleteShader(shader_ids[i])); + } + }; + + assert(m_id == 0); + + m_name = name; + + std::array(EShaderType::Count)> shader_ids = { 0 }; + + for (size_t i = 0; i < static_cast(EShaderType::Count); ++i) { + const std::string& source = sources[i]; + if (!source.empty()) + { + EShaderType type = static_cast(i); + auto [result, id] = create_shader(type); + if (result) + shader_ids[i] = id; + else + { + BOOST_LOG_TRIVIAL(error) << "glCreateShader() failed for " << shader_type_as_string(type) << " shader of shader program '" << name << "'"; + return false; + } + + const char* source_ptr = source.c_str(); + glsafe(::glShaderSource(id, 1, &source_ptr, nullptr)); + glsafe(::glCompileShader(id)); + GLint params; + glsafe(::glGetShaderiv(id, GL_COMPILE_STATUS, ¶ms)); + if (params == GL_FALSE) { + // Compilation failed. + glsafe(::glGetShaderiv(id, GL_INFO_LOG_LENGTH, ¶ms)); + std::vector msg(params); + glsafe(::glGetShaderInfoLog(id, params, ¶ms, msg.data())); + BOOST_LOG_TRIVIAL(error) << "Unable to compile " << shader_type_as_string(type) << " shader of shader program '" << name << "':\n" << msg.data(); + + // release shader + release_shaders(shader_ids); + return false; + } + } + } + + m_id = ::glCreateProgram(); + glcheck(); + if (m_id == 0) { + BOOST_LOG_TRIVIAL(error) << "glCreateProgram() failed for shader program '" << name << "'"; + + // release shaders + release_shaders(shader_ids); + return false; + } + + for (size_t i = 0; i < static_cast(EShaderType::Count); ++i) { + if (shader_ids[i] > 0) + glsafe(::glAttachShader(m_id, shader_ids[i])); + } + + glsafe(::glLinkProgram(m_id)); + GLint params; + glsafe(::glGetProgramiv(m_id, GL_LINK_STATUS, ¶ms)); + if (params == GL_FALSE) { + // Linking failed. + glsafe(::glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, ¶ms)); + std::vector msg(params); + glsafe(::glGetProgramInfoLog(m_id, params, ¶ms, msg.data())); + BOOST_LOG_TRIVIAL(error) << "Unable to link shader program '" << name << "':\n" << msg.data(); + + // release shaders + release_shaders(shader_ids); + + // release shader program + glsafe(::glDeleteProgram(m_id)); + m_id = 0; + + return false; + } + + // release shaders, they are no more needed + release_shaders(shader_ids); + + return true; +} + +bool GLShaderProgram::start_using() const +{ + if (m_id == 0) + return false; + + glsafe(::glUseProgram(m_id)); + return true; +} + +void GLShaderProgram::stop_using() const +{ + glsafe(::glUseProgram(0)); +} + +bool GLShaderProgram::set_uniform(const char* name, int value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform1i(id, static_cast(value))); + return true; + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, bool value) const +{ + return set_uniform(name, value ? 1 : 0); +} + +bool GLShaderProgram::set_uniform(const char* name, float value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform1f(id, static_cast(value))); + return true; + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); + return true; + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform4fv(id, 1, static_cast(value.data()))); + return true; + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t size) const +{ + if (size == 1) + return set_uniform(name, value[0]); + else if (size < 5) + { + int id = get_uniform_location(name); + if (id >= 0) { + if (size == 2) + glsafe(::glUniform2fv(id, 1, static_cast(value))); + else if (size == 3) + glsafe(::glUniform3fv(id, 1, static_cast(value))); + else + glsafe(::glUniform4fv(id, 1, static_cast(value))); + + return true; + } + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, const Transform3f& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.matrix().data()))); + return true; + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) const +{ + return set_uniform(name, value.cast()); +} + +int GLShaderProgram::get_attrib_location(const char* name) const +{ + return (m_id > 0) ? ::glGetAttribLocation(m_id, name) : -1; +} + +int GLShaderProgram::get_uniform_location(const char* name) const +{ + return (m_id > 0) ? ::glGetUniformLocation(m_id, name) : -1; +} + +} // namespace Slic3r +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include #include #include @@ -364,3 +642,6 @@ void Shader::reset() } } // namespace Slic3r +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index df2a23f15c..a4a8d32cb3 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -1,6 +1,64 @@ #ifndef slic3r_GLShader_hpp_ #define slic3r_GLShader_hpp_ +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +#include +#include + +namespace Slic3r { + +class GLShaderProgram +{ +public: + enum class EShaderType + { + Vertex, + Fragment, + Geometry, + TessEvaluation, + TessControl, + Compute, + Count + }; + + typedef std::array(EShaderType::Count)> ShaderFilenames; + typedef std::array(EShaderType::Count)> ShaderSources; + +private: + std::string m_name; + unsigned int m_id{ 0 }; + +public: + ~GLShaderProgram(); + + bool init_from_files(const std::string& name, const ShaderFilenames& filenames); + bool init_from_texts(const std::string& name, const ShaderSources& sources); + + const std::string& get_name() const { return m_name; } + unsigned int get_id() const { return m_id; } + + bool start_using() const; + void stop_using() const; + + bool set_uniform(const char* name, int value) const; + bool set_uniform(const char* name, bool value) const; + bool set_uniform(const char* name, float value) const; + bool set_uniform(const char* name, const std::array& value) const; + bool set_uniform(const char* name, const std::array& value) const; + bool set_uniform(const char* name, const float* value, size_t size) const; + bool set_uniform(const char* name, const Transform3f& value) const; + bool set_uniform(const char* name, const Transform3d& value) const; + + // returns -1 if not found + int get_attrib_location(const char* name) const; + // returns -1 if not found + int get_uniform_location(const char* name) const; +}; + +} // namespace Slic3r +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "libslic3r/libslic3r.h" #include "libslic3r/Point.hpp" @@ -67,5 +125,8 @@ private: }; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif /* slic3r_GLShader_hpp_ */ diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp new file mode 100644 index 0000000000..e4d6407224 --- /dev/null +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -0,0 +1,76 @@ +#include "libslic3r/libslic3r.h" +#include "GLShadersManager.hpp" + +#include +#include + +#if ENABLE_SHADERS_MANAGER + +namespace Slic3r { + +std::pair GLShadersManager::init() +{ + std::string error; + + auto append_shader = [this, &error](const std::string& name, const GLShaderProgram::ShaderFilenames& filenames) { + m_shaders.push_back(std::make_unique()); + if (!m_shaders.back()->init_from_files(name, filenames)) { + error += name + "\n"; + // if any error happens while initializating the shader, we remove it from the list + m_shaders.pop_back(); + return false; + } + return true; + }; + + assert(m_shaders.empty()); + + bool valid = true; + + // used to render bed axes, selection hints + valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); + // used to render printbed + valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); + // used to render tool changes in gcode preview + valid &= append_shader("toolchanges", { "toolchanges.vs", "toolchanges.fs" }); + // used to render color changes in gcode preview + valid &= append_shader("colorchanges", { "colorchanges.vs", "colorchanges.fs" }); + // used to render pause prints in gcode preview + valid &= append_shader("pauses", { "pauses.vs", "pauses.fs" }); + // used to render custom gcode points in gcode preview + valid &= append_shader("customs", { "customs.vs", "customs.fs" }); + // used to render retractions in gcode preview + valid &= append_shader("retractions", { "retractions.vs", "retractions.fs" }); + // used to render unretractions in gcode preview + valid &= append_shader("unretractions", { "unretractions.vs", "unretractions.fs" }); + // used to render extrusion paths in gcode preview + valid &= append_shader("extrusions", { "extrusions.vs", "extrusions.fs" }); + // used to render travel paths in gcode preview + valid &= append_shader("travels", { "travels.vs", "travels.fs" }); + // used to render shells in gcode preview + valid &= append_shader("shells", { "shells.vs", "shells.fs" }); + // used to render objects in 3d editor + valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }); + // used to render variable layers heights in 3d editor + valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); + + return { valid, error }; +} + +void GLShadersManager::shutdown() +{ + for (std::unique_ptr& shader : m_shaders) + { + shader.reset(); + } +} + +GLShaderProgram* GLShadersManager::get_shader(const std::string& shader_name) +{ + auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [shader_name](std::unique_ptr& p) { return p->get_name() == shader_name; }); + return (it != m_shaders.end()) ? it->get() : nullptr; +} + +} // namespace Slic3r + +#endif // ENABLE_SHADERS_MANAGER diff --git a/src/slic3r/GUI/GLShadersManager.hpp b/src/slic3r/GUI/GLShadersManager.hpp new file mode 100644 index 0000000000..0c624f9161 --- /dev/null +++ b/src/slic3r/GUI/GLShadersManager.hpp @@ -0,0 +1,31 @@ +#ifndef slic3r_GLShadersManager_hpp_ +#define slic3r_GLShadersManager_hpp_ + +#if ENABLE_SHADERS_MANAGER + +#include "GLShader.hpp" + +#include +#include +#include + +namespace Slic3r { + +class GLShadersManager +{ + std::vector> m_shaders; + +public: + std::pair init(); + // call this method before to release the OpenGL context + void shutdown(); + + // returns nullptr if not found + GLShaderProgram* get_shader(const std::string& shader_name); +}; + +} // namespace Slic3r + +#endif // ENABLE_SHADERS_MANAGER + +#endif // slic3r_GLShadersManager_hpp_ diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 984a1241e7..d0c1624d7c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -217,6 +217,16 @@ public: void gcode_thumbnails_debug(); #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + // returns nullptr if not found + GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); } + +// // returns 0 if not found +// unsigned int get_shader_id(const std::string& shader_name) const { return m_opengl_mgr.get_shader_id(shader_name); } +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + private: bool on_init_inner(); void init_app_config(); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index bdb005b1e2..e4674fffd1 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -28,6 +28,17 @@ namespace Slic3r { namespace GUI { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +// A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr. +inline std::string gl_get_string_safe(GLenum param, const std::string& default_value) +{ + const char* value = (const char*)::glGetString(param); + return std::string((value != nullptr) ? value : default_value); +} +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + const std::string& OpenGLManager::GLInfo::get_version() const { if (!m_detected) @@ -85,6 +96,14 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const void OpenGLManager::GLInfo::detect() const { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + m_version = gl_get_string_safe(GL_VERSION, "N/A"); + m_glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A"); + m_vendor = gl_get_string_safe(GL_VENDOR, "N/A"); + m_renderer = gl_get_string_safe(GL_RENDERER, "N/A"); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const char* data = (const char*)::glGetString(GL_VERSION); if (data != nullptr) m_version = data; @@ -100,6 +119,9 @@ void OpenGLManager::GLInfo::detect() const data = (const char*)::glGetString(GL_RENDERER); if (data != nullptr) m_renderer = data; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); @@ -119,6 +141,13 @@ bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, u if (!m_detected) detect(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + if (m_version == "N/A") + return false; +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + std::vector tokens; boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on); @@ -159,15 +188,34 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension std::string line_end = format_as_html ? "
" : "\n"; out << h2_start << "OpenGL installation" << h2_end << line_end; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + out << b_start << "GL version: " << b_end << m_version << line_end; + out << b_start << "Vendor: " << b_end << m_vendor << line_end; + out << b_start << "Renderer: " << b_end << m_renderer << line_end; + out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end; out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end; out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end; out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (extensions) { std::vector extensions_list; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, ""); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); if (!extensions_list.empty()) @@ -199,6 +247,12 @@ OpenGLManager::OSInfo OpenGLManager::s_os_info; OpenGLManager::~OpenGLManager() { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + m_shaders_manager.shutdown(); +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5 #ifdef __APPLE__ // This is an ugly hack needed to solve the crash happening when closing the application on OSX 10.9.5 with newer wxWidgets @@ -240,19 +294,43 @@ bool OpenGLManager::init_gl() else s_framebuffers_type = EFramebufferType::Unknown; - if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) { - // Complain about the OpenGL version. +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0); + if (!valid_version) { +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (!s_gl_info.is_version_greater_or_equal_to(2, 0)) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + + // Complain about the OpenGL version. wxString message = from_u8((boost::format( _utf8(L("PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" "while OpenGL version %s, render %s, vendor %s was detected."))) % s_gl_info.get_version() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str()); - message += "\n"; + message += "\n"; message += _L("You may need to update your graphics card driver."); #ifdef _WIN32 - message += "\n"; + message += "\n"; message += _L("As a workaround, you may run PrusaSlicer with a software rendered 3D graphics by running prusa-slicer.exe with the --sw_renderer parameter."); #endif wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Unsupported OpenGL version"), wxOK | wxICON_ERROR); } + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + if (valid_version) { + // load shaders + auto [result, error] = m_shaders_manager.init(); + if (!result) { + wxString message = from_u8((boost::format( + _utf8(L("Unable to load the following shaders:\n%s"))) % error).str()); + wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Error loading shaders"), wxOK | wxICON_ERROR); + } + } +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } return true; diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index 9d7ee5babb..c80deb6ef1 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -1,6 +1,12 @@ #ifndef slic3r_OpenGLManager_hpp_ #define slic3r_OpenGLManager_hpp_ +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +#include "GLShadersManager.hpp" +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + class wxWindow; class wxGLCanvas; class wxGLContext; @@ -70,6 +76,11 @@ private: bool m_gl_initialized{ false }; wxGLContext* m_context{ nullptr }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShadersManager m_shaders_manager; +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static GLInfo s_gl_info; #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5 #ifdef __APPLE__ @@ -86,9 +97,18 @@ public: ~OpenGLManager(); bool init_gl(); - wxGLContext* init_glcontext(wxGLCanvas& canvas); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + // returns nullptr if not found + GLShaderProgram* get_shader(const std::string& shader_name) { return m_shaders_manager.get_shader(shader_name); } + +// // returns 0 if not found +// unsigned int get_shader_id(const std::string& shader_name) const { return m_shaders_manager.get_shader_id(shader_name); } +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } static bool can_multisample() { return s_multisample == EMultisampleState::Enabled; } static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 3bab06b4d5..982a16bf00 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -8,6 +8,11 @@ #include "GUI_ObjectList.hpp" #include "Gizmos/GLGizmoBase.hpp" #include "3DScene.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if ENABLE_SHADERS_MANAGER +//#include "GLShader.hpp" +//#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "Camera.hpp" #include @@ -113,11 +118,17 @@ bool Selection::init() m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_arrows_shader.init("gouraud_light.vs", "gouraud_light.fs")) { BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; return false; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else if (!m_arrow.init()) return false; @@ -1253,13 +1264,31 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha if (sidebar_field.empty()) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = nullptr; +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_arrows_shader.is_initialized()) return; m_arrows_shader.start_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); #else shader.start_using(); @@ -1322,13 +1351,46 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha } if (boost::starts_with(sidebar_field, "position")) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + render_sidebar_position_hints(sidebar_field, *shader); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_position_hints(sidebar_field); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ else if (boost::starts_with(sidebar_field, "rotation")) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + render_sidebar_rotation_hints(sidebar_field, *shader); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_rotation_hints(sidebar_field); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +render_sidebar_scale_hints(sidebar_field, *shader); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + render_sidebar_scale_hints(sidebar_field); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ else if (boost::starts_with(sidebar_field, "scale")) render_sidebar_scale_hints(sidebar_field); else if (boost::starts_with(sidebar_field, "size")) render_sidebar_size_hints(sidebar_field); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field); @@ -1337,7 +1399,15 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_arrows_shader.stop_using(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else glsafe(::glDisable(GL_LIGHTING)); shader.stop_using(); @@ -1942,35 +2012,76 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons glsafe(::glEnd()); } -void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const -{ #if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader) const +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +{ +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (boost::ends_with(sidebar_field, "x")) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader.set_uniform("uniform_color", AXES_COLOR[0], 4); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); m_arrow.render(); } else if (boost::ends_with(sidebar_field, "y")) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader.set_uniform("uniform_color", AXES_COLOR[1], 4); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_arrow.render(); } else if (boost::ends_with(sidebar_field, "z")) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader.set_uniform("uniform_color", AXES_COLOR[2], 4); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); m_arrow.render(); } +} #else +void Selection::render_sidebar_position_hints(const std::string & sidebar_field) const +{ if (boost::ends_with(sidebar_field, "x")) { glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); @@ -1983,38 +2094,79 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); render_sidebar_position_hint(Z); } -#endif // ENABLE_GCODE_VIEWER } +#endif // ENABLE_GCODE_VIEWER -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const -{ #if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader) const +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +{ +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (boost::ends_with(sidebar_field, "x")) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader.set_uniform("uniform_color", AXES_COLOR[0], 4); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); render_sidebar_rotation_hint(X); } else if (boost::ends_with(sidebar_field, "y")) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader.set_uniform("uniform_color", AXES_COLOR[1], 4); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); render_sidebar_rotation_hint(Y); } else if (boost::ends_with(sidebar_field, "z")) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader.set_uniform("uniform_color", AXES_COLOR[2], 4); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_rotation_hint(Z); } +} #else +void Selection::render_sidebar_rotation_hints(const std::string & sidebar_field) const +{ if (boost::ends_with(sidebar_field, "x")) { glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); @@ -2027,37 +2179,74 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) } else if (boost::ends_with(sidebar_field, "z")) render_sidebar_rotation_hint(Z); -#endif // ENABLE_GCODE_VIEWER } +#endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader) const +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); -#if ENABLE_GCODE_VIEWER if (boost::ends_with(sidebar_field, "x") || uniform_scale) { glsafe(::glPushMatrix()); glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - render_sidebar_scale_hint(X); - glsafe(::glPopMatrix()); - } - - if (boost::ends_with(sidebar_field, "y") || uniform_scale) - { - glsafe(::glPushMatrix()); - render_sidebar_scale_hint(Y); - glsafe(::glPopMatrix()); - } - - if (boost::ends_with(sidebar_field, "z") || uniform_scale) - { - glsafe(::glPushMatrix()); - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - render_sidebar_scale_hint(Z); - glsafe(::glPopMatrix()); - } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + render_sidebar_scale_hint(X, shader); #else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + render_sidebar_scale_hint(X); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + glsafe(::glPopMatrix()); + } + + if (boost::ends_with(sidebar_field, "y") || uniform_scale) + { + glsafe(::glPushMatrix()); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + render_sidebar_scale_hint(Y, shader); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + render_sidebar_scale_hint(Y); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + glsafe(::glPopMatrix()); + } + + if (boost::ends_with(sidebar_field, "z") || uniform_scale) + { + glsafe(::glPushMatrix()); + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + render_sidebar_scale_hint(Z, shader); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + render_sidebar_scale_hint(Z); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + glsafe(::glPopMatrix()); + } +} +#else +void Selection::render_sidebar_scale_hints(const std::string & sidebar_field) const +{ + bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); + if (boost::ends_with(sidebar_field, "x") || uniform_scale) { glsafe(::glPushMatrix()); @@ -2080,13 +2269,19 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con render_sidebar_scale_hint(Z); glsafe(::glPopMatrix()); } -#endif // ENABLE_GCODE_VIEWER } +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_size_hints(const std::string& sidebar_field) const { render_sidebar_scale_hints(sidebar_field); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const { @@ -2178,13 +2373,29 @@ void Selection::render_sidebar_rotation_hint(Axis axis) const m_curved_arrow.render(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +void Selection::render_sidebar_scale_hint(Axis axis, GLShaderProgram& shader) const +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_scale_hint(Axis axis) const +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { #if ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + shader.set_uniform("uniform_color", (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? (const GLfloat*)UNIFORM_SCALE_COLOR : (const GLfloat*)AXES_COLOR[axis])); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else m_arrow.set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 53caf11060..7411fc97e5 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -6,7 +6,13 @@ #include "3DScene.hpp" #if ENABLE_GCODE_VIEWER #include "GLModel.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER #if ENABLE_RENDER_SELECTION_CENTER @@ -15,7 +21,15 @@ typedef class GLUquadric GLUquadricObj; #endif // ENABLE_RENDER_SELECTION_CENTER namespace Slic3r { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER +class GLShaderProgram; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class Shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ namespace GUI { class TransformationType { @@ -207,7 +221,13 @@ private: #if ENABLE_GCODE_VIEWER GL_Model m_arrow; GL_Model m_curved_arrow; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_arrows_shader; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else mutable GLArrow m_arrow; mutable GLCurvedArrow m_curved_arrow; @@ -368,16 +388,40 @@ private: void render_selected_volumes() const; void render_synchronized_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + void render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; + void render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; + void render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void render_sidebar_position_hints(const std::string& sidebar_field) const; void render_sidebar_rotation_hints(const std::string& sidebar_field) const; void render_sidebar_scale_hints(const std::string& sidebar_field) const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void render_sidebar_size_hints(const std::string& sidebar_field) const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void render_sidebar_layers_hints(const std::string& sidebar_field) const; #if !ENABLE_GCODE_VIEWER void render_sidebar_position_hint(Axis axis) const; #endif // !ENABLE_GCODE_VIEWER void render_sidebar_rotation_hint(Axis axis) const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SHADERS_MANAGER + void render_sidebar_scale_hint(Axis axis, GLShaderProgram& shader) const; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void render_sidebar_scale_hint(Axis axis) const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SHADERS_MANAGER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER void render_sidebar_size_hint(Axis axis, double length) const; #endif // !ENABLE_GCODE_VIEWER From cbfb09a241eeca1337a85bbb361518c304e29895 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 20 May 2020 17:03:53 +0200 Subject: [PATCH 083/826] Fixed build for all 4 cases of tech ENABLE_SHADERS_MANAGER and ENABLE_GCODE_VIEWER enabled/disabled and code cleanup --- src/libslic3r/GCode/ToolOrdering.cpp | 4 +- src/libslic3r/Technologies.hpp | 6 +- src/slic3r/GUI/3DBed.cpp | 29 ----- src/slic3r/GUI/3DBed.hpp | 12 -- src/slic3r/GUI/GCodeViewer.cpp | 81 ------------- src/slic3r/GUI/GCodeViewer.hpp | 32 ----- src/slic3r/GUI/GLCanvas3D.cpp | 68 +---------- src/slic3r/GUI/GLCanvas3D.hpp | 20 ---- src/slic3r/GUI/GLShader.cpp | 7 +- src/slic3r/GUI/GLShader.hpp | 6 +- src/slic3r/GUI/GUI_App.hpp | 5 - src/slic3r/GUI/OpenGLManager.cpp | 24 ---- src/slic3r/GUI/OpenGLManager.hpp | 9 -- src/slic3r/GUI/Selection.cpp | 171 +++++++-------------------- src/slic3r/GUI/Selection.hpp | 53 ++++----- 15 files changed, 72 insertions(+), 455 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 9c1a1900fd..db398f06c8 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -400,9 +400,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) { for (size_t i = 0; i + 1 < m_layer_tools.size();) { const LayerTools < = m_layer_tools[i]; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c274c1e841..d0673e2b4b 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -44,9 +44,9 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#define ENABLE_SHADERS_MANAGER (1 && ENABLE_GCODE_VIEWER) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) +// Enable the OpenGL shaders manager +#define ENABLE_SHADERS_MANAGER (1 && ENABLE_2_3_0_ALPHA1) + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 4bf8ce900e..73162645fc 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -15,11 +15,6 @@ #if ENABLE_GCODE_VIEWER #include "3DScene.hpp" #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if ENABLE_SHADERS_MANAGER -//#include "GLShader.hpp" -//#endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include @@ -168,17 +163,13 @@ Bed3D::Axes::~Axes() void Bed3D::Axes::render() const { #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER auto render_axis = [this](const Transform3f& transform) { #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto render_axis = [this](const Transform3f& transform, GLint color_id, const std::array& color) { if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(transform.data())); @@ -187,25 +178,20 @@ void Bed3D::Axes::render() const }; m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; if (!m_shader.is_initialized()) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glEnable(GL_DEPTH_TEST)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->start_using(); @@ -223,7 +209,6 @@ void Bed3D::Axes::render() const shader->stop_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.start_using(); GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); @@ -237,9 +222,7 @@ void Bed3D::Axes::render() const render_axis(Geometry::assemble_transform(m_origin).cast(), color_id, { 0.0f, 0.0f, 0.75f, 1.0f }); m_shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glDisable(GL_DEPTH_TEST)); #else @@ -585,7 +568,6 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const if (m_triangles.get_vertices_count() > 0) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("printbed"); if (shader != nullptr) @@ -594,7 +576,6 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const shader->set_uniform("transparent_background", bottom); shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_shader.get_shader_program_id() == 0) m_shader.init("printbed.vs", "printbed.fs"); @@ -603,9 +584,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const m_shader.start_using(); m_shader.set_uniform("transparent_background", bottom); m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_vbo_id == 0) { @@ -626,17 +605,13 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const unsigned int stride = m_triangles.get_vertex_data_size(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLint position_id = shader->get_attrib_location("v_position"); GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint position_id = m_shader.get_attrib_location("v_position"); GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // show the temporary texture while no compressed data is available GLuint tex_id = (GLuint)m_temp_texture.get_id(); @@ -674,15 +649,11 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const glsafe(::glDisable(GL_BLEND)); glsafe(::glDepthMask(GL_TRUE)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->stop_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } } } diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 9603015da8..d9a2c82620 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,13 +3,9 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER #include "GLModel.hpp" #endif // ENABLE_GCODE_VIEWER @@ -75,13 +71,9 @@ class Bed3D Vec3d m_origin{ Vec3d::Zero() }; float m_stem_length{ DefaultStemLength }; mutable GL_Model m_arrow; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ mutable Shader m_shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ public: #else @@ -130,13 +122,9 @@ private: mutable GLBed m_model; // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ mutable Shader m_shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ mutable unsigned int m_vbo_id; Axes m_axes; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 55f603829b..a584dc7eb1 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -12,11 +12,6 @@ #include "DoubleSlider.hpp" #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if ENABLE_SHADERS_MANAGER -//#include "GLShader.hpp" -//#endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GUI_Preview.hpp" #include "libslic3r/Model.hpp" #if ENABLE_GCODE_VIEWER_STATISTICS @@ -112,9 +107,7 @@ void GCodeViewer::IBuffer::reset() render_paths = std::vector(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src) { if (!shader.init(vertex_shader_src, fragment_shader_src)) { @@ -124,9 +117,7 @@ bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, con return true; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) { @@ -160,18 +151,13 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con void GCodeViewer::SequentialView::Marker::init() { m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ init_shader(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GCodeViewer::SequentialView::Marker::render() const { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER if (!m_visible) return; @@ -186,7 +172,6 @@ void GCodeViewer::SequentialView::Marker::render() const shader->start_using(); shader->set_uniform("uniform_color", m_color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_visible || !m_shader.is_initialized()) return; @@ -197,9 +182,7 @@ void GCodeViewer::SequentialView::Marker::render() const GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_color.data())); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(m_world_transform.data())); @@ -208,30 +191,22 @@ void GCodeViewer::SequentialView::Marker::render() const glsafe(::glPopMatrix()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->stop_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glDisable(GL_BLEND)); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void GCodeViewer::SequentialView::Marker::init_shader() { if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.50f, 0.50f, 0.50f }, // erNone @@ -440,7 +415,6 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range wxGetApp().plater()->update_preview_moves_slider(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER void GCodeViewer::init_shaders() { @@ -464,7 +438,6 @@ void GCodeViewer::init_shaders() } } #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GCodeViewer::init_shaders() { unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); @@ -499,9 +472,7 @@ bool GCodeViewer::init_shaders() return true; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { @@ -828,9 +799,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto set_color = [](GLint current_program_id, const Color& color) { if (current_program_id > 0) { GLint color_id = ::glGetUniformLocation(current_program_id, "uniform_color"); @@ -841,9 +810,7 @@ void GCodeViewer::render_toolpaths() const } BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glCullFace(GL_BACK)); glsafe(::glLineWidth(3.0f)); @@ -863,28 +830,20 @@ void GCodeViewer::render_toolpaths() const if (!buffer.visible) continue; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); if (shader != nullptr) { #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (buffer.shader.is_initialized()) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GCodeProcessor::EMoveType type = buffer_type(i); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->start_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ buffer.shader.start_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); @@ -893,15 +852,11 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Tool_change: { Color color = { 1.0f, 1.0f, 1.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -917,15 +872,11 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Color_change: { Color color = { 1.0f, 0.0f, 0.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -941,15 +892,11 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Pause_Print: { Color color = { 0.0f, 1.0f, 0.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -965,15 +912,11 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Custom_GCode: { Color color = { 0.0f, 0.0f, 1.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -989,15 +932,11 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Retract: { Color color = { 1.0f, 0.0f, 1.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -1013,15 +952,11 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Unretract: { Color color = { 0.0f, 1.0f, 1.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const RenderPath& path : buffer.render_paths) { glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); @@ -1038,15 +973,11 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", path.color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -1059,15 +990,11 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", path.color); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -1079,15 +1006,11 @@ void GCodeViewer::render_toolpaths() const } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->stop_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ buffer.shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } } @@ -1097,7 +1020,6 @@ void GCodeViewer::render_toolpaths() const void GCodeViewer::render_shells() const { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER if (!m_shells.visible || m_shells.volumes.empty()) return; @@ -1114,7 +1036,6 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_shells.visible || m_shells.volumes.empty() || !m_shells.shader.is_initialized()) return; @@ -1125,9 +1046,7 @@ void GCodeViewer::render_shells() const m_shells.shader.stop_using(); // glsafe(::glDepthMask(GL_TRUE)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GCodeViewer::render_legend() const diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index df28f4aa63..c1f9cfdd7b 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -2,13 +2,9 @@ #define slic3r_GCodeViewer_hpp_ #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GLModel.hpp" @@ -84,27 +80,19 @@ class GCodeViewer { unsigned int ibo_id{ 0 }; size_t indices_count{ 0 }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER std::string shader; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::vector paths; std::vector render_paths; bool visible{ false }; void reset(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); }; @@ -113,13 +101,9 @@ class GCodeViewer { GLVolumeCollection volumes; bool visible{ false }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ }; // helper to render extrusion paths @@ -229,13 +213,9 @@ public: Transform3f m_world_transform; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; bool m_visible{ false }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ public: void init(); @@ -250,14 +230,10 @@ public: void render() const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ private: void init_shader(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ }; struct Endpoints @@ -311,16 +287,12 @@ public: bool init() { set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); m_sequential_view.marker.init(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER init_shaders(); return true; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return init_shaders(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } // extract rendering data from the given parameters @@ -364,15 +336,11 @@ public: void enable_legend(bool enable) { m_legend_enabled = enable; } private: -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER void init_shaders(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool init_shaders(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c1c310b34c..51079c892f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -156,19 +156,15 @@ GLCanvas3D::LayersEditing::~LayersEditing() const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER void GLCanvas3D::LayersEditing::init() { #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) { if (!m_shader.init(vertex_shader_filename, fragment_shader_filename)) return false; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); @@ -179,13 +175,9 @@ bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return true; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) @@ -218,15 +210,11 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) bool GLCanvas3D::LayersEditing::is_allowed() const { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } bool GLCanvas3D::LayersEditing::is_enabled() const @@ -384,15 +372,11 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) bool GLCanvas3D::LayersEditing::is_initialized() const { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER return wxGetApp().get_shader("variable_layer_height") != nullptr; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return m_shader.is_initialized(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const @@ -426,7 +410,6 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); if (shader == nullptr) @@ -440,7 +423,6 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 shader->set_uniform("z_cursor_band_width", band_width); shader->set_uniform("object_max_z", m_object_max_z); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.start_using(); m_shader.set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); @@ -448,9 +430,7 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 m_shader.set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); m_shader.set_uniform("z_cursor_band_width", band_width); m_shader.set_uniform("object_max_z", m_object_max_z); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); @@ -470,15 +450,11 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 glsafe(::glEnd()); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->stop_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const @@ -512,7 +488,6 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G { assert(this->is_allowed()); assert(this->last_object_id != -1); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); assert(shader != nullptr); @@ -534,7 +509,6 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); shader->set_uniform("z_cursor_band_width", float(this->band_width)); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint shader_id = m_shader.get_shader()->shader_program_id; assert(shader_id > 0); @@ -565,9 +539,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glsafe(::glUniform1f(z_texture_row_to_normalized_id, GLfloat(1.0f / m_layers_texture.height))); glsafe(::glUniform1f(z_cursor_id, GLfloat(m_object_max_z) * GLfloat(this->get_cursor_z_relative(canvas)))); glsafe(::glUniform1f(z_cursor_band_width_id, GLfloat(this->band_width))); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Initialize the layer height texture mapping. GLsizei w = (GLsizei)m_layers_texture.width; GLsizei h = (GLsizei)m_layers_texture.height; @@ -583,28 +555,22 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G // Render the object using the layer editing shader and texture. if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); shader->set_uniform("object_max_z", GLfloat(0)); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (world_matrix_id != -1) glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); if (object_max_z_id != -1) glsafe(::glUniform1f(object_max_z_id, GLfloat(0))); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glvolume->render(); } // Revert back to the previous shader. glBindTexture(GL_TEXTURE_2D, 0); if (current_program_id > 0) glsafe(::glUseProgram(current_program_id)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } else { @@ -618,9 +584,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glvolume->render(); } } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void GLCanvas3D::LayersEditing::adjust_layer_height_profile() @@ -1744,12 +1708,10 @@ bool GLCanvas3D::init() if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER if (m_main_toolbar.is_enabled()) m_layers_editing.init(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_shader.init("gouraud.vs", "gouraud.fs")) { std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl; @@ -1761,9 +1723,7 @@ bool GLCanvas3D::init() std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; return false; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER if (!m_main_toolbar.is_enabled()) @@ -4623,17 +4583,13 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool return ret; }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER static const std::array orange = { 0.923f, 0.504f, 0.264f, 1.0f }; static const std::array gray = { 0.64f, 0.64f, 0.64f, 1.0f }; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const GLfloat orange[] = { 0.923f, 0.504f, 0.264f, 1.0f }; static const GLfloat gray[] = { 0.64f, 0.64f, 0.64f, 1.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLVolumePtrs visible_volumes; @@ -4677,7 +4633,6 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool camera.apply_projection(box, near_z, far_z); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader == nullptr) @@ -4692,7 +4647,6 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool shader->start_using(); shader->set_uniform("print_box.volume_detection", 0); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (transparent_background) glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); @@ -4708,37 +4662,27 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool if (print_box_detection_id != -1) glsafe(::glUniform1i(print_box_detection_id, 0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (const GLVolume* vol : visible_volumes) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (vol->printable && !vol->is_outside) ? orange : gray)); else glsafe(::glColor4fv((vol->printable && !vol->is_outside) ? orange : gray)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ vol->render(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->stop_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glDisable(GL_DEPTH_TEST)); @@ -5684,18 +5628,14 @@ void GLCanvas3D::_render_objects() const m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader != nullptr) { shader->start_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.start_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { int object_id = m_layers_editing.last_object_id; m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { @@ -5713,16 +5653,12 @@ void GLCanvas3D::_render_objects() const } m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->stop_using(); } #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_camera_clipping_plane = ClippingPlane::ClipsNothing(); } @@ -6142,8 +6078,12 @@ void GLCanvas3D::_render_selection_sidebar_hints() const { #if ENABLE_GCODE_VIEWER m_selection.render_sidebar_hints(m_sidebar_field); +#else +#if ENABLE_SHADERS_MANAGER + m_selection.render_sidebar_hints(m_sidebar_field); #else m_selection.render_sidebar_hints(m_sidebar_field, m_shader); +#endif // ENABLE_SHADERS_MANAGER #endif // ENABLE_GCODE_VIEWER } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 635d23e544..ff775a86e1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -7,13 +7,9 @@ #include "3DScene.hpp" #include "GLToolbar.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "Event.hpp" #include "Selection.hpp" #include "Gizmos/GLGizmosManager.hpp" @@ -173,13 +169,9 @@ private: private: bool m_enabled; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ unsigned int m_z_texture_id; // Not owned by LayersEditing. const DynamicPrintConfig *m_config; @@ -226,15 +218,11 @@ private: LayersEditing(); ~LayersEditing(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER void init(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void set_config(const DynamicPrintConfig* config); void select_object(const Model &model, int object_id); @@ -477,13 +465,9 @@ private: WarningTexture m_warning_texture; wxTimer m_timer; LayersEditing m_layers_editing; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Mouse m_mouse; mutable GLGizmosManager m_gizmos; mutable GLToolbar m_main_toolbar; @@ -613,13 +597,9 @@ public: void set_color_by(const std::string& value); void refresh_camera_scene_box(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const Shader& get_shader() const { return m_shader; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ BoundingBoxf3 volumes_bounding_box() const; BoundingBoxf3 scene_bounding_box() const; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 3b7c2abcc6..7aa2aff08a 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -1,6 +1,4 @@ -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "libslic3r/libslic3r.h" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" #include "3DScene.hpp" @@ -9,7 +7,6 @@ #include #include -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER #include @@ -283,7 +280,6 @@ int GLShaderProgram::get_uniform_location(const char* name) const } // namespace Slic3r #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include #include #include @@ -642,6 +638,5 @@ void Shader::reset() } } // namespace Slic3r -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index a4a8d32cb3..23d04b22c8 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -1,7 +1,6 @@ #ifndef slic3r_GLShader_hpp_ #define slic3r_GLShader_hpp_ -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER #include #include @@ -58,7 +57,6 @@ public: } // namespace Slic3r #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "libslic3r/libslic3r.h" #include "libslic3r/Point.hpp" @@ -125,8 +123,8 @@ private: }; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + + #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif /* slic3r_GLShader_hpp_ */ diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index d0c1624d7c..c4082cbb21 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -217,15 +217,10 @@ public: void gcode_thumbnails_debug(); #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER // returns nullptr if not found GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); } - -// // returns 0 if not found -// unsigned int get_shader_id(const std::string& shader_name) const { return m_opengl_mgr.get_shader_id(shader_name); } #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ private: bool on_init_inner(); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index e4674fffd1..deaa3cd199 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -28,7 +28,6 @@ namespace Slic3r { namespace GUI { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER // A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr. inline std::string gl_get_string_safe(GLenum param, const std::string& default_value) @@ -37,7 +36,6 @@ inline std::string gl_get_string_safe(GLenum param, const std::string& default_v return std::string((value != nullptr) ? value : default_value); } #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const std::string& OpenGLManager::GLInfo::get_version() const { @@ -96,14 +94,12 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const void OpenGLManager::GLInfo::detect() const { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER m_version = gl_get_string_safe(GL_VERSION, "N/A"); m_glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A"); m_vendor = gl_get_string_safe(GL_VENDOR, "N/A"); m_renderer = gl_get_string_safe(GL_RENDERER, "N/A"); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const char* data = (const char*)::glGetString(GL_VERSION); if (data != nullptr) m_version = data; @@ -119,9 +115,7 @@ void OpenGLManager::GLInfo::detect() const data = (const char*)::glGetString(GL_RENDERER); if (data != nullptr) m_renderer = data; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); @@ -141,12 +135,10 @@ bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, u if (!m_detected) detect(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER if (m_version == "N/A") return false; #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::vector tokens; boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on); @@ -188,34 +180,26 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension std::string line_end = format_as_html ? "
" : "\n"; out << h2_start << "OpenGL installation" << h2_end << line_end; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER out << b_start << "GL version: " << b_end << m_version << line_end; out << b_start << "Vendor: " << b_end << m_vendor << line_end; out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end; out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end; out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end; out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (extensions) { std::vector extensions_list; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, ""); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); if (!extensions_list.empty()) @@ -247,11 +231,9 @@ OpenGLManager::OSInfo OpenGLManager::s_os_info; OpenGLManager::~OpenGLManager() { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER m_shaders_manager.shutdown(); #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5 #ifdef __APPLE__ @@ -294,16 +276,12 @@ bool OpenGLManager::init_gl() else s_framebuffers_type = EFramebufferType::Unknown; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0); if (!valid_version) { #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!s_gl_info.is_version_greater_or_equal_to(2, 0)) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Complain about the OpenGL version. wxString message = from_u8((boost::format( @@ -318,7 +296,6 @@ bool OpenGLManager::init_gl() wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Unsupported OpenGL version"), wxOK | wxICON_ERROR); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER if (valid_version) { // load shaders @@ -330,7 +307,6 @@ bool OpenGLManager::init_gl() } } #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } return true; diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index c80deb6ef1..b645320240 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -1,11 +1,9 @@ #ifndef slic3r_OpenGLManager_hpp_ #define slic3r_OpenGLManager_hpp_ -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER #include "GLShadersManager.hpp" #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class wxWindow; class wxGLCanvas; @@ -76,11 +74,9 @@ private: bool m_gl_initialized{ false }; wxGLContext* m_context{ nullptr }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShadersManager m_shaders_manager; #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static GLInfo s_gl_info; #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5 #ifdef __APPLE__ @@ -99,15 +95,10 @@ public: bool init_gl(); wxGLContext* init_glcontext(wxGLCanvas& canvas); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER // returns nullptr if not found GLShaderProgram* get_shader(const std::string& shader_name) { return m_shaders_manager.get_shader(shader_name); } - -// // returns 0 if not found -// unsigned int get_shader_id(const std::string& shader_name) const { return m_shaders_manager.get_shader_id(shader_name); } #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } static bool can_multisample() { return s_multisample == EMultisampleState::Enabled; } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 982a16bf00..d3e4e37ef8 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -8,11 +8,6 @@ #include "GUI_ObjectList.hpp" #include "Gizmos/GLGizmoBase.hpp" #include "3DScene.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if ENABLE_SHADERS_MANAGER -//#include "GLShader.hpp" -//#endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "Camera.hpp" #include @@ -118,17 +113,13 @@ bool Selection::init() m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_arrows_shader.init("gouraud_light.vs", "gouraud_light.fs")) { BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; return false; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else if (!m_arrow.init()) return false; @@ -1258,22 +1249,23 @@ void Selection::render_center(bool gizmo_is_dragging) const #if ENABLE_GCODE_VIEWER void Selection::render_sidebar_hints(const std::string& sidebar_field) const #else +#if ENABLE_SHADERS_MANAGER +void Selection::render_sidebar_hints(const std::string& sidebar_field) const +#else void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const +#endif // ENABLE_SHADERS_MANAGER #endif // ENABLE_GCODE_VIEWER { if (sidebar_field.empty()) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = nullptr; #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) @@ -1281,19 +1273,24 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha shader->start_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_arrows_shader.is_initialized()) return; m_arrows_shader.start_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); +#else +#if ENABLE_SHADERS_MANAGER + shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); #else shader.start_using(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_LIGHTING)); +#endif // ENABLE_SHADERS_MANAGER + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); #endif // ENABLE_GCODE_VIEWER } @@ -1351,46 +1348,35 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha } if (boost::starts_with(sidebar_field, "position")) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER +#if ENABLE_GCODE_VIEWER render_sidebar_position_hints(sidebar_field, *shader); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_position_hints(sidebar_field); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +#else + render_sidebar_position_hints(sidebar_field); #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ else if (boost::starts_with(sidebar_field, "rotation")) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER +#if ENABLE_GCODE_VIEWER render_sidebar_rotation_hints(sidebar_field, *shader); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_rotation_hints(sidebar_field); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_GCODE_VIEWER - else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SHADERS_MANAGER -render_sidebar_scale_hints(sidebar_field, *shader); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - render_sidebar_scale_hints(sidebar_field); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - else if (boost::starts_with(sidebar_field, "scale")) - render_sidebar_scale_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "size")) - render_sidebar_size_hints(sidebar_field); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#else + render_sidebar_rotation_hints(sidebar_field); +#endif // ENABLE_SHADERS_MANAGER + else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) +#if ENABLE_SHADERS_MANAGER +#if ENABLE_GCODE_VIEWER + render_sidebar_scale_hints(sidebar_field, *shader); +#else + render_sidebar_scale_hints(sidebar_field); +#endif // ENABLE_GCODE_VIEWER +#else + render_sidebar_scale_hints(sidebar_field); +#endif // ENABLE_SHADERS_MANAGER else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field); @@ -1399,18 +1385,18 @@ render_sidebar_scale_hints(sidebar_field, *shader); if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader->stop_using(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_arrows_shader.stop_using(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#else +#if ENABLE_SHADERS_MANAGER + shader->stop_using(); #else glsafe(::glDisable(GL_LIGHTING)); shader.stop_using(); +#endif // ENABLE_SHADERS_MANAGER #endif // ENABLE_GCODE_VIEWER } } @@ -2013,74 +1999,54 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons } #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader) const #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (boost::ends_with(sidebar_field, "x")) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader.set_uniform("uniform_color", AXES_COLOR[0], 4); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); m_arrow.render(); } else if (boost::ends_with(sidebar_field, "y")) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader.set_uniform("uniform_color", AXES_COLOR[1], 4); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_arrow.render(); } else if (boost::ends_with(sidebar_field, "z")) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader.set_uniform("uniform_color", AXES_COLOR[2], 4); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); m_arrow.render(); } } #else -void Selection::render_sidebar_position_hints(const std::string & sidebar_field) const +void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const { if (boost::ends_with(sidebar_field, "x")) { @@ -2098,68 +2064,48 @@ void Selection::render_sidebar_position_hints(const std::string & sidebar_field) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader) const #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (boost::ends_with(sidebar_field, "x")) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader.set_uniform("uniform_color", AXES_COLOR[0], 4); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); render_sidebar_rotation_hint(X); } else if (boost::ends_with(sidebar_field, "y")) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader.set_uniform("uniform_color", AXES_COLOR[1], 4); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); render_sidebar_rotation_hint(Y); } else if (boost::ends_with(sidebar_field, "z")) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader.set_uniform("uniform_color", AXES_COLOR[2], 4); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_rotation_hint(Z); } @@ -2183,15 +2129,11 @@ void Selection::render_sidebar_rotation_hints(const std::string & sidebar_field) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader) const #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); @@ -2199,30 +2141,22 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con { glsafe(::glPushMatrix()); glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER render_sidebar_scale_hint(X, shader); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_scale_hint(X); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPopMatrix()); } if (boost::ends_with(sidebar_field, "y") || uniform_scale) { glsafe(::glPushMatrix()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER render_sidebar_scale_hint(Y, shader); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_scale_hint(Y); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPopMatrix()); } @@ -2230,15 +2164,11 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con { glsafe(::glPushMatrix()); glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER render_sidebar_scale_hint(Z, shader); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ render_sidebar_scale_hint(Z); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glPopMatrix()); } } @@ -2272,17 +2202,6 @@ void Selection::render_sidebar_scale_hints(const std::string & sidebar_field) co } #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -void Selection::render_sidebar_size_hints(const std::string& sidebar_field) const -{ - render_sidebar_scale_hints(sidebar_field); -} -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const { static const double Margin = 10.0; @@ -2373,29 +2292,25 @@ void Selection::render_sidebar_rotation_hint(Axis axis) const m_curved_arrow.render(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER +#if ENABLE_GCODE_VIEWER void Selection::render_sidebar_scale_hint(Axis axis, GLShaderProgram& shader) const #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::render_sidebar_scale_hint(Axis axis) const -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +#else +void Selection::render_sidebar_scale_hint(Axis axis) const #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER shader.set_uniform("uniform_color", (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? (const GLfloat*)UNIFORM_SCALE_COLOR : (const GLfloat*)AXES_COLOR[axis])); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else m_arrow.set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); #endif // ENABLE_GCODE_VIEWER @@ -2408,12 +2323,6 @@ void Selection::render_sidebar_scale_hint(Axis axis) const m_arrow.render(); } -#if !ENABLE_GCODE_VIEWER -void Selection::render_sidebar_size_hint(Axis axis, double length) const -{ -} -#endif // !ENABLE_GCODE_VIEWER - #ifndef NDEBUG static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 7411fc97e5..2f8ffe58c8 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -6,13 +6,13 @@ #include "3DScene.hpp" #if ENABLE_GCODE_VIEWER #include "GLModel.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "GLShader.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#else +#if !ENABLE_SHADERS_MANAGER +#include "GLShader.hpp" +#endif // !ENABLE_SHADERS_MANAGER #endif // ENABLE_GCODE_VIEWER #if ENABLE_RENDER_SELECTION_CENTER @@ -21,15 +21,12 @@ typedef class GLUquadric GLUquadricObj; #endif // ENABLE_RENDER_SELECTION_CENTER namespace Slic3r { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER +class Shader; +#endif // !ENABLE_GCODE_VIEWER #if ENABLE_SHADERS_MANAGER class GLShaderProgram; -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -class Shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ namespace GUI { class TransformationType { @@ -221,13 +218,9 @@ private: #if ENABLE_GCODE_VIEWER GL_Model m_arrow; GL_Model m_curved_arrow; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Shader m_arrows_shader; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else mutable GLArrow m_arrow; mutable GLCurvedArrow m_curved_arrow; @@ -348,8 +341,12 @@ public: #endif // ENABLE_RENDER_SELECTION_CENTER #if ENABLE_GCODE_VIEWER void render_sidebar_hints(const std::string& sidebar_field) const; +#else +#if ENABLE_SHADERS_MANAGER + void render_sidebar_hints(const std::string& sidebar_field) const; #else void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const; +#endif // ENABLE_SHADERS_MANAGER #endif // ENABLE_GCODE_VIEWER bool requires_local_axes() const; @@ -388,43 +385,35 @@ private: void render_selected_volumes() const; void render_synchronized_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER +#if ENABLE_GCODE_VIEWER void render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; void render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; void render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void render_sidebar_position_hints(const std::string& sidebar_field) const; void render_sidebar_rotation_hints(const std::string& sidebar_field) const; void render_sidebar_scale_hints(const std::string& sidebar_field) const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +#else + void render_sidebar_position_hints(const std::string& sidebar_field) const; + void render_sidebar_rotation_hints(const std::string& sidebar_field) const; + void render_sidebar_scale_hints(const std::string& sidebar_field) const; #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - void render_sidebar_size_hints(const std::string& sidebar_field) const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void render_sidebar_layers_hints(const std::string& sidebar_field) const; #if !ENABLE_GCODE_VIEWER void render_sidebar_position_hint(Axis axis) const; #endif // !ENABLE_GCODE_VIEWER void render_sidebar_rotation_hint(Axis axis) const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_SHADERS_MANAGER +#if ENABLE_GCODE_VIEWER void render_sidebar_scale_hint(Axis axis, GLShaderProgram& shader) const; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void render_sidebar_scale_hint(Axis axis) const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +#else + void render_sidebar_scale_hint(Axis axis) const; #endif // ENABLE_SHADERS_MANAGER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if !ENABLE_GCODE_VIEWER - void render_sidebar_size_hint(Axis axis, double length) const; -#endif // !ENABLE_GCODE_VIEWER public: enum SyncRotationType { From 5aa8cc577988ec3f9b335aa6c609b1f5f6c0e88f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 May 2020 10:15:00 +0200 Subject: [PATCH 084/826] ENABLE_SHADERS_MANAGER -> Unified client code of new GLShadersManager and GLShaderProgram classes --- src/slic3r/GUI/3DBed.cpp | 19 +++++++++-- src/slic3r/GUI/3DScene.cpp | 40 +++++++++++++++++++++++ src/slic3r/GUI/3DScene.hpp | 6 ---- src/slic3r/GUI/GLCanvas3D.cpp | 18 ++++++----- src/slic3r/GUI/GLShader.cpp | 50 ++++++++++++++++++++--------- src/slic3r/GUI/GLShader.hpp | 4 ++- src/slic3r/GUI/GLShadersManager.cpp | 14 ++++++++ src/slic3r/GUI/GLShadersManager.hpp | 3 ++ src/slic3r/GUI/GUI_App.hpp | 4 +-- src/slic3r/GUI/OpenGLManager.hpp | 2 +- 10 files changed, 123 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 73162645fc..3fdcdd6fab 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -196,15 +196,18 @@ void Bed3D::Axes::render() const shader->start_using(); // x axis - shader->set_uniform("uniform_color", { 0.75f, 0.0f, 0.0f, 1.0f }); + std::array color = { 0.75f, 0.0f, 0.0f, 1.0f }; + shader->set_uniform("uniform_color", color); render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0f }).cast()); // y axis - shader->set_uniform("uniform_color", { 0.0f, 0.75f, 0.0f, 1.0f }); + color = { 0.0f, 0.75f, 0.0f, 1.0f }; + shader->set_uniform("uniform_color", color); render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0f }).cast()); // z axis - shader->set_uniform("uniform_color", { 0.0f, 0.0f, 0.75f, 1.0f }); + color = { 0.0f, 0.0f, 0.75f, 1.0f }; + shader->set_uniform("uniform_color", color); render_axis(Geometry::assemble_transform(m_origin).cast()); shader->stop_using(); @@ -676,9 +679,19 @@ void Bed3D::render_model() const if (!m_model.get_filename().empty()) { +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) + { + shader->start_using(); + m_model.render(); + shader->stop_using(); + } +#else glsafe(::glEnable(GL_LIGHTING)); m_model.render(); glsafe(::glDisable(GL_LIGHTING)); +#endif // ENABLE_SHADERS_MANAGER } } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 29a4b84a30..6e42e52a21 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1,6 +1,10 @@ #include #include "3DScene.hpp" +#if ENABLE_SHADERS_MANAGER +#include "GLShader.hpp" +#include "GUI_App.hpp" +#endif // ENABLE_SHADERS_MANAGER #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" @@ -640,6 +644,12 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const { +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); + if (shader == nullptr) + return; +#endif // ENABLE_SHADERS_MANAGER + glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -650,6 +660,15 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("print_box.min", m_print_box_min, 3); + shader->set_uniform("print_box.max", m_print_box_max, 3); + shader->set_uniform("z_range", m_z_range, 2); + shader->set_uniform("clipping_plane", m_clipping_plane, 4); +#if ENABLE_SLOPE_RENDERING + shader->set_uniform("slope.z_range", m_slope.z_range); +#endif // ENABLE_SLOPE_RENDERING +#else GLint current_program_id; glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; @@ -684,11 +703,19 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab if (slope_z_range_id != -1) glsafe(::glUniform2fv(slope_z_range_id, 1, (const GLfloat*)m_slope.z_range.data())); #endif // ENABLE_SLOPE_RENDERING +#endif // ENABLE_SHADERS_MANAGER GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); #if ENABLE_SLOPE_RENDERING +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", volume.first->render_color, 4); + shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled); + shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); + shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); + shader->set_uniform("slope.volume_world_normal_matrix", volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast()); +#else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)volume.first->render_color)); else @@ -708,6 +735,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab Matrix3f normal_matrix = volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast(); glsafe(::glUniformMatrix3fv(slope_normal_matrix_id, 1, GL_FALSE, (const GLfloat*)normal_matrix.data())); } +#endif // ENABLE_SHADERS_MANAGER volume.first->render(); #else @@ -1912,6 +1940,12 @@ void GLModel::reset() void GLModel::render() const { +#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); + if (shader == nullptr) + return; +#endif // ENABLE_SHADERS_MANAGER + glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -1919,16 +1953,22 @@ void GLModel::render() const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +#if !ENABLE_SHADERS_MANAGER GLint current_program_id; glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; glcheck(); +#endif // !ENABLE_SHADERS_MANAGER #if ENABLE_SLOPE_RENDERING +#if ENABLE_SHADERS_MANAGER + shader->set_uniform("uniform_color", m_volume.render_color, 4); +#else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_volume.render_color)); else glsafe(::glColor4fv(m_volume.render_color)); +#endif // ENABLE_SHADERS_MANAGER m_volume.render(); #else diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 86072754d7..fd74cd4912 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -26,12 +26,6 @@ inline void glAssertRecentCall() { } #endif namespace Slic3r { -namespace GUI { -class Bed3D; -struct Camera; -class GLToolbar; -} // namespace GUI - class SLAPrintObject; enum SLAPrintObjectStep : unsigned int; class DynamicPrintConfig; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 51079c892f..2eff9ac193 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -490,16 +490,16 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G assert(this->last_object_id != -1); #if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); - assert(shader != nullptr); + if (shader == nullptr) + return; - GLint current_program_id = 0; - glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); - if (shader->get_id() != static_cast(current_program_id)) + GLShaderProgram* current_shader = wxGetApp().get_current_shader(); + if (shader->get_id() != current_shader->get_id()) // The layer editing shader is not yet active. Activate it. shader->start_using(); else // The layer editing shader was already active. - current_program_id = 0; + current_shader = nullptr; const_cast(this)->generate_layer_height_texture(); @@ -529,7 +529,6 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G GLint object_max_z_id = ::glGetUniformLocation(shader_id, "object_max_z"); glcheck(); - if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1) { const_cast(this)->generate_layer_height_texture(); @@ -568,9 +567,12 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G } // Revert back to the previous shader. glBindTexture(GL_TEXTURE_2D, 0); +#if ENABLE_SHADERS_MANAGER + if (current_shader != nullptr) + current_shader->start_using(); +#else if (current_program_id > 0) glsafe(::glUseProgram(current_program_id)); -#if !ENABLE_SHADERS_MANAGER } else { @@ -584,7 +586,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glvolume->render(); } } -#endif // !ENABLE_SHADERS_MANAGER +#endif // ENABLE_SHADERS_MANAGER } void GLCanvas3D::LayersEditing::adjust_layer_height_profile() diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 7aa2aff08a..d0b9267945 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -6,6 +6,7 @@ #include #include +#include #if ENABLE_SHADERS_MANAGER #include @@ -23,8 +24,7 @@ bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilen auto load_from_file = [](const std::string& filename) { std::string path = resources_dir() + "/shaders/" + filename; boost::nowide::ifstream s(path, boost::nowide::ifstream::binary); - if (!s.good()) - { + if (!s.good()) { BOOST_LOG_TRIVIAL(error) << "Couldn't open file: '" << path << "'"; return std::string(); } @@ -34,8 +34,7 @@ bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilen s.seekg(0, s.beg); std::string source(file_length, '\0'); s.read(source.data(), file_length); - if (!s.good()) - { + if (!s.good()) { BOOST_LOG_TRIVIAL(error) << "Error while loading file: '" << path << "'"; return std::string(); } @@ -49,8 +48,9 @@ bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilen sources[i] = filenames[i].empty() ? std::string() : load_from_file(filenames[i]); } - bool valid = (!sources[static_cast(EShaderType::Vertex)].empty() && !sources[static_cast(EShaderType::Fragment)].empty()) || - !sources[static_cast(EShaderType::Compute)].empty(); + bool valid = !sources[static_cast(EShaderType::Vertex)].empty() && !sources[static_cast(EShaderType::Fragment)].empty() && sources[static_cast(EShaderType::Compute)].empty(); + valid |= !sources[static_cast(EShaderType::Compute)].empty() && sources[static_cast(EShaderType::Vertex)].empty() && sources[static_cast(EShaderType::Fragment)].empty() && + sources[static_cast(EShaderType::Geometry)].empty() && sources[static_cast(EShaderType::TessEvaluation)].empty() && sources[static_cast(EShaderType::TessControl)].empty(); return valid ? init_from_texts(name, sources) : false; } @@ -107,9 +107,11 @@ bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSourc auto [result, id] = create_shader(type); if (result) shader_ids[i] = id; - else - { + else { BOOST_LOG_TRIVIAL(error) << "glCreateShader() failed for " << shader_type_as_string(type) << " shader of shader program '" << name << "'"; + + // release shaders + release_shaders(shader_ids); return false; } @@ -125,7 +127,7 @@ bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSourc glsafe(::glGetShaderInfoLog(id, params, ¶ms, msg.data())); BOOST_LOG_TRIVIAL(error) << "Unable to compile " << shader_type_as_string(type) << " shader of shader program '" << name << "':\n" << msg.data(); - // release shader + // release shaders release_shaders(shader_ids); return false; } @@ -173,13 +175,10 @@ bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSourc return true; } -bool GLShaderProgram::start_using() const +void GLShaderProgram::start_using() const { - if (m_id == 0) - return false; - + assert(m_id > 0); glsafe(::glUseProgram(m_id)); - return true; } void GLShaderProgram::stop_using() const @@ -212,6 +211,16 @@ bool GLShaderProgram::set_uniform(const char* name, float value) const return false; } +bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform2fv(id, 1, static_cast(value.data()))); + return true; + } + return false; +} + bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const { int id = get_uniform_location(name); @@ -236,8 +245,7 @@ bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t s { if (size == 1) return set_uniform(name, value[0]); - else if (size < 5) - { + else if (size < 5) { int id = get_uniform_location(name); if (id >= 0) { if (size == 2) @@ -268,6 +276,16 @@ bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) co return set_uniform(name, value.cast()); } +bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast(value.data()))); + return true; + } + return false; +} + int GLShaderProgram::get_attrib_location(const char* name) const { return (m_id > 0) ? ::glGetAttribLocation(m_id, name) : -1; diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 23d04b22c8..6994b91caf 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -37,17 +37,19 @@ public: const std::string& get_name() const { return m_name; } unsigned int get_id() const { return m_id; } - bool start_using() const; + void start_using() const; void stop_using() const; bool set_uniform(const char* name, int value) const; bool set_uniform(const char* name, bool value) const; bool set_uniform(const char* name, float value) const; + bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const float* value, size_t size) const; bool set_uniform(const char* name, const Transform3f& value) const; bool set_uniform(const char* name, const Transform3d& value) const; + bool set_uniform(const char* name, const Matrix3f& value) const; // returns -1 if not found int get_attrib_location(const char* name) const; diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index e4d6407224..feffaecdee 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -1,9 +1,12 @@ #include "libslic3r/libslic3r.h" #include "GLShadersManager.hpp" +#include "3DScene.hpp" #include #include +#include + #if ENABLE_SHADERS_MANAGER namespace Slic3r { @@ -71,6 +74,17 @@ GLShaderProgram* GLShadersManager::get_shader(const std::string& shader_name) return (it != m_shaders.end()) ? it->get() : nullptr; } +GLShaderProgram* GLShadersManager::get_current_shader() +{ + GLint id = 0; + glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &id)); + if (id == 0) + return false; + + auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr& p) { return p->get_id() == id; }); + return (it != m_shaders.end()) ? it->get() : nullptr; +} + } // namespace Slic3r #endif // ENABLE_SHADERS_MANAGER diff --git a/src/slic3r/GUI/GLShadersManager.hpp b/src/slic3r/GUI/GLShadersManager.hpp index 0c624f9161..f30472b123 100644 --- a/src/slic3r/GUI/GLShadersManager.hpp +++ b/src/slic3r/GUI/GLShadersManager.hpp @@ -22,6 +22,9 @@ public: // returns nullptr if not found GLShaderProgram* get_shader(const std::string& shader_name); + + // returns currently active shader, nullptr if none + GLShaderProgram* get_current_shader(); }; } // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index c4082cbb21..9bd8697815 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -218,8 +218,8 @@ public: #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG #if ENABLE_SHADERS_MANAGER - // returns nullptr if not found GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); } + GLShaderProgram* get_current_shader() { return m_opengl_mgr.get_current_shader(); } #endif // ENABLE_SHADERS_MANAGER private: @@ -240,6 +240,6 @@ private: DECLARE_APP(GUI_App) } // GUI -} //Slic3r +} // Slic3r #endif // slic3r_GUI_App_hpp_ diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index b645320240..fb3b33494b 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -96,8 +96,8 @@ public: wxGLContext* init_glcontext(wxGLCanvas& canvas); #if ENABLE_SHADERS_MANAGER - // returns nullptr if not found GLShaderProgram* get_shader(const std::string& shader_name) { return m_shaders_manager.get_shader(shader_name); } + GLShaderProgram* get_current_shader() { return m_shaders_manager.get_current_shader(); } #endif // ENABLE_SHADERS_MANAGER static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } From 3bbe2ef9600a03e7e4a4926109c465820274e343 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 May 2020 10:27:41 +0200 Subject: [PATCH 085/826] Fixed typo --- src/slic3r/GUI/GLShadersManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index feffaecdee..a3e48ca554 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -79,7 +79,7 @@ GLShaderProgram* GLShadersManager::get_current_shader() GLint id = 0; glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &id)); if (id == 0) - return false; + return nullptr; auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr& p) { return p->get_id() == id; }); return (it != m_shaders.end()) ? it->get() : nullptr; From 0d579f5467549ebd4680b0c53e0c8ed42834600f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 May 2020 12:13:24 +0200 Subject: [PATCH 086/826] ENABLE_SHADERS_MANAGER -> Small refactoring --- src/slic3r/GUI/GLShadersManager.cpp | 5 +- src/slic3r/GUI/Selection.cpp | 114 ++++++---------------------- src/slic3r/GUI/Selection.hpp | 20 ----- 3 files changed, 27 insertions(+), 112 deletions(-) diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index a3e48ca554..02d033b5a8 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -30,7 +30,7 @@ std::pair GLShadersManager::init() bool valid = true; - // used to render bed axes, selection hints + // used to render bed axes and model, selection hints, gcode sequential view marker model valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); // used to render printbed valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); @@ -62,8 +62,7 @@ std::pair GLShadersManager::init() void GLShadersManager::shutdown() { - for (std::unique_ptr& shader : m_shaders) - { + for (std::unique_ptr& shader : m_shaders) { shader.reset(); } } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index d3e4e37ef8..2949247b80 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1348,35 +1348,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha } if (boost::starts_with(sidebar_field, "position")) -#if ENABLE_SHADERS_MANAGER -#if ENABLE_GCODE_VIEWER - render_sidebar_position_hints(sidebar_field, *shader); -#else render_sidebar_position_hints(sidebar_field); -#endif // ENABLE_GCODE_VIEWER -#else - render_sidebar_position_hints(sidebar_field); -#endif // ENABLE_SHADERS_MANAGER else if (boost::starts_with(sidebar_field, "rotation")) -#if ENABLE_SHADERS_MANAGER -#if ENABLE_GCODE_VIEWER - render_sidebar_rotation_hints(sidebar_field, *shader); -#else render_sidebar_rotation_hints(sidebar_field); -#endif // ENABLE_GCODE_VIEWER -#else - render_sidebar_rotation_hints(sidebar_field); -#endif // ENABLE_SHADERS_MANAGER else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) -#if ENABLE_SHADERS_MANAGER -#if ENABLE_GCODE_VIEWER - render_sidebar_scale_hints(sidebar_field, *shader); -#else render_sidebar_scale_hints(sidebar_field); -#endif // ENABLE_GCODE_VIEWER -#else - render_sidebar_scale_hints(sidebar_field); -#endif // ENABLE_SHADERS_MANAGER else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field); @@ -1999,12 +1975,16 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons } #if ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER -void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader) const -#else void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const -#endif // ENABLE_SHADERS_MANAGER { +#if ENABLE_SHADERS_MANAGER + auto set_color = [](Axis axis) { + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader != nullptr) + shader->set_uniform("uniform_color", AXES_COLOR[axis], 4); + }; +#endif // ENABLE_SHADERS_MANAGER + #if !ENABLE_SHADERS_MANAGER GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); #endif // !ENABLE_SHADERS_MANAGER @@ -2012,7 +1992,7 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) if (boost::ends_with(sidebar_field, "x")) { #if ENABLE_SHADERS_MANAGER - shader.set_uniform("uniform_color", AXES_COLOR[0], 4); + set_color(X); #else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); @@ -2024,7 +2004,7 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) else if (boost::ends_with(sidebar_field, "y")) { #if ENABLE_SHADERS_MANAGER - shader.set_uniform("uniform_color", AXES_COLOR[1], 4); + set_color(Y); #else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); @@ -2035,7 +2015,7 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) else if (boost::ends_with(sidebar_field, "z")) { #if ENABLE_SHADERS_MANAGER - shader.set_uniform("uniform_color", AXES_COLOR[2], 4); + set_color(Z); #else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); @@ -2064,12 +2044,16 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader) const -#else void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const -#endif // ENABLE_SHADERS_MANAGER { +#if ENABLE_SHADERS_MANAGER + auto set_color = [](Axis axis) { + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader != nullptr) + shader->set_uniform("uniform_color", AXES_COLOR[axis], 4); + }; +#endif // ENABLE_SHADERS_MANAGER + #if !ENABLE_SHADERS_MANAGER GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); #endif // !ENABLE_SHADERS_MANAGER @@ -2077,7 +2061,7 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) if (boost::ends_with(sidebar_field, "x")) { #if ENABLE_SHADERS_MANAGER - shader.set_uniform("uniform_color", AXES_COLOR[0], 4); + set_color(X); #else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); @@ -2089,7 +2073,7 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) else if (boost::ends_with(sidebar_field, "y")) { #if ENABLE_SHADERS_MANAGER - shader.set_uniform("uniform_color", AXES_COLOR[1], 4); + set_color(Y); #else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); @@ -2101,7 +2085,7 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) else if (boost::ends_with(sidebar_field, "z")) { #if ENABLE_SHADERS_MANAGER - shader.set_uniform("uniform_color", AXES_COLOR[2], 4); + set_color(Z); #else if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); @@ -2128,52 +2112,7 @@ void Selection::render_sidebar_rotation_hints(const std::string & sidebar_field) } #endif // ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER -void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader) const -#else void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const -#endif // ENABLE_SHADERS_MANAGER -{ - bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); - - if (boost::ends_with(sidebar_field, "x") || uniform_scale) - { - glsafe(::glPushMatrix()); - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); -#if ENABLE_SHADERS_MANAGER - render_sidebar_scale_hint(X, shader); -#else - render_sidebar_scale_hint(X); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glPopMatrix()); - } - - if (boost::ends_with(sidebar_field, "y") || uniform_scale) - { - glsafe(::glPushMatrix()); -#if ENABLE_SHADERS_MANAGER - render_sidebar_scale_hint(Y, shader); -#else - render_sidebar_scale_hint(Y); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glPopMatrix()); - } - - if (boost::ends_with(sidebar_field, "z") || uniform_scale) - { - glsafe(::glPushMatrix()); - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); -#if ENABLE_SHADERS_MANAGER - render_sidebar_scale_hint(Z, shader); -#else - render_sidebar_scale_hint(Z); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glPopMatrix()); - } -} -#else -void Selection::render_sidebar_scale_hints(const std::string & sidebar_field) const { bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); @@ -2200,7 +2139,6 @@ void Selection::render_sidebar_scale_hints(const std::string & sidebar_field) co glsafe(::glPopMatrix()); } } -#endif // ENABLE_GCODE_VIEWER void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const { @@ -2293,18 +2231,16 @@ void Selection::render_sidebar_rotation_hint(Axis axis) const } #if ENABLE_SHADERS_MANAGER -#if ENABLE_GCODE_VIEWER -void Selection::render_sidebar_scale_hint(Axis axis, GLShaderProgram& shader) const -#else void Selection::render_sidebar_scale_hint(Axis axis) const -#endif // ENABLE_GCODE_VIEWER #else void Selection::render_sidebar_scale_hint(Axis axis) const #endif // ENABLE_SHADERS_MANAGER { #if ENABLE_GCODE_VIEWER #if ENABLE_SHADERS_MANAGER - shader.set_uniform("uniform_color", (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4); + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader != nullptr) + shader->set_uniform("uniform_color", (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4); #else GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 2f8ffe58c8..5541cc3fa9 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -385,35 +385,15 @@ private: void render_selected_volumes() const; void render_synchronized_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const; -#if ENABLE_SHADERS_MANAGER -#if ENABLE_GCODE_VIEWER - void render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; - void render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; - void render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader) const; -#else void render_sidebar_position_hints(const std::string& sidebar_field) const; void render_sidebar_rotation_hints(const std::string& sidebar_field) const; void render_sidebar_scale_hints(const std::string& sidebar_field) const; -#endif // ENABLE_GCODE_VIEWER -#else - void render_sidebar_position_hints(const std::string& sidebar_field) const; - void render_sidebar_rotation_hints(const std::string& sidebar_field) const; - void render_sidebar_scale_hints(const std::string& sidebar_field) const; -#endif // ENABLE_SHADERS_MANAGER void render_sidebar_layers_hints(const std::string& sidebar_field) const; #if !ENABLE_GCODE_VIEWER void render_sidebar_position_hint(Axis axis) const; #endif // !ENABLE_GCODE_VIEWER void render_sidebar_rotation_hint(Axis axis) const; -#if ENABLE_SHADERS_MANAGER -#if ENABLE_GCODE_VIEWER - void render_sidebar_scale_hint(Axis axis, GLShaderProgram& shader) const; -#else void render_sidebar_scale_hint(Axis axis) const; -#endif // ENABLE_GCODE_VIEWER -#else - void render_sidebar_scale_hint(Axis axis) const; -#endif // ENABLE_SHADERS_MANAGER public: enum SyncRotationType { From 4eb1b9432f9c90b6d57e96626d6b313e4b686944 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 May 2020 13:07:55 +0200 Subject: [PATCH 087/826] Fixed selection of thumbs into gcode sequential view slider --- src/slic3r/GUI/DoubleSlider.cpp | 7 +++++-- src/slic3r/GUI/DoubleSlider.hpp | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 6173680817..5c16e11eef 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -869,9 +869,12 @@ void Control::draw_cog_icon(wxDC& dc) m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim); } -void Control::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection) +void Control::update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection) { - const wxRect& rect = wxRect(begin_x, begin_y + (selection == ssLower ? int(m_thumb_size.y * 0.5) : 0), m_thumb_size.x, int(m_thumb_size.y*0.5)); + const wxRect rect = is_horizontal() ? + wxRect(begin_x + (selection == ssHigher ? m_thumb_size.x / 2 : 0), begin_y, m_thumb_size.x / 2, m_thumb_size.y) : + wxRect(begin_x, begin_y + (selection == ssLower ? m_thumb_size.y / 2 : 0), m_thumb_size.x, m_thumb_size.y / 2); + if (selection == ssLower) m_rect_lower_thumb = rect; else diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 71949f7f3e..7fbcf607ea 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -283,7 +283,7 @@ protected: void draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_side = true) const; void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; - void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection); + void update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection); bool detect_selected_slider(const wxPoint& pt); void correct_lower_value(); void correct_higher_value(); From 8a9dbb3414d5f9179e4884ef9a6af05ff83a1bc3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 May 2020 13:19:07 +0200 Subject: [PATCH 088/826] ENABLE_SHADERS_MANAGER -> Fixed crash while rendering selection hints --- src/slic3r/GUI/GCodeViewer.cpp | 14 +++++++------- src/slic3r/GUI/Selection.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a584dc7eb1..5d298c0ee1 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1108,14 +1108,14 @@ void GCodeViewer::render_legend() const ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); switch (m_view_type) { - case EViewType::FeatureType: { imgui.text(_u8L("Feature type")); break; } - case EViewType::Height: { imgui.text(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.text(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.text(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.text(_u8L("Fan Speed (%%)")); break; } + case EViewType::FeatureType: { imgui.text(_u8L("Feature type")); break; } + case EViewType::Height: { imgui.text(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.text(_u8L("Width (mm)")); break; } + case EViewType::Feedrate: { imgui.text(_u8L("Speed (mm/s)")); break; } + case EViewType::FanSpeed: { imgui.text(_u8L("Fan Speed (%%)")); break; } case EViewType::VolumetricRate: { imgui.text(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: { imgui.text(_u8L("Tool")); break; } - case EViewType::ColorPrint: { imgui.text(_u8L("Color Print")); break; } + case EViewType::Tool: { imgui.text(_u8L("Tool")); break; } + case EViewType::ColorPrint: { imgui.text(_u8L("Color Print")); break; } default: { break; } } ImGui::PopStyleColor(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2949247b80..f890f0f012 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -17,7 +17,7 @@ #include #endif // ENABLE_GCODE_VIEWER -static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f }; +static const float UNIFORM_SCALE_COLOR[4] = { 0.923f, 0.504f, 0.264f, 1.0f }; namespace Slic3r { namespace GUI { From df010a1d4e26d8279fc61262481c0f33132a3841 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 09:45:31 +0200 Subject: [PATCH 089/826] Added methods GUI_App::is_gl_version_greater_or_equal_to() and GUI_App::is_glsl_version_greater_or_equal_to() --- src/slic3r/GUI/GUI_App.hpp | 3 +++ src/slic3r/GUI/OpenGLManager.cpp | 25 +++++++++++++++++++------ src/slic3r/GUI/OpenGLManager.hpp | 1 + 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 9bd8697815..cbe2aafe16 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -222,6 +222,9 @@ public: GLShaderProgram* get_current_shader() { return m_opengl_mgr.get_current_shader(); } #endif // ENABLE_SHADERS_MANAGER + bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); } + bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); } + private: bool on_init_inner(); void init_app_config(); diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index deaa3cd199..b21fd01439 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -130,18 +130,15 @@ void OpenGLManager::GLInfo::detect() const m_detected = true; } -bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const +static bool version_greater_or_equal_to(const std::string& version, unsigned int major, unsigned int minor) { - if (!m_detected) - detect(); - #if ENABLE_SHADERS_MANAGER - if (m_version == "N/A") + if (version == "N/A") return false; #endif // ENABLE_SHADERS_MANAGER std::vector tokens; - boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on); + boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); if (tokens.empty()) return false; @@ -166,6 +163,22 @@ bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, u return gl_minor >= minor; } +bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const +{ + if (!m_detected) + detect(); + + return version_greater_or_equal_to(m_version, major, minor); +} + +bool OpenGLManager::GLInfo::is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const +{ + if (!m_detected) + detect(); + + return version_greater_or_equal_to(m_glsl_version, major, minor); +} + std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extensions) const { if (!m_detected) diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index fb3b33494b..e33df42491 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -45,6 +45,7 @@ public: float get_max_anisotropy() const; bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const; + bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const; std::string to_string(bool format_as_html, bool extensions) const; From 082a30a5db051072c846a94a72270ed4cdcbd89e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 09:49:42 +0200 Subject: [PATCH 090/826] ENABLE_SHADERS_MANAGER -> Added method GLShaderProgram::set_uniform(const char* name, double value) --- src/slic3r/GUI/GLShader.cpp | 5 +++++ src/slic3r/GUI/GLShader.hpp | 1 + 2 files changed, 6 insertions(+) diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index d0b9267945..38ded63326 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -211,6 +211,11 @@ bool GLShaderProgram::set_uniform(const char* name, float value) const return false; } +bool GLShaderProgram::set_uniform(const char* name, double value) const +{ + return set_uniform(name, static_cast(value)); +} + bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const { int id = get_uniform_location(name); diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 6994b91caf..91a1f66258 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -43,6 +43,7 @@ public: bool set_uniform(const char* name, int value) const; bool set_uniform(const char* name, bool value) const; bool set_uniform(const char* name, float value) const; + bool set_uniform(const char* name, double value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; From f345e583582872c06a4421a377ac70d222ff9abb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 09:51:57 +0200 Subject: [PATCH 091/826] Fix in ENABLE_CAMERA_STATISTICS --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5f9325b14d..5c49eb8295 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2182,7 +2182,7 @@ void GLCanvas3D::render() #endif // ENABLE_RENDER_STATISTICS #if ENABLE_CAMERA_STATISTICS - m_camera.debug_render(); + camera.debug_render(); #endif // ENABLE_CAMERA_STATISTICS std::string tooltip; From 6e279cbec28b6faf2b65b1016341718e0a8bd287 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 10:43:59 +0200 Subject: [PATCH 092/826] GCodeViewer -> Refactoring of sequential view marker positioning --- src/slic3r/GUI/GCodeViewer.cpp | 8 +++++++- src/slic3r/GUI/GCodeViewer.hpp | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 5d298c0ee1..b727388464 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -156,6 +156,12 @@ void GCodeViewer::SequentialView::Marker::init() #endif // !ENABLE_SHADERS_MANAGER } +void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) +{ + m_world_transform = (Geometry::assemble_transform(position.cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size()[2] * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); + m_world_bounding_box = m_model.get_bounding_box().transformed(m_world_transform.cast()); +} + void GCodeViewer::SequentialView::Marker::render() const { #if ENABLE_SHADERS_MANAGER @@ -346,7 +352,7 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); - m_sequential_view.marker.set_world_transform(Geometry::assemble_transform(m_sequential_view.current_position.cast() + (0.5 + 12.0) * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 }).cast()); + m_sequential_view.marker.set_world_position(m_sequential_view.current_position + m_sequential_view_marker_z_offset * Vec3f::UnitZ()); m_sequential_view.marker.render(); render_shells(); render_legend(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index c1f9cfdd7b..45a87a0069 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -211,6 +211,7 @@ public: { GL_Model m_model; Transform3f m_world_transform; + BoundingBoxf3 m_world_bounding_box; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; bool m_visible{ false }; #if !ENABLE_SHADERS_MANAGER @@ -220,9 +221,9 @@ public: public: void init(); - const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } + const BoundingBoxf3& get_bounding_box() const { return m_world_bounding_box; } - void set_world_transform(const Transform3f& transform) { m_world_transform = transform; } + void set_world_position(const Vec3f& position); void set_color(const std::array& color) { m_color = color; } bool is_visible() const { return m_visible; } @@ -273,6 +274,7 @@ private: std::vector m_extruder_ids; mutable Extrusions m_extrusions; mutable SequentialView m_sequential_view; + float m_sequential_view_marker_z_offset{ 0.5f }; Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; From 83ea38c2f31a452d8ad66730398887ed087f79d3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 11:52:07 +0200 Subject: [PATCH 093/826] GCodeViewer -> Refactoring of options coloring + options added to legend --- src/slic3r/GUI/GCodeViewer.cpp | 74 +++++++++++++++++++++++++--------- src/slic3r/GUI/GCodeViewer.hpp | 11 +++++ 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b727388464..453b9ecaeb 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -233,6 +233,15 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.00f, 0.00f, 0.00f } // erMixed }}; +const std::vector GCodeViewer::Options_Colors {{ + { 1.00f, 0.00f, 1.00f }, // Retractions + { 0.00f, 1.00f, 1.00f }, // Unretractions + { 1.00f, 1.00f, 1.00f }, // ToolChanges + { 1.00f, 0.00f, 0.00f }, // ColorChanges + { 0.00f, 1.00f, 0.00f }, // PausePrints + { 0.00f, 0.00f, 1.00f } // CustomGCodes +}}; + const std::vector GCodeViewer::Travel_Colors {{ { 0.0f, 0.0f, 0.5f }, // Move { 0.0f, 0.5f, 0.0f }, // Extrude @@ -857,11 +866,10 @@ void GCodeViewer::render_toolpaths() const { case GCodeProcessor::EMoveType::Tool_change: { - Color color = { 1.0f, 1.0f, 1.0f }; #if ENABLE_SHADERS_MANAGER - shader->set_uniform("uniform_color", color); + shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ToolChanges)]); #else - set_color(static_cast(buffer.shader.get_shader_program_id()), color); + set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::ToolChanges)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { @@ -877,11 +885,10 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Color_change: { - Color color = { 1.0f, 0.0f, 0.0f }; #if ENABLE_SHADERS_MANAGER - shader->set_uniform("uniform_color", color); + shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ColorChanges)]); #else - set_color(static_cast(buffer.shader.get_shader_program_id()), color); + set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::ColorChanges)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { @@ -897,11 +904,10 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Pause_Print: { - Color color = { 0.0f, 1.0f, 0.0f }; #if ENABLE_SHADERS_MANAGER - shader->set_uniform("uniform_color", color); + shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::PausePrints)]); #else - set_color(static_cast(buffer.shader.get_shader_program_id()), color); + set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::PausePrints)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { @@ -917,11 +923,10 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Custom_GCode: { - Color color = { 0.0f, 0.0f, 1.0f }; #if ENABLE_SHADERS_MANAGER - shader->set_uniform("uniform_color", color); + shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); #else - set_color(static_cast(buffer.shader.get_shader_program_id()), color); + set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { @@ -937,11 +942,10 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Retract: { - Color color = { 1.0f, 0.0f, 1.0f }; #if ENABLE_SHADERS_MANAGER - shader->set_uniform("uniform_color", color); + shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Retractions)]); #else - set_color(static_cast(buffer.shader.get_shader_program_id()), color); + set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::Retractions)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { @@ -957,11 +961,10 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Unretract: { - Color color = { 0.0f, 1.0f, 1.0f }; #if ENABLE_SHADERS_MANAGER - shader->set_uniform("uniform_color", color); + shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Unretractions)]); #else - set_color(static_cast(buffer.shader.get_shader_program_id()), color); + set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::Unretractions)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { @@ -1279,6 +1282,41 @@ void GCodeViewer::render_legend() const } } + auto any_option_visible = [this]() { + return m_buffers[buffer_id(GCodeProcessor::EMoveType::Color_change)].visible || + m_buffers[buffer_id(GCodeProcessor::EMoveType::Custom_GCode)].visible || + m_buffers[buffer_id(GCodeProcessor::EMoveType::Pause_Print)].visible || + m_buffers[buffer_id(GCodeProcessor::EMoveType::Retract)].visible || + m_buffers[buffer_id(GCodeProcessor::EMoveType::Tool_change)].visible || + m_buffers[buffer_id(GCodeProcessor::EMoveType::Unretract)].visible; + }; + + auto add_option = [this, add_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { + const IBuffer& buffer = m_buffers[buffer_id(move_type)]; + if (buffer.visible && buffer.indices_count > 0) + add_item(Options_Colors[static_cast(color)], text); + }; + + // options + if (any_option_visible()) + { + // title + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(_u8L("Options")); + ImGui::PopStyleColor(); + ImGui::Separator(); + + // items + add_option(GCodeProcessor::EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); + add_option(GCodeProcessor::EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Unretractions")); + add_option(GCodeProcessor::EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); + add_option(GCodeProcessor::EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); + add_option(GCodeProcessor::EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Pause prints")); + add_option(GCodeProcessor::EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom GCodes")); + } + imgui.end(); ImGui::PopStyleVar(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 45a87a0069..72f45aedca 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -22,9 +22,20 @@ class GCodeViewer { using Color = std::array; static const std::vector Extrusion_Role_Colors; + static const std::vector Options_Colors; static const std::vector Travel_Colors; static const std::vector Range_Colors; + enum class EOptionsColors : unsigned char + { + Retractions, + Unretractions, + ToolChanges, + ColorChanges, + PausePrints, + CustomGCodes + }; + // buffer containing vertices data struct VBuffer { From 4d05ec085638d8c5cc94c1291e2c78fed1f6101e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 13:21:43 +0200 Subject: [PATCH 094/826] GCodeViewer -> New shaders for options --- resources/shaders/colorchanges.fs | 45 ------------ resources/shaders/colorchanges.vs | 17 ----- resources/shaders/customs.fs | 45 ------------ resources/shaders/customs.vs | 17 ----- resources/shaders/options_110.fs | 8 +++ resources/shaders/options_110.vs | 11 +++ resources/shaders/options_120.fs | 15 ++++ resources/shaders/options_120.vs | 11 +++ resources/shaders/pauses.fs | 45 ------------ resources/shaders/pauses.vs | 17 ----- resources/shaders/retractions.fs | 45 ------------ resources/shaders/retractions.vs | 17 ----- resources/shaders/toolchanges.fs | 45 ------------ resources/shaders/toolchanges.vs | 17 ----- resources/shaders/unretractions.fs | 45 ------------ resources/shaders/unretractions.vs | 17 ----- src/slic3r/GUI/GCodeViewer.cpp | 108 +++++++++++++++++++++------- src/slic3r/GUI/GLShadersManager.cpp | 15 +--- 18 files changed, 132 insertions(+), 408 deletions(-) delete mode 100644 resources/shaders/colorchanges.fs delete mode 100644 resources/shaders/colorchanges.vs delete mode 100644 resources/shaders/customs.fs delete mode 100644 resources/shaders/customs.vs create mode 100644 resources/shaders/options_110.fs create mode 100644 resources/shaders/options_110.vs create mode 100644 resources/shaders/options_120.fs create mode 100644 resources/shaders/options_120.vs delete mode 100644 resources/shaders/pauses.fs delete mode 100644 resources/shaders/pauses.vs delete mode 100644 resources/shaders/retractions.fs delete mode 100644 resources/shaders/retractions.vs delete mode 100644 resources/shaders/toolchanges.fs delete mode 100644 resources/shaders/toolchanges.vs delete mode 100644 resources/shaders/unretractions.fs delete mode 100644 resources/shaders/unretractions.vs diff --git a/resources/shaders/colorchanges.fs b/resources/shaders/colorchanges.fs deleted file mode 100644 index fc81e487fb..0000000000 --- a/resources/shaders/colorchanges.fs +++ /dev/null @@ -1,45 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; -//varying float world_normal_z; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/colorchanges.vs b/resources/shaders/colorchanges.vs deleted file mode 100644 index 3b78a59700..0000000000 --- a/resources/shaders/colorchanges.vs +++ /dev/null @@ -1,17 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; - gl_Position = ftransform(); - - gl_PointSize = 15.0; -} diff --git a/resources/shaders/customs.fs b/resources/shaders/customs.fs deleted file mode 100644 index fc81e487fb..0000000000 --- a/resources/shaders/customs.fs +++ /dev/null @@ -1,45 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; -//varying float world_normal_z; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/customs.vs b/resources/shaders/customs.vs deleted file mode 100644 index 3b78a59700..0000000000 --- a/resources/shaders/customs.vs +++ /dev/null @@ -1,17 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; - gl_Position = ftransform(); - - gl_PointSize = 15.0; -} diff --git a/resources/shaders/options_110.fs b/resources/shaders/options_110.fs new file mode 100644 index 0000000000..3722058c87 --- /dev/null +++ b/resources/shaders/options_110.fs @@ -0,0 +1,8 @@ +#version 120 + +uniform vec3 uniform_color; + +void main() +{ + gl_FragColor = vec4(uniform_color, 1.0); +} diff --git a/resources/shaders/options_110.vs b/resources/shaders/options_110.vs new file mode 100644 index 0000000000..5361c88cec --- /dev/null +++ b/resources/shaders/options_110.vs @@ -0,0 +1,11 @@ +#version 110 + +uniform float zoom; +// x = min, y = max +uniform vec2 point_sizes; + +void main() +{ + gl_PointSize = clamp(zoom, point_sizes.x, point_sizes.y); + gl_Position = ftransform(); +} diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs new file mode 100644 index 0000000000..090cde292f --- /dev/null +++ b/resources/shaders/options_120.fs @@ -0,0 +1,15 @@ +#version 120 + +uniform vec3 uniform_color; + +void main() +{ + vec2 pos = gl_PointCoord - vec2(0.5, 0.5); + float sq_radius = pos.x * pos.x + pos.y * pos.y; + if (sq_radius > 0.25) + discard; + else if (sq_radius > 0.180625) + gl_FragColor = vec4(0.5 * uniform_color, 1.0); + else + gl_FragColor = vec4(uniform_color, 1.0); +} diff --git a/resources/shaders/options_120.vs b/resources/shaders/options_120.vs new file mode 100644 index 0000000000..ebf7428c9a --- /dev/null +++ b/resources/shaders/options_120.vs @@ -0,0 +1,11 @@ +#version 120 + +uniform float zoom; +// x = min, y = max +uniform vec2 point_sizes; + +void main() +{ + gl_PointSize = clamp(zoom, point_sizes.x, point_sizes.y); + gl_Position = ftransform(); +} diff --git a/resources/shaders/pauses.fs b/resources/shaders/pauses.fs deleted file mode 100644 index fc81e487fb..0000000000 --- a/resources/shaders/pauses.fs +++ /dev/null @@ -1,45 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; -//varying float world_normal_z; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/pauses.vs b/resources/shaders/pauses.vs deleted file mode 100644 index 3b78a59700..0000000000 --- a/resources/shaders/pauses.vs +++ /dev/null @@ -1,17 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; - gl_Position = ftransform(); - - gl_PointSize = 15.0; -} diff --git a/resources/shaders/retractions.fs b/resources/shaders/retractions.fs deleted file mode 100644 index fc81e487fb..0000000000 --- a/resources/shaders/retractions.fs +++ /dev/null @@ -1,45 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; -//varying float world_normal_z; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/retractions.vs b/resources/shaders/retractions.vs deleted file mode 100644 index 3b78a59700..0000000000 --- a/resources/shaders/retractions.vs +++ /dev/null @@ -1,17 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; - gl_Position = ftransform(); - - gl_PointSize = 15.0; -} diff --git a/resources/shaders/toolchanges.fs b/resources/shaders/toolchanges.fs deleted file mode 100644 index fc81e487fb..0000000000 --- a/resources/shaders/toolchanges.fs +++ /dev/null @@ -1,45 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; -//varying float world_normal_z; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/toolchanges.vs b/resources/shaders/toolchanges.vs deleted file mode 100644 index 3b78a59700..0000000000 --- a/resources/shaders/toolchanges.vs +++ /dev/null @@ -1,17 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; - gl_Position = ftransform(); - - gl_PointSize = 15.0; -} diff --git a/resources/shaders/unretractions.fs b/resources/shaders/unretractions.fs deleted file mode 100644 index fc81e487fb..0000000000 --- a/resources/shaders/unretractions.fs +++ /dev/null @@ -1,45 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; -//varying float world_normal_z; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/unretractions.vs b/resources/shaders/unretractions.vs deleted file mode 100644 index 3b78a59700..0000000000 --- a/resources/shaders/unretractions.vs +++ /dev/null @@ -1,17 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; - gl_Position = ftransform(); - - gl_PointSize = 15.0; -} diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 453b9ecaeb..cf2f689cef 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -440,12 +440,12 @@ void GCodeViewer::init_shaders() { switch (buffer_type(i)) { - case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = "toolchanges"; break; } - case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = "colorchanges"; break; } - case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = "pauses"; break; } - case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = "customs"; break; } - case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = "retractions"; break; } - case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = "unretractions"; break; } + case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } + case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } + case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } + case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } + case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } + case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "extrusions"; break; } case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "travels"; break; } default: { break; } @@ -827,6 +827,12 @@ void GCodeViewer::render_toolpaths() const }; #endif // !ENABLE_SHADERS_MANAGER + bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); + int detected_point_sizes[2]; + ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, detected_point_sizes); + std::array point_sizes = { 2.0f, std::min(64.0f, static_cast(detected_point_sizes[1])) }; + double zoom = wxGetApp().plater()->get_camera().get_zoom(); + glsafe(::glCullFace(GL_BACK)); glsafe(::glLineWidth(3.0f)); @@ -868,94 +874,139 @@ void GCodeViewer::render_toolpaths() const { #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ToolChanges)]); + shader->set_uniform("zoom", zoom); + shader->set_uniform("point_sizes", point_sizes); + if (is_glsl_120) + { + glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + } #else set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::ToolChanges)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } + if (is_glsl_120) + { + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + glsafe(::glDisable(GL_POINT_SPRITE)); + } break; } case GCodeProcessor::EMoveType::Color_change: { #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ColorChanges)]); + shader->set_uniform("zoom", zoom); + shader->set_uniform("point_sizes", point_sizes); + if (is_glsl_120) + { + glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + } #else set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::ColorChanges)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } + if (is_glsl_120) + { + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + glsafe(::glDisable(GL_POINT_SPRITE)); + } break; } case GCodeProcessor::EMoveType::Pause_Print: { #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::PausePrints)]); + shader->set_uniform("zoom", zoom); + shader->set_uniform("point_sizes", point_sizes); + if (is_glsl_120) + { + glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + } #else set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::PausePrints)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } + if (is_glsl_120) + { + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + glsafe(::glDisable(GL_POINT_SPRITE)); + } break; } case GCodeProcessor::EMoveType::Custom_GCode: { #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); + shader->set_uniform("zoom", zoom); + shader->set_uniform("point_sizes", point_sizes); + if (is_glsl_120) + { + glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + } #else set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } + if (is_glsl_120) + { + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + glsafe(::glDisable(GL_POINT_SPRITE)); + } break; } case GCodeProcessor::EMoveType::Retract: { #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Retractions)]); + shader->set_uniform("zoom", zoom); + shader->set_uniform("point_sizes", point_sizes); + if (is_glsl_120) + { + glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + } #else set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::Retractions)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + if (is_glsl_120) + { + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + glsafe(::glDisable(GL_POINT_SPRITE)); + } } break; } @@ -963,19 +1014,28 @@ void GCodeViewer::render_toolpaths() const { #if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Unretractions)]); + shader->set_uniform("zoom", zoom); + shader->set_uniform("point_sizes", point_sizes); + if (is_glsl_120) + { + glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + } #else set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::Unretractions)]); #endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { - glsafe(::glEnable(GL_PROGRAM_POINT_SIZE)); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); - glsafe(::glDisable(GL_PROGRAM_POINT_SIZE)); - #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } + if (is_glsl_120) + { + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + glsafe(::glDisable(GL_POINT_SPRITE)); + } break; } case GCodeProcessor::EMoveType::Extrude: diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 02d033b5a8..6df8c75208 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -34,18 +34,9 @@ std::pair GLShadersManager::init() valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); // used to render printbed valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); - // used to render tool changes in gcode preview - valid &= append_shader("toolchanges", { "toolchanges.vs", "toolchanges.fs" }); - // used to render color changes in gcode preview - valid &= append_shader("colorchanges", { "colorchanges.vs", "colorchanges.fs" }); - // used to render pause prints in gcode preview - valid &= append_shader("pauses", { "pauses.vs", "pauses.fs" }); - // used to render custom gcode points in gcode preview - valid &= append_shader("customs", { "customs.vs", "customs.fs" }); - // used to render retractions in gcode preview - valid &= append_shader("retractions", { "retractions.vs", "retractions.fs" }); - // used to render unretractions in gcode preview - valid &= append_shader("unretractions", { "unretractions.vs", "unretractions.fs" }); + // used to render options in gcode preview + valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); + valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" }); // used to render extrusion paths in gcode preview valid &= append_shader("extrusions", { "extrusions.vs", "extrusions.fs" }); // used to render travel paths in gcode preview From 314995fa0b5a9a0d49178cf934a0ea418991f93d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 16:08:02 +0200 Subject: [PATCH 095/826] ENABLE_SHADERS_MANAGER set as default --- resources/shaders/options_120.fs | 2 +- src/libslic3r/Technologies.hpp | 3 - src/slic3r/GUI/3DBed.cpp | 58 +---- src/slic3r/GUI/3DBed.hpp | 9 - src/slic3r/GUI/3DScene.cpp | 80 ------ src/slic3r/GUI/GCodeViewer.cpp | 150 +----------- src/slic3r/GUI/GCodeViewer.hpp | 29 --- src/slic3r/GUI/GLCanvas3D.cpp | 233 +++--------------- src/slic3r/GUI/GLCanvas3D.hpp | 17 +- src/slic3r/GUI/GLShader.cpp | 362 ---------------------------- src/slic3r/GUI/GLShader.hpp | 71 ------ src/slic3r/GUI/GLShadersManager.cpp | 3 - src/slic3r/GUI/GLShadersManager.hpp | 4 - src/slic3r/GUI/GUI_App.hpp | 2 - src/slic3r/GUI/OpenGLManager.cpp | 45 +--- src/slic3r/GUI/OpenGLManager.hpp | 6 - src/slic3r/GUI/Selection.cpp | 161 ++----------- src/slic3r/GUI/Selection.hpp | 20 -- 18 files changed, 57 insertions(+), 1198 deletions(-) diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs index 090cde292f..5f699f9204 100644 --- a/resources/shaders/options_120.fs +++ b/resources/shaders/options_120.fs @@ -8,7 +8,7 @@ void main() float sq_radius = pos.x * pos.x + pos.y * pos.y; if (sq_radius > 0.25) discard; - else if (sq_radius > 0.180625) + else if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) gl_FragColor = vec4(0.5 * uniform_color, 1.0); else gl_FragColor = vec4(uniform_color, 1.0); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index d0673e2b4b..3df9da961d 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -46,7 +46,4 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) -// Enable the OpenGL shaders manager -#define ENABLE_SHADERS_MANAGER (1 && ENABLE_2_3_0_ALPHA1) - #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 3fdcdd6fab..b5a34b2ee7 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -163,14 +163,7 @@ Bed3D::Axes::~Axes() void Bed3D::Axes::render() const { #if ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER auto render_axis = [this](const Transform3f& transform) { -#else - auto render_axis = [this](const Transform3f& transform, GLint color_id, const std::array& color) { - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color.data())); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(transform.data())); m_arrow.render(); @@ -178,21 +171,13 @@ void Bed3D::Axes::render() const }; m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); -#if ENABLE_SHADERS_MANAGER + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; -#else - if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) - BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; - - if (!m_shader.is_initialized()) - return; -#endif // ENABLE_SHADERS_MANAGER glsafe(::glEnable(GL_DEPTH_TEST)); -#if ENABLE_SHADERS_MANAGER shader->start_using(); // x axis @@ -211,21 +196,6 @@ void Bed3D::Axes::render() const render_axis(Geometry::assemble_transform(m_origin).cast()); shader->stop_using(); -#else - m_shader.start_using(); - GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); - - // x axis - render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0f }).cast(), color_id, { 0.75f, 0.0f, 0.0f, 1.0f }); - - // y axis - render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0f }).cast(), color_id, { 0.0f, 0.75f, 0.0f, 1.0f }); - - // z axis - render_axis(Geometry::assemble_transform(m_origin).cast(), color_id, { 0.0f, 0.0f, 0.75f, 1.0f }); - - m_shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glDisable(GL_DEPTH_TEST)); #else @@ -571,23 +541,12 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const if (m_triangles.get_vertices_count() > 0) { -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("printbed"); if (shader != nullptr) { shader->start_using(); shader->set_uniform("transparent_background", bottom); shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); -#else - if (m_shader.get_shader_program_id() == 0) - m_shader.init("printbed.vs", "printbed.fs"); - - if (m_shader.is_initialized()) - { - m_shader.start_using(); - m_shader.set_uniform("transparent_background", bottom); - m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); -#endif // ENABLE_SHADERS_MANAGER if (m_vbo_id == 0) { @@ -608,13 +567,8 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const unsigned int stride = m_triangles.get_vertex_data_size(); -#if ENABLE_SHADERS_MANAGER GLint position_id = shader->get_attrib_location("v_position"); GLint tex_coords_id = shader->get_attrib_location("v_tex_coords"); -#else - GLint position_id = m_shader.get_attrib_location("v_position"); - GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); -#endif // ENABLE_SHADERS_MANAGER // show the temporary texture while no compressed data is available GLuint tex_id = (GLuint)m_temp_texture.get_id(); @@ -652,11 +606,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const glsafe(::glDisable(GL_BLEND)); glsafe(::glDepthMask(GL_TRUE)); -#if ENABLE_SHADERS_MANAGER shader->stop_using(); -#else - m_shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER } } } @@ -679,7 +629,6 @@ void Bed3D::render_model() const if (!m_model.get_filename().empty()) { -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { @@ -687,11 +636,6 @@ void Bed3D::render_model() const m_model.render(); shader->stop_using(); } -#else - glsafe(::glEnable(GL_LIGHTING)); - m_model.render(); - glsafe(::glDisable(GL_LIGHTING)); -#endif // ENABLE_SHADERS_MANAGER } } diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index d9a2c82620..2487be2e47 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,9 +3,6 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -#if !ENABLE_SHADERS_MANAGER -#include "GLShader.hpp" -#endif // !ENABLE_SHADERS_MANAGER #if ENABLE_GCODE_VIEWER #include "GLModel.hpp" #endif // ENABLE_GCODE_VIEWER @@ -71,9 +68,6 @@ class Bed3D Vec3d m_origin{ Vec3d::Zero() }; float m_stem_length{ DefaultStemLength }; mutable GL_Model m_arrow; -#if !ENABLE_SHADERS_MANAGER - mutable Shader m_shader; -#endif // !ENABLE_SHADERS_MANAGER public: #else @@ -122,9 +116,6 @@ private: mutable GLBed m_model; // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; -#if !ENABLE_SHADERS_MANAGER - mutable Shader m_shader; -#endif // !ENABLE_SHADERS_MANAGER mutable unsigned int m_vbo_id; Axes m_axes; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6e42e52a21..fb7d4c24cf 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1,10 +1,8 @@ #include #include "3DScene.hpp" -#if ENABLE_SHADERS_MANAGER #include "GLShader.hpp" #include "GUI_App.hpp" -#endif // ENABLE_SHADERS_MANAGER #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" @@ -644,11 +642,9 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const { -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); if (shader == nullptr) return; -#endif // ENABLE_SHADERS_MANAGER glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -660,7 +656,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#if ENABLE_SHADERS_MANAGER shader->set_uniform("print_box.min", m_print_box_min, 3); shader->set_uniform("print_box.max", m_print_box_max, 3); shader->set_uniform("z_range", m_z_range, 2); @@ -668,74 +663,16 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab #if ENABLE_SLOPE_RENDERING shader->set_uniform("slope.z_range", m_slope.z_range); #endif // ENABLE_SLOPE_RENDERING -#else - GLint current_program_id; - glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); - GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; - GLint z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "z_range") : -1; - GLint clipping_plane_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "clipping_plane") : -1; - - GLint print_box_min_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.min") : -1; - GLint print_box_max_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.max") : -1; - GLint print_box_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.actived") : -1; - GLint print_box_worldmatrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_world_matrix") : -1; - -#if ENABLE_SLOPE_RENDERING - GLint slope_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.actived") : -1; - GLint slope_normal_matrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.volume_world_normal_matrix") : -1; - GLint slope_z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.z_range") : -1; -#endif // ENABLE_SLOPE_RENDERING - glcheck(); - - if (print_box_min_id != -1) - glsafe(::glUniform3fv(print_box_min_id, 1, (const GLfloat*)m_print_box_min)); - - if (print_box_max_id != -1) - glsafe(::glUniform3fv(print_box_max_id, 1, (const GLfloat*)m_print_box_max)); - - if (z_range_id != -1) - glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)m_z_range)); - - if (clipping_plane_id != -1) - glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)m_clipping_plane)); - -#if ENABLE_SLOPE_RENDERING - if (slope_z_range_id != -1) - glsafe(::glUniform2fv(slope_z_range_id, 1, (const GLfloat*)m_slope.z_range.data())); -#endif // ENABLE_SLOPE_RENDERING -#endif // ENABLE_SHADERS_MANAGER GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); #if ENABLE_SLOPE_RENDERING -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", volume.first->render_color, 4); shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled); shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); shader->set_uniform("slope.volume_world_normal_matrix", volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast()); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)volume.first->render_color)); - else - glsafe(::glColor4fv(volume.first->render_color)); - - if (print_box_active_id != -1) - glsafe(::glUniform1i(print_box_active_id, volume.first->shader_outside_printer_detection_enabled ? 1 : 0)); - - if (print_box_worldmatrix_id != -1) - glsafe(::glUniformMatrix4fv(print_box_worldmatrix_id, 1, GL_FALSE, (const GLfloat*)volume.first->world_matrix().cast().data())); - - if (slope_active_id != -1) - glsafe(::glUniform1i(slope_active_id, m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower ? 1 : 0)); - - if (slope_normal_matrix_id != -1) - { - Matrix3f normal_matrix = volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast(); - glsafe(::glUniformMatrix3fv(slope_normal_matrix_id, 1, GL_FALSE, (const GLfloat*)normal_matrix.data())); - } -#endif // ENABLE_SHADERS_MANAGER volume.first->render(); #else @@ -1940,11 +1877,9 @@ void GLModel::reset() void GLModel::render() const { -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); if (shader == nullptr) return; -#endif // ENABLE_SHADERS_MANAGER glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -1953,23 +1888,8 @@ void GLModel::render() const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#if !ENABLE_SHADERS_MANAGER - GLint current_program_id; - glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); - GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; - glcheck(); -#endif // !ENABLE_SHADERS_MANAGER - #if ENABLE_SLOPE_RENDERING -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", m_volume.render_color, 4); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_volume.render_color)); - else - glsafe(::glColor4fv(m_volume.render_color)); -#endif // ENABLE_SHADERS_MANAGER - m_volume.render(); #else m_volume.render(color_id, -1, -1); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index cf2f689cef..c2e680b0eb 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -107,18 +107,6 @@ void GCodeViewer::IBuffer::reset() render_paths = std::vector(); } -#if !ENABLE_SHADERS_MANAGER -bool GCodeViewer::IBuffer::init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src) -{ - if (!shader.init(vertex_shader_src, fragment_shader_src)) { - BOOST_LOG_TRIVIAL(error) << "Unable to initialize toolpaths shader: please, check that the files " << vertex_shader_src << " and " << fragment_shader_src << " are available"; - return false; - } - - return true; -} -#endif // !ENABLE_SHADERS_MANAGER - void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) { Path::Endpoint endpoint = { i_id, s_id, move.position }; @@ -151,9 +139,6 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con void GCodeViewer::SequentialView::Marker::init() { m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f)); -#if !ENABLE_SHADERS_MANAGER - init_shader(); -#endif // !ENABLE_SHADERS_MANAGER } void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) @@ -164,7 +149,6 @@ void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& positi void GCodeViewer::SequentialView::Marker::render() const { -#if ENABLE_SHADERS_MANAGER if (!m_visible) return; @@ -177,18 +161,6 @@ void GCodeViewer::SequentialView::Marker::render() const shader->start_using(); shader->set_uniform("uniform_color", m_color); -#else - if (!m_visible || !m_shader.is_initialized()) - return; - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - m_shader.start_using(); - GLint color_id = ::glGetUniformLocation(m_shader.get_shader_program_id(), "uniform_color"); - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_color.data())); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(m_world_transform.data())); @@ -197,23 +169,11 @@ void GCodeViewer::SequentialView::Marker::render() const glsafe(::glPopMatrix()); -#if ENABLE_SHADERS_MANAGER shader->stop_using(); -#else - m_shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glDisable(GL_BLEND)); } -#if !ENABLE_SHADERS_MANAGER -void GCodeViewer::SequentialView::Marker::init_shader() -{ - if (!m_shader.init("gouraud_light.vs", "gouraud_light.fs")) - BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; -} -#endif // !ENABLE_SHADERS_MANAGER - const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.50f, 0.50f, 0.50f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter @@ -430,7 +390,6 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range wxGetApp().plater()->update_preview_moves_slider(); } -#if ENABLE_SHADERS_MANAGER void GCodeViewer::init_shaders() { unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); @@ -452,42 +411,6 @@ void GCodeViewer::init_shaders() } } } -#else -bool GCodeViewer::init_shaders() -{ - unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); - unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); - - for (unsigned char i = begin_id; i < end_id; ++i) - { - std::string vertex_shader; - std::string fragment_shader; - - switch (buffer_type(i)) - { - case GCodeProcessor::EMoveType::Tool_change: { vertex_shader = "toolchanges.vs"; fragment_shader = "toolchanges.fs"; break; } - case GCodeProcessor::EMoveType::Color_change: { vertex_shader = "colorchanges.vs"; fragment_shader = "colorchanges.fs"; break; } - case GCodeProcessor::EMoveType::Pause_Print: { vertex_shader = "pauses.vs"; fragment_shader = "pauses.fs"; break; } - case GCodeProcessor::EMoveType::Custom_GCode: { vertex_shader = "customs.vs"; fragment_shader = "customs.fs"; break; } - case GCodeProcessor::EMoveType::Retract: { vertex_shader = "retractions.vs"; fragment_shader = "retractions.fs"; break; } - case GCodeProcessor::EMoveType::Unretract: { vertex_shader = "unretractions.vs"; fragment_shader = "unretractions.fs"; break; } - case GCodeProcessor::EMoveType::Extrude: { vertex_shader = "extrusions.vs"; fragment_shader = "extrusions.fs"; break; } - case GCodeProcessor::EMoveType::Travel: { vertex_shader = "travels.vs"; fragment_shader = "travels.fs"; break; } - default: { break; } - } - - if (vertex_shader.empty() || fragment_shader.empty() || !m_buffers[i].init_shader(vertex_shader, fragment_shader)) - return false; - } - - if (!m_shells.shader.init("shells.vs", "shells.fs")) { - BOOST_LOG_TRIVIAL(error) << "Unable to initialize shells shader: please, check that the files shells.vs and shells.fs are available"; - return false; - } - - return true; -} -#endif // ENABLE_SHADERS_MANAGER void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { @@ -814,19 +737,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { -#if !ENABLE_SHADERS_MANAGER - auto set_color = [](GLint current_program_id, const Color& color) { - if (current_program_id > 0) { - GLint color_id = ::glGetUniformLocation(current_program_id, "uniform_color"); - if (color_id >= 0) { - glsafe(::glUniform3fv(color_id, 1, (const GLfloat*)color.data())); - return; - } - } - BOOST_LOG_TRIVIAL(error) << "Unable to find uniform_color uniform"; - }; -#endif // !ENABLE_SHADERS_MANAGER - bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); int detected_point_sizes[2]; ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, detected_point_sizes); @@ -851,28 +761,18 @@ void GCodeViewer::render_toolpaths() const if (!buffer.visible) continue; -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); if (shader != nullptr) { -#else - if (buffer.shader.is_initialized()) { -#endif // ENABLE_SHADERS_MANAGER + shader->start_using(); GCodeProcessor::EMoveType type = buffer_type(i); -#if ENABLE_SHADERS_MANAGER - shader->start_using(); -#else - buffer.shader.start_using(); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); switch (type) { case GCodeProcessor::EMoveType::Tool_change: { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ToolChanges)]); shader->set_uniform("zoom", zoom); shader->set_uniform("point_sizes", point_sizes); @@ -881,9 +781,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); } -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::ToolChanges)]); -#endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); @@ -900,7 +797,6 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Color_change: { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ColorChanges)]); shader->set_uniform("zoom", zoom); shader->set_uniform("point_sizes", point_sizes); @@ -909,9 +805,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); } -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::ColorChanges)]); -#endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); @@ -928,7 +821,6 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Pause_Print: { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::PausePrints)]); shader->set_uniform("zoom", zoom); shader->set_uniform("point_sizes", point_sizes); @@ -937,9 +829,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); } -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::PausePrints)]); -#endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); @@ -956,7 +845,6 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Custom_GCode: { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); shader->set_uniform("zoom", zoom); shader->set_uniform("point_sizes", point_sizes); @@ -965,9 +853,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); } -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); -#endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); @@ -984,7 +869,6 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Retract: { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Retractions)]); shader->set_uniform("zoom", zoom); shader->set_uniform("point_sizes", point_sizes); @@ -993,9 +877,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); } -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::Retractions)]); -#endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); @@ -1012,7 +893,6 @@ void GCodeViewer::render_toolpaths() const } case GCodeProcessor::EMoveType::Unretract: { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Unretractions)]); shader->set_uniform("zoom", zoom); shader->set_uniform("point_sizes", point_sizes); @@ -1021,9 +901,6 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); } -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), Options_Colors[static_cast(EOptionsColors::Unretractions)]); -#endif // ENABLE_SHADERS_MANAGER for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); @@ -1042,11 +919,7 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", path.color); -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -1059,11 +932,7 @@ void GCodeViewer::render_toolpaths() const { for (const RenderPath& path : buffer.render_paths) { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", path.color); -#else - set_color(static_cast(buffer.shader.get_shader_program_id()), path.color); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -1075,11 +944,7 @@ void GCodeViewer::render_toolpaths() const } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); -#if ENABLE_SHADERS_MANAGER shader->stop_using(); -#else - buffer.shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER } } @@ -1089,7 +954,6 @@ void GCodeViewer::render_toolpaths() const void GCodeViewer::render_shells() const { -#if ENABLE_SHADERS_MANAGER if (!m_shells.visible || m_shells.volumes.empty()) return; @@ -1104,18 +968,6 @@ void GCodeViewer::render_shells() const shader->stop_using(); // glsafe(::glDepthMask(GL_TRUE)); -#else - if (!m_shells.visible || m_shells.volumes.empty() || !m_shells.shader.is_initialized()) - return; - -// glsafe(::glDepthMask(GL_FALSE)); - - m_shells.shader.start_using(); - m_shells.volumes.render(GLVolumeCollection::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix()); - m_shells.shader.stop_using(); - -// glsafe(::glDepthMask(GL_TRUE)); -#endif // ENABLE_SHADERS_MANAGER } void GCodeViewer::render_legend() const diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 72f45aedca..5ab9f68325 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -2,9 +2,6 @@ #define slic3r_GCodeViewer_hpp_ #if ENABLE_GCODE_VIEWER -#if !ENABLE_SHADERS_MANAGER -#include "GLShader.hpp" -#endif // !ENABLE_SHADERS_MANAGER #include "3DScene.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "GLModel.hpp" @@ -91,19 +88,12 @@ class GCodeViewer { unsigned int ibo_id{ 0 }; size_t indices_count{ 0 }; -#if ENABLE_SHADERS_MANAGER std::string shader; -#else - Shader shader; -#endif // ENABLE_SHADERS_MANAGER std::vector paths; std::vector render_paths; bool visible{ false }; void reset(); -#if !ENABLE_SHADERS_MANAGER - bool init_shader(const std::string& vertex_shader_src, const std::string& fragment_shader_src); -#endif // !ENABLE_SHADERS_MANAGER void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); }; @@ -112,9 +102,6 @@ class GCodeViewer { GLVolumeCollection volumes; bool visible{ false }; -#if !ENABLE_SHADERS_MANAGER - Shader shader; -#endif // !ENABLE_SHADERS_MANAGER }; // helper to render extrusion paths @@ -225,9 +212,6 @@ public: BoundingBoxf3 m_world_bounding_box; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; bool m_visible{ false }; -#if !ENABLE_SHADERS_MANAGER - Shader m_shader; -#endif // !ENABLE_SHADERS_MANAGER public: void init(); @@ -241,11 +225,6 @@ public: void set_visible(bool visible) { m_visible = visible; } void render() const; - -#if !ENABLE_SHADERS_MANAGER - private: - void init_shader(); -#endif // !ENABLE_SHADERS_MANAGER }; struct Endpoints @@ -300,12 +279,8 @@ public: bool init() { set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); m_sequential_view.marker.init(); -#if ENABLE_SHADERS_MANAGER init_shaders(); return true; -#else - return init_shaders(); -#endif // ENABLE_SHADERS_MANAGER } // extract rendering data from the given parameters @@ -349,11 +324,7 @@ public: void enable_legend(bool enable) { m_legend_enabled = enable; } private: -#if ENABLE_SHADERS_MANAGER void init_shaders(); -#else - bool init_shaders(); -#endif // ENABLE_SHADERS_MANAGER void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5c49eb8295..3a0958b109 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -156,16 +156,8 @@ GLCanvas3D::LayersEditing::~LayersEditing() const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; -#if ENABLE_SHADERS_MANAGER void GLCanvas3D::LayersEditing::init() { -#else -bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) -{ - if (!m_shader.init(vertex_shader_filename, fragment_shader_filename)) - return false; -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)); @@ -174,10 +166,6 @@ bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - -#if !ENABLE_SHADERS_MANAGER - return true; -#endif // !ENABLE_SHADERS_MANAGER } void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) @@ -210,11 +198,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) bool GLCanvas3D::LayersEditing::is_allowed() const { -#if ENABLE_SHADERS_MANAGER return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0; -#else - return m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; -#endif // ENABLE_SHADERS_MANAGER } bool GLCanvas3D::LayersEditing::is_enabled() const @@ -372,11 +356,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) bool GLCanvas3D::LayersEditing::is_initialized() const { -#if ENABLE_SHADERS_MANAGER return wxGetApp().get_shader("variable_layer_height") != nullptr; -#else - return m_shader.is_initialized(); -#endif // ENABLE_SHADERS_MANAGER } std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const @@ -410,7 +390,6 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const { -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); if (shader == nullptr) return; @@ -422,15 +401,6 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); shader->set_uniform("z_cursor_band_width", band_width); shader->set_uniform("object_max_z", m_object_max_z); -#else - m_shader.start_using(); - - m_shader.set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z)); - m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); - m_shader.set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); - m_shader.set_uniform("z_cursor_band_width", band_width); - m_shader.set_uniform("object_max_z", m_object_max_z); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); @@ -450,11 +420,7 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 glsafe(::glEnd()); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -#if ENABLE_SHADERS_MANAGER shader->stop_using(); -#else - m_shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER } void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const @@ -488,7 +454,6 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G { assert(this->is_allowed()); assert(this->last_object_id != -1); -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height"); if (shader == nullptr) return; @@ -508,85 +473,31 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height)); shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas))); shader->set_uniform("z_cursor_band_width", float(this->band_width)); -#else - GLint shader_id = m_shader.get_shader()->shader_program_id; - assert(shader_id > 0); - GLint current_program_id; - glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); - if (shader_id > 0 && shader_id != current_program_id) - // The layer editing shader is not yet active. Activate it. - glsafe(::glUseProgram(shader_id)); - else - // The layer editing shader was already active. - current_program_id = -1; + // Initialize the layer height texture mapping. + GLsizei w = (GLsizei)m_layers_texture.width; + GLsizei h = (GLsizei)m_layers_texture.height; + GLsizei half_w = w / 2; + GLsizei half_h = h / 2; + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); + glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); + for (const GLVolume* glvolume : volumes.volumes) { + // Render the object using the layer editing shader and texture. + if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) + continue; - GLint z_to_texture_row_id = ::glGetUniformLocation(shader_id, "z_to_texture_row"); - GLint z_texture_row_to_normalized_id = ::glGetUniformLocation(shader_id, "z_texture_row_to_normalized"); - GLint z_cursor_id = ::glGetUniformLocation(shader_id, "z_cursor"); - GLint z_cursor_band_width_id = ::glGetUniformLocation(shader_id, "z_cursor_band_width"); - GLint world_matrix_id = ::glGetUniformLocation(shader_id, "volume_world_matrix"); - GLint object_max_z_id = ::glGetUniformLocation(shader_id, "object_max_z"); - glcheck(); - - if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1) - { - const_cast(this)->generate_layer_height_texture(); - - // Uniforms were resolved, go ahead using the layer editing shader. - glsafe(::glUniform1f(z_to_texture_row_id, GLfloat(m_layers_texture.cells - 1) / (GLfloat(m_layers_texture.width) * GLfloat(m_object_max_z)))); - glsafe(::glUniform1f(z_texture_row_to_normalized_id, GLfloat(1.0f / m_layers_texture.height))); - glsafe(::glUniform1f(z_cursor_id, GLfloat(m_object_max_z) * GLfloat(this->get_cursor_z_relative(canvas)))); - glsafe(::glUniform1f(z_cursor_band_width_id, GLfloat(this->band_width))); -#endif // ENABLE_SHADERS_MANAGER - // Initialize the layer height texture mapping. - GLsizei w = (GLsizei)m_layers_texture.width; - GLsizei h = (GLsizei)m_layers_texture.height; - GLsizei half_w = w / 2; - GLsizei half_h = h / 2; - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); - glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); - for (const GLVolume* glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. - if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) - continue; -#if ENABLE_SHADERS_MANAGER - shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); - shader->set_uniform("object_max_z", GLfloat(0)); -#else - if (world_matrix_id != -1) - glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); - if (object_max_z_id != -1) - glsafe(::glUniform1f(object_max_z_id, GLfloat(0))); -#endif // ENABLE_SHADERS_MANAGER - glvolume->render(); - } - // Revert back to the previous shader. - glBindTexture(GL_TEXTURE_2D, 0); -#if ENABLE_SHADERS_MANAGER - if (current_shader != nullptr) - current_shader->start_using(); -#else - if (current_program_id > 0) - glsafe(::glUseProgram(current_program_id)); + shader->set_uniform("volume_world_matrix", glvolume->world_matrix()); + shader->set_uniform("object_max_z", GLfloat(0)); + glvolume->render(); } - else - { - // Something went wrong. Just render the object. - assert(false); - for (const GLVolume* glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. - if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) - continue; - glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); - glvolume->render(); - } - } -#endif // ENABLE_SHADERS_MANAGER + // Revert back to the previous shader. + glBindTexture(GL_TEXTURE_2D, 0); + if (current_shader != nullptr) + current_shader->start_using(); } void GLCanvas3D::LayersEditing::adjust_layer_height_profile() @@ -1710,22 +1621,8 @@ bool GLCanvas3D::init() if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); -#if ENABLE_SHADERS_MANAGER if (m_main_toolbar.is_enabled()) m_layers_editing.init(); -#else - if (!m_shader.init("gouraud.vs", "gouraud.fs")) - { - std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl; - return false; - } - - if (m_main_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) - { - std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; - return false; - } -#endif // ENABLE_SHADERS_MANAGER #if ENABLE_GCODE_VIEWER if (!m_main_toolbar.is_enabled()) @@ -4585,13 +4482,8 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool return ret; }; -#if ENABLE_SHADERS_MANAGER static const std::array orange = { 0.923f, 0.504f, 0.264f, 1.0f }; static const std::array gray = { 0.64f, 0.64f, 0.64f, 1.0f }; -#else - static const GLfloat orange[] = { 0.923f, 0.504f, 0.264f, 1.0f }; - static const GLfloat gray[] = { 0.64f, 0.64f, 0.64f, 1.0f }; -#endif // ENABLE_SHADERS_MANAGER GLVolumePtrs visible_volumes; @@ -4635,7 +4527,6 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool camera.apply_projection(box, near_z, far_z); -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader == nullptr) return; @@ -4648,43 +4539,14 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool shader->start_using(); shader->set_uniform("print_box.volume_detection", 0); -#else - if (transparent_background) - glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); - - glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - m_shader.start_using(); - - GLint shader_id = m_shader.get_shader_program_id(); - GLint color_id = ::glGetUniformLocation(shader_id, "uniform_color"); - GLint print_box_detection_id = ::glGetUniformLocation(shader_id, "print_box.volume_detection"); - glcheck(); - - if (print_box_detection_id != -1) - glsafe(::glUniform1i(print_box_detection_id, 0)); -#endif // ENABLE_SHADERS_MANAGER for (const GLVolume* vol : visible_volumes) { -#if ENABLE_SHADERS_MANAGER shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (vol->printable && !vol->is_outside) ? orange : gray)); - else - glsafe(::glColor4fv((vol->printable && !vol->is_outside) ? orange : gray)); -#endif // ENABLE_SHADERS_MANAGER - vol->render(); } -#if ENABLE_SHADERS_MANAGER shader->stop_using(); -#else - m_shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glDisable(GL_DEPTH_TEST)); @@ -5610,13 +5472,11 @@ void GLCanvas3D::_render_objects() const m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - if (m_picking_enabled) - { + if (m_picking_enabled) { // Update the layer editing selection to the first object selected, update the current object maximum Z. const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - if (m_config != nullptr) - { + if (m_config != nullptr) { const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); m_volumes.check_outside_state(m_config, nullptr); @@ -5630,37 +5490,28 @@ void GLCanvas3D::_render_objects() const m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); - if (shader != nullptr) - { + if (shader != nullptr) { shader->start_using(); -#else - m_shader.start_using(); -#endif // ENABLE_SHADERS_MANAGER - if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { - int object_id = m_layers_editing.last_object_id; - m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); - // Let LayersEditing handle rendering of the active object using the layer height profile shader. - m_layers_editing.render_volumes(*this, this->m_volumes); - } - else { + + if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { + int object_id = m_layers_editing.last_object_id; + m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); + // Let LayersEditing handle rendering of the active object using the layer height profile shader. + m_layers_editing.render_volumes(*this, this->m_volumes); + } else { // do not cull backfaces to show broken geometry, if any m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) { return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); }); - } + } - m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); -#if ENABLE_SHADERS_MANAGER - shader->stop_using(); + m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); + shader->stop_using(); } -#else - m_shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER m_camera_clipping_plane = ClippingPlane::ClipsNothing(); } @@ -6078,15 +5929,7 @@ void GLCanvas3D::_render_sla_slices() const void GLCanvas3D::_render_selection_sidebar_hints() const { -#if ENABLE_GCODE_VIEWER m_selection.render_sidebar_hints(m_sidebar_field); -#else -#if ENABLE_SHADERS_MANAGER - m_selection.render_sidebar_hints(m_sidebar_field); -#else - m_selection.render_sidebar_hints(m_sidebar_field, m_shader); -#endif // ENABLE_SHADERS_MANAGER -#endif // ENABLE_GCODE_VIEWER } void GLCanvas3D::_update_volumes_hover_state() const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ff775a86e1..7b6f67c2b3 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -7,9 +7,6 @@ #include "3DScene.hpp" #include "GLToolbar.hpp" -#if !ENABLE_SHADERS_MANAGER -#include "GLShader.hpp" -#endif // !ENABLE_SHADERS_MANAGER #include "Event.hpp" #include "Selection.hpp" #include "Gizmos/GLGizmosManager.hpp" @@ -169,9 +166,6 @@ private: private: bool m_enabled; -#if !ENABLE_SHADERS_MANAGER - Shader m_shader; -#endif // !ENABLE_SHADERS_MANAGER unsigned int m_z_texture_id; // Not owned by LayersEditing. const DynamicPrintConfig *m_config; @@ -218,11 +212,8 @@ private: LayersEditing(); ~LayersEditing(); -#if ENABLE_SHADERS_MANAGER void init(); -#else - bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); -#endif // ENABLE_SHADERS_MANAGER + void set_config(const DynamicPrintConfig* config); void select_object(const Model &model, int object_id); @@ -465,9 +456,6 @@ private: WarningTexture m_warning_texture; wxTimer m_timer; LayersEditing m_layers_editing; -#if !ENABLE_SHADERS_MANAGER - Shader m_shader; -#endif // !ENABLE_SHADERS_MANAGER Mouse m_mouse; mutable GLGizmosManager m_gizmos; mutable GLToolbar m_main_toolbar; @@ -597,9 +585,6 @@ public: void set_color_by(const std::string& value); void refresh_camera_scene_box(); -#if !ENABLE_SHADERS_MANAGER - const Shader& get_shader() const { return m_shader; } -#endif // !ENABLE_SHADERS_MANAGER BoundingBoxf3 volumes_bounding_box() const; BoundingBoxf3 scene_bounding_box() const; diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 38ded63326..42e5b0a9f5 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -8,7 +8,6 @@ #include #include -#if ENABLE_SHADERS_MANAGER #include namespace Slic3r { @@ -302,364 +301,3 @@ int GLShaderProgram::get_uniform_location(const char* name) const } } // namespace Slic3r -#else -#include -#include -#include - -namespace Slic3r { - -GLShader::~GLShader() -{ - assert(fragment_program_id == 0); - assert(vertex_program_id == 0); - assert(shader_program_id == 0); -} - -// A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr. -inline std::string gl_get_string_safe(GLenum param) -{ - const char *value = (const char*)glGetString(param); - return std::string(value ? value : "N/A"); -} - -bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader) -{ - std::string gl_version = gl_get_string_safe(GL_VERSION); - int major = atoi(gl_version.c_str()); - //int minor = atoi(gl_version.c_str() + gl_version.find('.') + 1); - if (major < 2) { - // Cannot create a shader object on OpenGL 1.x. - // Form an error message. - std::string gl_vendor = gl_get_string_safe(GL_VENDOR); - std::string gl_renderer = gl_get_string_safe(GL_RENDERER); - std::string glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION); - last_error = "Your computer does not support OpenGL shaders.\n"; -#ifdef _WIN32 - if (gl_vendor == "Microsoft Corporation" && gl_renderer == "GDI Generic") { - last_error = "Windows is using a software OpenGL renderer.\n" - "You are either connected over remote desktop,\n" - "or a hardware acceleration is not available.\n"; - } -#endif - last_error += "GL version: " + gl_version + "\n"; - last_error += "vendor: " + gl_vendor + "\n"; - last_error += "renderer: " + gl_renderer + "\n"; - last_error += "GLSL version: " + glsl_version + "\n"; - return false; - } - - if (fragment_shader != nullptr) { - this->fragment_program_id = ::glCreateShader(GL_FRAGMENT_SHADER); - glcheck(); - if (this->fragment_program_id == 0) { - last_error = "glCreateShader(GL_FRAGMENT_SHADER) failed."; - return false; - } - GLint len = (GLint)strlen(fragment_shader); - glsafe(::glShaderSource(this->fragment_program_id, 1, &fragment_shader, &len)); - glsafe(::glCompileShader(this->fragment_program_id)); - GLint params; - glsafe(::glGetShaderiv(this->fragment_program_id, GL_COMPILE_STATUS, ¶ms)); - if (params == GL_FALSE) { - // Compilation failed. Get the log. - glsafe(::glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, ¶ms)); - std::vector msg(params); - glsafe(::glGetShaderInfoLog(this->fragment_program_id, params, ¶ms, msg.data())); - this->last_error = std::string("Fragment shader compilation failed:\n") + msg.data(); - this->release(); - return false; - } - } - - if (vertex_shader != nullptr) { - this->vertex_program_id = ::glCreateShader(GL_VERTEX_SHADER); - glcheck(); - if (this->vertex_program_id == 0) { - last_error = "glCreateShader(GL_VERTEX_SHADER) failed."; - this->release(); - return false; - } - GLint len = (GLint)strlen(vertex_shader); - glsafe(::glShaderSource(this->vertex_program_id, 1, &vertex_shader, &len)); - glsafe(::glCompileShader(this->vertex_program_id)); - GLint params; - glsafe(::glGetShaderiv(this->vertex_program_id, GL_COMPILE_STATUS, ¶ms)); - if (params == GL_FALSE) { - // Compilation failed. Get the log. - glsafe(::glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms)); - std::vector msg(params); - glsafe(::glGetShaderInfoLog(this->vertex_program_id, params, ¶ms, msg.data())); - this->last_error = std::string("Vertex shader compilation failed:\n") + msg.data(); - this->release(); - return false; - } - } - - // Link shaders - this->shader_program_id = ::glCreateProgram(); - glcheck(); - if (this->shader_program_id == 0) { - last_error = "glCreateProgram() failed."; - this->release(); - return false; - } - - if (this->fragment_program_id) - glsafe(::glAttachShader(this->shader_program_id, this->fragment_program_id)); - if (this->vertex_program_id) - glsafe(::glAttachShader(this->shader_program_id, this->vertex_program_id)); - glsafe(::glLinkProgram(this->shader_program_id)); - - GLint params; - glsafe(::glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, ¶ms)); - if (params == GL_FALSE) { - // Linking failed. Get the log. - glsafe(::glGetProgramiv(this->shader_program_id, GL_INFO_LOG_LENGTH, ¶ms)); - std::vector msg(params); - glsafe(::glGetProgramInfoLog(this->shader_program_id, params, ¶ms, msg.data())); - this->last_error = std::string("Shader linking failed:\n") + msg.data(); - this->release(); - return false; - } - - last_error.clear(); - return true; -} - -bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename) -{ - const std::string& path = resources_dir() + "/shaders/"; - - boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary); - if (!vs.good()) - return false; - - vs.seekg(0, vs.end); - int file_length = (int)vs.tellg(); - vs.seekg(0, vs.beg); - std::string vertex_shader(file_length, '\0'); - vs.read(vertex_shader.data(), file_length); - if (!vs.good()) - return false; - - vs.close(); - - boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary); - if (!fs.good()) - return false; - - fs.seekg(0, fs.end); - file_length = (int)fs.tellg(); - fs.seekg(0, fs.beg); - std::string fragment_shader(file_length, '\0'); - fs.read(fragment_shader.data(), file_length); - if (!fs.good()) - return false; - - fs.close(); - - return load_from_text(fragment_shader.c_str(), vertex_shader.c_str()); -} - -void GLShader::release() -{ - if (this->shader_program_id) { - if (this->vertex_program_id) - glsafe(::glDetachShader(this->shader_program_id, this->vertex_program_id)); - if (this->fragment_program_id) - glsafe(::glDetachShader(this->shader_program_id, this->fragment_program_id)); - glsafe(::glDeleteProgram(this->shader_program_id)); - this->shader_program_id = 0; - } - - if (this->vertex_program_id) { - glsafe(::glDeleteShader(this->vertex_program_id)); - this->vertex_program_id = 0; - } - if (this->fragment_program_id) { - glsafe(::glDeleteShader(this->fragment_program_id)); - this->fragment_program_id = 0; - } -} - -void GLShader::enable() const -{ - glsafe(::glUseProgram(this->shader_program_id)); -} - -void GLShader::disable() const -{ - glsafe(::glUseProgram(0)); -} - -// Return shader vertex attribute ID -int GLShader::get_attrib_location(const char *name) const -{ - return this->shader_program_id ? glGetAttribLocation(this->shader_program_id, name) : -1; -} - -// Return shader uniform variable ID -int GLShader::get_uniform_location(const char *name) const -{ - return this->shader_program_id ? glGetUniformLocation(this->shader_program_id, name) : -1; -} - -bool GLShader::set_uniform(const char *name, float value) const -{ - int id = this->get_uniform_location(name); - if (id >= 0) { - glsafe(::glUniform1fARB(id, value)); - return true; - } - return false; -} - -bool GLShader::set_uniform(const char* name, const float* matrix) const -{ - int id = get_uniform_location(name); - if (id >= 0) - { - glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, (const GLfloat*)matrix)); - return true; - } - return false; -} - -bool GLShader::set_uniform(const char* name, int value) const -{ - int id = get_uniform_location(name); - if (id >= 0) - { - glsafe(::glUniform1i(id, value)); - return true; - } - return false; -} - -/* -# Set shader vector -sub SetVector -{ - my($self,$var,@values) = @_; - - my $id = $self->Map($var); - return 'Unable to map $var' if (!defined($id)); - - my $count = scalar(@values); - eval('glUniform'.$count.'fARB($id,@values)'); - - return ''; -} - -# Set shader 4x4 matrix -sub SetMatrix -{ - my($self,$var,$oga) = @_; - - my $id = $self->Map($var); - return 'Unable to map $var' if (!defined($id)); - - glUniformMatrix4fvARB_c($id,1,0,$oga->ptr()); - return ''; -} -*/ - -Shader::Shader() - : m_shader(nullptr) -{ -} - -Shader::~Shader() -{ - reset(); -} - -bool Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) -{ - if (is_initialized()) - return true; - - m_shader = new GLShader(); - if (m_shader != nullptr) - { - if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str())) - { - std::cout << "Compilaton of shader failed:" << std::endl; - std::cout << m_shader->last_error << std::endl; - reset(); - return false; - } - } - - return true; -} - -bool Shader::is_initialized() const -{ - return (m_shader != nullptr); -} - -bool Shader::start_using() const -{ - if (is_initialized()) - { - m_shader->enable(); - return true; - } - else - return false; -} - -void Shader::stop_using() const -{ - if (m_shader != nullptr) - m_shader->disable(); -} - -int Shader::get_attrib_location(const std::string& name) const -{ - return (m_shader != nullptr) ? m_shader->get_attrib_location(name.c_str()) : -1; -} - -int Shader::get_uniform_location(const std::string& name) const -{ - return (m_shader != nullptr) ? m_shader->get_uniform_location(name.c_str()) : -1; -} - -void Shader::set_uniform(const std::string& name, float value) const -{ - if (m_shader != nullptr) - m_shader->set_uniform(name.c_str(), value); -} - -void Shader::set_uniform(const std::string& name, const float* matrix) const -{ - if (m_shader != nullptr) - m_shader->set_uniform(name.c_str(), matrix); -} - -void Shader::set_uniform(const std::string& name, bool value) const -{ - if (m_shader != nullptr) - m_shader->set_uniform(name.c_str(), value ? 1 : 0); -} - -unsigned int Shader::get_shader_program_id() const -{ - return (m_shader != nullptr) ? m_shader->shader_program_id : 0; -} - -void Shader::reset() -{ - if (m_shader != nullptr) - { - m_shader->release(); - delete m_shader; - m_shader = nullptr; - } -} - -} // namespace Slic3r - -#endif // ENABLE_SHADERS_MANAGER diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 91a1f66258..e58437fbd1 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -1,7 +1,6 @@ #ifndef slic3r_GLShader_hpp_ #define slic3r_GLShader_hpp_ -#if ENABLE_SHADERS_MANAGER #include #include @@ -59,75 +58,5 @@ public: }; } // namespace Slic3r -#else -#include "libslic3r/libslic3r.h" -#include "libslic3r/Point.hpp" - -namespace Slic3r { - -class GLShader -{ -public: - GLShader() : - fragment_program_id(0), - vertex_program_id(0), - shader_program_id(0) - {} - ~GLShader(); - - bool load_from_text(const char *fragment_shader, const char *vertex_shader); - bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename); - - void release(); - - int get_attrib_location(const char *name) const; - int get_uniform_location(const char *name) const; - - bool set_uniform(const char *name, float value) const; - bool set_uniform(const char* name, const float* matrix) const; - bool set_uniform(const char* name, int value) const; - - void enable() const; - void disable() const; - - unsigned int fragment_program_id; - unsigned int vertex_program_id; - unsigned int shader_program_id; - std::string last_error; -}; - -class Shader -{ - GLShader* m_shader; - -public: - Shader(); - ~Shader(); - - bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); - - bool is_initialized() const; - - bool start_using() const; - void stop_using() const; - - int get_attrib_location(const std::string& name) const; - int get_uniform_location(const std::string& name) const; - - void set_uniform(const std::string& name, float value) const; - void set_uniform(const std::string& name, const float* matrix) const; - void set_uniform(const std::string& name, bool value) const; - - const GLShader* get_shader() const { return m_shader; } - unsigned int get_shader_program_id() const; - -private: - void reset(); -}; - -} - - -#endif // ENABLE_SHADERS_MANAGER #endif /* slic3r_GLShader_hpp_ */ diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 6df8c75208..dd77351bd0 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -7,8 +7,6 @@ #include -#if ENABLE_SHADERS_MANAGER - namespace Slic3r { std::pair GLShadersManager::init() @@ -77,4 +75,3 @@ GLShaderProgram* GLShadersManager::get_current_shader() } // namespace Slic3r -#endif // ENABLE_SHADERS_MANAGER diff --git a/src/slic3r/GUI/GLShadersManager.hpp b/src/slic3r/GUI/GLShadersManager.hpp index f30472b123..b2bbc140bd 100644 --- a/src/slic3r/GUI/GLShadersManager.hpp +++ b/src/slic3r/GUI/GLShadersManager.hpp @@ -1,8 +1,6 @@ #ifndef slic3r_GLShadersManager_hpp_ #define slic3r_GLShadersManager_hpp_ -#if ENABLE_SHADERS_MANAGER - #include "GLShader.hpp" #include @@ -29,6 +27,4 @@ public: } // namespace Slic3r -#endif // ENABLE_SHADERS_MANAGER - #endif // slic3r_GLShadersManager_hpp_ diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index cbe2aafe16..bdbc164acb 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -217,10 +217,8 @@ public: void gcode_thumbnails_debug(); #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG -#if ENABLE_SHADERS_MANAGER GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); } GLShaderProgram* get_current_shader() { return m_opengl_mgr.get_current_shader(); } -#endif // ENABLE_SHADERS_MANAGER bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); } bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); } diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index b21fd01439..13c58f8472 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -28,14 +28,12 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SHADERS_MANAGER // A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr. inline std::string gl_get_string_safe(GLenum param, const std::string& default_value) { const char* value = (const char*)::glGetString(param); return std::string((value != nullptr) ? value : default_value); } -#endif // ENABLE_SHADERS_MANAGER const std::string& OpenGLManager::GLInfo::get_version() const { @@ -94,28 +92,10 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const void OpenGLManager::GLInfo::detect() const { -#if ENABLE_SHADERS_MANAGER m_version = gl_get_string_safe(GL_VERSION, "N/A"); m_glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A"); m_vendor = gl_get_string_safe(GL_VENDOR, "N/A"); m_renderer = gl_get_string_safe(GL_RENDERER, "N/A"); -#else - const char* data = (const char*)::glGetString(GL_VERSION); - if (data != nullptr) - m_version = data; - - data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION); - if (data != nullptr) - m_glsl_version = data; - - data = (const char*)::glGetString(GL_VENDOR); - if (data != nullptr) - m_vendor = data; - - data = (const char*)::glGetString(GL_RENDERER); - if (data != nullptr) - m_renderer = data; -#endif // ENABLE_SHADERS_MANAGER glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); @@ -132,10 +112,8 @@ void OpenGLManager::GLInfo::detect() const static bool version_greater_or_equal_to(const std::string& version, unsigned int major, unsigned int minor) { -#if ENABLE_SHADERS_MANAGER if (version == "N/A") return false; -#endif // ENABLE_SHADERS_MANAGER std::vector tokens; boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); @@ -193,26 +171,15 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension std::string line_end = format_as_html ? "
" : "\n"; out << h2_start << "OpenGL installation" << h2_end << line_end; -#if ENABLE_SHADERS_MANAGER out << b_start << "GL version: " << b_end << m_version << line_end; out << b_start << "Vendor: " << b_end << m_vendor << line_end; out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; -#else - out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end; - out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end; - out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end; - out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end; -#endif // ENABLE_SHADERS_MANAGER if (extensions) { std::vector extensions_list; -#if ENABLE_SHADERS_MANAGER std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, ""); -#else - std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS); -#endif // ENABLE_SHADERS_MANAGER boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off); if (!extensions_list.empty()) @@ -244,9 +211,7 @@ OpenGLManager::OSInfo OpenGLManager::s_os_info; OpenGLManager::~OpenGLManager() { -#if ENABLE_SHADERS_MANAGER m_shaders_manager.shutdown(); -#endif // ENABLE_SHADERS_MANAGER #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5 #ifdef __APPLE__ @@ -289,13 +254,8 @@ bool OpenGLManager::init_gl() else s_framebuffers_type = EFramebufferType::Unknown; -#if ENABLE_SHADERS_MANAGER bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0); if (!valid_version) { -#else - if (!s_gl_info.is_version_greater_or_equal_to(2, 0)) { -#endif // ENABLE_SHADERS_MANAGER - // Complain about the OpenGL version. wxString message = from_u8((boost::format( _utf8(L("PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" @@ -309,7 +269,6 @@ bool OpenGLManager::init_gl() wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Unsupported OpenGL version"), wxOK | wxICON_ERROR); } -#if ENABLE_SHADERS_MANAGER if (valid_version) { // load shaders auto [result, error] = m_shaders_manager.init(); @@ -319,7 +278,6 @@ bool OpenGLManager::init_gl() wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Error loading shaders"), wxOK | wxICON_ERROR); } } -#endif // ENABLE_SHADERS_MANAGER } return true; @@ -327,8 +285,7 @@ bool OpenGLManager::init_gl() wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas) { - if (m_context == nullptr) - { + if (m_context == nullptr) { m_context = new wxGLContext(&canvas); #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5 diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index e33df42491..c89cdf3a61 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -1,9 +1,7 @@ #ifndef slic3r_OpenGLManager_hpp_ #define slic3r_OpenGLManager_hpp_ -#if ENABLE_SHADERS_MANAGER #include "GLShadersManager.hpp" -#endif // ENABLE_SHADERS_MANAGER class wxWindow; class wxGLCanvas; @@ -75,9 +73,7 @@ private: bool m_gl_initialized{ false }; wxGLContext* m_context{ nullptr }; -#if ENABLE_SHADERS_MANAGER GLShadersManager m_shaders_manager; -#endif // ENABLE_SHADERS_MANAGER static GLInfo s_gl_info; #if ENABLE_HACK_CLOSING_ON_OSX_10_9_5 #ifdef __APPLE__ @@ -96,10 +92,8 @@ public: bool init_gl(); wxGLContext* init_glcontext(wxGLCanvas& canvas); -#if ENABLE_SHADERS_MANAGER GLShaderProgram* get_shader(const std::string& shader_name) { return m_shaders_manager.get_shader(shader_name); } GLShaderProgram* get_current_shader() { return m_shaders_manager.get_current_shader(); } -#endif // ENABLE_SHADERS_MANAGER static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } static bool can_multisample() { return s_multisample == EMultisampleState::Enabled; } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index f890f0f012..cbc3942305 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -112,14 +112,6 @@ bool Selection::init() #if ENABLE_GCODE_VIEWER m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); - -#if !ENABLE_SHADERS_MANAGER - if (!m_arrows_shader.init("gouraud_light.vs", "gouraud_light.fs")) - { - BOOST_LOG_TRIVIAL(error) << "Unable to initialize gouraud_light shader: please, check that the files gouraud_light.vs and gouraud_light.fs are available"; - return false; - } -#endif // !ENABLE_SHADERS_MANAGER #else if (!m_arrow.init()) return false; @@ -1246,76 +1238,40 @@ void Selection::render_center(bool gizmo_is_dragging) const } #endif // ENABLE_RENDER_SELECTION_CENTER -#if ENABLE_GCODE_VIEWER void Selection::render_sidebar_hints(const std::string& sidebar_field) const -#else -#if ENABLE_SHADERS_MANAGER -void Selection::render_sidebar_hints(const std::string& sidebar_field) const -#else -void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const -#endif // ENABLE_SHADERS_MANAGER -#endif // ENABLE_GCODE_VIEWER { if (sidebar_field.empty()) return; -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = nullptr; -#endif // ENABLE_SHADERS_MANAGER if (!boost::starts_with(sidebar_field, "layer")) { -#if ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; shader->start_using(); -#else - if (!m_arrows_shader.is_initialized()) - return; - - m_arrows_shader.start_using(); -#endif // ENABLE_SHADERS_MANAGER glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); -#else -#if ENABLE_SHADERS_MANAGER - shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); -#else - shader.start_using(); - glsafe(::glEnable(GL_LIGHTING)); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); -#endif // ENABLE_GCODE_VIEWER } glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); - if (!boost::starts_with(sidebar_field, "layer")) - { + if (!boost::starts_with(sidebar_field, "layer")) { const Vec3d& center = get_bounding_box().center(); - if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) - { + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { glsafe(::glTranslated(center(0), center(1), center(2))); - if (!boost::starts_with(sidebar_field, "position")) - { + if (!boost::starts_with(sidebar_field, "position")) { Transform3d orient_matrix = Transform3d::Identity(); if (boost::starts_with(sidebar_field, "scale")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::starts_with(sidebar_field, "rotation")) - { + else if (boost::starts_with(sidebar_field, "rotation")) { if (boost::ends_with(sidebar_field, "x")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::ends_with(sidebar_field, "y")) - { + else if (boost::ends_with(sidebar_field, "y")) { const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); if (rotation(0) == 0.0) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); @@ -1326,21 +1282,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha glsafe(::glMultMatrixd(orient_matrix.data())); } - } - else if (is_single_volume() || is_single_modifier()) - { + } else if (is_single_volume() || is_single_modifier()) { glsafe(::glTranslated(center(0), center(1), center(2))); Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); if (!boost::starts_with(sidebar_field, "position")) orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); - } - else - { + } else { glsafe(::glTranslated(center(0), center(1), center(2))); - if (requires_local_axes()) - { + if (requires_local_axes()) { Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } @@ -1359,22 +1310,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha glsafe(::glPopMatrix()); if (!boost::starts_with(sidebar_field, "layer")) - { -#if ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER shader->stop_using(); -#else - m_arrows_shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER -#else -#if ENABLE_SHADERS_MANAGER - shader->stop_using(); -#else - glsafe(::glDisable(GL_LIGHTING)); - shader.stop_using(); -#endif // ENABLE_SHADERS_MANAGER -#endif // ENABLE_GCODE_VIEWER - } } bool Selection::requires_local_axes() const @@ -1977,50 +1913,21 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons #if ENABLE_GCODE_VIEWER void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const { -#if ENABLE_SHADERS_MANAGER auto set_color = [](Axis axis) { GLShaderProgram* shader = wxGetApp().get_current_shader(); if (shader != nullptr) shader->set_uniform("uniform_color", AXES_COLOR[axis], 4); }; -#endif // ENABLE_SHADERS_MANAGER -#if !ENABLE_SHADERS_MANAGER - GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); -#endif // !ENABLE_SHADERS_MANAGER - - if (boost::ends_with(sidebar_field, "x")) - { -#if ENABLE_SHADERS_MANAGER + if (boost::ends_with(sidebar_field, "x")) { set_color(X); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "y")) - { -#if ENABLE_SHADERS_MANAGER + } else if (boost::ends_with(sidebar_field, "y")) { set_color(Y); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); -#endif // ENABLE_SHADERS_MANAGER - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "z")) - { -#if ENABLE_SHADERS_MANAGER + } else if (boost::ends_with(sidebar_field, "z")) { set_color(Z); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); m_arrow.render(); } @@ -2046,51 +1953,22 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field) #if ENABLE_GCODE_VIEWER void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const { -#if ENABLE_SHADERS_MANAGER auto set_color = [](Axis axis) { GLShaderProgram* shader = wxGetApp().get_current_shader(); if (shader != nullptr) shader->set_uniform("uniform_color", AXES_COLOR[axis], 4); }; -#endif // ENABLE_SHADERS_MANAGER -#if !ENABLE_SHADERS_MANAGER - GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); -#endif // !ENABLE_SHADERS_MANAGER - - if (boost::ends_with(sidebar_field, "x")) - { -#if ENABLE_SHADERS_MANAGER + if (boost::ends_with(sidebar_field, "x")) { set_color(X); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[0])); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); render_sidebar_rotation_hint(X); - } - else if (boost::ends_with(sidebar_field, "y")) - { -#if ENABLE_SHADERS_MANAGER + } else if (boost::ends_with(sidebar_field, "y")) { set_color(Y); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[1])); -#endif // ENABLE_SHADERS_MANAGER - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); render_sidebar_rotation_hint(Y); - } - else if (boost::ends_with(sidebar_field, "z")) - { -#if ENABLE_SHADERS_MANAGER + } else if (boost::ends_with(sidebar_field, "z")) { set_color(Z); -#else - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)AXES_COLOR[2])); -#endif // ENABLE_SHADERS_MANAGER - render_sidebar_rotation_hint(Z); } } @@ -2230,23 +2108,12 @@ void Selection::render_sidebar_rotation_hint(Axis axis) const m_curved_arrow.render(); } -#if ENABLE_SHADERS_MANAGER void Selection::render_sidebar_scale_hint(Axis axis) const -#else -void Selection::render_sidebar_scale_hint(Axis axis) const -#endif // ENABLE_SHADERS_MANAGER { #if ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER GLShaderProgram* shader = wxGetApp().get_current_shader(); if (shader != nullptr) shader->set_uniform("uniform_color", (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4); -#else - GLint color_id = ::glGetUniformLocation(m_arrows_shader.get_shader_program_id(), "uniform_color"); - - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? (const GLfloat*)UNIFORM_SCALE_COLOR : (const GLfloat*)AXES_COLOR[axis])); -#endif // ENABLE_SHADERS_MANAGER #else m_arrow.set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 5541cc3fa9..34024875b3 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -6,13 +6,6 @@ #include "3DScene.hpp" #if ENABLE_GCODE_VIEWER #include "GLModel.hpp" -#if !ENABLE_SHADERS_MANAGER -#include "GLShader.hpp" -#endif // !ENABLE_SHADERS_MANAGER -#else -#if !ENABLE_SHADERS_MANAGER -#include "GLShader.hpp" -#endif // !ENABLE_SHADERS_MANAGER #endif // ENABLE_GCODE_VIEWER #if ENABLE_RENDER_SELECTION_CENTER @@ -24,9 +17,7 @@ namespace Slic3r { #if !ENABLE_GCODE_VIEWER class Shader; #endif // !ENABLE_GCODE_VIEWER -#if ENABLE_SHADERS_MANAGER class GLShaderProgram; -#endif // ENABLE_SHADERS_MANAGER namespace GUI { class TransformationType { @@ -218,9 +209,6 @@ private: #if ENABLE_GCODE_VIEWER GL_Model m_arrow; GL_Model m_curved_arrow; -#if !ENABLE_SHADERS_MANAGER - Shader m_arrows_shader; -#endif // !ENABLE_SHADERS_MANAGER #else mutable GLArrow m_arrow; mutable GLCurvedArrow m_curved_arrow; @@ -339,15 +327,7 @@ public: #if ENABLE_RENDER_SELECTION_CENTER void render_center(bool gizmo_is_dragging) const; #endif // ENABLE_RENDER_SELECTION_CENTER -#if ENABLE_GCODE_VIEWER void render_sidebar_hints(const std::string& sidebar_field) const; -#else -#if ENABLE_SHADERS_MANAGER - void render_sidebar_hints(const std::string& sidebar_field) const; -#else - void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const; -#endif // ENABLE_SHADERS_MANAGER -#endif // ENABLE_GCODE_VIEWER bool requires_local_axes() const; From 43b78b630cee4979161a0d3757aead16b4c2b3cc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 22 May 2020 16:37:53 +0200 Subject: [PATCH 096/826] GCodeViewer -> Enhanced legend icons --- src/slic3r/GUI/GCodeViewer.cpp | 67 +++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c2e680b0eb..330a5a261b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -987,12 +987,43 @@ void GCodeViewer::render_legend() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); - auto add_item = [draw_list, &imgui](const Color& color, const std::string& label, std::function callback = nullptr) { + enum class EItemType : unsigned char + { + Rect, + Circle, + Line + }; + + auto add_item = [draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { float icon_size = ImGui::GetTextLineHeight(); ImVec2 pos = ImGui::GetCursorPos(); - draw_list->AddRect({ pos.x, pos.y }, { pos.x + icon_size, pos.y + icon_size }, ICON_BORDER_COLOR, 0.0f, 0); - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + switch (type) + { + default: + case EItemType::Rect: + { + draw_list->AddRect({ pos.x, pos.y }, { pos.x + icon_size, pos.y + icon_size }, ICON_BORDER_COLOR, 0.0f, 0); + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; + } + case EItemType::Circle: + { + draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 3.0f, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 1.5f, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + break; + } + case EItemType::Line: + { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1}, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + break; + } + } // draw text ImGui::Dummy({ icon_size, icon_size }); @@ -1010,7 +1041,7 @@ void GCodeViewer::render_legend() const auto add_range_item = [this, draw_list, &imgui, add_item](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); - add_item(Range_Colors[i], buf); + add_item(EItemType::Rect, Range_Colors[i], buf); }; float step_size = range.step_size(); @@ -1052,7 +1083,7 @@ void GCodeViewer::render_legend() const if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - add_item(Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { + add_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { if (role < erCount) { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); @@ -1082,7 +1113,7 @@ void GCodeViewer::render_legend() const if (it == m_extruder_ids.end()) continue; - add_item(m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); } break; } @@ -1093,7 +1124,7 @@ void GCodeViewer::render_legend() const if (extruders_count == 1) { // single extruder use case if (custom_gcode_per_print_z.empty()) // no data to show - add_item(m_tool_colors.front(), _u8L("Default print color")); + add_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default print color")); else { std::vector> cp_values; cp_values.reserve(custom_gcode_per_print_z.size()); @@ -1117,7 +1148,7 @@ void GCodeViewer::render_legend() const const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There is no one color change, but there are some pause print or custom Gcode - add_item(m_tool_colors.front(), _u8L("Default print color")); + add_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default print color")); } else { for (int i = items_cnt; i >= 0; --i) { @@ -1125,14 +1156,14 @@ void GCodeViewer::render_legend() const std::string id_str = " (" + std::to_string(i + 1) + ")"; if (i == 0) { - add_item(m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str() + id_str); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str() + id_str); break; } else if (i == items_cnt) { - add_item(m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str() + id_str); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str() + id_str); continue; } - add_item(m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second% cp_values[i].first).str() + id_str); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second% cp_values[i].first).str() + id_str); } } } @@ -1141,7 +1172,7 @@ void GCodeViewer::render_legend() const { // extruders for (unsigned int i = 0; i < (unsigned int)extruders_count; ++i) { - add_item(m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); } // color changes @@ -1152,7 +1183,7 @@ void GCodeViewer::render_legend() const // create label for color change item std::string id_str = " (" + std::to_string(color_change_idx--) + ")"; - add_item(m_tool_colors[last_color_id--], + add_item(EItemType::Rect, m_tool_colors[last_color_id--], (boost::format(_u8L("Color change for Extruder %d at %.2f mm")) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str() + id_str); } } @@ -1185,9 +1216,9 @@ void GCodeViewer::render_legend() const ImGui::Separator(); // items - add_item(Travel_Colors[0], _u8L("Movement")); - add_item(Travel_Colors[1], _u8L("Extrusion")); - add_item(Travel_Colors[2], _u8L("Retraction")); + add_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); + add_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); + add_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); break; } @@ -1206,7 +1237,7 @@ void GCodeViewer::render_legend() const auto add_option = [this, add_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { const IBuffer& buffer = m_buffers[buffer_id(move_type)]; if (buffer.visible && buffer.indices_count > 0) - add_item(Options_Colors[static_cast(color)], text); + add_item(EItemType::Circle, Options_Colors[static_cast(color)], text); }; // options From 1c826c063b4bf75a0ec1fff991e219deb6b1e8fa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 25 May 2020 10:48:53 +0200 Subject: [PATCH 097/826] GCodeViewer refactoring and GLShaderProgram upgrade --- resources/shaders/options_120.fs | 5 +- src/slic3r/GUI/3DScene.cpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 148 ++++++------------------------- src/slic3r/GUI/GLShader.cpp | 25 ++++++ src/slic3r/GUI/GLShader.hpp | 3 + 5 files changed, 59 insertions(+), 124 deletions(-) diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs index 5f699f9204..236174f3a3 100644 --- a/resources/shaders/options_120.fs +++ b/resources/shaders/options_120.fs @@ -5,10 +5,11 @@ uniform vec3 uniform_color; void main() { vec2 pos = gl_PointCoord - vec2(0.5, 0.5); - float sq_radius = pos.x * pos.x + pos.y * pos.y; + float sq_radius = dot(pos, pos); if (sq_radius > 0.25) discard; - else if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) + + if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) gl_FragColor = vec4(0.5 * uniform_color, 1.0); else gl_FragColor = vec4(uniform_color, 1.0); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index fb7d4c24cf..49b1c255b7 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -672,7 +672,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled); shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); - shader->set_uniform("slope.volume_world_normal_matrix", volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast()); + shader->set_uniform("slope.volume_world_normal_matrix", static_cast(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast())); volume.first->render(); #else diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 330a5a261b..54c7d3bb5a 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -740,9 +740,29 @@ void GCodeViewer::render_toolpaths() const bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); int detected_point_sizes[2]; ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, detected_point_sizes); - std::array point_sizes = { 2.0f, std::min(64.0f, static_cast(detected_point_sizes[1])) }; + std::array point_sizes = { std::min(8.0f, static_cast(detected_point_sizes[1])), std::min(48.0f, static_cast(detected_point_sizes[1])) }; double zoom = wxGetApp().plater()->get_camera().get_zoom(); + auto render_options = [this, is_glsl_120, zoom, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { + shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); + shader.set_uniform("zoom", zoom); + shader.set_uniform("point_sizes", point_sizes); + if (is_glsl_120) { + glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + } + for (const RenderPath& path : buffer.render_paths) { + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_points_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + if (is_glsl_120) { + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + glsafe(::glDisable(GL_POINT_SPRITE)); + } + }; + glsafe(::glCullFace(GL_BACK)); glsafe(::glLineWidth(3.0f)); @@ -773,146 +793,32 @@ void GCodeViewer::render_toolpaths() const { case GCodeProcessor::EMoveType::Tool_change: { - shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ToolChanges)]); - shader->set_uniform("zoom", zoom); - shader->set_uniform("point_sizes", point_sizes); - if (is_glsl_120) - { - glsafe(::glEnable(GL_POINT_SPRITE)); - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - } - for (const RenderPath& path : buffer.render_paths) - { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - if (is_glsl_120) - { - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glDisable(GL_POINT_SPRITE)); - } + render_options(buffer, EOptionsColors::ToolChanges, *shader); break; } case GCodeProcessor::EMoveType::Color_change: { - shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::ColorChanges)]); - shader->set_uniform("zoom", zoom); - shader->set_uniform("point_sizes", point_sizes); - if (is_glsl_120) - { - glsafe(::glEnable(GL_POINT_SPRITE)); - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - } - for (const RenderPath& path : buffer.render_paths) - { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - if (is_glsl_120) - { - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glDisable(GL_POINT_SPRITE)); - } + render_options(buffer, EOptionsColors::ColorChanges, *shader); break; } case GCodeProcessor::EMoveType::Pause_Print: { - shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::PausePrints)]); - shader->set_uniform("zoom", zoom); - shader->set_uniform("point_sizes", point_sizes); - if (is_glsl_120) - { - glsafe(::glEnable(GL_POINT_SPRITE)); - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - } - for (const RenderPath& path : buffer.render_paths) - { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - if (is_glsl_120) - { - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glDisable(GL_POINT_SPRITE)); - } + render_options(buffer, EOptionsColors::PausePrints, *shader); break; } case GCodeProcessor::EMoveType::Custom_GCode: { - shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::CustomGCodes)]); - shader->set_uniform("zoom", zoom); - shader->set_uniform("point_sizes", point_sizes); - if (is_glsl_120) - { - glsafe(::glEnable(GL_POINT_SPRITE)); - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - } - for (const RenderPath& path : buffer.render_paths) - { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - if (is_glsl_120) - { - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glDisable(GL_POINT_SPRITE)); - } + render_options(buffer, EOptionsColors::CustomGCodes, *shader); break; } case GCodeProcessor::EMoveType::Retract: { - shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Retractions)]); - shader->set_uniform("zoom", zoom); - shader->set_uniform("point_sizes", point_sizes); - if (is_glsl_120) - { - glsafe(::glEnable(GL_POINT_SPRITE)); - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - } - for (const RenderPath& path : buffer.render_paths) - { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - if (is_glsl_120) - { - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glDisable(GL_POINT_SPRITE)); - } - } + render_options(buffer, EOptionsColors::Retractions, *shader); break; } case GCodeProcessor::EMoveType::Unretract: { - shader->set_uniform("uniform_color", Options_Colors[static_cast(EOptionsColors::Unretractions)]); - shader->set_uniform("zoom", zoom); - shader->set_uniform("point_sizes", point_sizes); - if (is_glsl_120) - { - glsafe(::glEnable(GL_POINT_SPRITE)); - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - } - for (const RenderPath& path : buffer.render_paths) - { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - if (is_glsl_120) - { - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glDisable(GL_POINT_SPRITE)); - } + render_options(buffer, EOptionsColors::Unretractions, *shader); break; } case GCodeProcessor::EMoveType::Extrude: diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 42e5b0a9f5..a6d641f89f 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -215,6 +215,16 @@ bool GLShaderProgram::set_uniform(const char* name, double value) const return set_uniform(name, static_cast(value)); } +bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform4iv(id, 1, static_cast(value.data()))); + return true; + } + return false; +} + bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const { int id = get_uniform_location(name); @@ -290,6 +300,21 @@ bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const return false; } +bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); + return true; + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const +{ + return set_uniform(name, static_cast(value.cast())); +} + int GLShaderProgram::get_attrib_location(const char* name) const { return (m_id > 0) ? ::glGetAttribLocation(m_id, name) : -1; diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index e58437fbd1..521f6796f1 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -43,6 +43,7 @@ public: bool set_uniform(const char* name, bool value) const; bool set_uniform(const char* name, float value) const; bool set_uniform(const char* name, double value) const; + bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; @@ -50,6 +51,8 @@ public: bool set_uniform(const char* name, const Transform3f& value) const; bool set_uniform(const char* name, const Transform3d& value) const; bool set_uniform(const char* name, const Matrix3f& value) const; + bool set_uniform(const char* name, const Vec3f& value) const; + bool set_uniform(const char* name, const Vec3d& value) const; // returns -1 if not found int get_attrib_location(const char* name) const; From 1af798dbd7ed9b4763bd28c9cd9e65911cd7a2bd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 25 May 2020 11:16:40 +0200 Subject: [PATCH 098/826] DoubleSlider::Control thumb text rendered closer to the slider --- resources/shaders/options_120.fs | 1 + src/slic3r/GUI/DoubleSlider.cpp | 10 +++++----- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs index 236174f3a3..1c53f6c725 100644 --- a/resources/shaders/options_120.fs +++ b/resources/shaders/options_120.fs @@ -1,3 +1,4 @@ +// version 120 is needed for gl_PointCoord #version 120 uniform vec3 uniform_color; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 5c16e11eef..7415f333f8 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -566,12 +566,12 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_ dc.GetMultiLineTextExtent(label, &text_width, &text_height); wxPoint text_pos; if (right_side) - text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) : - wxPoint(pos.x + m_thumb_size.x+1, pos.y - 0.5*text_height - 1); + text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x / 4) : + wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1); else - text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x - text_height) : - wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5*text_height + 1); - dc.DrawText(label, text_pos); + text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x / 4 - text_height) : + wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1); + dc.DrawText(label, text_pos); } void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 1784dbccc1..fdbd396e26 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -343,7 +343,7 @@ bool Preview::init(wxWindow* parent, Model* model) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 4 * GetTextExtent("m").y), wxSL_HORIZONTAL); + m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); From 6810550a6cb5fcca497f6017e182cc88b4aaa7ba Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 25 May 2020 11:59:12 +0200 Subject: [PATCH 099/826] DoubleSlider::Control background color --- src/slic3r/GUI/DoubleSlider.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 7415f333f8..1682677df4 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -399,7 +399,11 @@ void Control::draw_focus_rect() void Control::render() { +#if ENABLE_GCODE_VIEWER + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#else SetBackgroundColour(GetParent()->GetBackgroundColour()); +#endif // ENABLE_GCODE_VIEWER draw_focus_rect(); wxPaintDC dc(this); @@ -770,7 +774,11 @@ void Control::draw_colored_band(wxDC& dc) // don't color a band for MultiExtruder mode if (m_ticks.empty() || m_mode == t_mode::MultiExtruder) { +#if ENABLE_GCODE_VIEWER + draw_band(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), main_band); +#else draw_band(dc, GetParent()->GetBackgroundColour(), main_band); +#endif // ENABLE_GCODE_VIEWER return; } From a63e5b352ee91276215fb0b087aa3dcde447f660 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 25 May 2020 12:08:09 +0200 Subject: [PATCH 100/826] ENABLE_GCODE_VIEWER -> Reduced vertical size of horizontal slider --- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index fdbd396e26..08cb3cdd76 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -343,7 +343,7 @@ bool Preview::init(wxWindow* parent, Model* model) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); + m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 2.5 * GetTextExtent("m").y), wxSL_HORIZONTAL); m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); From 2759380000f4407ea43e30deb7215dfbaf59b485 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 25 May 2020 13:53:41 +0200 Subject: [PATCH 101/826] DoubleSlider:Control platform dependent background color --- src/slic3r/GUI/DoubleSlider.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 1682677df4..087b1774e5 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -400,7 +400,11 @@ void Control::draw_focus_rect() void Control::render() { #if ENABLE_GCODE_VIEWER +#ifdef _WIN32 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#else + SetBackgroundColour(GetParent()->GetBackgroundColour()); +#endif // _WIN32 #else SetBackgroundColour(GetParent()->GetBackgroundColour()); #endif // ENABLE_GCODE_VIEWER @@ -570,10 +574,10 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_ dc.GetMultiLineTextExtent(label, &text_width, &text_height); wxPoint text_pos; if (right_side) - text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x / 4) : + text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x / 3) : wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1); else - text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x / 4 - text_height) : + text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x / 3 - text_height) : wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1); dc.DrawText(label, text_pos); } @@ -775,7 +779,11 @@ void Control::draw_colored_band(wxDC& dc) if (m_ticks.empty() || m_mode == t_mode::MultiExtruder) { #if ENABLE_GCODE_VIEWER +#ifdef _WIN32 draw_band(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), main_band); +#else + draw_band(dc, GetParent()->GetBackgroundColour(), main_band); +#endif // _WIN32 #else draw_band(dc, GetParent()->GetBackgroundColour(), main_band); #endif // ENABLE_GCODE_VIEWER From 1d317489fd096751335c633a85b2ff2ee1669e2d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 May 2020 08:16:08 +0200 Subject: [PATCH 102/826] GCodeViewer -> Temporary ImGui dialog for editing shaders parameters --- resources/shaders/options_120.fs | 27 +++++-- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GCodeViewer.cpp | 127 ++++++++++++++++++++++++++++--- src/slic3r/GUI/GCodeViewer.hpp | 26 +++++-- src/slic3r/GUI/GLCanvas3D.cpp | 3 + 5 files changed, 165 insertions(+), 20 deletions(-) diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs index 1c53f6c725..90d417b6e7 100644 --- a/resources/shaders/options_120.fs +++ b/resources/shaders/options_120.fs @@ -2,6 +2,26 @@ #version 120 uniform vec3 uniform_color; +uniform float percent_outline_radius; +uniform float percent_center_radius; + +vec4 hard_color(float sq_radius) +{ + if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) + return vec4(0.5 * uniform_color, 1.0); + else + return vec4(uniform_color, 1.0); +} + +vec4 custom_color(float sq_radius) +{ + float in_radius = 0.5 * percent_center_radius; + float out_radius = 0.5 * (1.0 - percent_outline_radius); + if ((sq_radius < in_radius * in_radius) || (sq_radius > out_radius * out_radius)) + return vec4(0.5 * uniform_color, 1.0); + else + return vec4(uniform_color, 1.0); +} void main() { @@ -9,9 +29,6 @@ void main() float sq_radius = dot(pos, pos); if (sq_radius > 0.25) discard; - - if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) - gl_FragColor = vec4(0.5 * uniform_color, 1.0); - else - gl_FragColor = vec4(uniform_color, 1.0); + + gl_FragColor = custom_color(sq_radius); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 3df9da961d..5631dadf34 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -45,5 +45,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (1 && ENABLE_GCODE_VIEWER) + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 54c7d3bb5a..6d41976941 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -221,6 +221,19 @@ const std::vector GCodeViewer::Range_Colors {{ { 0.761f, 0.322f, 0.235f } // reddish }}; +bool GCodeViewer::init() +{ + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); + m_sequential_view.marker.init(); + init_shaders(); + + std::array point_sizes; + ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); + m_detected_point_sizes = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; + + return true; +} + void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized) { // avoid processing if called with the same gcode_result @@ -328,6 +341,9 @@ void GCodeViewer::render() const #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + render_shaders_editor(); +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR } bool GCodeViewer::is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const @@ -737,30 +753,47 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + bool is_glsl_120 = m_shaders_editor.glsl_version == 1 && wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); + std::array point_sizes; + if (m_shaders_editor.size_dependent_on_zoom) + { + point_sizes = { std::min(static_cast(m_shaders_editor.sizes[0]), m_detected_point_sizes[1]), std::min(static_cast(m_shaders_editor.sizes[1]), m_detected_point_sizes[1]) }; + } + else + point_sizes = { static_cast(m_shaders_editor.fixed_size), static_cast(m_shaders_editor.fixed_size) }; +#else bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - int detected_point_sizes[2]; - ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, detected_point_sizes); - std::array point_sizes = { std::min(8.0f, static_cast(detected_point_sizes[1])), std::min(48.0f, static_cast(detected_point_sizes[1])) }; + std::array point_sizes = { std::min(8.0f, m_detected_point_sizes[1]), std::min(48.0f, m_detected_point_sizes[1]) }; +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR double zoom = wxGetApp().plater()->get_camera().get_zoom(); auto render_options = [this, is_glsl_120, zoom, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + shader.set_uniform("zoom", m_shaders_editor.size_dependent_on_zoom ? zoom : 1.0f); + shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.percent_outline)); + shader.set_uniform("percent_center_radius", 0.01f * static_cast(m_shaders_editor.percent_center)); +#else shader.set_uniform("zoom", zoom); + shader.set_uniform("percent_outline_radius", 0.15f); + shader.set_uniform("percent_center_radius", 0.15f); +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("point_sizes", point_sizes); - if (is_glsl_120) { + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); + if (is_glsl_120) glsafe(::glEnable(GL_POINT_SPRITE)); - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - } + for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } - if (is_glsl_120) { - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); + if (is_glsl_120) glsafe(::glDisable(GL_POINT_SPRITE)); - } + + glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; glsafe(::glCullFace(GL_BACK)); @@ -900,7 +933,11 @@ void GCodeViewer::render_legend() const Line }; +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + auto add_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { +#else auto add_item = [draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR float icon_size = ImGui::GetTextLineHeight(); ImVec2 pos = ImGui::GetCursorPos(); switch (type) @@ -915,6 +952,20 @@ void GCodeViewer::render_legend() const } case EItemType::Circle: { +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = ((0.5f * icon_size) - 2.0f) * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + if (m_shaders_editor.percent_center > 0) + { + radius = ((0.5f * icon_size) - 2.0f) * 0.01f * static_cast(m_shaders_editor.percent_center); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + } +#else draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); @@ -922,6 +973,7 @@ void GCodeViewer::render_legend() const ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 1.5f, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR break; } case EItemType::Line: @@ -1266,6 +1318,63 @@ void GCodeViewer::render_statistics() const } #endif // ENABLE_GCODE_VIEWER_STATISTICS +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR +void GCodeViewer::render_shaders_editor() const +{ + auto set_shader = [this](const std::string& shader) { + unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); + unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Custom_GCode); + for (unsigned char i = begin_id; i <= end_id; ++i) { + m_buffers[i].shader = shader; + } + }; + + static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()), 0.5f * static_cast(cnv_size.get_height()), ImGuiCond_Once, 1.0f, 0.5f); + imgui.begin(std::string("Shaders editor (DEV only)"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + + ImGui::RadioButton("glsl version 1.10 (low end PCs)", &m_shaders_editor.glsl_version, 0); + ImGui::RadioButton("glsl version 1.20 (default)", &m_shaders_editor.glsl_version, 1); + switch (m_shaders_editor.glsl_version) + { + case 0: { set_shader("options_110"); break; } + case 1: { set_shader("options_120"); break; } + } + + if (ImGui::CollapsingHeader("Options", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("size dependent on zoom", &m_shaders_editor.size_dependent_on_zoom); + if (m_shaders_editor.size_dependent_on_zoom) + { + if (ImGui::SliderInt("min size (min zoom)", &m_shaders_editor.sizes[0], 1, 100)) + { + if (m_shaders_editor.sizes[1] < m_shaders_editor.sizes[0]) + m_shaders_editor.sizes[1] = m_shaders_editor.sizes[0]; + } + ImGui::SliderInt("max size (max zoom)", &m_shaders_editor.sizes[1], 1, 100); + { + if (m_shaders_editor.sizes[1] < m_shaders_editor.sizes[0]) + m_shaders_editor.sizes[0] = m_shaders_editor.sizes[1]; + } + } + else + ImGui::SliderInt("fixed size", &m_shaders_editor.fixed_size, 1, 100); + + if (m_shaders_editor.glsl_version == 1) + { + ImGui::SliderInt("percent outline", &m_shaders_editor.percent_outline, 0, 50); + ImGui::SliderInt("percent center", &m_shaders_editor.percent_center, 0, 50); + } + } + + imgui.end(); +} +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR + bool GCodeViewer::is_travel_in_z_range(size_t id) const { const IBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)]; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5ab9f68325..e7c620fc5e 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -202,6 +202,18 @@ class GCodeViewer }; #endif // ENABLE_GCODE_VIEWER_STATISTICS +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + struct ShadersEditor + { + int glsl_version{ 1 }; + bool size_dependent_on_zoom{ true }; + int fixed_size{ 16 }; + std::array sizes{ 8, 64 }; + int percent_outline{ 15 }; + int percent_center{ 15 }; + }; +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR + public: struct SequentialView { @@ -271,17 +283,16 @@ private: #if ENABLE_GCODE_VIEWER_STATISTICS mutable Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + mutable ShadersEditor m_shaders_editor; +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR + std::array m_detected_point_sizes = { 0.0f, 0.0f }; public: GCodeViewer() = default; ~GCodeViewer() { reset(); } - bool init() { - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); - m_sequential_view.marker.init(); - init_shaders(); - return true; - } + bool init(); // extract rendering data from the given parameters void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized); @@ -334,6 +345,9 @@ private: #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + void render_shaders_editor() const; +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR bool is_visible(ExtrusionRole role) const { return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ee23783c38..0c0bad7a09 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3502,6 +3502,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #ifdef SLIC3R_DEBUG_MOUSE_EVENTS printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ +#if ENABLE_GCODE_VIEWER + m_dirty = true; +#endif // ENABLE_GCODE_VIEWER // do not return if dragging or tooltip not empty to allow for tooltip update if (!m_mouse.dragging && m_tooltip.is_empty()) return; From 8f91b4f4f4a726fe82b961357e284888ea04121d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 May 2020 08:34:19 +0200 Subject: [PATCH 103/826] DoubleSlider::Control -> Tweaks to text position for horizontal case --- src/slic3r/GUI/DoubleSlider.cpp | 4 ++-- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 087b1774e5..582a356754 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -574,10 +574,10 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_ dc.GetMultiLineTextExtent(label, &text_width, &text_height); wxPoint text_pos; if (right_side) - text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x / 3) : + text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x / 2 + 1) : wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1); else - text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x / 3 - text_height) : + text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x / 2 - text_height - 1) : wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1); dc.DrawText(label, text_pos); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 08cb3cdd76..fdbd396e26 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -343,7 +343,7 @@ bool Preview::init(wxWindow* parent, Model* model) #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 2.5 * GetTextExtent("m").y), wxSL_HORIZONTAL); + m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL); From aa04f0e555b1f8e073ec1383324d0e30b5c6da24 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 08:06:02 +0200 Subject: [PATCH 104/826] ENABLE_GCODE_VIEWER -> Completed implementation of new GLModel class --- src/slic3r/GUI/3DBed.cpp | 29 +++++++++++-- src/slic3r/GUI/3DBed.hpp | 15 ++++++- src/slic3r/GUI/3DScene.cpp | 4 +- src/slic3r/GUI/3DScene.hpp | 4 +- src/slic3r/GUI/GCodeViewer.hpp | 2 +- src/slic3r/GUI/GLModel.cpp | 79 ++++++++++++++++++++++------------ src/slic3r/GUI/GLModel.hpp | 12 ++++-- src/slic3r/GUI/Selection.hpp | 4 +- 8 files changed, 105 insertions(+), 44 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index b5a34b2ee7..450a538d04 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -358,14 +358,23 @@ void Bed3D::calc_bounding_boxes() const // extend to contain axes #if ENABLE_GCODE_VIEWER - m_extended_bounding_box.merge(m_axes.get_total_length() * Vec3d::Ones()); -#else - m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones()); -#endif // ENABLE_GCODE_VIEWER + m_extended_bounding_box.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); + m_extended_bounding_box.merge(m_extended_bounding_box.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, m_extended_bounding_box.max(2))); + // extend to contain model, if any + BoundingBoxf3 model_bb = m_model.get_bounding_box(); + if (model_bb.defined) + { + model_bb.translate(m_model_offset); + m_extended_bounding_box.merge(model_bb); + } +#else + m_extended_bounding_box.merge(m_axes.get_total_length() * Vec3d::Ones()); + m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones()); // extend to contain model, if any if (!m_model.get_filename().empty()) m_extended_bounding_box.merge(m_model.get_transformed_bounding_box()); +#endif // ENABLE_GCODE_VIEWER } void Bed3D::calc_triangles(const ExPolygon& poly) @@ -621,7 +630,11 @@ void Bed3D::render_model() const // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad Vec3d shift = m_bounding_box.center(); shift(2) = -0.03; +#if ENABLE_GCODE_VIEWER + m_model_offset = shift; +#else m_model.set_offset(shift); +#endif // ENABLE_GCODE_VIEWER // update extended bounding box calc_bounding_boxes(); @@ -633,7 +646,15 @@ void Bed3D::render_model() const if (shader != nullptr) { shader->start_using(); +#if ENABLE_GCODE_VIEWER + shader->set_uniform("uniform_color", m_model_color); + ::glPushMatrix(); + ::glTranslated(m_model_offset(0), m_model_offset(1), m_model_offset(2)); +#endif // ENABLE_GCODE_VIEWER m_model.render(); +#if ENABLE_GCODE_VIEWER + ::glPopMatrix(); +#endif // ENABLE_GCODE_VIEWER shader->stop_using(); } } diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 2487be2e47..b9e952c4a4 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -8,6 +8,9 @@ #endif // ENABLE_GCODE_VIEWER #include +#if ENABLE_GCODE_VIEWER +#include +#endif // ENABLE_GCODE_VIEWER #if !ENABLE_GCODE_VIEWER class GLUquadric; @@ -52,10 +55,13 @@ class Bed3D #if ENABLE_GCODE_VIEWER class Axes { + public: static const float DefaultStemRadius; static const float DefaultStemLength; static const float DefaultTipRadius; static const float DefaultTipLength; + + private: #else struct Axes { @@ -67,7 +73,7 @@ class Bed3D #if ENABLE_GCODE_VIEWER Vec3d m_origin{ Vec3d::Zero() }; float m_stem_length{ DefaultStemLength }; - mutable GL_Model m_arrow; + mutable GLModel m_arrow; public: #else @@ -82,6 +88,7 @@ class Bed3D #endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER + const Vec3d& get_origin() const { return m_origin; } void set_origin(const Vec3d& origin) { m_origin = origin; } void set_stem_length(float length); float get_total_length() const { return m_stem_length + DefaultTipLength; } @@ -113,7 +120,13 @@ private: GeometryBuffer m_triangles; GeometryBuffer m_gridlines; mutable GLTexture m_texture; +#if ENABLE_GCODE_VIEWER + mutable GLModel m_model; + mutable Vec3d m_model_offset{ Vec3d::Zero() }; + std::array m_model_color{ 0.235f, 0.235f, 0.235f, 1.0f }; +#else mutable GLBed m_model; +#endif // ENABLE_GCODE_VIEWER // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; mutable unsigned int m_vbo_id; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 49b1c255b7..59be43271a 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1822,6 +1822,7 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height thick_point_to_verts(point, width, height, volume); } +#if !ENABLE_GCODE_VIEWER GLModel::GLModel() : m_filename("") { @@ -1904,7 +1905,6 @@ void GLModel::render() const glsafe(::glDisable(GL_BLEND)); } -#if !ENABLE_GCODE_VIEWER bool GLArrow::on_init() { Pointf3s vertices; @@ -2076,7 +2076,6 @@ bool GLCurvedArrow::on_init() m_volume.indexed_vertex_array.finalize_geometry(true); return true; } -#endif // !ENABLE_GCODE_VIEWER bool GLBed::on_init_from_file(const std::string& filename) { @@ -2108,5 +2107,6 @@ bool GLBed::on_init_from_file(const std::string& filename) return true; } +#endif // !ENABLE_GCODE_VIEWER } // namespace Slic3r diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index fd74cd4912..d7bd0c1b3c 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -599,6 +599,7 @@ private: GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); +#if !ENABLE_GCODE_VIEWER class GLModel { protected: @@ -636,7 +637,6 @@ protected: virtual bool on_init_from_file(const std::string& filename) { return false; } }; -#if !ENABLE_GCODE_VIEWER class GLArrow : public GLModel { protected: @@ -653,13 +653,13 @@ public: protected: bool on_init() override; }; -#endif // !ENABLE_GCODE_VIEWER class GLBed : public GLModel { protected: bool on_init_from_file(const std::string& filename) override; }; +#endif // !ENABLE_GCODE_VIEWER struct _3DScene { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index e7c620fc5e..1d940b66a7 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -219,7 +219,7 @@ public: { class Marker { - GL_Model m_model; + GLModel m_model; Transform3f m_world_transform; BoundingBoxf3 m_world_bounding_box; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index e5b6cbdcb6..e738aa3c49 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -3,13 +3,17 @@ #include "3DScene.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Model.hpp" + +#include +#include #include namespace Slic3r { namespace GUI { -void GL_Model::init_from(const GLModelInitializationData& data) +void GLModel::init_from(const GLModelInitializationData& data) { assert(!data.positions.empty() && !data.triangles.empty()); assert(data.positions.size() == data.normals.size()); @@ -20,8 +24,9 @@ void GL_Model::init_from(const GLModelInitializationData& data) // vertices/normals data std::vector vertices(6 * data.positions.size()); for (size_t i = 0; i < data.positions.size(); ++i) { - ::memcpy(static_cast(&vertices[i * 6]), static_cast(data.positions[i].data()), 3 * sizeof(float)); - ::memcpy(static_cast(&vertices[3 + i * 6]), static_cast(data.normals[i].data()), 3 * sizeof(float)); + size_t offset = i * 6; + ::memcpy(static_cast(&vertices[offset]), static_cast(data.positions[i].data()), 3 * sizeof(float)); + ::memcpy(static_cast(&vertices[3 + offset]), static_cast(data.normals[i].data()), 3 * sizeof(float)); } // indices data @@ -41,34 +46,26 @@ void GL_Model::init_from(const GLModelInitializationData& data) send_to_gpu(vertices, indices); } -void GL_Model::init_from(const TriangleMesh& mesh) +void GLModel::init_from(const TriangleMesh& mesh) { - auto get_normal = [](const std::array& triangle) { - return (triangle[1] - triangle[0]).cross(triangle[2] - triangle[0]).normalized(); - }; - if (m_vbo_id > 0) // call reset() if you want to reuse this model return; - assert(!mesh.its.vertices.empty() && !mesh.its.indices.empty()); // call require_shared_vertices() before to pass the mesh to this method + std::vector vertices = std::vector(18 * mesh.stl.stats.number_of_facets); + std::vector indices = std::vector(3 * mesh.stl.stats.number_of_facets); - // vertices data -> load from mesh - std::vector vertices(6 * mesh.its.vertices.size()); - for (size_t i = 0; i < mesh.its.vertices.size(); ++i) { - ::memcpy(static_cast(&vertices[i * 6]), static_cast(mesh.its.vertices[i].data()), 3 * sizeof(float)); - } - - // indices/normals data -> load from mesh - std::vector indices(3 * mesh.its.indices.size()); - for (size_t i = 0; i < mesh.its.indices.size(); ++i) { - const stl_triangle_vertex_indices& triangle = mesh.its.indices[i]; - for (size_t j = 0; j < 3; ++j) { - indices[i * 3 + j] = static_cast(triangle[j]); + unsigned int vertices_count = 0; + for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) { + const stl_facet& facet = mesh.stl.facet_start[i]; + for (uint32_t j = 0; j < 3; ++j) { + uint32_t offset = i * 18 + j * 6; + ::memcpy(static_cast(&vertices[offset]), static_cast(facet.vertex[j].data()), 3 * sizeof(float)); + ::memcpy(static_cast(&vertices[3 + offset]), static_cast(facet.normal.data()), 3 * sizeof(float)); } - Vec3f normal = get_normal({ mesh.its.vertices[triangle[0]], mesh.its.vertices[triangle[1]], mesh.its.vertices[triangle[2]] }); - ::memcpy(static_cast(&vertices[3 + static_cast(triangle[0]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); - ::memcpy(static_cast(&vertices[3 + static_cast(triangle[1]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); - ::memcpy(static_cast(&vertices[3 + static_cast(triangle[2]) * 6]), static_cast(normal.data()), 3 * sizeof(float)); + for (uint32_t j = 0; j < 3; ++j) { + indices[i * 3 + j] = vertices_count + j; + } + vertices_count += 3; } m_indices_count = static_cast(indices.size()); @@ -77,7 +74,32 @@ void GL_Model::init_from(const TriangleMesh& mesh) send_to_gpu(vertices, indices); } -void GL_Model::reset() +bool GLModel::init_from_file(const std::string& filename) +{ + if (!boost::filesystem::exists(filename)) + return false; + + if (!boost::algorithm::iends_with(filename, ".stl")) + return false; + + Model model; + try + { + model = Model::read_from_file(filename); + } + catch (std::exception&) + { + return false; + } + + init_from(model.mesh()); + + m_filename = filename; + + return true; +} + +void GLModel::reset() { // release gpu memory if (m_ibo_id > 0) { @@ -92,9 +114,10 @@ void GL_Model::reset() m_indices_count = 0; m_bounding_box = BoundingBoxf3(); + m_filename = std::string(); } -void GL_Model::render() const +void GLModel::render() const { if (m_vbo_id == 0 || m_ibo_id == 0) return; @@ -116,7 +139,7 @@ void GL_Model::render() const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } -void GL_Model::send_to_gpu(const std::vector& vertices, const std::vector& indices) +void GLModel::send_to_gpu(const std::vector& vertices, const std::vector& indices) { // vertex data -> send to gpu glsafe(::glGenBuffers(1, &m_vbo_id)); diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index a11073b191..0b4a69bdb0 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -4,6 +4,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" #include +#include namespace Slic3r { @@ -18,24 +19,28 @@ namespace GUI { std::vector triangles; }; - class GL_Model + class GLModel { unsigned int m_vbo_id{ 0 }; unsigned int m_ibo_id{ 0 }; size_t m_indices_count{ 0 }; BoundingBoxf3 m_bounding_box; + std::string m_filename; public: - virtual ~GL_Model() { reset(); } + virtual ~GLModel() { reset(); } void init_from(const GLModelInitializationData& data); void init_from(const TriangleMesh& mesh); + bool init_from_file(const std::string& filename); void reset(); void render() const; const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } + const std::string& get_filename() const { return m_filename; } + private: void send_to_gpu(const std::vector& vertices, const std::vector& indices); }; @@ -44,8 +49,7 @@ namespace GUI { // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution // the origin of the arrow is in the center of the stem cap // the arrow has its axis of symmetry along the Z axis and is pointing upward - GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, - float stem_radius, float stem_height); + GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); // create an arrow whose stem is a quarter of circle, with the given dimensions and resolution // the origin of the arrow is in the center of the circle diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 34024875b3..c4ba30ed56 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -207,8 +207,8 @@ private: GLUquadricObj* m_quadric; #endif // ENABLE_RENDER_SELECTION_CENTER #if ENABLE_GCODE_VIEWER - GL_Model m_arrow; - GL_Model m_curved_arrow; + GLModel m_arrow; + GLModel m_curved_arrow; #else mutable GLArrow m_arrow; mutable GLCurvedArrow m_curved_arrow; From 94a4689b003b9e6e35487b09213d2dfd29c42954 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 11:50:29 +0200 Subject: [PATCH 105/826] DoubleSlider::Control -> Change text position at the edges of horizontal slider --- src/slic3r/GUI/DoubleSlider.cpp | 33 +++++++++++++++++++++++++++------ src/slic3r/GUI/DoubleSlider.hpp | 4 ++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 582a356754..8cf4aea35d 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -272,14 +272,14 @@ wxCoord Control::get_position_from_value(const int value) return wxCoord(SLIDER_MARGIN + int(val*step + 0.5)); } -wxSize Control::get_size() +wxSize Control::get_size() const { int w, h; get_size(&w, &h); return wxSize(w, h); } -void Control::get_size(int *w, int *h) +void Control::get_size(int* w, int* h) const { GetSize(w, h); is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim; @@ -574,11 +574,32 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_ dc.GetMultiLineTextExtent(label, &text_width, &text_height); wxPoint text_pos; if (right_side) - text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x / 2 + 1) : - wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1); + { + if (is_horizontal()) + { + int width; + int height; + get_size(&width, &height); + + int x_right = pos.x + 1 + text_width; + int xx = (x_right < width) ? pos.x + 1 : pos.x - text_width - 1; + text_pos = wxPoint(xx, pos.y + m_thumb_size.x / 2 + 1); + } + else + text_pos = wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1); + } else - text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x / 2 - text_height - 1) : - wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1); + { + if (is_horizontal()) + { + int x = pos.x - text_width - 1; + int xx = (x > 0) ? x : pos.x + 1; + text_pos = wxPoint(xx, pos.y - m_thumb_size.x / 2 - text_height - 1); + } + else + text_pos = wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1); + } + dc.DrawText(label, text_pos); } diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index a3d836c3f2..8aaba86a63 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -301,8 +301,8 @@ private: int get_value_from_position(const wxCoord x, const wxCoord y); int get_value_from_position(const wxPoint pos) { return get_value_from_position(pos.x, pos.y); } wxCoord get_position_from_value(const int value); - wxSize get_size(); - void get_size(int *w, int *h); + wxSize get_size() const; + void get_size(int* w, int* h) const; double get_double_value(const SelectedSlider& selection); wxString get_tooltip(int tick = -1); int get_edited_tick_for_position(wxPoint pos, const std::string& gcode = ColorChangeCode); From a0acf24ab8067e14acf977b920d93933f51efb4f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 14:29:27 +0200 Subject: [PATCH 106/826] DoubleSlider::Control -> Fixed crash when pressing numpad [+] and [-] keys while the horizontal slider has focus --- src/slic3r/GUI/DoubleSlider.cpp | 38 +++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 8cf4aea35d..3374863b88 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1387,6 +1387,22 @@ void Control::OnWheel(wxMouseEvent& event) void Control::OnKeyDown(wxKeyEvent &event) { const int key = event.GetKeyCode(); +#if ENABLE_GCODE_VIEWER + if (m_draw_mode != dmSequentialGCodeView && key == WXK_NUMPAD_ADD) { + // OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice. + // To avoid this case we should suppress second add_tick() call. + m_ticks.suppress_plus(true); + add_current_tick(true); + } + else if (m_draw_mode != dmSequentialGCodeView && (key == WXK_NUMPAD_SUBTRACT || key == WXK_DELETE || key == WXK_BACK)) { + // OnChar() is called immediately after OnKeyDown(), which can cause call of delete_tick() twice. + // To avoid this case we should suppress second delete_tick() call. + m_ticks.suppress_minus(true); + delete_current_tick(); + } + else if (m_draw_mode != dmSequentialGCodeView && event.GetKeyCode() == WXK_SHIFT) + UseDefaultColors(false); +#else if (key == WXK_NUMPAD_ADD) { // OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice. // To avoid this case we should suppress second add_tick() call. @@ -1401,6 +1417,7 @@ void Control::OnKeyDown(wxKeyEvent &event) } else if (event.GetKeyCode() == WXK_SHIFT) UseDefaultColors(false); +#endif // ENABLE_GCODE_VIEWER else if (is_horizontal()) { #if ENABLE_GCODE_VIEWER @@ -1451,14 +1468,21 @@ void Control::OnKeyUp(wxKeyEvent &event) void Control::OnChar(wxKeyEvent& event) { const int key = event.GetKeyCode(); - if (key == '+' && !m_ticks.suppressed_plus()) { - add_current_tick(true); - m_ticks.suppress_plus(false); - } - else if (key == '-' && !m_ticks.suppressed_minus()) { - delete_current_tick(); - m_ticks.suppress_minus(false); +#if ENABLE_GCODE_VIEWER + if (m_draw_mode != dmSequentialGCodeView) + { +#endif // ENABLE_GCODE_VIEWER + if (key == '+' && !m_ticks.suppressed_plus()) { + add_current_tick(true); + m_ticks.suppress_plus(false); + } + else if (key == '-' && !m_ticks.suppressed_minus()) { + delete_current_tick(); + m_ticks.suppress_minus(false); + } +#if ENABLE_GCODE_VIEWER } +#endif // ENABLE_GCODE_VIEWER if (key == 'G') #if ENABLE_GCODE_VIEWER jump_to_value(); From 100484dabe9909b62c9d6646c79f13443aa4727b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 15:28:24 +0200 Subject: [PATCH 107/826] Added missing include --- src/slic3r/GUI/GCodeViewer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6d41976941..2ba0a6e39d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -5,6 +5,7 @@ #include "libslic3r/Print.hpp" #include "libslic3r/Geometry.hpp" #include "GUI_App.hpp" +#include "Plater.hpp" #include "PresetBundle.hpp" #include "Camera.hpp" #include "I18N.hpp" From e77fa3512ac0c9d697b9ad9620a5e955cf35209e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 16:03:40 +0200 Subject: [PATCH 108/826] DoubleSlider::Control -> Shift and Ctrl used as accelerators for moving thumbs with arrows key and mouse wheel --- src/slic3r/GUI/DoubleSlider.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 45e242709d..384c984e52 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1345,6 +1345,12 @@ void Control::move_current_thumb(const bool condition) if (is_horizontal()) delta *= -1; + // accelerators + if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL)) + delta *= 10; + else if (wxGetKeyState(WXK_CONTROL)) + delta *= 5; + if (m_selection == ssLower) { m_lower_value -= delta; correct_lower_value(); From af3765c04c6ebba2c2b1e3961f96c4e37b7a4099 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 16:14:14 +0200 Subject: [PATCH 109/826] Follow up of e77fa3512ac0c9d697b9ad9620a5e955cf35209e -> changed logic for DoubleSlider::Control accelerators --- src/slic3r/GUI/DoubleSlider.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 384c984e52..b68ff7e4e0 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1346,10 +1346,13 @@ void Control::move_current_thumb(const bool condition) delta *= -1; // accelerators - if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL)) - delta *= 10; - else if (wxGetKeyState(WXK_CONTROL)) - delta *= 5; + int accelerator = 0; + if (wxGetKeyState(WXK_SHIFT)) + accelerator += 5; + if (wxGetKeyState(WXK_CONTROL)) + accelerator += 5; + if (accelerator > 0) + delta *= accelerator; if (m_selection == ssLower) { m_lower_value -= delta; From 35190936a3fcca57376f30a9cafd8505c0ce508e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 16:19:40 +0200 Subject: [PATCH 110/826] GCodeViewer -> Newer version of shader for options --- .../{options_120.fs => options_120_flat.fs} | 7 +- .../{options_120.vs => options_120_flat.vs} | 0 resources/shaders/options_120_solid.fs | 88 +++++++++++++++++++ resources/shaders/options_120_solid.vs | 14 +++ resources/shaders/shells.fs | 13 --- resources/shaders/shells.vs | 42 --------- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/Camera.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 77 +++++++++------- src/slic3r/GUI/GCodeViewer.hpp | 8 +- src/slic3r/GUI/GLShader.cpp | 20 +++++ src/slic3r/GUI/GLShader.hpp | 2 + src/slic3r/GUI/GLShadersManager.cpp | 11 ++- 13 files changed, 187 insertions(+), 98 deletions(-) rename resources/shaders/{options_120.fs => options_120_flat.fs} (81%) rename resources/shaders/{options_120.vs => options_120_flat.vs} (100%) create mode 100644 resources/shaders/options_120_solid.fs create mode 100644 resources/shaders/options_120_solid.vs delete mode 100644 resources/shaders/shells.fs delete mode 100644 resources/shaders/shells.vs diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120_flat.fs similarity index 81% rename from resources/shaders/options_120.fs rename to resources/shaders/options_120_flat.fs index 90d417b6e7..656eccd1d4 100644 --- a/resources/shaders/options_120.fs +++ b/resources/shaders/options_120_flat.fs @@ -5,7 +5,7 @@ uniform vec3 uniform_color; uniform float percent_outline_radius; uniform float percent_center_radius; -vec4 hard_color(float sq_radius) +vec4 hardcoded_color(float sq_radius) { if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) return vec4(0.5 * uniform_color, 1.0); @@ -13,7 +13,7 @@ vec4 hard_color(float sq_radius) return vec4(uniform_color, 1.0); } -vec4 custom_color(float sq_radius) +vec4 customizable_color(float sq_radius) { float in_radius = 0.5 * percent_center_radius; float out_radius = 0.5 * (1.0 - percent_outline_radius); @@ -30,5 +30,6 @@ void main() if (sq_radius > 0.25) discard; - gl_FragColor = custom_color(sq_radius); + gl_FragColor = customizable_color(sq_radius); +// gl_FragColor = hardcoded_color(sq_radius); } diff --git a/resources/shaders/options_120.vs b/resources/shaders/options_120_flat.vs similarity index 100% rename from resources/shaders/options_120.vs rename to resources/shaders/options_120_flat.vs diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs new file mode 100644 index 0000000000..68d0bd4eec --- /dev/null +++ b/resources/shaders/options_120_solid.fs @@ -0,0 +1,88 @@ +// version 120 is needed for gl_PointCoord +#version 120 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +uniform vec3 uniform_color; +uniform float percent_outline_radius; +uniform float percent_center_radius; + +// x = width, y = height +uniform ivec2 viewport_sizes; +uniform vec2 z_range; +uniform mat4 inv_proj_matrix; + +varying vec3 eye_center; + +float radius = 0.5; +// x = tainted, y = specular; +vec2 intensity; + +vec3 eye_position_from_fragment() +{ + // Convert screen coordinates to normalized device coordinates (NDC) + vec4 ndc = vec4( + (gl_FragCoord.x / viewport_sizes.x - 0.5) * 2.0, + (gl_FragCoord.y / viewport_sizes.y - 0.5) * 2.0, + (gl_FragCoord.z - 0.5) * 2.0, + 1.0); + + // Convert NDC throuch inverse clip coordinates to view coordinates + vec4 clip = inv_proj_matrix * ndc; + return (clip / clip.w).xyz; +} + +vec3 eye_position_on_sphere(vec3 eye_fragment_position) +{ + vec3 eye_dir = normalize(eye_fragment_position); + float a = dot(eye_dir, eye_dir); + float b = 2.0 * dot(-eye_center, eye_dir); + float c = dot(eye_center, eye_center) - radius * radius; + float discriminant = b * b - 4 * a * c; + float t = -(b + sqrt(discriminant)) / (2.0 * a); + return t * eye_dir; +} + +vec4 on_sphere_color(vec3 eye_on_sphere_position) +{ + vec3 eye_normal = normalize(eye_on_sphere_position - eye_center); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_on_sphere_position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + return vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, 1.0); +} + +void main() +{ + vec2 pos = gl_PointCoord - vec2(0.5, 0.5); + float sq_radius = dot(pos, pos); + if (sq_radius > 0.25) + discard; + + vec3 eye_on_sphere_position = eye_position_on_sphere(eye_position_from_fragment()); + +// gl_FragDepth = eye_on_sphere_position.z; +// gl_FragDepth = (eye_on_sphere_position.z - z_range.x) / (z_range.y - z_range.x); + gl_FragColor = on_sphere_color(eye_on_sphere_position); +} diff --git a/resources/shaders/options_120_solid.vs b/resources/shaders/options_120_solid.vs new file mode 100644 index 0000000000..0ad75003c9 --- /dev/null +++ b/resources/shaders/options_120_solid.vs @@ -0,0 +1,14 @@ +#version 120 + +uniform float zoom; +// x = min, y = max +uniform vec2 point_sizes; + +varying vec3 eye_center; + +void main() +{ + gl_PointSize = clamp(zoom, point_sizes.x, point_sizes.y); + eye_center = (gl_ModelViewMatrix * gl_Vertex).xyz; + gl_Position = ftransform(); +} diff --git a/resources/shaders/shells.fs b/resources/shaders/shells.fs deleted file mode 100644 index 0c3388df70..0000000000 --- a/resources/shaders/shells.fs +++ /dev/null @@ -1,13 +0,0 @@ -#version 110 - -const vec3 ZERO = vec3(0.0, 0.0, 0.0); - -uniform vec4 uniform_color; - -// x = tainted, y = specular; -varying vec2 intensity; - -void main() -{ - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a); -} diff --git a/resources/shaders/shells.vs b/resources/shaders/shells.vs deleted file mode 100644 index bb9c144e65..0000000000 --- a/resources/shaders/shells.vs +++ /dev/null @@ -1,42 +0,0 @@ -#version 110 - -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -#define INTENSITY_AMBIENT 0.3 - -// x = tainted, y = specular; -varying vec2 intensity; - -void main() -{ - // First transform the normal into camera space and normalize the result. - vec3 normal = normalize(gl_NormalMatrix * gl_Normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = 0.0; - - if (NdotL > 0.0) - { - vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; - intensity.y += LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - } - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - intensity.x += max(dot(normal, LIGHT_FRONT_DIR), 0.0) * LIGHT_FRONT_DIFFUSE; - - gl_Position = ftransform(); -} diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 5631dadf34..984373ea4a 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -15,7 +15,7 @@ #define ENABLE_RENDER_STATISTICS 0 // Shows an imgui dialog with camera related data #define ENABLE_CAMERA_STATISTICS 0 -// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) +// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) #define ENABLE_RENDER_PICKING_PASS 0 // Enable extracting thumbnails from selected gcode and save them as png files #define ENABLE_THUMBNAIL_GENERATOR_DEBUG 0 diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index ece999c078..6e42562351 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -84,6 +84,7 @@ public: double get_near_z() const { return m_frustrum_zs.first; } double get_far_z() const { return m_frustrum_zs.second; } + const std::pair& get_z_range() const { return m_frustrum_zs; } double get_fov() const; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6d41976941..978b4b95a6 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -415,12 +415,12 @@ void GCodeViewer::init_shaders() { switch (buffer_type(i)) { - case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } - case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } - case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } - case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } - case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } - case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } + case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "extrusions"; break; } case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "travels"; break; } default: { break; } @@ -754,21 +754,26 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - bool is_glsl_120 = m_shaders_editor.glsl_version == 1 && wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); + bool is_glsl_120 = m_shaders_editor.shader_version >= 1 && wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); std::array point_sizes; if (m_shaders_editor.size_dependent_on_zoom) - { point_sizes = { std::min(static_cast(m_shaders_editor.sizes[0]), m_detected_point_sizes[1]), std::min(static_cast(m_shaders_editor.sizes[1]), m_detected_point_sizes[1]) }; - } else point_sizes = { static_cast(m_shaders_editor.fixed_size), static_cast(m_shaders_editor.fixed_size) }; #else bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); std::array point_sizes = { std::min(8.0f, m_detected_point_sizes[1]), std::min(48.0f, m_detected_point_sizes[1]) }; #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR - double zoom = wxGetApp().plater()->get_camera().get_zoom(); + const Camera& camera = wxGetApp().plater()->get_camera(); + double zoom = camera.get_zoom(); + const std::array& viewport = camera.get_viewport(); + std::array viewport_sizes = { viewport[2], viewport[3] }; + const std::pair& camera_z_range = camera.get_z_range(); + std::array z_range = { static_cast(camera_z_range.first), static_cast(camera_z_range.second) }; - auto render_options = [this, is_glsl_120, zoom, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { + Transform3d inv_proj = camera.get_projection_matrix().inverse(); + + auto render_options = [this, is_glsl_120, zoom, viewport, inv_proj, viewport_sizes, z_range, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("zoom", m_shaders_editor.size_dependent_on_zoom ? zoom : 1.0f); @@ -779,6 +784,9 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("percent_outline_radius", 0.15f); shader.set_uniform("percent_center_radius", 0.15f); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR + shader.set_uniform("viewport_sizes", viewport_sizes); + shader.set_uniform("inv_proj_matrix", inv_proj); + shader.set_uniform("z_range", z_range); shader.set_uniform("point_sizes", point_sizes); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); if (is_glsl_120) @@ -896,7 +904,7 @@ void GCodeViewer::render_shells() const if (!m_shells.visible || m_shells.volumes.empty()) return; - GLShaderProgram* shader = wxGetApp().get_shader("shells"); + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; @@ -954,25 +962,25 @@ void GCodeViewer::render_legend() const { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = ((0.5f * icon_size) - 2.0f) * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - if (m_shaders_editor.percent_center > 0) - { - radius = ((0.5f * icon_size) - 2.0f) * 0.01f * static_cast(m_shaders_editor.percent_center); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, + if (m_shaders_editor.shader_version == 1) { + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = ((0.5f * icon_size) - 2.0f) * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + if (m_shaders_editor.percent_center > 0) { + radius = ((0.5f * icon_size) - 2.0f) * 0.01f * static_cast(m_shaders_editor.percent_center); + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + } + } else { + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); } #else draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 3.0f, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 1.5f, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR break; } @@ -1195,7 +1203,11 @@ void GCodeViewer::render_legend() const auto add_option = [this, add_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { const IBuffer& buffer = m_buffers[buffer_id(move_type)]; if (buffer.visible && buffer.indices_count > 0) - add_item(EItemType::Circle, Options_Colors[static_cast(color)], text); +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + add_item((m_shaders_editor.shader_version == 0) ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); +#else + add_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR }; // options @@ -1337,12 +1349,15 @@ void GCodeViewer::render_shaders_editor() const imgui.set_next_window_pos(static_cast(cnv_size.get_width()), 0.5f * static_cast(cnv_size.get_height()), ImGuiCond_Once, 1.0f, 0.5f); imgui.begin(std::string("Shaders editor (DEV only)"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); - ImGui::RadioButton("glsl version 1.10 (low end PCs)", &m_shaders_editor.glsl_version, 0); - ImGui::RadioButton("glsl version 1.20 (default)", &m_shaders_editor.glsl_version, 1); - switch (m_shaders_editor.glsl_version) + ImGui::RadioButton("glsl version 1.10 (low end PCs)", &m_shaders_editor.shader_version, 0); + ImGui::RadioButton("glsl version 1.20 flat (billboards)", &m_shaders_editor.shader_version, 1); + ImGui::RadioButton("glsl version 1.20 solid (spheres default)", &m_shaders_editor.shader_version, 2); + + switch (m_shaders_editor.shader_version) { case 0: { set_shader("options_110"); break; } - case 1: { set_shader("options_120"); break; } + case 1: { set_shader("options_120_flat"); break; } + case 2: { set_shader("options_120_solid"); break; } } if (ImGui::CollapsingHeader("Options", ImGuiTreeNodeFlags_DefaultOpen)) @@ -1364,7 +1379,7 @@ void GCodeViewer::render_shaders_editor() const else ImGui::SliderInt("fixed size", &m_shaders_editor.fixed_size, 1, 100); - if (m_shaders_editor.glsl_version == 1) + if (m_shaders_editor.shader_version == 1) { ImGui::SliderInt("percent outline", &m_shaders_editor.percent_outline, 0, 50); ImGui::SliderInt("percent center", &m_shaders_editor.percent_center, 0, 50); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 1d940b66a7..a2f9c14af7 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -205,12 +205,12 @@ class GCodeViewer #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR struct ShadersEditor { - int glsl_version{ 1 }; + int shader_version{ 2 }; bool size_dependent_on_zoom{ true }; int fixed_size{ 16 }; - std::array sizes{ 8, 64 }; - int percent_outline{ 15 }; - int percent_center{ 15 }; + std::array sizes{ 3, 21 }; + int percent_outline{ 0 }; + int percent_center{ 33 }; }; #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index a6d641f89f..3c2612b45e 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -215,6 +215,26 @@ bool GLShaderProgram::set_uniform(const char* name, double value) const return set_uniform(name, static_cast(value)); } +bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform2iv(id, 1, static_cast(value.data()))); + return true; + } + return false; +} + +bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const +{ + int id = get_uniform_location(name); + if (id >= 0) { + glsafe(::glUniform3iv(id, 1, static_cast(value.data()))); + return true; + } + return false; +} + bool GLShaderProgram::set_uniform(const char* name, const std::array& value) const { int id = get_uniform_location(name); diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 521f6796f1..a1160f8e98 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -43,6 +43,8 @@ public: bool set_uniform(const char* name, bool value) const; bool set_uniform(const char* name, float value) const; bool set_uniform(const char* name, double value) const; + bool set_uniform(const char* name, const std::array& value) const; + bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; bool set_uniform(const char* name, const std::array& value) const; diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index dd77351bd0..4bebf7b984 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "GLShadersManager.hpp" #include "3DScene.hpp" +#include "GUI_App.hpp" #include #include @@ -28,19 +29,21 @@ std::pair GLShadersManager::init() bool valid = true; - // used to render bed axes and model, selection hints, gcode sequential view marker model + // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); // used to render printbed valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); // used to render options in gcode preview valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); - valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" }); + if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) + { + valid &= append_shader("options_120_flat", { "options_120_flat.vs", "options_120_flat.fs" }); + valid &= append_shader("options_120_solid", { "options_120_solid.vs", "options_120_solid.fs" }); + } // used to render extrusion paths in gcode preview valid &= append_shader("extrusions", { "extrusions.vs", "extrusions.fs" }); // used to render travel paths in gcode preview valid &= append_shader("travels", { "travels.vs", "travels.fs" }); - // used to render shells in gcode preview - valid &= append_shader("shells", { "shells.vs", "shells.fs" }); // used to render objects in 3d editor valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }); // used to render variable layers heights in 3d editor From abd7d7480053060144faa155b08efbce89ff5ad8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 May 2020 16:31:02 +0200 Subject: [PATCH 111/826] GCodeViewer -> Small refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 978b4b95a6..6dea0511bf 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -411,16 +411,17 @@ void GCodeViewer::init_shaders() unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); for (unsigned char i = begin_id; i < end_id; ++i) { switch (buffer_type(i)) { - case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "extrusions"; break; } case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "travels"; break; } default: { break; } From 0cb4a5ce56f60c07f2e190a9ad9ccdd38cf939cc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 28 May 2020 07:06:54 +0200 Subject: [PATCH 112/826] GCodeViewer -> Improved depth detection in shader for options --- resources/shaders/options_120_solid.fs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs index 68d0bd4eec..b7770cf0db 100644 --- a/resources/shaders/options_120_solid.fs +++ b/resources/shaders/options_120_solid.fs @@ -21,7 +21,9 @@ uniform float percent_center_radius; // x = width, y = height uniform ivec2 viewport_sizes; -uniform vec2 z_range; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//uniform vec2 z_range; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ uniform mat4 inv_proj_matrix; varying vec3 eye_center; @@ -73,6 +75,15 @@ vec4 on_sphere_color(vec3 eye_on_sphere_position) return vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, 1.0); } +float fragment_depth(vec3 eye_pos) +{ + // see: https://stackoverflow.com/questions/10264949/glsl-gl-fragcoord-z-calculation-and-setting-gl-fragdepth + vec4 clip_pos = gl_ProjectionMatrix * vec4(eye_pos, 1.0); + float ndc_depth = clip_pos.z / clip_pos.w; + + return (((gl_DepthRange.far - gl_DepthRange.near) * ndc_depth) + gl_DepthRange.near + gl_DepthRange.far) / 2.0; +} + void main() { vec2 pos = gl_PointCoord - vec2(0.5, 0.5); @@ -82,6 +93,7 @@ void main() vec3 eye_on_sphere_position = eye_position_on_sphere(eye_position_from_fragment()); + gl_FragDepth = fragment_depth(eye_on_sphere_position); // gl_FragDepth = eye_on_sphere_position.z; // gl_FragDepth = (eye_on_sphere_position.z - z_range.x) / (z_range.y - z_range.x); gl_FragColor = on_sphere_color(eye_on_sphere_position); From edaabf3fbde559698b3e25f547b0c4050bed8aef Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 28 May 2020 07:52:11 +0200 Subject: [PATCH 113/826] GCodeViewer -> Experimental hexagonal icons for toolpaths in legend --- src/slic3r/GUI/GCodeViewer.cpp | 68 +++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c0acba344e..25432f1cf6 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -929,6 +929,8 @@ void GCodeViewer::render_legend() const ImGuiWrapper& imgui = *wxGetApp().imgui(); +#define USE_ICON_HEXAGON 1 + imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); @@ -940,6 +942,7 @@ void GCodeViewer::render_legend() const { Rect, Circle, + Hexagon, Line }; @@ -963,22 +966,19 @@ void GCodeViewer::render_legend() const case EItemType::Circle: { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddCircle(center, 0.5f * icon_size, ICON_BORDER_COLOR, 16); if (m_shaders_editor.shader_version == 1) { - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, + draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); float radius = ((0.5f * icon_size) - 2.0f) * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); if (m_shaders_editor.percent_center > 0) { radius = ((0.5f * icon_size) - 2.0f) * 0.01f * static_cast(m_shaders_editor.percent_center); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, radius, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); } - } else { - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - } + } else + draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); #else draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, @@ -986,6 +986,14 @@ void GCodeViewer::render_legend() const #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR break; } + case EItemType::Hexagon: + { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgon(center, 0.5f * icon_size, ICON_BORDER_COLOR, 6); + draw_list->AddNgonFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + break; + } case EItemType::Line: { draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1}, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); @@ -1009,7 +1017,11 @@ void GCodeViewer::render_legend() const auto add_range_item = [this, draw_list, &imgui, add_item](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, Range_Colors[i], buf); +#else add_item(EItemType::Rect, Range_Colors[i], buf); +#endif // USE_ICON_HEXAGON }; float step_size = range.step_size(); @@ -1051,7 +1063,11 @@ void GCodeViewer::render_legend() const if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { +#else add_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { +#endif // USE_ICON_HEXAGON if (role < erCount) { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); @@ -1081,7 +1097,11 @@ void GCodeViewer::render_legend() const if (it == m_extruder_ids.end()) continue; +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); +#else add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); +#endif // USE_ICON_HEXAGON } break; } @@ -1092,7 +1112,11 @@ void GCodeViewer::render_legend() const if (extruders_count == 1) { // single extruder use case if (custom_gcode_per_print_z.empty()) // no data to show +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors.front(), _u8L("Default print color")); +#else add_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default print color")); +#endif // USE_ICON_HEXAGON else { std::vector> cp_values; cp_values.reserve(custom_gcode_per_print_z.size()); @@ -1116,7 +1140,11 @@ void GCodeViewer::render_legend() const const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There is no one color change, but there are some pause print or custom Gcode +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors.front(), _u8L("Default print color")); +#else add_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default print color")); +#endif // USE_ICON_HEXAGON } else { for (int i = items_cnt; i >= 0; --i) { @@ -1124,14 +1152,26 @@ void GCodeViewer::render_legend() const std::string id_str = " (" + std::to_string(i + 1) + ")"; if (i == 0) { +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str() + id_str); +#else add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str() + id_str); +#endif // USE_ICON_HEXAGON break; } else if (i == items_cnt) { +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str() + id_str); +#else add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str() + id_str); +#endif // USE_ICON_HEXAGON continue; } +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second% cp_values[i].first).str() + id_str); +#else add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second% cp_values[i].first).str() + id_str); +#endif // USE_ICON_HEXAGON } } } @@ -1140,7 +1180,11 @@ void GCodeViewer::render_legend() const { // extruders for (unsigned int i = 0; i < (unsigned int)extruders_count; ++i) { +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); +#else add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); +#endif // USE_ICON_HEXAGON } // color changes @@ -1151,7 +1195,11 @@ void GCodeViewer::render_legend() const // create label for color change item std::string id_str = " (" + std::to_string(color_change_idx--) + ")"; +#if USE_ICON_HEXAGON + add_item(EItemType::Hexagon, m_tool_colors[last_color_id--], +#else add_item(EItemType::Rect, m_tool_colors[last_color_id--], +#endif // USE_ICON_HEXAGON (boost::format(_u8L("Color change for Extruder %d at %.2f mm")) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str() + id_str); } } From 9c8892c869a4e2421e3097fb588a7a8853b52516 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 28 May 2020 09:23:30 +0200 Subject: [PATCH 114/826] GCodeViewer -> Shaders code cleanup --- resources/shaders/extrusions.fs | 5 ----- resources/shaders/extrusions.vs | 4 ---- resources/shaders/options_120_solid.fs | 9 --------- resources/shaders/travels.fs | 5 ----- resources/shaders/travels.vs | 4 ---- src/slic3r/GUI/GCodeViewer.cpp | 13 ++++++++++--- 6 files changed, 10 insertions(+), 30 deletions(-) diff --git a/resources/shaders/extrusions.fs b/resources/shaders/extrusions.fs index fc81e487fb..65db0667a9 100644 --- a/resources/shaders/extrusions.fs +++ b/resources/shaders/extrusions.fs @@ -17,7 +17,6 @@ uniform vec3 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; -//varying float world_normal_z; // x = tainted, y = specular; vec2 intensity; @@ -37,9 +36,5 @@ void main() NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); } diff --git a/resources/shaders/extrusions.vs b/resources/shaders/extrusions.vs index d97adbabe0..8980f3944b 100644 --- a/resources/shaders/extrusions.vs +++ b/resources/shaders/extrusions.vs @@ -2,14 +2,10 @@ varying vec3 eye_position; varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; void main() { eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; gl_Position = ftransform(); } diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs index b7770cf0db..c7a57d547b 100644 --- a/resources/shaders/options_120_solid.fs +++ b/resources/shaders/options_120_solid.fs @@ -16,14 +16,9 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define INTENSITY_AMBIENT 0.3 uniform vec3 uniform_color; -uniform float percent_outline_radius; -uniform float percent_center_radius; // x = width, y = height uniform ivec2 viewport_sizes; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//uniform vec2 z_range; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ uniform mat4 inv_proj_matrix; varying vec3 eye_center; @@ -77,10 +72,8 @@ vec4 on_sphere_color(vec3 eye_on_sphere_position) float fragment_depth(vec3 eye_pos) { - // see: https://stackoverflow.com/questions/10264949/glsl-gl-fragcoord-z-calculation-and-setting-gl-fragdepth vec4 clip_pos = gl_ProjectionMatrix * vec4(eye_pos, 1.0); float ndc_depth = clip_pos.z / clip_pos.w; - return (((gl_DepthRange.far - gl_DepthRange.near) * ndc_depth) + gl_DepthRange.near + gl_DepthRange.far) / 2.0; } @@ -94,7 +87,5 @@ void main() vec3 eye_on_sphere_position = eye_position_on_sphere(eye_position_from_fragment()); gl_FragDepth = fragment_depth(eye_on_sphere_position); -// gl_FragDepth = eye_on_sphere_position.z; -// gl_FragDepth = (eye_on_sphere_position.z - z_range.x) / (z_range.y - z_range.x); gl_FragColor = on_sphere_color(eye_on_sphere_position); } diff --git a/resources/shaders/travels.fs b/resources/shaders/travels.fs index fc81e487fb..65db0667a9 100644 --- a/resources/shaders/travels.fs +++ b/resources/shaders/travels.fs @@ -17,7 +17,6 @@ uniform vec3 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; -//varying float world_normal_z; // x = tainted, y = specular; vec2 intensity; @@ -37,9 +36,5 @@ void main() NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; -// // darkens fragments whose normal points downward -// if (world_normal_z < 0.0) -// intensity.x *= (1.0 + world_normal_z * (1.0 - INTENSITY_AMBIENT)); - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); } diff --git a/resources/shaders/travels.vs b/resources/shaders/travels.vs index d97adbabe0..8980f3944b 100644 --- a/resources/shaders/travels.vs +++ b/resources/shaders/travels.vs @@ -2,14 +2,10 @@ varying vec3 eye_position; varying vec3 eye_normal; -//// world z component of the normal used to darken the lower side of the toolpaths -//varying float world_normal_z; void main() { eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); -// eye_normal = gl_NormalMatrix * gl_Normal; -// world_normal_z = gl_Normal.z; gl_Position = ftransform(); } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 25432f1cf6..896a42b10f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -771,11 +771,16 @@ void GCodeViewer::render_toolpaths() const const std::array& viewport = camera.get_viewport(); std::array viewport_sizes = { viewport[2], viewport[3] }; const std::pair& camera_z_range = camera.get_z_range(); - std::array z_range = { static_cast(camera_z_range.first), static_cast(camera_z_range.second) }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// std::array z_range = { static_cast(camera_z_range.first), static_cast(camera_z_range.second) }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Transform3d inv_proj = camera.get_projection_matrix().inverse(); - auto render_options = [this, is_glsl_120, zoom, viewport, inv_proj, viewport_sizes, z_range, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + auto render_options = [this, is_glsl_120, zoom, viewport, inv_proj, viewport_sizes, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { +// auto render_options = [this, is_glsl_120, zoom, viewport, inv_proj, viewport_sizes, z_range, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("zoom", m_shaders_editor.size_dependent_on_zoom ? zoom : 1.0f); @@ -788,7 +793,9 @@ void GCodeViewer::render_toolpaths() const #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("viewport_sizes", viewport_sizes); shader.set_uniform("inv_proj_matrix", inv_proj); - shader.set_uniform("z_range", z_range); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// shader.set_uniform("z_range", z_range); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ shader.set_uniform("point_sizes", point_sizes); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); if (is_glsl_120) From 2904ee6e1a122012a6d158137261482769aa8ca4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 28 May 2020 09:38:08 +0200 Subject: [PATCH 115/826] Added missing include --- src/slic3r/GUI/Camera.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index ac32767c4b..866c7e979d 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -5,6 +5,7 @@ #include "AppConfig.hpp" #if ENABLE_CAMERA_STATISTICS #include "Mouse3DController.hpp" +#include "Plater.hpp" #endif // ENABLE_CAMERA_STATISTICS #include From 96db6aaadb936c324f2f07369937a6fac8f8fc11 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 28 May 2020 11:14:56 +0200 Subject: [PATCH 116/826] Attempt to fix rambling crash on Mac Asan --- src/slic3r/GUI/GCodeViewer.cpp | 9 --------- src/slic3r/GUI/GUI_Preview.cpp | 4 ++++ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 896a42b10f..2cf2bb7057 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -771,16 +771,10 @@ void GCodeViewer::render_toolpaths() const const std::array& viewport = camera.get_viewport(); std::array viewport_sizes = { viewport[2], viewport[3] }; const std::pair& camera_z_range = camera.get_z_range(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// std::array z_range = { static_cast(camera_z_range.first), static_cast(camera_z_range.second) }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Transform3d inv_proj = camera.get_projection_matrix().inverse(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto render_options = [this, is_glsl_120, zoom, viewport, inv_proj, viewport_sizes, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { -// auto render_options = [this, is_glsl_120, zoom, viewport, inv_proj, viewport_sizes, z_range, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("zoom", m_shaders_editor.size_dependent_on_zoom ? zoom : 1.0f); @@ -793,9 +787,6 @@ void GCodeViewer::render_toolpaths() const #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("viewport_sizes", viewport_sizes); shader.set_uniform("inv_proj_matrix", inv_proj); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// shader.set_uniform("z_range", z_range); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ shader.set_uniform("point_sizes", point_sizes); glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); if (is_glsl_120) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index c673292efc..65791150a0 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1144,6 +1144,10 @@ void Preview::update_layers_slider_from_canvas(wxKeyEvent& event) void Preview::update_moves_slider() { const GCodeViewer::SequentialView& view = m_canvas->get_gcode_sequential_view(); + // this should not be needed, but it is here to try to prevent rambling crashes on Mac Asan + if (view.endpoints.last < view.endpoints.first) + return; + std::vector values(view.endpoints.last - view.endpoints.first + 1); unsigned int count = 0; for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i) From dcec684cc7fe8979d25b726f6bdf560353139691 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 29 May 2020 12:29:04 +0200 Subject: [PATCH 117/826] ENABLE_GCODE_VIEWER -> Refactoring of shaders for options --- resources/shaders/options_110.fs | 2 +- resources/shaders/options_110.vs | 8 +-- resources/shaders/options_120_flat.fs | 20 +++--- resources/shaders/options_120_flat.vs | 8 +-- resources/shaders/options_120_solid.fs | 2 +- resources/shaders/options_120_solid.vs | 8 +-- src/slic3r/GUI/GCodeViewer.cpp | 87 +++++++++++++------------- src/slic3r/GUI/GCodeViewer.hpp | 4 +- 8 files changed, 67 insertions(+), 72 deletions(-) diff --git a/resources/shaders/options_110.fs b/resources/shaders/options_110.fs index 3722058c87..474e355e09 100644 --- a/resources/shaders/options_110.fs +++ b/resources/shaders/options_110.fs @@ -1,4 +1,4 @@ -#version 120 +#version 110 uniform vec3 uniform_color; diff --git a/resources/shaders/options_110.vs b/resources/shaders/options_110.vs index 5361c88cec..7592f86a42 100644 --- a/resources/shaders/options_110.vs +++ b/resources/shaders/options_110.vs @@ -1,11 +1,11 @@ #version 110 uniform float zoom; -// x = min, y = max -uniform vec2 point_sizes; +uniform float point_size; +uniform float near_plane_height; void main() { - gl_PointSize = clamp(zoom, point_sizes.x, point_sizes.y); - gl_Position = ftransform(); + gl_Position = ftransform(); + gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w; } diff --git a/resources/shaders/options_120_flat.fs b/resources/shaders/options_120_flat.fs index 656eccd1d4..e98e5693f3 100644 --- a/resources/shaders/options_120_flat.fs +++ b/resources/shaders/options_120_flat.fs @@ -5,31 +5,31 @@ uniform vec3 uniform_color; uniform float percent_outline_radius; uniform float percent_center_radius; -vec4 hardcoded_color(float sq_radius) +vec4 hardcoded_color(float sq_radius, vec3 color) { if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) - return vec4(0.5 * uniform_color, 1.0); + return vec4(0.5 * color, 1.0); else - return vec4(uniform_color, 1.0); + return vec4(color, 1.0); } -vec4 customizable_color(float sq_radius) +vec4 customizable_color(float sq_radius, vec3 color) { float in_radius = 0.5 * percent_center_radius; float out_radius = 0.5 * (1.0 - percent_outline_radius); if ((sq_radius < in_radius * in_radius) || (sq_radius > out_radius * out_radius)) - return vec4(0.5 * uniform_color, 1.0); + return vec4(0.5 * color, 1.0); else - return vec4(uniform_color, 1.0); + return vec4(color, 1.0); } void main() { - vec2 pos = gl_PointCoord - vec2(0.5, 0.5); + vec2 pos = gl_PointCoord - vec2(0.5); float sq_radius = dot(pos, pos); if (sq_radius > 0.25) discard; - - gl_FragColor = customizable_color(sq_radius); -// gl_FragColor = hardcoded_color(sq_radius); + + gl_FragColor = customizable_color(sq_radius, uniform_color); +// gl_FragColor = hardcoded_color(sq_radius, uniform_color); } diff --git a/resources/shaders/options_120_flat.vs b/resources/shaders/options_120_flat.vs index ebf7428c9a..baf3cd3a7f 100644 --- a/resources/shaders/options_120_flat.vs +++ b/resources/shaders/options_120_flat.vs @@ -1,11 +1,11 @@ #version 120 uniform float zoom; -// x = min, y = max -uniform vec2 point_sizes; +uniform float point_size; +uniform float near_plane_height; void main() { - gl_PointSize = clamp(zoom, point_sizes.x, point_sizes.y); - gl_Position = ftransform(); + gl_Position = ftransform(); + gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w; } diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs index c7a57d547b..18410b4062 100644 --- a/resources/shaders/options_120_solid.fs +++ b/resources/shaders/options_120_solid.fs @@ -79,7 +79,7 @@ float fragment_depth(vec3 eye_pos) void main() { - vec2 pos = gl_PointCoord - vec2(0.5, 0.5); + vec2 pos = gl_PointCoord - vec2(0.5); float sq_radius = dot(pos, pos); if (sq_radius > 0.25) discard; diff --git a/resources/shaders/options_120_solid.vs b/resources/shaders/options_120_solid.vs index 0ad75003c9..745ec8ddd1 100644 --- a/resources/shaders/options_120_solid.vs +++ b/resources/shaders/options_120_solid.vs @@ -1,14 +1,14 @@ #version 120 uniform float zoom; -// x = min, y = max -uniform vec2 point_sizes; +uniform float point_size; +uniform float near_plane_height; varying vec3 eye_center; void main() { - gl_PointSize = clamp(zoom, point_sizes.x, point_sizes.y); eye_center = (gl_ModelViewMatrix * gl_Vertex).xyz; - gl_Position = ftransform(); + gl_Position = ftransform(); + gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ed98996b41..24960c79fb 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -756,40 +756,36 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - bool is_glsl_120 = m_shaders_editor.shader_version >= 1 && wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - std::array point_sizes; - if (m_shaders_editor.size_dependent_on_zoom) - point_sizes = { std::min(static_cast(m_shaders_editor.sizes[0]), m_detected_point_sizes[1]), std::min(static_cast(m_shaders_editor.sizes[1]), m_detected_point_sizes[1]) }; - else - point_sizes = { static_cast(m_shaders_editor.fixed_size), static_cast(m_shaders_editor.fixed_size) }; + float point_size = m_shaders_editor.point_size; #else - bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - std::array point_sizes = { std::min(8.0f, m_detected_point_sizes[1]), std::min(48.0f, m_detected_point_sizes[1]) }; + float point_size = 1.0f; #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR const Camera& camera = wxGetApp().plater()->get_camera(); double zoom = camera.get_zoom(); const std::array& viewport = camera.get_viewport(); std::array viewport_sizes = { viewport[2], viewport[3] }; + float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : + static_cast(viewport[3]) * 0.0005; Transform3d inv_proj = camera.get_projection_matrix().inverse(); - auto render_options = [this, is_glsl_120, zoom, viewport, inv_proj, viewport_sizes, point_sizes](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { + auto render_options = [this, zoom, inv_proj, viewport_sizes, point_size, near_plane_height](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); + shader.set_uniform("zoom", zoom); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - shader.set_uniform("zoom", m_shaders_editor.size_dependent_on_zoom ? zoom : 1.0f); shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.percent_outline)); shader.set_uniform("percent_center_radius", 0.01f * static_cast(m_shaders_editor.percent_center)); #else - shader.set_uniform("zoom", zoom); shader.set_uniform("percent_outline_radius", 0.15f); shader.set_uniform("percent_center_radius", 0.15f); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("viewport_sizes", viewport_sizes); shader.set_uniform("inv_proj_matrix", inv_proj); - shader.set_uniform("point_sizes", point_sizes); + shader.set_uniform("point_size", point_size); + shader.set_uniform("near_plane_height", near_plane_height); + glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - if (is_glsl_120) - glsafe(::glEnable(GL_POINT_SPRITE)); + glsafe(::glEnable(GL_POINT_SPRITE)); for (const RenderPath& path : buffer.render_paths) { glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); @@ -797,9 +793,8 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } - if (is_glsl_120) - glsafe(::glDisable(GL_POINT_SPRITE)); - + + glsafe(::glDisable(GL_POINT_SPRITE)); glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; @@ -964,31 +959,49 @@ void GCodeViewer::render_legend() const { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddCircle(center, 0.5f * icon_size, ICON_BORDER_COLOR, 16); if (m_shaders_editor.shader_version == 1) { - draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = ((0.5f * icon_size) - 2.0f) * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); + float radius = 0.5f * icon_size * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); if (m_shaders_editor.percent_center > 0) { - radius = ((0.5f * icon_size) - 2.0f) * 0.01f * static_cast(m_shaders_editor.percent_center); + radius = 0.5f * icon_size * 0.01f * static_cast(m_shaders_editor.percent_center); draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); } - } else - draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + } + else + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + +// ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); +// draw_list->AddCircle(center, 0.5f * icon_size, ICON_BORDER_COLOR, 16); +// if (m_shaders_editor.shader_version == 1) { +// draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, +// ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); +// float radius = ((0.5f * icon_size) - 2.0f) * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); +// draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); +// if (m_shaders_editor.percent_center > 0) { +// radius = ((0.5f * icon_size) - 2.0f) * 0.01f * static_cast(m_shaders_editor.percent_center); +// draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); +// } +// } else +// draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); #else - draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, + draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + +// draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); +// draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, +// ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR break; } case EItemType::Hexagon: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgon(center, 0.5f * icon_size, ICON_BORDER_COLOR, 6); - draw_list->AddNgonFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); +// draw_list->AddNgon(center, 0.5f * icon_size, ICON_BORDER_COLOR, 6); +// draw_list->AddNgonFilled(center, (0.5f * icon_size) - 2.0f, +// ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); break; } case EItemType::Line: @@ -1409,23 +1422,7 @@ void GCodeViewer::render_shaders_editor() const if (ImGui::CollapsingHeader("Options", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Checkbox("size dependent on zoom", &m_shaders_editor.size_dependent_on_zoom); - if (m_shaders_editor.size_dependent_on_zoom) - { - if (ImGui::SliderInt("min size (min zoom)", &m_shaders_editor.sizes[0], 1, 100)) - { - if (m_shaders_editor.sizes[1] < m_shaders_editor.sizes[0]) - m_shaders_editor.sizes[1] = m_shaders_editor.sizes[0]; - } - ImGui::SliderInt("max size (max zoom)", &m_shaders_editor.sizes[1], 1, 100); - { - if (m_shaders_editor.sizes[1] < m_shaders_editor.sizes[0]) - m_shaders_editor.sizes[0] = m_shaders_editor.sizes[1]; - } - } - else - ImGui::SliderInt("fixed size", &m_shaders_editor.fixed_size, 1, 100); - + ImGui::SliderFloat("point size", &m_shaders_editor.point_size, 0.5f, 1.5f, "%.1f"); if (m_shaders_editor.shader_version == 1) { ImGui::SliderInt("percent outline", &m_shaders_editor.percent_outline, 0, 50); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index a2f9c14af7..6f24eef435 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -206,9 +206,7 @@ class GCodeViewer struct ShadersEditor { int shader_version{ 2 }; - bool size_dependent_on_zoom{ true }; - int fixed_size{ 16 }; - std::array sizes{ 3, 21 }; + float point_size{ 1.0f }; int percent_outline{ 0 }; int percent_center{ 33 }; }; From 707268d41dc737a1aa621ba0ad2cbc1cf63da3d1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 1 Jun 2020 08:55:44 +0200 Subject: [PATCH 118/826] ENABLE_GCODE_VIEWER -> Improvements in shaders for options --- resources/shaders/options_120_flat.fs | 26 ++++++++------------- resources/shaders/options_120_solid.fs | 31 +++++++++++++------------- src/slic3r/GUI/GCodeViewer.cpp | 7 +++--- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/resources/shaders/options_120_flat.fs b/resources/shaders/options_120_flat.fs index e98e5693f3..5bcc718761 100644 --- a/resources/shaders/options_120_flat.fs +++ b/resources/shaders/options_120_flat.fs @@ -5,31 +5,23 @@ uniform vec3 uniform_color; uniform float percent_outline_radius; uniform float percent_center_radius; -vec4 hardcoded_color(float sq_radius, vec3 color) +vec4 hardcoded_color(float radius, vec3 color) { - if ((sq_radius < 0.005625) || (sq_radius > 0.180625)) - return vec4(0.5 * color, 1.0); - else - return vec4(color, 1.0); + return ((radius < 0.15) || (radius > 0.85)) ? vec4(0.5 * color, 1.0) : vec4(color, 1.0); } -vec4 customizable_color(float sq_radius, vec3 color) +vec4 customizable_color(float radius, vec3 color) { - float in_radius = 0.5 * percent_center_radius; - float out_radius = 0.5 * (1.0 - percent_outline_radius); - if ((sq_radius < in_radius * in_radius) || (sq_radius > out_radius * out_radius)) - return vec4(0.5 * color, 1.0); - else - return vec4(color, 1.0); + return ((radius < percent_center_radius) || (radius > 1.0 - percent_outline_radius)) ? + vec4(0.5 * color, 1.0) : vec4(color, 1.0); } void main() { - vec2 pos = gl_PointCoord - vec2(0.5); - float sq_radius = dot(pos, pos); - if (sq_radius > 0.25) + vec2 pos = (gl_PointCoord - 0.5) * 2.0; + float radius = length(pos); + if (radius > 1.0) discard; - gl_FragColor = customizable_color(sq_radius, uniform_color); -// gl_FragColor = hardcoded_color(sq_radius, uniform_color); + gl_FragColor = customizable_color(radius, uniform_color); } diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs index 18410b4062..ebe7135274 100644 --- a/resources/shaders/options_120_solid.fs +++ b/resources/shaders/options_120_solid.fs @@ -17,28 +17,26 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); uniform vec3 uniform_color; -// x = width, y = height -uniform ivec2 viewport_sizes; +uniform ivec4 viewport; +uniform float point_size; uniform mat4 inv_proj_matrix; varying vec3 eye_center; - -float radius = 0.5; // x = tainted, y = specular; vec2 intensity; +float radius = 0.5 * point_size; + vec3 eye_position_from_fragment() { // Convert screen coordinates to normalized device coordinates (NDC) - vec4 ndc = vec4( - (gl_FragCoord.x / viewport_sizes.x - 0.5) * 2.0, - (gl_FragCoord.y / viewport_sizes.y - 0.5) * 2.0, - (gl_FragCoord.z - 0.5) * 2.0, - 1.0); - + vec4 ndc = vec4((gl_FragCoord.x / viewport.z - 0.5) * 2.0, + (gl_FragCoord.y / viewport.w - 0.5) * 2.0, + (gl_FragCoord.z - 0.5) * 2.0, + gl_FragCoord.w); // Convert NDC throuch inverse clip coordinates to view coordinates vec4 clip = inv_proj_matrix * ndc; - return (clip / clip.w).xyz; + return clip.xyz; } vec3 eye_position_on_sphere(vec3 eye_fragment_position) @@ -67,21 +65,22 @@ vec4 on_sphere_color(vec3 eye_on_sphere_position) NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - return vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color.rgb * intensity.x, 1.0); + return vec4(intensity + uniform_color.rgb * intensity.x, 1.0); +// return vec4(vec3(intensity.y) + uniform_color.rgb * intensity.x, 1.0); } float fragment_depth(vec3 eye_pos) { vec4 clip_pos = gl_ProjectionMatrix * vec4(eye_pos, 1.0); float ndc_depth = clip_pos.z / clip_pos.w; - return (((gl_DepthRange.far - gl_DepthRange.near) * ndc_depth) + gl_DepthRange.near + gl_DepthRange.far) / 2.0; + return ((gl_DepthRange.far - gl_DepthRange.near) * ndc_depth + gl_DepthRange.near + gl_DepthRange.far) / 2.0; } void main() { - vec2 pos = gl_PointCoord - vec2(0.5); - float sq_radius = dot(pos, pos); - if (sq_radius > 0.25) + vec2 pos = (gl_PointCoord - 0.5) * 2.0; + float radius = length(pos); + if (radius > 1.0) discard; vec3 eye_on_sphere_position = eye_position_on_sphere(eye_position_from_fragment()); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 24960c79fb..3709aaa888 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -763,13 +763,12 @@ void GCodeViewer::render_toolpaths() const const Camera& camera = wxGetApp().plater()->get_camera(); double zoom = camera.get_zoom(); const std::array& viewport = camera.get_viewport(); - std::array viewport_sizes = { viewport[2], viewport[3] }; float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : static_cast(viewport[3]) * 0.0005; Transform3d inv_proj = camera.get_projection_matrix().inverse(); - auto render_options = [this, zoom, inv_proj, viewport_sizes, point_size, near_plane_height](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { + auto render_options = [this, zoom, inv_proj, viewport, point_size, near_plane_height](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); shader.set_uniform("zoom", zoom); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR @@ -779,7 +778,7 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("percent_outline_radius", 0.15f); shader.set_uniform("percent_center_radius", 0.15f); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR - shader.set_uniform("viewport_sizes", viewport_sizes); + shader.set_uniform("viewport", viewport); shader.set_uniform("inv_proj_matrix", inv_proj); shader.set_uniform("point_size", point_size); shader.set_uniform("near_plane_height", near_plane_height); @@ -1422,7 +1421,7 @@ void GCodeViewer::render_shaders_editor() const if (ImGui::CollapsingHeader("Options", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderFloat("point size", &m_shaders_editor.point_size, 0.5f, 1.5f, "%.1f"); + ImGui::SliderFloat("point size", &m_shaders_editor.point_size, 0.5f, 3.0f, "%.1f"); if (m_shaders_editor.shader_version == 1) { ImGui::SliderInt("percent outline", &m_shaders_editor.percent_outline, 0, 50); From 7b33e780a2cf86b819dd6839844142111904efad Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 1 Jun 2020 09:11:16 +0200 Subject: [PATCH 119/826] Follow-up of 707268d41dc737a1aa621ba0ad2cbc1cf63da3d1 -> Fixed typo --- resources/shaders/options_120_solid.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs index ebe7135274..912b809e07 100644 --- a/resources/shaders/options_120_solid.fs +++ b/resources/shaders/options_120_solid.fs @@ -65,7 +65,7 @@ vec4 on_sphere_color(vec3 eye_on_sphere_position) NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - return vec4(intensity + uniform_color.rgb * intensity.x, 1.0); + return vec4(intensity.y + uniform_color.rgb * intensity.x, 1.0); // return vec4(vec3(intensity.y) + uniform_color.rgb * intensity.x, 1.0); } From 06e329655d94659239e503861ef9b81fd8440c98 Mon Sep 17 00:00:00 2001 From: Manuel Coenen Date: Thu, 4 Jun 2020 11:20:09 +0200 Subject: [PATCH 120/826] Add support for DuetSoftwareFramework based machines Also extend Http to be able to send PUT requests as well as setting POST body data directly. --- src/slic3r/Utils/Duet.cpp | 103 ++++++++++++++++++++++++++------------ src/slic3r/Utils/Duet.hpp | 11 ++-- src/slic3r/Utils/Http.cpp | 68 ++++++++++++++++++++----- src/slic3r/Utils/Http.hpp | 13 ++++- 4 files changed, 144 insertions(+), 51 deletions(-) diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index 4536dd2174..cc6b0ebd94 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -36,12 +36,10 @@ const char* Duet::get_name() const { return "Duet"; } bool Duet::test(wxString &msg) const { - bool connected = connect(msg); - if (connected) { - disconnect(); - } + auto connectionType = connect(msg); + disconnect(connectionType); - return connected; + return connectionType != Duet::ConnectionType::ERROR; } wxString Duet::get_test_ok_msg () const @@ -59,33 +57,39 @@ wxString Duet::get_test_failed_msg (wxString &msg) const bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const { wxString connect_msg; - if (!connect(connect_msg)) { + auto connectionType = connect(connect_msg); + if (connectionType == Duet::ConnectionType::ERROR) { error_fn(std::move(connect_msg)); return false; } bool res = true; + bool dsf = (connectionType == Duet::ConnectionType::DSF); - auto upload_cmd = get_upload_url(upload_data.upload_path.string()); + auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType); BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, print: %3%, command: %4%") % upload_data.source_path % upload_data.upload_path % upload_data.start_print % upload_cmd; - auto http = Http::post(std::move(upload_cmd)); - http.set_post_body(upload_data.source_path) - .on_complete([&](std::string body, unsigned status) { + auto http = (dsf ? Http::put(std::move(upload_cmd)) : Http::post(std::move(upload_cmd))); + if (dsf) { + http.set_put_body(upload_data.source_path); + } else { + http.set_post_body(upload_data.source_path); + } + http.on_complete([&](std::string body, unsigned status) { BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: File uploaded: HTTP %1%: %2%") % status % body; - int err_code = get_err_code_from_body(body); + int err_code = dsf ? (status == 201 ? 0 : 1) : get_err_code_from_body(body); if (err_code != 0) { BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Request completed but error code was received: %1%") % err_code; error_fn(format_error(body, L("Unknown error occured"), 0)); res = false; } else if (upload_data.start_print) { wxString errormsg; - res = start_print(errormsg, upload_data.upload_path.string()); + res = start_print(errormsg, upload_data.upload_path.string(), connectionType); if (! res) { error_fn(std::move(errormsg)); } @@ -106,20 +110,28 @@ bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn e }) .perform_sync(); - disconnect(); + disconnect(connectionType); return res; } -bool Duet::connect(wxString &msg) const +Duet::ConnectionType Duet::connect(wxString &msg) const { - bool res = false; - auto url = get_connect_url(); + auto res = Duet::ConnectionType::ERROR; + auto url = get_connect_url(false); auto http = Http::get(std::move(url)); http.on_error([&](std::string body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body; - msg = format_error(body, error, status); + auto dsfUrl = get_connect_url(true); + auto dsfHttp = Http::get(std::move(dsfUrl)); + dsfHttp.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error connecting: %1%, HTTP %2%, body: `%3%`") % error % status % body; + msg = format_error(body, error, status); + }) + .on_complete([&](std::string body, unsigned) { + res = Duet::ConnectionType::DSF; + }) + .perform_sync(); }) .on_complete([&](std::string body, unsigned) { BOOST_LOG_TRIVIAL(debug) << boost::format("Duet: Got: %1%") % body; @@ -127,7 +139,7 @@ bool Duet::connect(wxString &msg) const int err_code = get_err_code_from_body(body); switch (err_code) { case 0: - res = true; + res = Duet::ConnectionType::RRF; break; case 1: msg = format_error(body, L("Wrong password"), 0); @@ -146,8 +158,12 @@ bool Duet::connect(wxString &msg) const return res; } -void Duet::disconnect() const +void Duet::disconnect(Duet::ConnectionType connectionType) const { + // we don't need to disconnect from DSF or if it failed anyway + if (connectionType != Duet::ConnectionType::RRF) { + return; + } auto url = (boost::format("%1%rr_disconnect") % get_base_url()).str(); @@ -159,20 +175,31 @@ void Duet::disconnect() const .perform_sync(); } -std::string Duet::get_upload_url(const std::string &filename) const +std::string Duet::get_upload_url(const std::string &filename, Duet::ConnectionType connectionType) const { - return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%") - % get_base_url() - % Http::url_encode(filename) - % timestamp_str()).str(); + if (connectionType == Duet::ConnectionType::DSF) { + return (boost::format("%1%machine/file/gcodes/%2%") + % get_base_url() + % Http::url_encode(filename)).str(); + } else { + return (boost::format("%1%rr_upload?name=0:/gcodes/%2%&%3%") + % get_base_url() + % Http::url_encode(filename) + % timestamp_str()).str(); + } } -std::string Duet::get_connect_url() const +std::string Duet::get_connect_url(const bool dsfUrl) const { - return (boost::format("%1%rr_connect?password=%2%&%3%") - % get_base_url() - % (password.empty() ? "reprap" : password) - % timestamp_str()).str(); + if (dsfUrl) { + return (boost::format("%1%machine/status") + % get_base_url()).str(); + } else { + return (boost::format("%1%rr_connect?password=%2%&%3%") + % get_base_url() + % (password.empty() ? "reprap" : password) + % timestamp_str()).str(); + } } std::string Duet::get_base_url() const @@ -201,15 +228,25 @@ std::string Duet::timestamp_str() const return std::string(buffer); } -bool Duet::start_print(wxString &msg, const std::string &filename) const +bool Duet::start_print(wxString &msg, const std::string &filename, Duet::ConnectionType connectionType) const { bool res = false; + bool dsf = (connectionType == Duet::ConnectionType::DSF); - auto url = (boost::format("%1%rr_gcode?gcode=M32%%20\"%2%\"") + auto url = dsf + ? (boost::format("%1%machine/code") + % get_base_url()).str() + : (boost::format("%1%rr_gcode?gcode=M32%%20\"0:/gcodes/%2%\"") % get_base_url() % Http::url_encode(filename)).str(); - auto http = Http::get(std::move(url)); + auto http = (dsf ? Http::post(std::move(url)) : Http::get(std::move(url))); + if (dsf) { + http.set_post_body( + (boost::format("M32 \"0:/gcodes/%1%\"") + % filename).str() + ); + } http.on_error([&](std::string body, std::string error, unsigned status) { BOOST_LOG_TRIVIAL(error) << boost::format("Duet: Error starting print: %1%, HTTP %2%, body: `%3%`") % error % status % body; msg = format_error(body, error, status); diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index 702efbddb6..41c6b1d755 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -29,16 +29,17 @@ public: std::string get_host() const override { return host; } private: + enum ConnectionType { RRF, DSF, ERROR }; std::string host; std::string password; - std::string get_upload_url(const std::string &filename) const; - std::string get_connect_url() const; + std::string get_upload_url(const std::string &filename, Duet::ConnectionType connectionType) const; + std::string get_connect_url(const bool dsfUrl) const; std::string get_base_url() const; std::string timestamp_str() const; - bool connect(wxString &msg) const; - void disconnect() const; - bool start_print(wxString &msg, const std::string &filename) const; + Duet::ConnectionType connect(wxString &msg) const; + void disconnect(Duet::ConnectionType connectionType) const; + bool start_print(wxString &msg, const std::string &filename, Duet::ConnectionType connectionType) const; int get_err_code_from_body(const std::string &body) const; }; diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index a16aac5b5b..3360f6eff5 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -35,11 +35,11 @@ struct CurlGlobalInit { static std::unique_ptr instance; std::string message; - + CurlGlobalInit() { #ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON - + // Look for a set of distro specific directories. Don't change the // order: https://bugzilla.redhat.com/show_bug.cgi?id=1053882 static const char * CA_BUNDLES[] = { @@ -48,17 +48,17 @@ struct CurlGlobalInit "/usr/share/ssl/certs/ca-bundle.crt", "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD "/etc/ssl/cert.pem", - "/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed + "/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed }; - + namespace fs = boost::filesystem; // Env var name for the OpenSSL CA bundle (SSL_CERT_FILE nomally) const char *const SSL_CA_FILE = X509_get_default_cert_file_env(); const char * ssl_cafile = ::getenv(SSL_CA_FILE); - + if (!ssl_cafile) ssl_cafile = X509_get_default_cert_file(); - + int replace = true; if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) { const char * bundle = nullptr; @@ -86,15 +86,15 @@ struct CurlGlobalInit } #endif // OPENSSL_CERT_OVERRIDE - + if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) { message += _u8L("CURL init has failed. PrusaSlicer will be unable to establish " "network connections. See logs for additional details."); - + BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec); } } - + ~CurlGlobalInit() { ::curl_global_cleanup(); } }; @@ -120,6 +120,7 @@ struct Http::priv std::string error_buffer; // Used for CURLOPT_ERRORBUFFER size_t limit; bool cancel; + fs::ifstream* putFile; std::thread io_thread; Http::CompleteFn completefn; @@ -138,6 +139,8 @@ struct Http::priv void set_timeout_connect(long timeout); void form_add_file(const char *name, const fs::path &path, const char* filename); void set_post_body(const fs::path &path); + void set_post_body(const std::string body); + void set_put_body(const fs::path &path); std::string curl_error(CURLcode curlcode); std::string body_size_error(); @@ -152,9 +155,10 @@ Http::priv::priv(const std::string &url) , error_buffer(CURL_ERROR_SIZE + 1, '\0') , limit(0) , cancel(false) + , putFile(nullptr) { Http::tls_global_init(); - + if (curl == nullptr) { throw std::runtime_error(std::string("Could not construct Curl object")); } @@ -284,6 +288,22 @@ void Http::priv::set_post_body(const fs::path &path) postfields = file_content; } +void Http::priv::set_post_body(const std::string body) +{ + postfields = body; +} + +void Http::priv::set_put_body(const fs::path &path) +{ + boost::system::error_code ec; + boost::uintmax_t filesize = file_size(path, ec); + if (!ec) { + putFile = new fs::ifstream(path); + ::curl_easy_setopt(curl, CURLOPT_READDATA, (void *) (putFile)); + ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, filesize); + } +} + std::string Http::priv::curl_error(CURLcode curlcode) { return (boost::format("%1%:\n%2%\n[Error %3%]") @@ -335,6 +355,11 @@ void Http::priv::http_perform() CURLcode res = ::curl_easy_perform(curl); + if (putFile != nullptr) { + delete putFile; + putFile = nullptr; + } + if (res != CURLE_OK) { if (res == CURLE_ABORTED_BY_CALLBACK) { if (cancel) { @@ -455,6 +480,18 @@ Http& Http::set_post_body(const fs::path &path) return *this; } +Http& Http::set_post_body(const std::string body) +{ + if (p) { p->set_post_body(body); } + return *this; +} + +Http& Http::set_put_body(const fs::path &path) +{ + if (p) { p->set_put_body(path);} + return *this; +} + Http& Http::on_complete(CompleteFn fn) { if (p) { p->completefn = std::move(fn); } @@ -509,6 +546,13 @@ Http Http::post(std::string url) return http; } +Http Http::put(std::string url) +{ + Http http{std::move(url)}; + curl_easy_setopt(http.p->curl, CURLOPT_UPLOAD, 1L); + return http; +} + bool Http::ca_file_supported() { ::CURL *curl = ::curl_easy_init(); @@ -521,7 +565,7 @@ std::string Http::tls_global_init() { if (!CurlGlobalInit::instance) CurlGlobalInit::instance = std::make_unique(); - + return CurlGlobalInit::instance->message; } @@ -532,7 +576,7 @@ std::string Http::tls_system_cert_store() #ifdef OPENSSL_CERT_OVERRIDE ret = ::getenv(X509_get_default_cert_file_env()); #endif - + return ret; } diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index f162362796..1ee47606e0 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -49,6 +49,7 @@ public: // for a GET and a POST request respectively. static Http get(std::string url); static Http post(std::string url); + static Http put(std::string url); ~Http(); Http(const Http &) = delete; @@ -80,6 +81,16 @@ public: // This can be used for hosts which do not support multipart requests. Http& set_post_body(const boost::filesystem::path &path); + // Set the POST request body. + // The data is used verbatim, it is not additionally encoded in any way. + // This can be used for hosts which do not support multipart requests. + Http& set_post_body(const std::string body); + + // Set the file contents as a PUT request body. + // The data is used verbatim, it is not additionally encoded in any way. + // This can be used for hosts which do not support multipart requests. + Http& set_put_body(const boost::filesystem::path &path); + // Callback called on HTTP request complete Http& on_complete(CompleteFn fn); // Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup, @@ -100,7 +111,7 @@ public: // Tells whether current backend supports seting up a CA file using ca_file() static bool ca_file_supported(); - + // Return empty string on success or error message on fail. static std::string tls_global_init(); static std::string tls_system_cert_store(); From ca17948f87d68e616a0dc888e906462a48b7699d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 8 Jun 2020 09:12:20 +0200 Subject: [PATCH 121/826] ENABLE_GCODE_VIEWER_AS_STATE -> Load gcode from file and process it --- src/libslic3r/GCode/GCodeProcessor.cpp | 18 +++------ src/libslic3r/GCode/GCodeProcessor.hpp | 5 ++- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/GCodeViewer.cpp | 23 ++++++++---- src/slic3r/GUI/GLCanvas3D.hpp | 8 ---- src/slic3r/GUI/GUI_App.cpp | 2 - src/slic3r/GUI/GUI_App.hpp | 2 - src/slic3r/GUI/GUI_Preview.cpp | 14 ------- src/slic3r/GUI/MainFrame.cpp | 51 ++------------------------ src/slic3r/GUI/MainFrame.hpp | 8 ---- src/slic3r/GUI/Plater.cpp | 35 +++++------------- src/slic3r/GUI/Plater.hpp | 16 ++++---- 12 files changed, 46 insertions(+), 138 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index b43f1466c1..c74820a04c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -37,7 +37,7 @@ void GCodeProcessor::CpColor::reset() current = 0; } -unsigned int GCodeProcessor::Result::id = 0; +unsigned int GCodeProcessor::s_result_id = 0; void GCodeProcessor::apply_config(const PrintConfig& config) { @@ -84,19 +84,19 @@ void GCodeProcessor::reset() m_cp_color.reset(); m_result.reset(); + m_result.id = ++s_result_id; } void GCodeProcessor::process_file(const std::string& filename) { + m_result.id = ++s_result_id; m_result.moves.emplace_back(MoveVertex()); m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); } void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { -/* - std::cout << line.raw() << std::endl; -*/ +/* std::cout << line.raw() << std::endl; */ // update start position m_start_position = m_end_position; @@ -296,24 +296,18 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) type = EMoveType::Travel; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE - if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) - { - if (m_extrusion_role != erCustom) - { + if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) { + if (m_extrusion_role != erCustom) { m_width = 0.5f; m_height = 0.5f; } type = EMoveType::Travel; } #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f || !is_valid_extrusion_role(m_extrusion_role))) type = EMoveType::Travel; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return type; }; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 4f6cf7430f..e8c43350c0 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -104,9 +104,9 @@ namespace Slic3r { struct Result { - static unsigned int id; + unsigned int id; std::vector moves; - void reset() { ++id; moves = std::vector(); } + void reset() { moves = std::vector(); } }; private: @@ -134,6 +134,7 @@ namespace Slic3r { CpColor m_cp_color; Result m_result; + static unsigned int s_result_id; public: GCodeProcessor() { reset(); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 6b60517f61..31cd28b0d2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -55,9 +55,7 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (1 && ENABLE_GCODE_VIEWER) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3709aaa888..31065d230d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -5,6 +5,9 @@ #include "libslic3r/Print.hpp" #include "libslic3r/Geometry.hpp" #include "GUI_App.hpp" +#if ENABLE_GCODE_VIEWER_AS_STATE +#include "MainFrame.hpp" +#endif // ENABLE_GCODE_VIEWER_AS_STATE #include "Plater.hpp" #include "PresetBundle.hpp" #include "Camera.hpp" @@ -443,13 +446,19 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) return; // vertex data / bounding box -> extract from result - std::vector vertices_data(m_vertices.vertices_count * 3); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - if (move.type == GCodeProcessor::EMoveType::Extrude) - m_bounding_box.merge(move.position.cast()); - ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); - } + std::vector vertices_data(m_vertices.vertices_count * 3); + for (size_t i = 0; i < m_vertices.vertices_count; ++i) { + const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; +#if ENABLE_GCODE_VIEWER_AS_STATE + if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { +#endif // ENABLE_GCODE_VIEWER_AS_STATE + if (move.type == GCodeProcessor::EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) + m_bounding_box.merge(move.position.cast()); +#if ENABLE_GCODE_VIEWER_AS_STATE + } +#endif // ENABLE_GCODE_VIEWER_AS_STATE + ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); + } m_bounding_box.merge(m_bounding_box.max + m_sequential_view.marker.get_bounding_box().max[2] * Vec3d::UnitZ()); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 810a99f211..c8b7d3231c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -127,9 +127,7 @@ class GLCanvas3D static const double DefaultCameraZoomToBoxMarginFactor; public: -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ struct GCodePreviewVolumeIndex { enum EType @@ -156,9 +154,7 @@ public: void reset() { first_volumes.clear(); } }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ private: class LayersEditing @@ -514,13 +510,9 @@ private: bool m_reload_delayed; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GCodePreviewVolumeIndex m_gcode_preview_volume_index; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_RENDER_PICKING_PASS bool m_show_picking_texture; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index ad26582986..d7f019e41b 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -760,7 +760,6 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const dialog.GetPaths(input_files); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const { @@ -774,7 +773,6 @@ void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const input_file = dialog.GetPath(); } #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool GUI_App::switch_language() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 11f3a7c3cc..ad874bd1ef 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -156,11 +156,9 @@ public: void keyboard_shortcuts(); void load_project(wxWindow *parent, wxString& input_file) const; void import_model(wxWindow *parent, wxArrayString& input_files) const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void load_gcode(wxWindow* parent, wxString& input_file) const; #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static bool catch_error(std::function cb, const std::string& err); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6b3866ca47..51e8e81876 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -13,11 +13,9 @@ #include "PresetBundle.hpp" #include "DoubleSlider.hpp" #include "Plater.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE #include "MainFrame.hpp" #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include #include @@ -1193,13 +1191,11 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent & event) void Preview::load_print_as_fff(bool keep_z_range) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe == nullptr) // avoid proessing while mainframe is being constructed return; #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_loaded || m_process->current_printer_technology() != ptFFF) return; @@ -1224,23 +1220,17 @@ void Preview::load_print_as_fff(bool keep_z_range) } } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer && !has_layers) #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (! has_layers) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { #if ENABLE_GCODE_VIEWER hide_layers_slider(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GetSizer()->Hide(m_bottom_toolbar_panel); GetSizer()->Layout(); Refresh(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else reset_sliders(true); m_canvas->reset_legend_texture(); @@ -1270,15 +1260,11 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool gcode_preview_data_valid = print->is_step_done(psGCodeExport); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #else bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty(); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 89be99e06c..f09f611e6e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -81,7 +81,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // initialize tabpanel and menubar init_tabpanel(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE init_editor_menubar(); init_gcodeviewer_menubar(); @@ -99,11 +98,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SetAcceleratorTable(accel); #endif // _WIN32 #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ init_menubar(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // set default tooltip timer in msec // SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values @@ -248,7 +244,6 @@ void MainFrame::shutdown() } #endif // _WIN32 -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE if (m_plater != nullptr) { m_plater->stop_jobs(); @@ -263,7 +258,6 @@ void MainFrame::shutdown() m_plater->reset_canvas_volumes(); } #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_plater) m_plater->stop_jobs(); @@ -275,9 +269,7 @@ void MainFrame::shutdown() // Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours // see: https://github.com/prusa3d/PrusaSlicer/issues/3964 if (m_plater) m_plater->reset_canvas_volumes(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Weird things happen as the Paint messages are floating around the windows being destructed. // Avoid the Paint messages by hiding the main window. @@ -288,9 +280,8 @@ void MainFrame::shutdown() if (m_settings_dialog) m_settings_dialog->Destroy(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_plater != nullptr) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // restore sidebar if it was hidden when switching to gcode viewer mode if (m_restore_from_gcode_viewer.collapsed_sidebar) m_plater->collapse_sidebar(false); // Stop the background thread (Windows and Linux). @@ -298,9 +289,7 @@ void MainFrame::shutdown() m_plater->get_mouse3d_controller().shutdown(); // Store the device parameter database back to appconfig. m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater. wxGetApp().removable_drive_manager()->shutdown(); @@ -642,7 +631,6 @@ void MainFrame::on_sys_color_changed() msw_rescale_menu(menu_bar->GetMenu(id)); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE #ifdef _MSC_VER // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, @@ -716,11 +704,8 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, void MainFrame::init_editor_menubar() #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void MainFrame::init_menubar() -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { #ifdef __APPLE__ wxMenuBar::SetAutoWindowMenu(false); @@ -880,21 +865,17 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() {return true; }, this); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { set_mode(EMode::GCodeViewer); }); #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #ifdef _MSC_VER // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, // as the simple numeric accelerators spoil all numeric data entry. @@ -904,9 +885,7 @@ void MainFrame::init_menubar() wxString sep = " - "; wxString sep_space = ""; #endif -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Edit menu wxMenu* editMenu = nullptr; @@ -990,9 +969,7 @@ void MainFrame::init_menubar() [this](){return can_change_view(); }, this); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if _WIN32 // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad wxAcceleratorEntry entries[6]; @@ -1005,9 +982,7 @@ void MainFrame::init_menubar() wxAcceleratorTable accel(6, entries); SetAcceleratorTable(accel); #endif // _WIN32 -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), @@ -1019,11 +994,9 @@ void MainFrame::init_menubar() wxMenu* viewMenu = nullptr; if (m_plater) { viewMenu = new wxMenu(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this)); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // The camera control accelerators are captured by GLCanvas3D::on_char(). append_menu_item(viewMenu, wxID_ANY, _L("Iso") + sep + "&0", _L("Iso View"), [this](wxCommandEvent&) { select_view("iso"); }, "", nullptr, [this](){return can_change_view(); }, this); @@ -1042,9 +1015,7 @@ void MainFrame::init_menubar() "", nullptr, [this](){return can_change_view(); }, this); append_menu_item(viewMenu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [this](wxCommandEvent&) { select_view("right"); }, "", nullptr, [this](){return can_change_view(); }, this); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ viewMenu->AppendSeparator(); #if ENABLE_SLOPE_RENDERING wxMenu* options_menu = new wxMenu(); @@ -1066,11 +1037,9 @@ void MainFrame::init_menubar() } // Help menu -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE auto helpMenu = generate_help_menu(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto helpMenu = new wxMenu(); { append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"), @@ -1105,14 +1074,11 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); }); #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // menubar // assign menubar to frame after appending items, otherwise special items // will not be handled correctly -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE m_editor_menubar = new wxMenuBar(); m_editor_menubar->Append(fileMenu, _L("&File")); @@ -1124,7 +1090,6 @@ void MainFrame::init_menubar() m_editor_menubar->Append(helpMenu, _L("&Help")); SetMenuBar(m_editor_menubar); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto menubar = new wxMenuBar(); menubar->Append(fileMenu, _(L("&File"))); if (editMenu) menubar->Append(editMenu, _(L("&Edit"))); @@ -1134,9 +1099,7 @@ void MainFrame::init_menubar() wxGetApp().add_config_menu(menubar); menubar->Append(helpMenu, _(L("&Help"))); SetMenuBar(menubar); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #ifdef __APPLE__ // This fixes a bug on Mac OS where the quit command doesn't emit window close events @@ -1150,18 +1113,13 @@ void MainFrame::init_menubar() #endif if (plater()->printer_technology() == ptSLA) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE update_editor_menubar(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_menubar(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void MainFrame::init_gcodeviewer_menubar() { @@ -1204,6 +1162,7 @@ void MainFrame::set_mode(EMode mode) case EMode::Editor: { m_plater->reset(); +// m_plater->reset_last_loaded_gcode(); // switch view m_plater->select_view_3D("3D"); @@ -1229,6 +1188,7 @@ void MainFrame::set_mode(EMode mode) case EMode::GCodeViewer: { m_plater->reset(); + m_plater->reset_last_loaded_gcode(); // reinitialize undo/redo stack m_plater->clear_undo_redo_stack_main(); @@ -1258,17 +1218,12 @@ void MainFrame::set_mode(EMode mode) } } #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void MainFrame::update_editor_menubar() #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void MainFrame::update_menubar() -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { const bool is_fff = plater()->printer_technology() == ptFFF; diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 8a68fa36fd..c51567dd31 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -68,7 +68,6 @@ class MainFrame : public DPIFrame wxString m_qs_last_input_file = wxEmptyString; wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE wxMenuBar* m_editor_menubar{ nullptr }; wxMenuBar* m_gcodeviewer_menubar{ nullptr }; @@ -81,7 +80,6 @@ class MainFrame : public DPIFrame RestoreFromGCodeViewer m_restore_from_gcode_viewer; #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if 0 wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now @@ -135,7 +133,6 @@ class MainFrame : public DPIFrame slDlg, } m_layout; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE public: enum class EMode : unsigned char @@ -147,7 +144,6 @@ public: private: EMode m_mode{ EMode::Editor }; #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -167,7 +163,6 @@ public: void init_tabpanel(); void create_preset_tabs(); void add_created_tab(Tab* panel); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void init_editor_menubar(); void update_editor_menubar(); @@ -176,12 +171,9 @@ public: EMode get_mode() const { return m_mode; } void set_mode(EMode mode); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void init_menubar(); void update_menubar(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void update_ui_from_settings(); bool is_loaded() const { return m_loaded; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cb306caba1..7e9779f17e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -33,17 +33,13 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE #include "libslic3r/GCode/GCodeProcessor.hpp" #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" #endif // !ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" @@ -2735,11 +2731,9 @@ void Plater::priv::reset() model.custom_gcode_per_print_z.gcodes.clear(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE gcode_result.reset(); #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } void Plater::priv::mirror(Axis axis) @@ -4550,7 +4544,6 @@ void Plater::extract_config_from_project() load_files(input_paths, false, true); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void Plater::load_gcode() { @@ -4563,25 +4556,27 @@ void Plater::load_gcode() void Plater::load_gcode(const wxString& filename) { - if (filename.empty()) + if (filename.empty() || m_last_loaded_gcode == filename) return; -// p->gcode_result.reset(); + m_last_loaded_gcode = filename; + + // cleanup view before to start loading/processing + p->gcode_result.reset(); + p->preview->reload_print(false); + p->get_current_canvas3D()->render(); wxBusyCursor wait; - wxBusyInfo info(_L("Processing GCode") + "...", get_current_canvas3D()->get_wxglcanvas()); +// wxBusyInfo info(_L("Processing GCode") + "...", get_current_canvas3D()->get_wxglcanvas()); GCodeProcessor processor; // processor.apply_config(config); processor.process_file(filename.ToUTF8().data()); p->gcode_result = std::move(processor.extract_result()); -// select_view_3D("Preview"); - p->preview->load_print(false); -// p->preview->load_gcode_preview(); + p->preview->reload_print(false); } #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); } @@ -5513,16 +5508,12 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); - if (wxGetApp().mainframe) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (wxGetApp().mainframe != nullptr) #if ENABLE_GCODE_VIEWER_AS_STATE wxGetApp().mainframe->update_editor_menubar(); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().mainframe->update_menubar(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ p->update_main_toolbar_tooltips(); @@ -5674,28 +5665,24 @@ bool Plater::init_view_toolbar() return p->init_view_toolbar(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void Plater::enable_view_toolbar(bool enable) { p->view_toolbar.set_enabled(enable); } #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool Plater::init_collapse_toolbar() { return p->init_collapse_toolbar(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void Plater::enable_collapse_toolbar(bool enable) { p->collapse_toolbar.set_enabled(enable); } #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const Camera& Plater::get_camera() const { @@ -5819,11 +5806,9 @@ bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 92b46c9373..fdead2a1cc 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -173,12 +173,10 @@ public: void add_model(bool imperial_units = false); void import_sl1_archive(); void extract_config_from_project(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void load_gcode(); void load_gcode(const wxString& filename); #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); // To be called when providing a list of files to the GUI slic3r on command line. @@ -258,11 +256,9 @@ public: bool search_string_getter(int idx, const char** label, const char** tooltip); // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void clear_undo_redo_stack_main(); #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. void enter_gizmos_stack(); void leave_gizmos_stack(); @@ -326,17 +322,13 @@ public: void sys_color_changed(); bool init_view_toolbar(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void enable_view_toolbar(bool enable); #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool init_collapse_toolbar(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_AS_STATE void enable_collapse_toolbar(bool enable); #endif // ENABLE_GCODE_VIEWER_AS_STATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const Camera& get_camera() const; Camera& get_camera(); @@ -360,6 +352,10 @@ public: void update_preview_moves_slider(); #endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_AS_STATE + void reset_last_loaded_gcode() { m_last_loaded_gcode = ""; } +#endif // ENABLE_GCODE_VIEWER_AS_STATE + const Mouse3DController& get_mouse3d_controller() const; Mouse3DController& get_mouse3d_controller(); @@ -414,6 +410,10 @@ private: bool m_tracking_popup_menu = false; wxString m_tracking_popup_menu_error_message; +#if ENABLE_GCODE_VIEWER_AS_STATE + wxString m_last_loaded_gcode; +#endif // ENABLE_GCODE_VIEWER_AS_STATE + void suppress_snapshots(); void allow_snapshots(); From 70478f226f739f08dba2fac59e44b0ddd9e009e5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 8 Jun 2020 12:27:32 +0200 Subject: [PATCH 122/826] ENABLE_GCODE_VIEWER_AS_STATE -> Use default printbed in gcode viewer --- src/slic3r/GUI/GCodeViewer.cpp | 17 +++++++++++++++- src/slic3r/GUI/GLCanvas3D.cpp | 37 +++++++++++++++++++++++++--------- src/slic3r/GUI/GLCanvas3D.hpp | 3 +++ src/slic3r/GUI/MainFrame.cpp | 11 +++++++++- src/slic3r/GUI/Plater.cpp | 37 ++++++++++++++++++---------------- src/slic3r/GUI/Plater.hpp | 3 +++ 6 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 31065d230d..281ccfa67d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -251,6 +251,19 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& load_toolpaths(gcode_result); load_shells(print, initialized); + +#if ENABLE_GCODE_VIEWER_AS_STATE + // adjust printbed size + const double margin = 10.0; + Vec2d min(m_bounding_box.min(0) - margin, m_bounding_box.min(1) - margin); + Vec2d max(m_bounding_box.max(0) + margin, m_bounding_box.max(1) + margin); + Pointfs bed_shape = { + { min(0), min(1) }, + { max(0), min(1) }, + { max(0), max(1) }, + { min(0), max(1) } }; + wxGetApp().plater()->set_bed_shape(bed_shape, "", ""); +#endif // ENABLE_GCODE_VIEWER_AS_STATE } void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) @@ -450,7 +463,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (size_t i = 0; i < m_vertices.vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; #if ENABLE_GCODE_VIEWER_AS_STATE - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { + if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) + m_bounding_box.merge(move.position.cast()); + else { #endif // ENABLE_GCODE_VIEWER_AS_STATE if (move.type == GCodeProcessor::EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) m_bounding_box.merge(move.position.cast()); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 304bc9fe7c..29d3b9aa57 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1930,6 +1930,13 @@ void GLCanvas3D::zoom_to_selection() _zoom_to_box(m_selection.get_bounding_box()); } +#if ENABLE_GCODE_VIEWER_AS_STATE +void GLCanvas3D::zoom_to_gcode() +{ + _zoom_to_box(m_gcode_viewer.get_bounding_box(), 1.05); +} +#endif // ENABLE_GCODE_VIEWER_AS_STATE + void GLCanvas3D::select_view(const std::string& direction) { wxGetApp().plater()->get_camera().select_view(direction); @@ -2706,7 +2713,10 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); - _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); +#if ENABLE_GCODE_VIEWER_AS_STATE + if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) +#endif // ENABLE_GCODE_VIEWER_AS_STATE + _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); } void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) @@ -5351,8 +5361,7 @@ static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) BoundingBoxf3 ret; const ConfigOptionPoints* opt = dynamic_cast(config.option("bed_shape")); - if (opt != nullptr) - { + if (opt != nullptr) { BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); ret = BoundingBoxf3(Vec3d(unscale(bed_box_2D.min(0)) - tolerance_x, unscale(bed_box_2D.min(1)) - tolerance_y, 0.0), Vec3d(unscale(bed_box_2D.max(0)) + tolerance_x, unscale(bed_box_2D.max(1)) + tolerance_y, config.opt_float("max_print_height"))); // Allow the objects to protrude below the print bed @@ -5365,14 +5374,22 @@ static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) void GLCanvas3D::_render_background() const { #if ENABLE_GCODE_VIEWER - bool use_error_color = m_dynamic_background_enabled; - if (!m_volumes.empty()) - use_error_color &= _is_any_volume_outside(); - else - { - BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; +#if ENABLE_GCODE_VIEWER_AS_STATE + bool use_error_color = false; + if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { + use_error_color = m_dynamic_background_enabled; +#else + bool use_error_color = m_dynamic_background_enabled; +#endif // ENABLE_GCODE_VIEWER_AS_STATE + if (!m_volumes.empty()) + use_error_color &= _is_any_volume_outside(); + else { + BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); + use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; + } +#if ENABLE_GCODE_VIEWER_AS_STATE } +#endif // ENABLE_GCODE_VIEWER_AS_STATE #endif // ENABLE_GCODE_VIEWER glsafe(::glPushMatrix()); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index c8b7d3231c..782a9425d7 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -624,6 +624,9 @@ public: void zoom_to_bed(); void zoom_to_volumes(); void zoom_to_selection(); +#if ENABLE_GCODE_VIEWER_AS_STATE + void zoom_to_gcode(); +#endif // ENABLE_GCODE_VIEWER_AS_STATE void select_view(const std::string& direction); void update_volumes_colors_by_extruder(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f09f611e6e..f9e757251f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1162,10 +1162,14 @@ void MainFrame::set_mode(EMode mode) case EMode::Editor: { m_plater->reset(); -// m_plater->reset_last_loaded_gcode(); // switch view m_plater->select_view_3D("3D"); + m_plater->select_view("iso"); + + // switch printbed + m_plater->set_bed_shape(); + // switch menubar SetMenuBar(m_editor_menubar); @@ -1196,6 +1200,11 @@ void MainFrame::set_mode(EMode mode) // switch view m_plater->select_view_3D("Preview"); + m_plater->select_view("iso"); + + // switch printbed + m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", ""); + // switch menubar SetMenuBar(m_gcodeviewer_menubar); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7e9779f17e..e5bbf73c61 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2014,21 +2014,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); - view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) - { - set_bed_shape(config->option("bed_shape")->values, - config->option("bed_custom_texture")->value, - config->option("bed_custom_model")->value); - }); + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); }); // Preview events: preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) - { - set_bed_shape(config->option("bed_shape")->values, - config->option("bed_custom_texture")->value, - config->option("bed_custom_model")->value); - }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); #if ENABLE_GCODE_VIEWER preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, [this](wxKeyEvent& evt) { preview->move_layers_slider(evt); }); @@ -4567,14 +4557,16 @@ void Plater::load_gcode(const wxString& filename) p->get_current_canvas3D()->render(); wxBusyCursor wait; -// wxBusyInfo info(_L("Processing GCode") + "...", get_current_canvas3D()->get_wxglcanvas()); + // process gcode GCodeProcessor processor; // processor.apply_config(config); processor.process_file(filename.ToUTF8().data()); p->gcode_result = std::move(processor.extract_result()); + // show results p->preview->reload_print(false); + p->preview->get_canvas3d()->zoom_to_gcode(); } #endif // ENABLE_GCODE_VIEWER_AS_STATE @@ -5318,9 +5310,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } if (bed_shape_changed) - p->set_bed_shape(p->config->option("bed_shape")->values, - p->config->option("bed_custom_texture")->value, - p->config->option("bed_custom_model")->value); + set_bed_shape(); if (update_scheduled) update(); @@ -5331,11 +5321,24 @@ void Plater::on_config_change(const DynamicPrintConfig &config) void Plater::set_bed_shape() const { - p->set_bed_shape(p->config->option("bed_shape")->values, +#if ENABLE_GCODE_VIEWER_AS_STATE + set_bed_shape(p->config->option("bed_shape")->values, + p->config->option("bed_custom_texture")->value, + p->config->option("bed_custom_model")->value); +#else + p->set_bed_shape(p->config->option("bed_shape")->values, p->config->option("bed_custom_texture")->value, p->config->option("bed_custom_model")->value); +#endif // ENABLE_GCODE_VIEWER_AS_STATE } +#if ENABLE_GCODE_VIEWER_AS_STATE +void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) const +{ + p->set_bed_shape(shape, custom_texture, custom_model); +} +#endif // ENABLE_GCODE_VIEWER_AS_STATE + void Plater::force_filament_colors_update() { bool update_scheduled = false; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index fdead2a1cc..9759a7ffc3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -360,6 +360,9 @@ public: Mouse3DController& get_mouse3d_controller(); void set_bed_shape() const; +#if ENABLE_GCODE_VIEWER_AS_STATE + void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) const; +#endif // ENABLE_GCODE_VIEWER_AS_STATE // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. class SuppressSnapshots From ea0e9a5873aecdc9799d39bc276e034b38126ff1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 8 Jun 2020 13:17:07 +0200 Subject: [PATCH 123/826] Follow-up of 70478f226f739f08dba2fac59e44b0ddd9e009e5 -> Fixed printbed for regular gcode preview --- src/slic3r/GUI/GCodeViewer.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 281ccfa67d..61d432b9a8 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -253,16 +253,18 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& load_shells(print, initialized); #if ENABLE_GCODE_VIEWER_AS_STATE - // adjust printbed size - const double margin = 10.0; - Vec2d min(m_bounding_box.min(0) - margin, m_bounding_box.min(1) - margin); - Vec2d max(m_bounding_box.max(0) + margin, m_bounding_box.max(1) + margin); - Pointfs bed_shape = { - { min(0), min(1) }, - { max(0), min(1) }, - { max(0), max(1) }, - { min(0), max(1) } }; - wxGetApp().plater()->set_bed_shape(bed_shape, "", ""); + if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { + // adjust printbed size + const double margin = 10.0; + Vec2d min(m_bounding_box.min(0) - margin, m_bounding_box.min(1) - margin); + Vec2d max(m_bounding_box.max(0) + margin, m_bounding_box.max(1) + margin); + Pointfs bed_shape = { + { min(0), min(1) }, + { max(0), min(1) }, + { max(0), max(1) }, + { min(0), max(1) } }; + wxGetApp().plater()->set_bed_shape(bed_shape, "", ""); + } #endif // ENABLE_GCODE_VIEWER_AS_STATE } From 9f94f89808d9651438f51cf45beffb02eaea0f9a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 8 Jun 2020 14:37:40 +0200 Subject: [PATCH 124/826] ENABLE_GCODE_VIEWER_AS_STATE -> Smoother transition between states --- src/slic3r/GUI/MainFrame.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f9e757251f..efc3efa8a3 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1163,6 +1163,8 @@ void MainFrame::set_mode(EMode mode) { m_plater->reset(); + m_plater->Freeze(); + // switch view m_plater->select_view_3D("3D"); m_plater->select_view("iso"); @@ -1187,6 +1189,8 @@ void MainFrame::set_mode(EMode mode) m_restore_from_gcode_viewer.collapsed_sidebar = false; } + m_plater->Thaw(); + break; } case EMode::GCodeViewer: @@ -1194,6 +1198,8 @@ void MainFrame::set_mode(EMode mode) m_plater->reset(); m_plater->reset_last_loaded_gcode(); + m_plater->Freeze(); + // reinitialize undo/redo stack m_plater->clear_undo_redo_stack_main(); m_plater->take_snapshot(_L("New Project")); @@ -1222,6 +1228,8 @@ void MainFrame::set_mode(EMode mode) m_restore_from_gcode_viewer.collapsed_sidebar = true; } + m_plater->Thaw(); + break; } } From d358fe85fa03f6bdc807c14978d592bb066682da Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 9 Jun 2020 08:12:51 +0200 Subject: [PATCH 125/826] GCodeViewer -> Show tool marker position when enabled --- src/slic3r/GUI/GCodeViewer.cpp | 38 +++++++++++++++++++++++++++++++--- src/slic3r/GUI/GCodeViewer.hpp | 3 ++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 61d432b9a8..0042340992 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -146,8 +146,9 @@ void GCodeViewer::SequentialView::Marker::init() } void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) -{ - m_world_transform = (Geometry::assemble_transform(position.cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size()[2] * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); +{ + m_world_position = position; + m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size()[2] * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); m_world_bounding_box = m_model.get_bounding_box().transformed(m_world_transform.cast()); } @@ -176,6 +177,37 @@ void GCodeViewer::SequentialView::Marker::render() const shader->stop_using(); glsafe(::glDisable(GL_BLEND)); + + static float last_window_width = 0.0f; + static size_t last_text_length = 0; + static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.25f); + imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(_u8L("Tool position") + ":"); + ImGui::PopStyleColor(); + ImGui::SameLine(); + char buf[1024]; + sprintf(buf, "X: %.2f, Y: %.2f, Z: %.2f", m_world_position(0), m_world_position(1), m_world_position(2)); + imgui.text(std::string(buf)); + + // force extra frame to automatically update window size + float width = ImGui::GetWindowWidth(); + size_t length = strlen(buf); + if (width != last_window_width || length != last_text_length) { + last_window_width = width; + last_text_length = length; + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + + imgui.end(); + ImGui::PopStyleVar(); } const std::vector GCodeViewer::Extrusion_Role_Colors {{ @@ -353,7 +385,7 @@ void GCodeViewer::render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); - m_sequential_view.marker.set_world_position(m_sequential_view.current_position + m_sequential_view_marker_z_offset * Vec3f::UnitZ()); + m_sequential_view.marker.set_world_position(m_sequential_view.current_position); m_sequential_view.marker.render(); render_shells(); render_legend(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 6f24eef435..413f4ef4ad 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -218,8 +218,10 @@ public: class Marker { GLModel m_model; + Vec3f m_world_position; Transform3f m_world_transform; BoundingBoxf3 m_world_bounding_box; + float m_z_offset{ 0.5f }; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; bool m_visible{ false }; @@ -274,7 +276,6 @@ private: std::vector m_extruder_ids; mutable Extrusions m_extrusions; mutable SequentialView m_sequential_view; - float m_sequential_view_marker_z_offset{ 0.5f }; Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; From 345c01c54ff1ef85fbb98a09864aaa124d8fa0a2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 9 Jun 2020 08:37:24 +0200 Subject: [PATCH 126/826] ENABLE_GCODE_VIEWER -> Updated keyboard shortcuts dialog --- src/slic3r/GUI/KBShortcutsDialog.cpp | 30 ++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 556b610e91..51ba06ba45 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -1,3 +1,4 @@ +#include "libslic3r/libslic3r.h" #include "KBShortcutsDialog.hpp" #include "I18N.hpp" #include "libslic3r/Utils.hpp" @@ -29,7 +30,7 @@ namespace Slic3r { namespace GUI { KBShortcutsDialog::KBShortcutsDialog() - : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")), + : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Keyboard Shortcuts"), #if ENABLE_SCROLLABLE wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) #else @@ -146,7 +147,7 @@ void KBShortcutsDialog::fill_shortcuts() { "?", L("Show keyboard shortcuts list") } }; - m_full_shortcuts.push_back(std::make_pair(_(L("Commands")), commands_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Commands"), commands_shortcuts)); Shortcuts plater_shortcuts = { { "A", L("Arrange") }, @@ -186,7 +187,7 @@ void KBShortcutsDialog::fill_shortcuts() #endif // ENABLE_RENDER_PICKING_PASS }; - m_full_shortcuts.push_back(std::make_pair(_(L("Plater")), plater_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Plater"), plater_shortcuts)); Shortcuts gizmos_shortcuts = { { "Shift+", L("Press to snap by 5% in Gizmo scale\nor to snap by 1mm in Gizmo move") }, @@ -195,7 +196,7 @@ void KBShortcutsDialog::fill_shortcuts() { alt, L("Press to scale (in Gizmo scale) or rotate (in Gizmo rotate)\nselected objects around their own center") }, }; - m_full_shortcuts.push_back(std::make_pair(_(L("Gizmos")), gizmos_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Gizmos"), gizmos_shortcuts)); Shortcuts preview_shortcuts = { { L("Arrow Up"), L("Upper Layer") }, @@ -205,7 +206,7 @@ void KBShortcutsDialog::fill_shortcuts() { "L", L("Show/Hide Legend") } }; - m_full_shortcuts.push_back(std::make_pair(_(L("Preview")), preview_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Preview"), preview_shortcuts)); Shortcuts layers_slider_shortcuts = { { L("Arrow Up"), L("Move current slider thumb Up") }, @@ -213,10 +214,23 @@ void KBShortcutsDialog::fill_shortcuts() { L("Arrow Left"), L("Set upper thumb to current slider thumb") }, { L("Arrow Right"), L("Set lower thumb to current slider thumb") }, { "+", L("Add color change marker for current layer") }, - { "-", L("Delete color change marker for current layer") } + { "-", L("Delete color change marker for current layer") }, + { "Shift+", L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") }, + { ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") }, }; - m_full_shortcuts.push_back(std::make_pair(_(L("Layers Slider")), layers_slider_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Layers Slider"), layers_slider_shortcuts)); + +#if ENABLE_GCODE_VIEWER + Shortcuts sequential_slider_shortcuts = { + { L("Arrow Left"), L("Move current slider thumb Left") }, + { L("Arrow Right"), L("Move current slider thumb Right") }, + { "Shift+", L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") }, + { ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") }, + }; + + m_full_shortcuts.push_back(std::make_pair(_L("Sequential Slider"), sequential_slider_shortcuts)); +#endif // ENABLE_GCODE_VIEWER } wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_font) @@ -239,7 +253,7 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); // text - wxStaticText* text = new wxStaticText(panel, wxID_ANY, _(L("Keyboard shortcuts"))); + wxStaticText* text = new wxStaticText(panel, wxID_ANY, _L("Keyboard shortcuts")); text->SetFont(header_font); sizer->Add(text, 0, wxALIGN_CENTER_VERTICAL); From 48cc358b7271b70f6c5d1ca2aa15ba42ab37eac8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 9 Jun 2020 11:44:25 +0200 Subject: [PATCH 127/826] Fixed build on Mac --- src/slic3r/GUI/MainFrame.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index efc3efa8a3..ba4568bbce 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1104,7 +1104,11 @@ void MainFrame::init_menubar() #ifdef __APPLE__ // This fixes a bug on Mac OS where the quit command doesn't emit window close events // wx bug: https://trac.wxwidgets.org/ticket/18328 +#if ENABLE_GCODE_VIEWER_AS_STATE + wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu(); +#else wxMenu *apple_menu = menubar->OSXGetAppleMenu(); +#endif // ENABLE_GCODE_VIEWER_AS_STATE if (apple_menu != nullptr) { apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(); From 4c51a258ef59cab6ce344b52badef40082522b58 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 9 Jun 2020 12:44:22 +0200 Subject: [PATCH 128/826] GCodeViewer -> Fixed bottom panel not disappearing when switching to gcode viewer from preview --- src/slic3r/GUI/Plater.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e5bbf73c61..0420c1d031 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2708,6 +2708,10 @@ void Plater::priv::reset() if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); +#if ENABLE_GCODE_VIEWER_AS_STATE + gcode_result.reset(); +#endif // ENABLE_GCODE_VIEWER_AS_STATE + // Stop and reset the Print content. this->background_process.reset(); model.clear_objects(); @@ -2720,10 +2724,6 @@ void Plater::priv::reset() this->sidebar->show_sliced_info_sizer(false); model.custom_gcode_per_print_z.gcodes.clear(); - -#if ENABLE_GCODE_VIEWER_AS_STATE - gcode_result.reset(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE } void Plater::priv::mirror(Axis axis) From aa14b4263841d6e3276ad64c31518190d875bfad Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 12 Jun 2020 09:01:20 +0200 Subject: [PATCH 129/826] GCodeProcessor -> Added processing of gcode lines G0 --- resources/shaders/options_120_solid.fs | 1 - src/libslic3r/GCode/GCodeProcessor.cpp | 6 ++++++ src/libslic3r/GCode/GCodeProcessor.hpp | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs index 912b809e07..4719ff96a6 100644 --- a/resources/shaders/options_120_solid.fs +++ b/resources/shaders/options_120_solid.fs @@ -66,7 +66,6 @@ vec4 on_sphere_color(vec3 eye_on_sphere_position) intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; return vec4(intensity.y + uniform_color.rgb * intensity.x, 1.0); -// return vec4(vec3(intensity.y) + uniform_color.rgb * intensity.x, 1.0); } float fragment_depth(vec3 eye_pos) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c74820a04c..d561647262 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -110,6 +110,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { switch (::atoi(&cmd[1])) { + case 0: { process_G0(line); break; } // Move case 1: { process_G1(line); break; } // Move case 10: { process_G10(line); break; } // Retract case 11: { process_G11(line); break; } // Unretract @@ -263,6 +264,11 @@ void GCodeProcessor::process_tags(const std::string& comment) } } +void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line) +{ + process_G1(line); +} + void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) { auto absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index e8c43350c0..bc49245848 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -155,6 +155,7 @@ namespace Slic3r { void process_tags(const std::string& comment); // Move + void process_G0(const GCodeReader::GCodeLine& line); void process_G1(const GCodeReader::GCodeLine& line); // Retract From 43e6e4f18c89257d2df547253fda600ed093d324 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 16 Jun 2020 12:57:49 +0200 Subject: [PATCH 130/826] Code refactoring: - PresetCombpBoxes are extracted to the separate file. - All preset icons are moved to the PresetComboBox from Preset and PresetBundle - First "steps" to add physical printers to the printers list on the sidebar. --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 3 - src/slic3r/GUI/MainFrame.cpp | 12 - src/slic3r/GUI/Plater.cpp | 212 ++------ src/slic3r/GUI/Plater.hpp | 41 +- src/slic3r/GUI/Preset.cpp | 410 ++++---------- src/slic3r/GUI/Preset.hpp | 228 ++++++-- src/slic3r/GUI/PresetBundle.cpp | 270 +--------- src/slic3r/GUI/PresetBundle.hpp | 22 - src/slic3r/GUI/PresetComboBoxes.cpp | 794 ++++++++++++++++++++++++++++ src/slic3r/GUI/PresetComboBoxes.hpp | 179 +++++++ src/slic3r/GUI/Tab.cpp | 46 +- src/slic3r/GUI/Tab.hpp | 7 +- src/slic3r/GUI/wxExtensions.cpp | 88 --- src/slic3r/GUI/wxExtensions.hpp | 31 -- 15 files changed, 1304 insertions(+), 1041 deletions(-) create mode 100644 src/slic3r/GUI/PresetComboBoxes.cpp create mode 100644 src/slic3r/GUI/PresetComboBoxes.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 7e02c0fdd7..98389e7dae 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -80,6 +80,8 @@ set(SLIC3R_GUI_SOURCES GUI/MainFrame.cpp GUI/MainFrame.hpp GUI/Plater.cpp + GUI/PresetComboBoxes.hpp + GUI/PresetComboBoxes.cpp GUI/Plater.hpp GUI/GUI_ObjectList.cpp GUI/GUI_ObjectList.hpp diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c11ed66ab8..2f201180a8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -88,9 +88,6 @@ ObjectList::ObjectList(wxWindow* parent) : { // Fill CATEGORY_ICON { - // Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget, - // see note in PresetBundle::load_compatible_bitmaps() - // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ef837c200d..d4ce21fc00 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -74,11 +74,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SLIC3R_VERSION + _(L(" - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/releases"))); - /* Load default preset bitmaps before a tabpanel initialization, - * but after filling of an em_unit value - */ - wxGetApp().preset_bundle->load_default_preset_bitmaps(); - // initialize tabpanel and menubar init_tabpanel(); init_menubar(); @@ -540,11 +535,6 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) wxGetApp().update_fonts(); this->SetFont(this->normal_font()); - /* Load default preset bitmaps before a tabpanel initialization, - * but after filling of an em_unit value - */ - wxGetApp().preset_bundle->load_default_preset_bitmaps(); - // update Plater wxGetApp().plater()->msw_rescale(); @@ -586,8 +576,6 @@ void MainFrame::on_sys_color_changed() // update label colors in respect to the system mode wxGetApp().init_label_colours(); - wxGetApp().preset_bundle->load_default_preset_bitmaps(); - // update Plater wxGetApp().plater()->sys_color_changed(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8c3a90370a..339badc96d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -77,6 +76,7 @@ #include "../Utils/UndoRedo.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" +#include "PresetComboBoxes.hpp" #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -253,153 +253,6 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w info_vec[idx].second->Show(show); } -PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : -PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)), - preset_type(preset_type), - last_selected(wxNOT_FOUND), - m_em_unit(wxGetApp().em_unit()) -{ - SetFont(wxGetApp().normal_font()); -#ifdef _WIN32 - // Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that - // the index of the item inside CBN_EDITCHANGE may no more be valid. - EnableTextChangedEvents(false); -#endif /* _WIN32 */ - Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { - auto selected_item = evt.GetSelection(); - - auto marker = reinterpret_cast(this->GetClientData(selected_item)); - if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { - this->SetSelection(this->last_selected); - evt.StopPropagation(); - if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { - ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; - switch (marker) { - case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; - case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; - case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; - } - wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); - } - } else if ( this->last_selected != selected_item || - wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) { - this->last_selected = selected_item; - evt.SetInt(this->preset_type); - evt.Skip(); - } else { - evt.StopPropagation(); - } - }); - - if (preset_type == Slic3r::Preset::TYPE_FILAMENT) - { - Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - const Preset* selected_preset = preset_bundle->filaments.find_preset(preset_bundle->filament_presets[extruder_idx]); - // Wide icons are shown if the currently selected preset is not compatible with the current printer, - // and red flag is drown in front of the selected preset. - bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible; - float scale = m_em_unit*0.1f; - - int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0; -#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED) - shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image -#endif - int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5); - int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x; - if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) { - // Let the combo box process the mouse click. - event.Skip(); - return; - } - - // Swallow the mouse click and open the color picker. - - // get current color - DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config(); - auto colors = static_cast(cfg->option("extruder_colour")->clone()); - wxColour clr(colors->values[extruder_idx]); - if (!clr.IsOk()) - clr = wxColour(0,0,0); // Don't set alfa to transparence - - auto data = new wxColourData(); - data->SetChooseFull(1); - data->SetColour(clr); - - wxColourDialog dialog(this, data); - dialog.CenterOnParent(); - if (dialog.ShowModal() == wxID_OK) - { - colors->values[extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); - - DynamicPrintConfig cfg_new = *cfg; - cfg_new.set_key_value("extruder_colour", colors); - - wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); - preset_bundle->update_plater_filament_ui(extruder_idx, this); - wxGetApp().plater()->on_config_change(cfg_new); - } - }); - } - - edit_btn = new ScalableButton(parent, wxID_ANY, "cog"); - edit_btn->SetToolTip(_L("Click to edit preset")); - - edit_btn->Bind(wxEVT_BUTTON, ([preset_type, this](wxCommandEvent) - { - Tab* tab = wxGetApp().get_tab(preset_type); - if (!tab) - return; - - int page_id = wxGetApp().tab_panel()->FindPage(tab); - if (page_id == wxNOT_FOUND) - return; - - wxGetApp().tab_panel()->SetSelection(page_id); - - // Switch to Settings NotePad - wxGetApp().mainframe->select_tab(); - - /* In a case of a multi-material printing, for editing another Filament Preset - * it's needed to select this preset for the "Filament settings" Tab - */ - if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) - { - const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); - - // Call select_preset() only if there is new preset and not just modified - if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) - { - const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); - tab->select_preset(/*selected_preset*/preset_name); - } - } - })); -} - -PresetComboBox::~PresetComboBox() -{ - if (edit_btn) - edit_btn->Destroy(); -} - - -void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) -{ - this->SetClientData(item, (void*)label_item_type); -} - -void PresetComboBox::check_selection(int selection) -{ - this->last_selected = selection; -} - -void PresetComboBox::msw_rescale() -{ - m_em_unit = wxGetApp().em_unit(); - edit_btn->msw_rescale(); -} - // Frequently changed parameters class FreqChangedParams : public OG_Settings @@ -697,12 +550,12 @@ struct Sidebar::priv ModeSizer *mode_sizer; wxFlexGridSizer *sizer_presets; - PresetComboBox *combo_print; - std::vector combos_filament; + PlaterPresetComboBox *combo_print; + std::vector combos_filament; wxBoxSizer *sizer_filaments; - PresetComboBox *combo_sla_print; - PresetComboBox *combo_sla_material; - PresetComboBox *combo_printer; + PlaterPresetComboBox *combo_sla_print; + PlaterPresetComboBox *combo_sla_material; + PlaterPresetComboBox *combo_printer; wxBoxSizer *sizer_params; FreqChangedParams *frequently_changed_parameters{ nullptr }; @@ -801,10 +654,10 @@ Sidebar::Sidebar(Plater *parent) p->sizer_filaments = new wxBoxSizer(wxVERTICAL); - auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { + auto init_combo = [this](PlaterPresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :"); text->SetFont(wxGetApp().small_font()); - *combo = new PresetComboBox(p->presets_panel, preset_type); + *combo = new PlaterPresetComboBox(p->presets_panel, preset_type); auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); @@ -941,8 +794,8 @@ Sidebar::Sidebar(Plater *parent) Sidebar::~Sidebar() {} -void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { - *combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); +void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx) { + *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); // # copy icons from first choice // $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; @@ -977,18 +830,18 @@ void Sidebar::update_all_preset_comboboxes() // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) - preset_bundle.prints.update_plater_ui(p->combo_print); + p->combo_print->update(); else { - preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); - preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); + p->combo_sla_print->update(); + p->combo_sla_material->update(); } // Update the printer choosers, update the dirty flags. - preset_bundle.printers.update_plater_ui(p->combo_printer); + p->combo_printer->update(); // Update the filament choosers to only contain the compatible presets, update the color preview, // update the dirty flags. if (print_tech == ptFFF) { - for (size_t i = 0; i < p->combos_filament.size(); ++i) - preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); + for (PlaterPresetComboBox* cb : p->combos_filament) + cb->update(); } } @@ -1010,23 +863,22 @@ void Sidebar::update_presets(Preset::Type preset_type) preset_bundle.set_filament_preset(0, name); } - for (size_t i = 0; i < filament_cnt; i++) { - preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); - } + for (size_t i = 0; i < filament_cnt; i++) + p->combos_filament[i]->update(); break; } case Preset::TYPE_PRINT: - preset_bundle.prints.update_plater_ui(p->combo_print); + p->combo_print->update(); break; case Preset::TYPE_SLA_PRINT: - preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); + p->combo_sla_print->update(); break; case Preset::TYPE_SLA_MATERIAL: - preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); + p->combo_sla_material->update(); break; case Preset::TYPE_PRINTER: @@ -1062,18 +914,14 @@ void Sidebar::msw_rescale() p->mode_sizer->msw_rescale(); - // Rescale preset comboboxes in respect to the current em_unit ... - for (PresetComboBox* combo : std::vector { p->combo_print, + for (PlaterPresetComboBox* combo : std::vector { p->combo_print, p->combo_sla_print, p->combo_sla_material, p->combo_printer } ) combo->msw_rescale(); - for (PresetComboBox* combo : p->combos_filament) + for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); - // ... then refill them and set min size to correct layout of the sidebar - update_all_preset_comboboxes(); - p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); @@ -1096,12 +944,12 @@ void Sidebar::sys_color_changed() { // Update preset comboboxes in respect to the system color ... // combo->msw_rescale() updates icon on button, so use it - for (PresetComboBox* combo : std::vector{ p->combo_print, + for (PlaterPresetComboBox* combo : std::vector{ p->combo_print, p->combo_sla_print, p->combo_sla_material, p->combo_printer }) combo->msw_rescale(); - for (PresetComboBox* combo : p->combos_filament) + for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); // ... then refill them and set min size to correct layout of the sidebar @@ -1458,7 +1306,7 @@ void Sidebar::update_ui_from_settings() update_sliced_info_sizer(); } -std::vector& Sidebar::combos_filament() +std::vector& Sidebar::combos_filament() { return p->combos_filament; } @@ -3339,7 +3187,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) void Plater::priv::on_select_preset(wxCommandEvent &evt) { auto preset_type = static_cast(evt.GetInt()); - auto *combo = static_cast(evt.GetEventObject()); + auto *combo = static_cast(evt.GetEventObject()); // see https://github.com/prusa3d/PrusaSlicer/issues/3889 // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender"), @@ -3368,7 +3216,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { // Only update the plater UI for the 2nd and other filaments. - wxGetApp().preset_bundle->update_plater_filament_ui(idx, combo); + combo->update(); } else { wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); @@ -5154,12 +5002,12 @@ void Plater::on_extruders_change(size_t num_extruders) size_t i = choices.size(); while ( i < num_extruders ) { - PresetComboBox* choice/*{ nullptr }*/; + PlaterPresetComboBox* choice/*{ nullptr }*/; sidebar().init_filament_combo(&choice, i); choices.push_back(choice); // initialize selection - wxGetApp().preset_bundle->update_plater_filament_ui(i, choice); + choice->update(); ++i; } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5d60e006b0..d2acc7632e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -6,14 +6,12 @@ #include #include -#include #include "Preset.hpp" #include "Selection.hpp" #include "libslic3r/BoundingBox.hpp" #include "Jobs/Job.hpp" -#include "wxExtensions.hpp" #include "Search.hpp" class wxButton; @@ -50,46 +48,13 @@ class Mouse3DController; struct Camera; class Bed3D; class GLToolbar; +class PlaterPresetComboBox; using t_optgroups = std::vector >; class Plater; enum class ActionButtonType : int; -class PresetComboBox : public PresetBitmapComboBox -{ -public: - PresetComboBox(wxWindow *parent, Preset::Type preset_type); - ~PresetComboBox(); - - ScalableButton* edit_btn { nullptr }; - - enum LabelItemType { - LABEL_ITEM_MARKER = 0xffffff01, - LABEL_ITEM_WIZARD_PRINTERS, - LABEL_ITEM_WIZARD_FILAMENTS, - LABEL_ITEM_WIZARD_MATERIALS, - - LABEL_ITEM_MAX, - }; - - void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); - void set_extruder_idx(const int extr_idx) { extruder_idx = extr_idx; } - int get_extruder_idx() const { return extruder_idx; } - int em_unit() const { return m_em_unit; } - void check_selection(int selection); - - void msw_rescale(); - -private: - typedef std::size_t Marker; - - Preset::Type preset_type; - int last_selected; - int extruder_idx = -1; - int m_em_unit; -}; - class Sidebar : public wxPanel { ConfigOptionMode m_mode; @@ -101,7 +66,7 @@ public: Sidebar &operator=(const Sidebar &) = delete; ~Sidebar(); - void init_filament_combo(PresetComboBox **combo, const int extr_idx); + void init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx); void remove_unused_filament_combos(const size_t current_extruder_count); void update_all_preset_comboboxes(); void update_presets(Slic3r::Preset::Type preset_type); @@ -139,7 +104,7 @@ public: void update_searcher(); void update_ui_from_settings(); - std::vector& combos_filament(); + std::vector& combos_filament(); Search::OptionsSearcher& get_searcher(); std::string& get_search_line(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index d810c399d5..883dc438ab 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -2,9 +2,7 @@ #include "Preset.hpp" #include "AppConfig.hpp" -#include "BitmapCache.hpp" #include "I18N.hpp" -#include "wxExtensions.hpp" #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN @@ -30,15 +28,9 @@ #include #include -#include -#include -#include -#include - #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "libslic3r/PlaceholderParser.hpp" -#include "Plater.hpp" using boost::property_tree::ptree; @@ -590,10 +582,7 @@ const std::vector& Preset::sla_printer_options() PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : m_type(type), m_edited_preset(type, "", false), - m_idx_selected(0), - m_bitmap_main_frame(new wxBitmap), - m_bitmap_add(new wxBitmap), - m_bitmap_cache(new GUI::BitmapCache) + m_idx_selected(0) { // Insert just the default preset. this->add_default_preset(keys, defaults, default_name); @@ -602,12 +591,6 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectorget_selected_idx() == size_t(-1)) @@ -1119,279 +1092,15 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil // Delete the current preset, activate the first visible preset. //void PresetCollection::delete_current_preset(); -// Update the wxChoice UI component from this list of presets. -// Hide the -void PresetCollection::update_plater_ui(GUI::PresetComboBox *ui) -{ - if (ui == nullptr) - return; - - // Otherwise fill in the list from scratch. - ui->Freeze(); - ui->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected - - const Preset &selected_preset = this->get_selected_preset(); - // Show wide icons if the currently selected preset is not compatible with the current printer, - // and draw a red flag in front of the selected preset. - bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr; - - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const float scale_f = ui->em_unit() * 0.1f; - const int icon_height = 16 * scale_f + 0.5f; - const int icon_width = 16 * scale_f + 0.5f; - const int thin_space_icon_width = 4 * scale_f + 0.5f; - const int wide_space_icon_width = 6 * scale_f + 0.5f; - - std::map nonsys_presets; - wxString selected = ""; - wxString tooltip = ""; - if (!this->m_presets.front().is_visible) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) { - const Preset &preset = this->m_presets[i]; - if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) - continue; - - std::string bitmap_key = ""; - // !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame - wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap; - if (m_type == Preset::TYPE_PRINTER && preset.printer_technology()==ptSLA ) { - bitmap_key = "sla_printer"; - main_bmp = create_scaled_bitmap("sla_printer"); - } - - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(main_bmp); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - - const std::string name = preset.alias.empty() ? preset.name : preset.alias; - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? main_bmp : *bmp); - if (i == m_idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) { - selected_preset_item = ui->GetCount() - 1; - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) { - selected = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - if (i + 1 == m_num_default_presets) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - } - if (!nonsys_presets.empty()) - { - ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - } - } - if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(*m_bitmap_main_frame); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); -// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - if (m_type == Preset::TYPE_SLA_MATERIAL) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS); - else - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove printers")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_PRINTERS); - } - - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - - ui->SetSelection(selected_preset_item); - ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip); - ui->check_selection(selected_preset_item); - ui->Thaw(); - - // Update control min size after rescale (changed Display DPI under MSW) - if (ui->GetMinWidth() != 20 * ui->em_unit()) - ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight())); -} - -size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em/* = 10*/) -{ - if (ui == nullptr) - return 0; - ui->Freeze(); - ui->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected - - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored(empty) icons used for preset - * and scale them in respect to em_unit value - */ - const float scale_f = em * 0.1f; - const int icon_height = 16 * scale_f + 0.5f; - const int icon_width = 16 * scale_f + 0.5f; - - std::map nonsys_presets; - wxString selected = ""; - if (!this->m_presets.front().is_visible) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { - const Preset &preset = this->m_presets[i]; - if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) - continue; - std::string bitmap_key = "tab"; - - // !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame - wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap; - if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) { - bitmap_key = "sla_printer"; - main_bmp = create_scaled_bitmap("sla_printer"); - } - - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; - bmps.emplace_back((tmp_bmp == 0) ? main_bmp : *tmp_bmp); - // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? main_bmp : *bmp); - if (i == m_idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - } - if (i + 1 == m_num_default_presets) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); - } - if (!nonsys_presets.empty()) - { - ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - } - } - if (m_type == Preset::TYPE_PRINTER) { - wxBitmap *bmp = m_bitmap_cache->find("edit_printer_list"); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(*m_bitmap_main_frame); -// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert("add_printer_tab", bmps); - } - ui->Append(PresetCollection::separator("Add a new printer"), *bmp); - } - - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - - ui->SetSelection(selected_preset_item); - ui->SetToolTip(ui->GetString(selected_preset_item)); - ui->Thaw(); - return selected_preset_item; -} - -// Update a dirty floag of the current preset, update the labels of the UI component accordingly. +// Update a dirty flag of the current preset // Return true if the dirty flag changed. -bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) +bool PresetCollection::update_dirty() { - wxWindowUpdateLocker noUpdates(ui); - // 1) Update the dirty flag of the current preset. bool was_dirty = this->get_selected_preset().is_dirty; bool is_dirty = current_is_dirty(); this->get_selected_preset().is_dirty = is_dirty; this->get_edited_preset().is_dirty = is_dirty; - // 2) Update the labels. - for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) { - std::string old_label = ui->GetString(ui_id).utf8_str().data(); - std::string preset_name = Preset::remove_suffix_modified(old_label); - const Preset *preset = this->find_preset(preset_name, false); -// The old_label could be the "----- system presets ------" or the "------- user presets --------" separator. -// assert(preset != nullptr); - if (preset != nullptr) { - std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; - if (old_label != new_label) - ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); - } - } -#ifdef __APPLE__ - // wxWidgets on OSX do not upload the text of the combo box line automatically. - // Force it to update by re-selecting. - ui->SetSelection(ui->GetSelection()); -#endif /* __APPLE __ */ + return was_dirty != is_dirty; } @@ -1605,16 +1314,6 @@ std::string PresetCollection::path_from_name(const std::string &new_name) const return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } -void PresetCollection::clear_bitmap_cache() -{ - m_bitmap_cache->clear(); -} - -wxString PresetCollection::separator(const std::string &label) -{ - return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail()); -} - const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const { const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); @@ -1631,7 +1330,108 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model return it != cend() ? &*it : nullptr; } +/* +PhysicalPrinter& PhysicalPrinterCollection::load_external_printer( + // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) + const std::string& path, + // Name of the profile, derived from the source file name. + const std::string& name, + // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. + const std::string& original_name, + // Config to initialize the preset from. + const DynamicPrintConfig& config, + // Select the preset after loading? + bool select) +{ + // Load the preset over a default preset, so that the missing fields are filled in from the default preset. + DynamicPrintConfig cfg(this->default_printer().config); + cfg.apply_only(config, cfg.keys(), true); + // Is there a preset already loaded with the name stored inside the config? + std::deque::iterator it = this->find_printer_internal(original_name); + bool found = it != m_printers.end() && it->name == original_name; + if (!found) { + // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. + / * + it = this->find_preset_renamed(original_name); + found = it != m_presets.end(); + * / + } + if (found) { + if (profile_print_params_same(it->config, cfg)) { + // The preset exists and it matches the values stored inside config. + if (select) + this->select_printer(it - m_printers.begin()); + return *it; + } + if (profile_host_params_same_or_anonymized(it->config, cfg) == ProfileHostParams::Anonymized) { + // The project being loaded is anonymized. Replace the empty host keys of the loaded profile with the data from the original profile. + // See "Octoprint Settings when Opening a .3MF file" GH issue #3244 + auto opt_update = [it, &cfg](const std::string& opt_key) { + auto opt = it->config.option(opt_key); + if (opt != nullptr) + cfg.set_key_value(opt_key, opt->clone()); + }; + opt_update("print_host"); + opt_update("printhost_apikey"); + opt_update("printhost_cafile"); + } + } + // The external preset does not match an internal preset, load the external preset. + std::string new_name; + for (size_t idx = 0;; ++idx) { + std::string suffix; + if (original_name.empty()) { + if (idx > 0) + suffix = " (" + std::to_string(idx) + ")"; + } + else { + if (idx == 0) + suffix = " (" + original_name + ")"; + else + suffix = " (" + original_name + "-" + std::to_string(idx) + ")"; + } + new_name = name + suffix; + it = this->find_printer_internal(new_name); + if (it == m_printers.end() || it->name != new_name) + // Unique profile name. Insert a new profile. + break; + if (profile_print_params_same(it->config, cfg)) { + // The preset exists and it matches the values stored inside config. + if (select) + this->select_printer(it - m_printers.begin()); + return *it; + } + // Form another profile name. + } + // Insert a new profile. + PhysicalPrinter& printer = this->load_printer(path, new_name, std::move(cfg), select); + return printer; +} + +void PhysicalPrinterCollection::save_printer(const std::string& new_name) +{ + // 1) Find the printer with a new_name or create a new one, + // initialize it with the edited config. + auto it = this->find_printer_internal(new_name); + if (it != m_printers.end() && it->name == new_name) { + // Preset with the same name found. + PhysicalPrinter& printer = *it; + // Overwriting an existing preset. + printer.config = std::move(m_edited_printer.config); + } + else { + // Creating a new printer. + PhysicalPrinter& printer = *m_printers.insert(it, m_edited_printer); + std::string old_name = printer.name; + printer.name = new_name; + } + // 2) Activate the saved preset. + this->select_printer_by_name(new_name, true); + // 3) Store the active preset to disk. + this->get_selected_preset().save(); +} +*/ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) { diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index dc00780918..8a8fa024b6 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -24,11 +24,6 @@ namespace Slic3r { class AppConfig; class PresetBundle; -namespace GUI { - class BitmapCache; - class PresetComboBox; -} - enum ConfigFileType { CONFIG_FILE_TYPE_UNKNOWN, @@ -322,18 +317,6 @@ public: // returns true if the preset was deleted successfully. bool delete_preset(const std::string& name); - // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. - void load_bitmap_default(const std::string &file_name); - - // Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame. - void load_bitmap_add(const std::string &file_name); - - // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. - void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } - void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; } - void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; } - void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; } - // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); bool is_default_suppressed() const { return m_default_suppressed; } @@ -446,18 +429,9 @@ public: // Return a sorted list of system preset names. std::vector system_preset_names() const; - // Update the choice UI from the list of presets. - // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. - // If an incompatible preset is selected, it is shown as well. - size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em = 10); - // Update the choice UI from the list of presets. - // Only the compatible presets are shown. - // If an incompatible preset is selected, it is shown as well. - void update_plater_ui(GUI::PresetComboBox *ui); - - // Update a dirty floag of the current preset, update the labels of the UI component accordingly. + // Update a dirty flag of the current preset // Return true if the dirty flag changed. - bool update_dirty_ui(wxBitmapComboBox *ui); + bool update_dirty(); // Select a profile by its name. Return true if the selection changed. // Without force, the selection is only updated if the index changes. @@ -467,16 +441,7 @@ public: // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; - void clear_bitmap_cache(); - -#ifdef __linux__ - static const char* separator_head() { return "------- "; } - static const char* separator_tail() { return " -------"; } -#else /* __linux__ */ - static const char* separator_head() { return "————— "; } - static const char* separator_tail() { return " —————"; } -#endif /* __linux__ */ - static wxString separator(const std::string &label); + size_t num_default_presets() { return m_num_default_presets; } protected: // Select a preset, if it exists. If it does not exist, select an invalid (-1) index. @@ -547,23 +512,10 @@ private: // Is the "- default -" preset suppressed? bool m_default_suppressed = true; size_t m_num_default_presets = 0; - // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Plater. - // These bitmaps are not owned by PresetCollection, but by a PresetBundle. - const wxBitmap *m_bitmap_compatible = nullptr; - const wxBitmap *m_bitmap_incompatible = nullptr; - const wxBitmap *m_bitmap_lock = nullptr; - const wxBitmap *m_bitmap_lock_open = nullptr; - // Marks placed at the wxBitmapComboBox of a MainFrame. - // These bitmaps are owned by PresetCollection. - wxBitmap *m_bitmap_main_frame; - // "Add printer profile" icon, owned by PresetCollection. - wxBitmap *m_bitmap_add; + // Path to the directory to store the config files into. std::string m_dir_path; - // Caching color bitmaps for the filament combo box. - GUI::BitmapCache *m_bitmap_cache = nullptr; - // to access select_preset_by_name_strict() friend class PresetBundle; }; @@ -585,6 +537,178 @@ namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); } // namespace PresetUtils + +////////////////////////////////////////////////////////////////////// + +class PhysicalPrinter +{ +public: + PhysicalPrinter(const std::string& name) : name(name) {} + + // Name of the Physical Printer, usually derived form the file name. + std::string name; + // File name of the Physical Printer. + std::string file; + // Name of the related Printer preset + std::string preset_name; + + // Has this profile been loaded? + bool loaded = false; + + // Configuration data, loaded from a file, or set from the defaults. + DynamicPrintConfig config; + + void save() { this->config.save(this->file); } + + // Return a printer technology, return ptFFF if the printer technology is not set. + static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) { + auto* opt = cfg.option>("printer_technology"); + // The following assert may trigger when importing some legacy profile, + // but it is safer to keep it here to capture the cases where the "printer_technology" key is queried, where it should not. + return (opt == nullptr) ? ptFFF : opt->value; + } + PrinterTechnology printer_technology() const { return printer_technology(this->config); } + + // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. + bool operator<(const Preset& other) const { return this->name < other.name; } + +protected: + friend class PhysicalPrinterCollection; +}; +/* +// Collections of presets of the same type (one of the Print, Filament or Printer type). +class PhysicalPrinterCollection +{ +public: + // Initialize the PresetCollection with the "- default -" preset. + PhysicalPrinterCollection(const std::vector& keys) : m_idx_selected(0) {} + ~PhysicalPrinterCollection() {} + + typedef std::deque::iterator Iterator; + typedef std::deque::const_iterator ConstIterator; + Iterator begin() { return m_printers.begin(); } + ConstIterator begin() const { return m_printers.cbegin(); } + ConstIterator cbegin() const { return m_printers.cbegin(); } + Iterator end() { return m_printers.end(); } + ConstIterator end() const { return m_printers.cend(); } + ConstIterator cend() const { return m_printers.cend(); } + + void reset(bool delete_files) {}; + + const std::deque& operator()() const { return m_printers; } + + // Load ini files of the particular type from the provided directory path. + void load_printers(const std::string& dir_path, const std::string& subdir){}; + + // Load a preset from an already parsed config file, insert it into the sorted sequence of presets + // and select it, losing previous modifications. + PhysicalPrinter& load_printer(const std::string& path, const std::string& name, const DynamicPrintConfig& config, bool select = true); + PhysicalPrinter& load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select = true); + + PhysicalPrinter& load_external_printer( + // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) + const std::string& path, + // Name of the profile, derived from the source file name. + const std::string& name, + // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. + const std::string& original_name, + // Config to initialize the preset from. + const DynamicPrintConfig& config, + // Select the preset after loading? + bool select = true); + + // Save the printer under a new name. If the name is different from the old one, + // a new printer is stored into the list of printers. + // ? New printer is activated. + void save_printer(const std::string& new_name); + + // Delete the current preset, activate the first visible preset. + // returns true if the preset was deleted successfully. + bool delete_current_printer() {return true;} + // Delete the current preset, activate the first visible preset. + // returns true if the preset was deleted successfully. + bool delete_printer(const std::string& name) { return true; } + + // Select a printer. If an invalid index is provided, the first visible printer is selected. + PhysicalPrinter& select_printer(size_t idx); + // Return the selected preset, without the user modifications applied. + PhysicalPrinter& get_selected_preset() { return m_printers[m_idx_selected]; } + const PhysicalPrinter& get_selected_preset() const { return m_printers[m_idx_selected]; } + size_t get_selected_idx() const { return m_idx_selected; } + // Returns the name of the selected preset, or an empty string if no preset is selected. + std::string get_selected_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_preset().name; } + PhysicalPrinter& get_edited_preset() { return m_edited_printer; } + const PhysicalPrinter& get_edited_preset() const { return m_edited_printer; } + + // Return a preset possibly with modifications. + PhysicalPrinter& default_printer(size_t idx = 0) { return m_printers[idx]; } + const PhysicalPrinter& default_printer(size_t idx = 0) const { return m_printers[idx]; } + + // used to update preset_choice from Tab + const std::deque& get_presets() const { return m_printers; } + size_t get_idx_selected() { return m_idx_selected; } + + // Return a preset by an index. If the preset is active, a temporary copy is returned. + PhysicalPrinter& printer(size_t idx) { return (idx == m_idx_selected) ? m_edited_printer : m_printers[idx]; } + const PhysicalPrinter& printer(size_t idx) const { return const_cast(this)->printer(idx); } + + // Return a preset by its name. If the preset is active, a temporary copy is returned. + // If a preset is not found by its name, null is returned. + PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false); + const PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false) const + { + return const_cast(this)->find_printer(name, first_visible_if_not_found); + } + + // Return number of presets including the "- default -" preset. + size_t size() const { return m_printers.size(); } + + // Select a profile by its name. Return true if the selection changed. + // Without force, the selection is only updated if the index changes. + // With force, the changes are reverted if the new index is the same as the old index. + bool select_printer_by_name(const std::string& name, bool force) {}; + + // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. + std::string path_from_name(const std::string& new_name) const; + +private: +// PhysicalPrinterCollection(); + PhysicalPrinterCollection(const PhysicalPrinterCollection& other); + PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); + + // Find a preset position in the sorted list of presets. + // The "-- default -- " preset is always the first, so it needs + // to be handled differently. + // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. + std::deque::iterator find_printer_internal(const std::string& name) + { + PhysicalPrinter key(name); + auto it = std::lower_bound(m_printers.begin()+0, m_printers.end(), key); + return it; + } + std::deque::const_iterator find_printer_internal(const std::string& name) const + { + return const_cast(this)->find_printer_internal(name); + } + + static std::vector dirty_options(const Preset* edited, const Preset* reference, const bool is_printer_type = false); + + // List of presets, starting with the "- default -" preset. + // Use deque to force the container to allocate an object per each entry, + // so that the addresses of the presets don't change during resizing of the container. + std::deque m_printers; + // Initially this printer contains a copy of the selected printer. Later on, this copy may be modified by the user. + PhysicalPrinter m_edited_printer; + // Selected preset. + size_t m_idx_selected; + + // Path to the directory to store the config files into. + std::string m_dir_path; +}; + +////////////////////////////////////////////////////////////////////// +*/ + } // namespace Slic3r #endif /* slic3r_Preset_hpp_ */ diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index ba806a0b2d..024884b00a 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1,12 +1,10 @@ #include #include "PresetBundle.hpp" -#include "BitmapCache.hpp" #include "Plater.hpp" -#include "I18N.hpp" -#include "wxExtensions.hpp" #include +#include #include #include #include @@ -21,16 +19,13 @@ #include #include -#include #include -#include -#include -#include #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "GUI_App.hpp" +#include "libslic3r/CustomGCode.hpp" // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. @@ -52,12 +47,7 @@ PresetBundle::PresetBundle() : filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast(SLAFullPrintConfig::defaults())), - printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"), - m_bitmapCompatible(new wxBitmap), - m_bitmapIncompatible(new wxBitmap), - m_bitmapLock(new wxBitmap), - m_bitmapLockOpen(new wxBitmap), - m_bitmapCache(new GUI::BitmapCache) + printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -") { if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler); @@ -112,16 +102,6 @@ PresetBundle::PresetBundle() : preset.inherits(); } - // Load the default preset bitmaps. - // #ys_FIXME_to_delete we'll load them later, using em_unit() -// this->prints .load_bitmap_default("cog"); -// this->sla_prints .load_bitmap_default("package_green.png"); -// this->filaments .load_bitmap_default("spool.png"); -// this->sla_materials.load_bitmap_default("package_green.png"); -// this->printers .load_bitmap_default("printer_empty.png"); -// this->printers .load_bitmap_add("add.png"); -// this->load_compatible_bitmaps(); - // Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above. this->prints .select_preset(0); this->sla_prints .select_preset(0); @@ -134,20 +114,6 @@ PresetBundle::PresetBundle() : PresetBundle::~PresetBundle() { - assert(m_bitmapCompatible != nullptr); - assert(m_bitmapIncompatible != nullptr); - assert(m_bitmapLock != nullptr); - assert(m_bitmapLockOpen != nullptr); - delete m_bitmapCompatible; - m_bitmapCompatible = nullptr; - delete m_bitmapIncompatible; - m_bitmapIncompatible = nullptr; - delete m_bitmapLock; - m_bitmapLock = nullptr; - delete m_bitmapLockOpen; - m_bitmapLockOpen = nullptr; - delete m_bitmapCache; - m_bitmapCache = nullptr; } void PresetBundle::reset(bool delete_files) @@ -486,36 +452,6 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "printer", printers.get_selected_preset_name()); } -void PresetBundle::load_compatible_bitmaps() -{ - *m_bitmapCompatible = create_scaled_bitmap("flag_green"); - *m_bitmapIncompatible = create_scaled_bitmap("flag_red"); - *m_bitmapLock = create_scaled_bitmap("lock_closed"); - *m_bitmapLockOpen = create_scaled_bitmap("lock_open"); - - prints .set_bitmap_compatible(m_bitmapCompatible); - filaments .set_bitmap_compatible(m_bitmapCompatible); - sla_prints .set_bitmap_compatible(m_bitmapCompatible); - sla_materials.set_bitmap_compatible(m_bitmapCompatible); - - prints .set_bitmap_incompatible(m_bitmapIncompatible); - filaments .set_bitmap_incompatible(m_bitmapIncompatible); - sla_prints .set_bitmap_incompatible(m_bitmapIncompatible); - sla_materials.set_bitmap_incompatible(m_bitmapIncompatible); - - prints .set_bitmap_lock(m_bitmapLock); - filaments .set_bitmap_lock(m_bitmapLock); - sla_prints .set_bitmap_lock(m_bitmapLock); - sla_materials.set_bitmap_lock(m_bitmapLock); - printers .set_bitmap_lock(m_bitmapLock); - - prints .set_bitmap_lock_open(m_bitmapLock); - filaments .set_bitmap_lock_open(m_bitmapLock); - sla_prints .set_bitmap_lock_open(m_bitmapLock); - sla_materials.set_bitmap_lock_open(m_bitmapLock); - printers .set_bitmap_lock_open(m_bitmapLock); -} - DynamicPrintConfig PresetBundle::full_config() const { return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? @@ -886,7 +822,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 4) Load the project config values (the per extruder wipe matrix etc). this->project_config.apply_only(config, s_project_options); - update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); + CustomGCode::update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); break; } @@ -1544,207 +1480,11 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { - if (name.find_first_of(PresetCollection::separator_head()) == 0) - return; - - if (idx >= filament_presets.size()) + if (idx >= filament_presets.size()) filament_presets.resize(idx + 1, filaments.default_preset().name); filament_presets[idx] = Preset::remove_suffix_modified(name); } -void PresetBundle::load_default_preset_bitmaps() -{ - // Clear bitmap cache, before load new scaled default preset bitmaps - m_bitmapCache->clear(); - this->prints.clear_bitmap_cache(); - this->sla_prints.clear_bitmap_cache(); - this->filaments.clear_bitmap_cache(); - this->sla_materials.clear_bitmap_cache(); - this->printers.clear_bitmap_cache(); - - this->prints.load_bitmap_default("cog"); - this->sla_prints.load_bitmap_default("cog"); - this->filaments.load_bitmap_default("spool.png"); - this->sla_materials.load_bitmap_default("resin"); - this->printers.load_bitmap_default("printer"); - this->printers.load_bitmap_add("add.png"); - this->load_compatible_bitmaps(); -} - -void PresetBundle::update_plater_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 ) - return; - - unsigned char rgb[3]; - std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); - if (!m_bitmapCache->parse_color(extruder_color, rgb)) - // Extruder color is not defined. - extruder_color.clear(); - - // Fill in the list from scratch. - ui->Freeze(); - ui->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected - - const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]); - // Show wide icons if the currently selected preset is not compatible with the current printer, - // and draw a red flag in front of the selected preset. - bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr; - assert(selected_preset != nullptr); - std::map nonsys_presets; - wxString selected_str = ""; - if (!this->filaments().front().is_visible) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const float scale_f = ui->em_unit() * 0.1f; - - // To avoid the errors of number rounding for different combination of monitor configuration, - // let use scaled 8px, as a smallest icon unit - const int icon_unit = 8 * scale_f + 0.5f; - const int normal_icon_width = 2 * icon_unit; //16 * scale_f + 0.5f; - const int thin_icon_width = icon_unit; //8 * scale_f + 0.5f; - const int wide_icon_width = 3 * icon_unit; //24 * scale_f + 0.5f; - - const int space_icon_width = 2 * scale_f + 0.5f; - - // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so - // set a bitmap height to m_bitmapLock->GetHeight() - // - // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size. - // But for some display scaling (for example 125% or 175%) normal_icon_width differs from icon width. - // So: - // for nonsystem presets set a width of empty bitmap to m_bitmapLock->GetWidth() - // for compatible presets set a width of empty bitmap to m_bitmapIncompatible->GetWidth() - // - // Note, under OSX we should use a Scaled Height/Width because of Retina scale -#ifdef __APPLE__ - const int icon_height = m_bitmapLock->GetScaledHeight(); - const int lock_icon_width = m_bitmapLock->GetScaledWidth(); - const int flag_icon_width = m_bitmapIncompatible->GetScaledWidth(); -#else - const int icon_height = m_bitmapLock->GetHeight(); - const int lock_icon_width = m_bitmapLock->GetWidth(); - const int flag_icon_width = m_bitmapIncompatible->GetWidth(); -#endif - - wxString tooltip = ""; - - for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) { - const Preset &preset = this->filaments.preset(i); - bool selected = this->filament_presets[idx_extruder] == preset.name; - if (! preset.is_visible || (! preset.is_compatible && ! selected)) - continue; - // Assign an extruder color to the selected item if the extruder color is defined. - std::string filament_rgb = preset.config.opt_string("filament_colour", 0); - std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; - bool single_bar = filament_rgb == extruder_rgb; - std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - if (preset.is_dirty) - bitmap_key += ",drty"; - wxBitmap *bitmap = m_bitmapCache->find(bitmap_key); - if (bitmap == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(flag_icon_width, icon_height) : *m_bitmapIncompatible); - // Paint the color bars. - m_bitmapCache->parse_color(filament_rgb, rgb); - bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb)); - if (! single_bar) { - m_bitmapCache->parse_color(extruder_rgb, rgb); - bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb)); - } - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmapCache->mkclear(space_icon_width, icon_height)); - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmapLock : m_bitmapCache->mkclear(lock_icon_width, icon_height)); -// (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); - bitmap = m_bitmapCache->insert(bitmap_key, bmps); - } - - const std::string name = preset.alias.empty() ? preset.name : preset.alias; - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bitmap == 0) ? wxNullBitmap : *bitmap); - if (selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX ) { - selected_preset_item = ui->GetCount() - 1; - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bitmap == 0) ? &wxNullBitmap : bitmap); - if (selected) { - selected_str = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); - tooltip = wxString::FromUTF8(preset.name.c_str()); - } - } - if (preset.is_default) - ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - } - - if (!nonsys_presets.empty()) - { - ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected_str || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) { - selected_preset_item = ui->GetCount() - 1; - } - } - } - - std::string bitmap_key = ""; - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - wxBitmap* bmp = m_bitmapCache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmapCache->mkclear(flag_icon_width, icon_height)); - // Paint the color bars + a lock at the system presets. - bmps.emplace_back(m_bitmapCache->mkclear(wide_icon_width+space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmapCache->insert(bitmap_key, bmps); - } - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_FILAMENTS); - - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove filaments") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = ui->GetCount() - 1; - - ui->SetSelection(selected_preset_item); - ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip); - ui->check_selection(selected_preset_item); - ui->Thaw(); - - // Update control min size after rescale (changed Display DPI under MSW) - if (ui->GetMinWidth() != 20 * ui->em_unit()) - ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight())); -} - void PresetBundle::set_default_suppressed(bool default_suppressed) { prints.set_default_suppressed(default_suppressed); diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index bf1bba21db..7d137bb7ad 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -5,7 +5,6 @@ #include "Preset.hpp" #include -#include #include #include @@ -13,10 +12,6 @@ class wxWindow; namespace Slic3r { -namespace GUI { - class BitmapCache; -}; - // Bundle of Print + Filament + Printer presets. class PresetBundle { @@ -110,9 +105,6 @@ public: // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path, bool export_system_settings = false); - // Update a filament selection combo box on the plater for an idx_extruder. - void update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui); - // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); @@ -132,8 +124,6 @@ public: void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible); void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); } - void load_default_preset_bitmaps(); - // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. // If the "vendor" section is missing, enable all models and variants of the particular vendor. @@ -163,21 +153,9 @@ private: // If it is not an external config, then the config will be stored into the user profile directory. void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); - void load_compatible_bitmaps(); DynamicPrintConfig full_fff_config() const; DynamicPrintConfig full_sla_config() const; - - // Indicator, that the preset is compatible with the selected printer. - wxBitmap *m_bitmapCompatible; - // Indicator, that the preset is NOT compatible with the selected printer. - wxBitmap *m_bitmapIncompatible; - // Indicator, that the preset is system and not modified. - wxBitmap *m_bitmapLock; - // Indicator, that the preset is system and user modified. - wxBitmap *m_bitmapLockOpen; - // Caching color bitmaps for the filament combo box. - GUI::BitmapCache *m_bitmapCache; }; } // namespace Slic3r diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp new file mode 100644 index 0000000000..380edb48ad --- /dev/null +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -0,0 +1,794 @@ +#include "PresetComboBoxes.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" + +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" +#include "format.hpp" +#include "Tab.hpp" +#include "PresetBundle.hpp" +#include "PrintHostDialogs.hpp" +#include "ConfigWizard.hpp" +#include "../Utils/ASCIIFolding.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/FixModelByWin10.hpp" +#include "../Utils/UndoRedo.hpp" +#include "RemovableDriveManager.hpp" +#include "BitmapCache.hpp" + +using Slic3r::GUI::format_wxstr; + +static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; + +namespace Slic3r { +namespace GUI { + +// --------------------------------- +// *** PresetComboBox *** +// --------------------------------- + +/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina + * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean + * "please scale this to such and such" but rather + * "the wxImage is already sized for backing scale such and such". ) + * Unfortunately, the constructor changes the size of wxBitmap too. + * Thus We need to use unscaled size value for bitmaps that we use + * to avoid scaled size of control items. + * For this purpose control drawing methods and + * control size calculation methods (virtual) are overridden. + **/ + +PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size) : + wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY), + m_type(preset_type), + m_last_selected(wxNOT_FOUND), + m_em_unit(wxGetApp().em_unit()), + m_preset_bundle(wxGetApp().preset_bundle), + m_bitmap_cache(new BitmapCache) +{ + SetFont(wxGetApp().normal_font()); +#ifdef _WIN32 + // Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that + // the index of the item inside CBN_EDITCHANGE may no more be valid. + EnableTextChangedEvents(false); +#endif /* _WIN32 */ + + switch (m_type) + { + case Preset::TYPE_PRINT: { + m_collection = &m_preset_bundle->prints; + m_main_bitmap_name = "cog"; + break; + } + case Preset::TYPE_FILAMENT: { + m_collection = &m_preset_bundle->filaments; + m_main_bitmap_name = "spool"; + break; + } + case Preset::TYPE_SLA_PRINT: { + m_collection = &m_preset_bundle->sla_prints; + m_main_bitmap_name = "cog"; + break; + } + case Preset::TYPE_SLA_MATERIAL: { + m_collection = &m_preset_bundle->sla_materials; + m_main_bitmap_name = "resin"; + break; + } + case Preset::TYPE_PRINTER: { + m_collection = &m_preset_bundle->printers; + m_main_bitmap_name = "printer"; + break; + } + default: break; + } + + m_bitmapCompatible = ScalableBitmap(nullptr, "flag_green"); + m_bitmapIncompatible = ScalableBitmap(nullptr, "flag_red"); + m_bitmapLock = ScalableBitmap(nullptr, "lock_closed"); + + // parameters for an icon's drawing + fill_width_height(); +} + +PresetComboBox::~PresetComboBox() +{ + delete m_bitmap_cache; + m_bitmap_cache = nullptr; +} + +void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) +{ + this->SetClientData(item, (void*)label_item_type); +} + +void PresetComboBox::msw_rescale() +{ + m_em_unit = wxGetApp().em_unit(); + + m_bitmapLock.msw_rescale(); + m_bitmapIncompatible.msw_rescale(); + m_bitmapCompatible.msw_rescale(); + + // parameters for an icon's drawing + fill_width_height(); + + // update the control to redraw the icons + update(); +} + +void PresetComboBox::fill_width_height() +{ + // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so + // set a bitmap's height to m_bitmapLock->GetHeight() and norm_icon_width to m_bitmapLock->GetWidth() + icon_height = m_bitmapLock.GetBmpHeight(); + norm_icon_width = m_bitmapLock.GetBmpWidth(); + + /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. + * So set sizes for solid_colored icons used for filament preset + * and scale them in respect to em_unit value + */ + const float scale_f = (float)m_em_unit * 0.1f; + + thin_icon_width = lroundf(8 * scale_f); // analogue to 8px; + wide_icon_width = norm_icon_width + thin_icon_width; + + space_icon_width = lroundf(2 * scale_f); + thin_space_icon_width = 2 * space_icon_width; + wide_space_icon_width = 3 * space_icon_width; +} + +wxString PresetComboBox::separator(const std::string& label) +{ + return wxString::FromUTF8(separator_head()) + _(label) + wxString::FromUTF8(separator_tail()); +} + +#ifdef __APPLE__ +bool PresetComboBox::OnAddBitmap(const wxBitmap& bitmap) +{ + if (bitmap.IsOk()) + { + // we should use scaled! size values of bitmap + int width = (int)bitmap.GetScaledWidth(); + int height = (int)bitmap.GetScaledHeight(); + + if (m_usedImgSize.x < 0) + { + // If size not yet determined, get it from this image. + m_usedImgSize.x = width; + m_usedImgSize.y = height; + + // Adjust control size to vertically fit the bitmap + wxWindow* ctrl = GetControl(); + ctrl->InvalidateBestSize(); + wxSize newSz = ctrl->GetBestSize(); + wxSize sz = ctrl->GetSize(); + if (newSz.y > sz.y) + ctrl->SetSize(sz.x, newSz.y); + else + DetermineIndent(); + } + + wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y, + false, + "you can only add images of same size"); + + return true; + } + + return false; +} + +void PresetComboBox::OnDrawItem(wxDC& dc, + const wxRect& rect, + int item, + int flags) const +{ + const wxBitmap& bmp = *(wxBitmap*)m_bitmaps[item]; + if (bmp.IsOk()) + { + // we should use scaled! size values of bitmap + wxCoord w = bmp.GetScaledWidth(); + wxCoord h = bmp.GetScaledHeight(); + + const int imgSpacingLeft = 4; + + // Draw the image centered + dc.DrawBitmap(bmp, + rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft, + rect.y + (rect.height - h) / 2, + true); + } + + wxString text = GetString(item); + if (!text.empty()) + dc.DrawText(text, + rect.x + m_imgAreaWidth + 1, + rect.y + (rect.height - dc.GetCharHeight()) / 2); +} +#endif + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset_type) : + PresetComboBox(parent, preset_type, wxSize(15 * wxGetApp().em_unit(), -1)) +{ + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { + auto selected_item = evt.GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { + this->SetSelection(this->m_last_selected); + evt.StopPropagation(); + if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) + { + PhysicalPrinterDialog dlg; + dlg.ShowModal(); + return; + } + if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { + ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; + switch (marker) { + case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; + case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; + case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; + default: break; + } + wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); + } + } else if ( this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { + this->m_last_selected = selected_item; + evt.SetInt(this->m_type); + evt.Skip(); + } else { + evt.StopPropagation(); + } + }); + + if (m_type == Preset::TYPE_FILAMENT) + { + Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { + const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); + // Wide icons are shown if the currently selected preset is not compatible with the current printer, + // and red flag is drown in front of the selected preset. + bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible; + float scale = m_em_unit*0.1f; + + int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0; +#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED) + shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image +#endif + int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5); + int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x; + if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) { + // Let the combo box process the mouse click. + event.Skip(); + return; + } + + // Swallow the mouse click and open the color picker. + + // get current color + DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config(); + auto colors = static_cast(cfg->option("extruder_colour")->clone()); + wxColour clr(colors->values[m_extruder_idx]); + if (!clr.IsOk()) + clr = wxColour(0,0,0); // Don't set alfa to transparence + + auto data = new wxColourData(); + data->SetChooseFull(1); + data->SetColour(clr); + + wxColourDialog dialog(this, data); + dialog.CenterOnParent(); + if (dialog.ShowModal() == wxID_OK) + { + colors->values[m_extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); + + DynamicPrintConfig cfg_new = *cfg; + cfg_new.set_key_value("extruder_colour", colors); + + wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); + this->update(); + wxGetApp().plater()->on_config_change(cfg_new); + } + }); + } + + edit_btn = new ScalableButton(parent, wxID_ANY, "cog"); + edit_btn->SetToolTip(_L("Click to edit preset")); + + edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) + { + Tab* tab = wxGetApp().get_tab(m_type); + if (!tab) + return; + + int page_id = wxGetApp().tab_panel()->FindPage(tab); + if (page_id == wxNOT_FOUND) + return; + + wxGetApp().tab_panel()->SetSelection(page_id); + + // Switch to Settings NotePad + wxGetApp().mainframe->select_tab(); + + /* In a case of a multi-material printing, for editing another Filament Preset + * it's needed to select this preset for the "Filament settings" Tab + */ + if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + { + const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); + + // Call select_preset() only if there is new preset and not just modified + if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) + { + const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); + tab->select_preset(preset_name); + } + } + }); +} + +PlaterPresetComboBox::~PlaterPresetComboBox() +{ + if (edit_btn) + edit_btn->Destroy(); +} + +// Only the compatible presets are shown. +// If an incompatible preset is selected, it is shown as well. +void PlaterPresetComboBox::update() +{ + if (m_type == Preset::TYPE_FILAMENT && + (m_collection->get_edited_preset().printer_technology() == ptSLA || + m_preset_bundle->filament_presets.size() <= m_extruder_idx) ) + return; + + // Otherwise fill in the list from scratch. + this->Freeze(); + this->Clear(); + size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + + const Preset* selected_filament_preset; + std::string extruder_color; + if (m_type == Preset::TYPE_FILAMENT) + { + unsigned char rgb[3]; + extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx); + if (!m_bitmap_cache->parse_color(extruder_color, rgb)) + // Extruder color is not defined. + extruder_color.clear(); + selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); + assert(selected_filament_preset); + } + + const Preset& selected_preset = m_type == Preset::TYPE_FILAMENT ? *selected_filament_preset : m_collection->get_selected_preset(); + // Show wide icons if the currently selected preset is not compatible with the current printer, + // and draw a red flag in front of the selected preset. + bool wide_icons = !selected_preset.is_compatible; + + std::map nonsys_presets; + std::map physical_printers; + + wxString selected = ""; + wxString tooltip = ""; + const std::deque& presets = m_collection->get_presets(); + + if (!presets.front().is_visible) + this->set_label_marker(this->Append(separator(L("System presets")), wxNullBitmap)); + + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) + { + const Preset& preset = presets[i]; + bool is_selected = m_type == Preset::TYPE_FILAMENT ? + m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : + i == m_collection->get_selected_idx(); + + if (!preset.is_visible || (!preset.is_compatible && !is_selected)) + continue; + + std::string bitmap_key, filament_rgb, extruder_rgb; + bool single_bar = false; + if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) + bitmap_key = "sla_printer"; + else if (m_type == Preset::TYPE_FILAMENT) + { + // Assign an extruder color to the selected item if the extruder color is defined. + filament_rgb = preset.config.opt_string("filament_colour", 0); + extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; + single_bar = filament_rgb == extruder_rgb; + + bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; + } + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); + + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); + + if (m_type == Preset::TYPE_FILAMENT) + { + unsigned char rgb[3]; + // Paint the color bars. + m_bitmap_cache->parse_color(filament_rgb, rgb); + bmps.emplace_back(m_bitmap_cache->mksolid(single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb)); + if (!single_bar) { + m_bitmap_cache->parse_color(extruder_rgb, rgb); + bmps.emplace_back(m_bitmap_cache->mksolid(thin_icon_width, icon_height, rgb)); + } + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(space_icon_width, icon_height)); + } + else + { + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(main_bmp); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + } + bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + + const std::string name = preset.alias.empty() ? preset.name : preset.alias; + if (preset.is_default || preset.is_system) { + Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), + !bmp ? main_bmp : *bmp); + if (is_selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) { + selected_preset_item = GetCount() - 1; + tooltip = wxString::FromUTF8(preset.name.c_str()); + } + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + if (is_selected) { + selected = wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + tooltip = wxString::FromUTF8(preset.name.c_str()); + } + } + if (i + 1 == m_collection->num_default_presets()) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + } + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + Append(it->first, *it->second); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + if (!physical_printers.empty()) + { + set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + for (std::map::iterator it = physical_printers.begin(); it != physical_printers.end(); ++it) { + Append(it->first, *it->second); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars.update_plater_ui + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + if (m_type == Preset::TYPE_SLA_MATERIAL) + set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); + else + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); + } + if (m_type == Preset::TYPE_PRINTER) { + std::string bitmap_key = ""; + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("printer")); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + set_label_marker(Append(separator(L("Add physical printer")), *bmp), LABEL_ITEM_PHYSICAL_PRINTERS); + } + + /* But, if selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + if (selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + + SetSelection(selected_preset_item); + SetToolTip(tooltip.IsEmpty() ? GetString(selected_preset_item) : tooltip); + m_last_selected = selected_preset_item; + Thaw(); + + // Update control min size after rescale (changed Display DPI under MSW) + if (GetMinWidth() != 20 * m_em_unit) + SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight())); +} + +void PlaterPresetComboBox::msw_rescale() +{ + PresetComboBox::msw_rescale(); + edit_btn->msw_rescale(); +} + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) : + PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1)) +{ + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + // see https://github.com/prusa3d/PrusaSlicer/issues/3889 + // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") + // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. + // So, use GetSelection() from event parameter + auto selected_item = evt.GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { + this->SetSelection(this->m_last_selected); + if (marker == LABEL_ITEM_WIZARD_PRINTERS) + wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + } + else if (m_last_selected != selected_item || m_collection->current_is_dirty()) { + std::string selected_string = this->GetString(selected_item).ToUTF8().data(); + Tab* tab = wxGetApp().get_tab(this->m_type); + assert (tab); + tab->select_preset(selected_string); + } + + evt.StopPropagation(); + }); +} + +// Update the choice UI from the list of presets. +// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. +// If an incompatible preset is selected, it is shown as well. +void TabPresetComboBox::update() +{ + Freeze(); + Clear(); + size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + + const std::deque& presets = m_collection->get_presets(); + + std::map nonsys_presets; + wxString selected = ""; + if (!presets.front().is_visible) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + int idx_selected = m_collection->get_selected_idx(); + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { + const Preset& preset = presets[i]; + if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) + continue; + + std::string bitmap_key = "tab"; + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); + if (m_type == Preset::TYPE_PRINTER) { + bitmap_key += "_printer"; + if (preset.printer_technology() == ptSLA) + bitmap_key += "_sla"; + } + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? main_bmp : preset.is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); + // Paint a lock at the system presets. + bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + + if (preset.is_default || preset.is_system) { + Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), + (bmp == 0) ? main_bmp : *bmp); + if (i == idx_selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + if (i == idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + } + if (i + 1 == m_collection->num_default_presets()) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + } + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + Append(it->first, *it->second); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + if (m_type == Preset::TYPE_PRINTER) { + std::string bitmap_key = "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); + } + + /* But, if selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + if (selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + + SetSelection(selected_preset_item); + SetToolTip(GetString(selected_preset_item)); + Thaw(); + + m_last_selected = selected_preset_item; +} + +void TabPresetComboBox::msw_rescale() +{ + PresetComboBox::msw_rescale(); + wxSize sz = wxSize(35 * m_em_unit, -1); + SetMinSize(sz); + SetSize(sz); +} + +void TabPresetComboBox::update_dirty() +{ + // 1) Update the dirty flag of the current preset. + m_collection->update_dirty(); + + // 2) Update the labels. + wxWindowUpdateLocker noUpdates(this); + for (unsigned int ui_id = 0; ui_id < GetCount(); ++ui_id) { + std::string old_label = GetString(ui_id).utf8_str().data(); + std::string preset_name = Preset::remove_suffix_modified(old_label); + const Preset* preset = m_collection->find_preset(preset_name, false); + if (preset) { + std::string new_label = preset->is_dirty ? preset->name + Preset::suffix_modified() : preset->name; + if (old_label != new_label) + SetString(ui_id, wxString::FromUTF8(new_label.c_str())); + } + } +#ifdef __APPLE__ + // wxWidgets on OSX do not upload the text of the combo box line automatically. + // Force it to update by re-selecting. + SetSelection(GetSelection()); +#endif /* __APPLE __ */ +} + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + + +PhysicalPrinterDialog::PhysicalPrinterDialog() + : DPIDialog(NULL, wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + int border = 10; + int em = em_unit(); + + printer_text = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + printer_presets = new PlaterPresetComboBox(this, Preset::TYPE_PRINTER); + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(printer_text , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(btns , 0, wxEXPAND | wxALL, border); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + + const wxSize& size = wxSize(40 * em, 30 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp new file mode 100644 index 0000000000..63110e4329 --- /dev/null +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -0,0 +1,179 @@ +#ifndef slic3r_PresetComboBoxes_hpp_ +#define slic3r_PresetComboBoxes_hpp_ + +#include + +#include +#include +#include + +#include "Preset.hpp" +#include "wxExtensions.hpp" +#include "GUI_Utils.hpp" + +class wxString; +class wxTextCtrl; + +namespace Slic3r { + +namespace GUI { + +class BitmapCache; + + +// --------------------------------- +// *** PresetComboBox *** +// --------------------------------- + +// BitmapComboBox used to presets list on Sidebar and Tabs +class PresetComboBox : public wxBitmapComboBox +{ +public: + PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize); + ~PresetComboBox(); + + enum LabelItemType { + LABEL_ITEM_MARKER = 0xffffff01, + LABEL_ITEM_PHYSICAL_PRINTERS, + LABEL_ITEM_WIZARD_PRINTERS, + LABEL_ITEM_WIZARD_FILAMENTS, + LABEL_ITEM_WIZARD_MATERIALS, + + LABEL_ITEM_MAX, + }; + + void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); + int em_unit() const { return m_em_unit; } + + virtual void update() {}; + virtual void msw_rescale(); + +protected: + typedef std::size_t Marker; + + Preset::Type m_type; + std::string m_main_bitmap_name; + + PresetBundle* m_preset_bundle {nullptr}; + PresetCollection* m_collection {nullptr}; + + // Caching color bitmaps for the filament combo box. + BitmapCache* m_bitmap_cache {nullptr}; + // Indicator, that the preset is compatible with the selected printer. + ScalableBitmap m_bitmapCompatible; + // Indicator, that the preset is NOT compatible with the selected printer. + ScalableBitmap m_bitmapIncompatible; + // Indicator, that the preset is system and not modified. + ScalableBitmap m_bitmapLock; + + int m_last_selected; + int m_em_unit; + + // parameters for an icon's drawing + int icon_height; + int norm_icon_width; + int thin_icon_width; + int wide_icon_width; + int space_icon_width; + int thin_space_icon_width; + int wide_space_icon_width; + +#ifdef __linux__ + static const char* separator_head() { return "------- "; } + static const char* separator_tail() { return " -------"; } +#else // __linux__ + static const char* separator_head() { return "————— "; } + static const char* separator_tail() { return " —————"; } +#endif // __linux__ + static wxString separator(const std::string& label); + +#ifdef __APPLE__ + /* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina + * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean + * "please scale this to such and such" but rather + * "the wxImage is already sized for backing scale such and such". ) + * Unfortunately, the constructor changes the size of wxBitmap too. + * Thus We need to use unscaled size value for bitmaps that we use + * to avoid scaled size of control items. + * For this purpose control drawing methods and + * control size calculation methods (virtual) are overridden. + **/ + virtual bool OnAddBitmap(const wxBitmap& bitmap) override; + virtual void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; +#endif + +private: + void fill_width_height(); +}; + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +class PlaterPresetComboBox : public PresetComboBox +{ +public: + PlaterPresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~PlaterPresetComboBox(); + + ScalableButton* edit_btn { nullptr }; + + void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; } + int get_extruder_idx() const { return m_extruder_idx; } + + void update() override; + void msw_rescale() override; + +private: + int m_extruder_idx = -1; +}; + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +class TabPresetComboBox : public PresetComboBox +{ +public: + TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~TabPresetComboBox() {} + void set_show_incompatible_presets(bool show_incompatible_presets) { + show_incompatible = show_incompatible_presets; + } + + void update() override; + void update_dirty(); + void msw_rescale() override; + +private: + bool show_incompatible{false}; +}; + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + +class PhysicalPrinterDialog : public DPIDialog +{ + std::string printer_name; + std::string preset_name; + + wxTextCtrl* printer_text { nullptr }; + PresetComboBox* printer_presets; + +public: + PhysicalPrinterDialog(); + ~PhysicalPrinterDialog() {} + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override {}; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 84bc5a5726..b128ec03d4 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -27,6 +27,7 @@ #include #include "wxExtensions.hpp" +#include "PresetComboBoxes.hpp" #include #include "GUI_App.hpp" @@ -160,10 +161,7 @@ void Tab::create_preset_tab() #endif //__WXOSX__ // preset chooser - m_presets_choice = new PresetBitmapComboBox(panel, wxSize(35 * m_em_unit, -1)); - - // search combox -// m_search = new Search::SearchCtrl(panel); + m_presets_choice = new TabPresetComboBox(panel, m_type); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -278,35 +276,6 @@ void Tab::create_preset_tab() m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); - m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, - //! but the OSX version derived from wxOwnerDrawnCombo, instead of: - //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); - //! we doing next: - // int selected_item = m_presets_choice->GetSelection(); - - // see https://github.com/prusa3d/PrusaSlicer/issues/3889 - // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") - // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. - // So, use GetSelection() from event parameter - int selected_item = e.GetSelection(); - if (m_selected_preset_item == size_t(selected_item) && !m_presets->current_is_dirty()) - return; - if (selected_item >= 0) { - std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string.find(PresetCollection::separator_head()) == 0 - /*selected_string == "------- System presets -------" || - selected_string == "------- User presets -------"*/) { - m_presets_choice->SetSelection(m_selected_preset_item); - if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) - wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER); }); - return; - } - m_selected_preset_item = selected_item; - select_preset(selected_string); - } - })); - m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { @@ -778,14 +747,14 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) // comparing the selected preset config with $self->{config}. void Tab::update_dirty() { - m_presets->update_dirty_ui(m_presets_choice); + m_presets_choice->update_dirty(); on_presets_changed(); update_changed_ui(); } void Tab::update_tab_ui() { - m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets, m_em_unit); + m_presets_choice->update(); } // Load a provied DynamicConfig into the tab, modifying the active preset. @@ -850,12 +819,10 @@ void Tab::msw_rescale() m_em_unit = wxGetApp().em_unit(); m_mode_sizer->msw_rescale(); + m_presets_choice->msw_rescale(); - m_presets_choice->SetSize(35 * m_em_unit, -1); m_treectrl->SetMinSize(wxSize(20 * m_em_unit, -1)); - update_tab_ui(); - // rescale buttons and cached bitmaps for (const auto btn : m_scaled_buttons) btn->msw_rescale(); @@ -963,7 +930,7 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo // Don't select another profile if this profile happens to become incompatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); } - m_presets->update_dirty_ui(m_presets_choice); + m_presets_choice->update_dirty(); on_presets_changed(); update(); } @@ -3360,6 +3327,7 @@ void Tab::delete_preset() void Tab::toggle_show_hide_incompatible() { m_show_incompatible_presets = !m_show_incompatible_presets; + m_presets_choice->set_show_incompatible_presets(m_show_incompatible_presets); update_show_hide_incompatible_button(); update_tab_ui(); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 5805809bfb..a0bf536c19 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -39,6 +39,8 @@ namespace Slic3r { namespace GUI { +class TabPresetComboBox; + // Single Tab page containing a{ vsizer } of{ optgroups } // package Slic3r::GUI::Tab::Page; using ConfigOptionsGroupShp = std::shared_ptr; @@ -113,7 +115,7 @@ protected: Preset::Type m_type; std::string m_name; const wxString m_title; - PresetBitmapComboBox* m_presets_choice; + TabPresetComboBox* m_presets_choice; ScalableButton* m_search_btn; ScalableButton* m_btn_save_preset; ScalableButton* m_btn_delete_preset; @@ -206,8 +208,6 @@ protected: bool m_is_nonsys_values{ true }; bool m_postpone_update_ui {false}; - size_t m_selected_preset_item{ 0 }; - void set_type(); int m_em_unit; @@ -320,7 +320,6 @@ public: DynamicPrintConfig* get_config() { return m_config; } PresetCollection* get_presets() { return m_presets; } - size_t get_selected_preset_item() { return m_selected_preset_item; } void on_value_change(const std::string& opt_key, const boost::any& value); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index ad9f0a121e..39b3e154b4 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -300,94 +300,6 @@ void wxCheckListBoxComboPopup::OnListBoxSelection(wxCommandEvent& evt) } -namespace Slic3r { -namespace GUI { - -// *** PresetBitmapComboBox *** - -/* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ - -PresetBitmapComboBox::PresetBitmapComboBox(wxWindow* parent, const wxSize& size) : - wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY) -{} - -#ifdef __APPLE__ -bool PresetBitmapComboBox::OnAddBitmap(const wxBitmap& bitmap) -{ - if (bitmap.IsOk()) - { - // we should use scaled! size values of bitmap - int width = (int)bitmap.GetScaledWidth(); - int height = (int)bitmap.GetScaledHeight(); - - if (m_usedImgSize.x < 0) - { - // If size not yet determined, get it from this image. - m_usedImgSize.x = width; - m_usedImgSize.y = height; - - // Adjust control size to vertically fit the bitmap - wxWindow* ctrl = GetControl(); - ctrl->InvalidateBestSize(); - wxSize newSz = ctrl->GetBestSize(); - wxSize sz = ctrl->GetSize(); - if (newSz.y > sz.y) - ctrl->SetSize(sz.x, newSz.y); - else - DetermineIndent(); - } - - wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y, - false, - "you can only add images of same size"); - - return true; - } - - return false; -} - -void PresetBitmapComboBox::OnDrawItem(wxDC& dc, - const wxRect& rect, - int item, - int flags) const -{ - const wxBitmap& bmp = *(wxBitmap*)m_bitmaps[item]; - if (bmp.IsOk()) - { - // we should use scaled! size values of bitmap - wxCoord w = bmp.GetScaledWidth(); - wxCoord h = bmp.GetScaledHeight(); - - const int imgSpacingLeft = 4; - - // Draw the image centered - dc.DrawBitmap(bmp, - rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft, - rect.y + (rect.height - h) / 2, - true); - } - - wxString text = GetString(item); - if (!text.empty()) - dc.DrawText(text, - rect.x + m_imgAreaWidth + 1, - rect.y + (rect.height - dc.GetCharHeight()) / 2); -} -#endif -} -} - - // *** wxDataViewTreeCtrlComboPopup *** const unsigned int wxDataViewTreeCtrlComboPopup::DefaultWidth = 270; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 569257e1b4..17fe8992c5 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -95,37 +95,6 @@ public: void OnListBoxSelection(wxCommandEvent& evt); }; -namespace Slic3r { -namespace GUI { -// *** PresetBitmapComboBox *** - -// BitmapComboBox used to presets list on Sidebar and Tabs -class PresetBitmapComboBox: public wxBitmapComboBox -{ -public: - PresetBitmapComboBox(wxWindow* parent, const wxSize& size = wxDefaultSize); - ~PresetBitmapComboBox() {} - -#ifdef __APPLE__ -protected: - /* For PresetBitmapComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ - virtual bool OnAddBitmap(const wxBitmap& bitmap) override; - virtual void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; -#endif -}; - -} -} - // *** wxDataViewTreeCtrlComboBox *** From 19c4f3260429ba6db33411fd78b6bfc74ac2b8e9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 16 Jun 2020 16:58:41 +0200 Subject: [PATCH 131/826] Preset and PresetBundle are moved to the _libslic3r_ folder --- src/libslic3r/CMakeLists.txt | 4 ++++ src/{slic3r/GUI => libslic3r}/Preset.cpp | 23 +++++++++++++------ src/{slic3r/GUI => libslic3r}/Preset.hpp | 14 +++-------- .../GUI => libslic3r}/PresetBundle.cpp | 17 +++----------- .../GUI => libslic3r}/PresetBundle.hpp | 3 +-- src/slic3r/CMakeLists.txt | 4 ---- src/slic3r/Config/Snapshot.cpp | 3 +-- src/slic3r/GUI/3DBed.cpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GUI_App.cpp | 4 ++-- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/GUI_ObjectLayers.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 +- src/slic3r/GUI/GUI_ObjectSettings.cpp | 2 +- src/slic3r/GUI/GUI_Preview.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- src/slic3r/GUI/I18N.hpp | 2 +- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Mouse3DController.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 4 +++- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 2 +- src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- src/slic3r/GUI/PresetHints.cpp | 1 - src/slic3r/GUI/PresetHints.hpp | 2 +- src/slic3r/GUI/Search.cpp | 2 +- src/slic3r/GUI/Search.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 3 +-- src/slic3r/GUI/Tab.hpp | 2 +- src/slic3r/Utils/PresetUpdater.cpp | 2 +- 38 files changed, 61 insertions(+), 73 deletions(-) rename src/{slic3r/GUI => libslic3r}/Preset.cpp (99%) rename src/{slic3r/GUI => libslic3r}/Preset.hpp (99%) rename src/{slic3r/GUI => libslic3r}/PresetBundle.cpp (99%) rename src/{slic3r/GUI => libslic3r}/PresetBundle.hpp (99%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1a58bdbbd9..1605c52cde 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -147,6 +147,10 @@ add_library(libslic3r STATIC PolygonTrimmer.hpp Polyline.cpp Polyline.hpp + Preset.cpp + Preset.hpp + PresetBundle.cpp + PresetBundle.hpp Print.cpp Print.hpp PrintBase.cpp diff --git a/src/slic3r/GUI/Preset.cpp b/src/libslic3r/Preset.cpp similarity index 99% rename from src/slic3r/GUI/Preset.cpp rename to src/libslic3r/Preset.cpp index 883dc438ab..e46cd6c822 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1,8 +1,7 @@ #include #include "Preset.hpp" -#include "AppConfig.hpp" -#include "I18N.hpp" +#include "slic3r/GUI/AppConfig.hpp" #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN @@ -10,6 +9,16 @@ #include #endif /* _MSC_VER */ +// instead of #include "slic3r/GUI/I18N.hpp" : +#ifndef L +// !!! If you needed to translate some string, +// !!! please use _L(string) +// !!! _() - is a standard wxWidgets macro to translate +// !!! L() is used only for marking localizable string +// !!! It will be used in "xgettext" to create a Locating Message Catalog. +#define L(s) s +#endif /* L */ + #include #include #include @@ -28,9 +37,9 @@ #include #include -#include "libslic3r/libslic3r.h" -#include "libslic3r/Utils.hpp" -#include "libslic3r/PlaceholderParser.hpp" +#include "libslic3r.h" +#include "Utils.hpp" +#include "PlaceholderParser.hpp" using boost::property_tree::ptree; @@ -237,9 +246,9 @@ const std::string& Preset::suffix_modified() return g_suffix_modified; } -void Preset::update_suffix_modified() +void Preset::update_suffix_modified(const std::string& new_suffix_modified) { - g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data(); + g_suffix_modified = new_suffix_modified; } // Remove an optional "(modified)" suffix from a name. // This converts a UI name to a unique preset identifier. diff --git a/src/slic3r/GUI/Preset.hpp b/src/libslic3r/Preset.hpp similarity index 99% rename from src/slic3r/GUI/Preset.hpp rename to src/libslic3r/Preset.hpp index 8a8fa024b6..b0af2f142b 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -8,16 +8,8 @@ #include #include -#include "libslic3r/libslic3r.h" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/Semver.hpp" - -class wxBitmap; -class wxBitmapComboBox; -class wxChoice; -class wxItemContainer; -class wxString; -class wxWindow; +#include "PrintConfig.hpp" +#include "Semver.hpp" namespace Slic3r { @@ -231,7 +223,7 @@ public: static const std::vector& sla_material_options(); static const std::vector& sla_print_options(); - static void update_suffix_modified(); + static void update_suffix_modified(const std::string& new_suffix_modified); static const std::string& suffix_modified(); static std::string remove_suffix_modified(const std::string& name); static void normalize(DynamicPrintConfig &config); diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp similarity index 99% rename from src/slic3r/GUI/PresetBundle.cpp rename to src/libslic3r/PresetBundle.cpp index 024884b00a..9da7731a45 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1,7 +1,9 @@ #include #include "PresetBundle.hpp" -#include "Plater.hpp" +#include "libslic3r.h" +#include "Utils.hpp" +#include "Model.hpp" #include #include @@ -19,14 +21,6 @@ #include #include -#include - -#include "libslic3r/libslic3r.h" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" -#include "GUI_App.hpp" -#include "libslic3r/CustomGCode.hpp" - // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions. @@ -49,9 +43,6 @@ PresetBundle::PresetBundle() : sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast(SLAFullPrintConfig::defaults())), printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -") { - if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) - wxImage::AddHandler(new wxPNGHandler); - // The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes, // therefore they need to be handled differently. As they have no counterpart in StaticPrintConfig, they are not being // initialized based on PrintConfigDef(), but to empty values (zeros, empty vectors, empty strings). @@ -822,8 +813,6 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 4) Load the project config values (the per extruder wipe matrix etc). this->project_config.apply_only(config, s_project_options); - CustomGCode::update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); - break; } case ptSLA: diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp similarity index 99% rename from src/slic3r/GUI/PresetBundle.hpp rename to src/libslic3r/PresetBundle.hpp index 7d137bb7ad..19d4093d63 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -1,14 +1,13 @@ #ifndef slic3r_PresetBundle_hpp_ #define slic3r_PresetBundle_hpp_ -#include "AppConfig.hpp" #include "Preset.hpp" #include #include #include -class wxWindow; +#include "slic3r/GUI/AppConfig.hpp" namespace Slic3r { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 98389e7dae..49e0692858 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -61,10 +61,6 @@ set(SLIC3R_GUI_SOURCES GUI/GLToolbar.cpp GUI/Preferences.cpp GUI/Preferences.hpp - GUI/Preset.cpp - GUI/Preset.hpp - GUI/PresetBundle.cpp - GUI/PresetBundle.hpp GUI/PresetHints.cpp GUI/PresetHints.hpp GUI/GUI.cpp diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 2264afa7d2..f7d313418d 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -1,6 +1,5 @@ #include "Snapshot.hpp" #include "../GUI/AppConfig.hpp" -#include "../GUI/PresetBundle.hpp" #include @@ -11,7 +10,7 @@ #include #include - +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/libslic3r.h" #include "libslic3r/Time.hpp" #include "libslic3r/Config.hpp" diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 6c070ca99a..f2f9f63012 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -7,7 +7,7 @@ #include "libslic3r/BoundingBox.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GLCanvas3D.hpp" #include diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index a0df4c6598..cd8463a772 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -2,7 +2,7 @@ #include "ConfigManipulation.hpp" #include "I18N.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index c99c5952b9..be2919861f 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -20,9 +20,9 @@ #include #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" #include "slic3r/Utils/PresetUpdater.hpp" #include "AppConfig.hpp" -#include "PresetBundle.hpp" #include "BedShapeDialog.hpp" #include "GUI.hpp" #include "wxExtensions.hpp" diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b4e672c4fb..b5cd9fb2a1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -13,11 +13,11 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" +#include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/BackgroundSlicingProcess.hpp" #include "slic3r/GUI/GLShader.hpp" #include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Tab.hpp" #include "slic3r/GUI/GUI_Preview.hpp" #include "slic3r/GUI/OpenGLManager.hpp" diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3c000f62e5..157875e708 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -31,11 +31,11 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/I18N.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" #include "AppConfig.hpp" -#include "PresetBundle.hpp" #include "3DScene.hpp" #include "MainFrame.hpp" #include "Plater.hpp" @@ -935,7 +935,7 @@ bool GUI_App::load_language(wxString language, bool initial) m_imgui->set_language(into_u8(language_info->CanonicalName)); //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified(); + Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); return true; } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index c2b257f458..23567695cd 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -3,10 +3,10 @@ #include #include -#include "Preset.hpp" #include "ImGuiWrapper.hpp" #include "ConfigWizard.hpp" #include "OpenGLManager.hpp" +#include "libslic3r/Preset.hpp" #include #include diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index b1a5512d4b..90a725fbfd 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -3,7 +3,7 @@ #include "OptionsGroup.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2f201180a8..b87565b03a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,4 +1,5 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/PresetBundle.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" @@ -7,7 +8,6 @@ #include "Plater.hpp" #include "OptionsGroup.hpp" -#include "PresetBundle.hpp" #include "Tab.hpp" #include "wxExtensions.hpp" #include "libslic3r/Model.hpp" diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 2c35fc316d..7243e8c73a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -6,7 +6,7 @@ #include "OptionsGroup.hpp" #include "GUI_App.hpp" #include "wxExtensions.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Geometry.hpp" #include "Selection.hpp" diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index ef78123a4c..398cd51d45 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -4,8 +4,8 @@ #include "OptionsGroup.hpp" #include "GUI_App.hpp" #include "wxExtensions.hpp" -#include "PresetBundle.hpp" #include "Plater.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" #include diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index c1e8b4c33c..f068ef37dc 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -8,7 +8,7 @@ #include "BackgroundSlicingProcess.hpp" #include "OpenGLManager.hpp" #include "GLCanvas3D.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "DoubleSlider.hpp" #include "Plater.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cd42857247..7aa5168459 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -6,9 +6,9 @@ #include #include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Plater.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 658db64cab..273384da2e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -9,7 +9,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 908fe27b11..2856bb35de 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -16,7 +16,7 @@ #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/SLAPrint.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 051e9cf880..6742f5cdef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -8,7 +8,7 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 511c68735c..c33ba2850e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -5,7 +5,6 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/Utils/UndoRedo.hpp" @@ -19,6 +18,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" #include diff --git a/src/slic3r/GUI/I18N.hpp b/src/slic3r/GUI/I18N.hpp index 25e46930ba..7bad6880e9 100644 --- a/src/slic3r/GUI/I18N.hpp +++ b/src/slic3r/GUI/I18N.hpp @@ -12,7 +12,7 @@ #ifndef L // !!! If you needed to translate some wxString, -// !!! please use _(L(string)) +// !!! please use _L(string) // !!! _() - is a standard wxWidgets macro to translate // !!! L() is used only for marking localizable string // !!! It will be used in "xgettext" to create a Locating Message Catalog. diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 4e9f08ff23..cc779df2ad 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -4,11 +4,11 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/AppConfig.hpp" #include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/Utils/SLAImport.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" #include #include diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d4ce21fc00..08caf299b5 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -15,9 +15,9 @@ #include "libslic3r/Print.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/SLAPrint.hpp" +#include "libslic3r/PresetBundle.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #include "ProgressStatusBar.hpp" #include "3DScene.hpp" #include "AppConfig.hpp" diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index baa9356b69..91d2414d51 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -1,9 +1,9 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/PresetBundle.hpp" #include "Mouse3DController.hpp" #include "Camera.hpp" #include "GUI_App.hpp" -#include "PresetBundle.hpp" #include "AppConfig.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 339badc96d..a78683bd4e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -43,6 +43,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -65,7 +66,6 @@ #include "Jobs/ArrangeJob.hpp" #include "Jobs/RotoptimizeJob.hpp" #include "Jobs/SLAImportJob.hpp" -#include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" #include "PrintHostDialogs.hpp" @@ -2120,6 +2120,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (!config.empty()) { Preset::normalize(config); wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config)); + if (printer_technology == ptFFF) + CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &wxGetApp().preset_bundle->project_config); wxGetApp().load_current_presets(); is_project_file = true; } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index d2acc7632e..38fc679825 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -7,9 +7,9 @@ #include -#include "Preset.hpp" #include "Selection.hpp" +#include "libslic3r/Preset.hpp" #include "libslic3r/BoundingBox.hpp" #include "Jobs/Job.hpp" #include "Search.hpp" diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 380edb48ad..51b5a0c8de 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -15,6 +15,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI.hpp" #include "GUI_App.hpp" @@ -22,7 +23,6 @@ #include "MainFrame.hpp" #include "format.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #include "PrintHostDialogs.hpp" #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 63110e4329..3d9a13490f 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -7,7 +7,7 @@ #include #include -#include "Preset.hpp" +#include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 24afeb526e..c40c4c6acb 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -4,7 +4,6 @@ #include "libslic3r/Slicing.hpp" #include "libslic3r/libslic3r.h" -#include "PresetBundle.hpp" #include "PresetHints.hpp" #include diff --git a/src/slic3r/GUI/PresetHints.hpp b/src/slic3r/GUI/PresetHints.hpp index be049c2c87..a61310f408 100644 --- a/src/slic3r/GUI/PresetHints.hpp +++ b/src/slic3r/GUI/PresetHints.hpp @@ -3,7 +3,7 @@ #include -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 613a39ccef..242e3d7256 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -9,10 +9,10 @@ #include "wx/dataview.h" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI_App.hpp" #include "Plater.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #define FTS_FUZZY_MATCH_IMPLEMENTATION #include "fts_fuzzy_match.h" diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 9701e68088..8202222e9d 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -14,8 +14,8 @@ #include #include "GUI_Utils.hpp" -#include "Preset.hpp" #include "wxExtensions.hpp" +#include "libslic3r/Preset.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b128ec03d4..88c11030d6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1,8 +1,8 @@ // #include "libslic3r/GCodeSender.hpp" #include "slic3r/Utils/Serial.hpp" #include "Tab.hpp" -#include "PresetBundle.hpp" #include "PresetHints.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" @@ -32,7 +32,6 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" -#include "ConfigWizard.hpp" #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index a0bf536c19..bc15efa359 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -33,8 +33,8 @@ #include "Event.hpp" #include "wxExtensions.hpp" #include "ConfigManipulation.hpp" -#include "Preset.hpp" #include "OptionsGroup.hpp" +#include "libslic3r/Preset.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index c32613c468..dec2518580 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -19,9 +19,9 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/format.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/I18N.hpp" -#include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/UpdateDialogs.hpp" #include "slic3r/GUI/ConfigWizard.hpp" #include "slic3r/GUI/GUI_App.hpp" From 7c7dcab03299932cab681fe82996ac96e579f0df Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 18 Jun 2020 11:39:25 +0200 Subject: [PATCH 132/826] First filling of the PhysicalPrinterDialog + Fixed scaling of the icons for the BitmapComboBoxes + Fixed calling of the blinking icons on the Tabs --- src/slic3r/GUI/Field.cpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 46 +++++++++++++++++------------ src/slic3r/GUI/PresetComboBoxes.hpp | 7 ++--- src/slic3r/GUI/Tab.cpp | 4 ++- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 3a06c3056e..8ab82e20d8 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -295,6 +295,7 @@ void Field::msw_rescale(bool rescale_sidetext) { m_Undo_to_sys_btn->msw_rescale(); m_Undo_btn->msw_rescale(); + m_blinking_bmp->msw_rescale(); // update em_unit value m_em_unit = em_unit(m_parent); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 51b5a0c8de..6c38c866d1 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -58,7 +58,7 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size, 0, nullptr, wxCB_READONLY), m_type(preset_type), m_last_selected(wxNOT_FOUND), - m_em_unit(wxGetApp().em_unit()), + m_em_unit(em_unit(this)), m_preset_bundle(wxGetApp().preset_bundle), m_bitmap_cache(new BitmapCache) { @@ -99,9 +99,9 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const default: break; } - m_bitmapCompatible = ScalableBitmap(nullptr, "flag_green"); - m_bitmapIncompatible = ScalableBitmap(nullptr, "flag_red"); - m_bitmapLock = ScalableBitmap(nullptr, "lock_closed"); + m_bitmapCompatible = ScalableBitmap(this, "flag_green"); + m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); + m_bitmapLock = ScalableBitmap(this, "lock_closed"); // parameters for an icon's drawing fill_width_height(); @@ -120,7 +120,7 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) void PresetComboBox::msw_rescale() { - m_em_unit = wxGetApp().em_unit(); + m_em_unit = em_unit(this); m_bitmapLock.msw_rescale(); m_bitmapIncompatible.msw_rescale(); @@ -241,7 +241,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset evt.StopPropagation(); if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) { - PhysicalPrinterDialog dlg; + PhysicalPrinterDialog dlg(_L("New Physical Printer"), this->m_last_selected); dlg.ShowModal(); return; } @@ -360,7 +360,7 @@ PlaterPresetComboBox::~PlaterPresetComboBox() void PlaterPresetComboBox::update() { if (m_type == Preset::TYPE_FILAMENT && - (m_collection->get_edited_preset().printer_technology() == ptSLA || + (m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA || m_preset_bundle->filament_presets.size() <= m_extruder_idx) ) return; @@ -586,13 +586,13 @@ void PlaterPresetComboBox::msw_rescale() // --------------------------------- -// *** PlaterPresetComboBox *** +// *** TabPresetComboBox *** // --------------------------------- -TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) : +TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type, bool is_from_physical_printer/* = false*/) : PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1)) { - Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + Bind(wxEVT_COMBOBOX, [this, is_from_physical_printer](wxCommandEvent& evt) { // see https://github.com/prusa3d/PrusaSlicer/issues/3889 // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. @@ -603,9 +603,16 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); if (marker == LABEL_ITEM_WIZARD_PRINTERS) - wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + wxTheApp->CallAfter([this, is_from_physical_printer]() { + wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); + if (is_from_physical_printer) + update(); + }); } - else if (m_last_selected != selected_item || m_collection->current_is_dirty()) { + else if ( is_from_physical_printer) { + // do nothing + } + else if (m_last_selected != selected_item || m_collection->current_is_dirty() ) { std::string selected_string = this->GetString(selected_item).ToUTF8().data(); Tab* tab = wxGetApp().get_tab(this->m_type); assert (tab); @@ -638,7 +645,7 @@ void TabPresetComboBox::update() continue; std::string bitmap_key = "tab"; - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this); if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) @@ -694,8 +701,8 @@ void TabPresetComboBox::update() if (bmp == nullptr) { // Create the bitmap with color bars. std::vector bmps; - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name, this)); + bmps.emplace_back(create_scaled_bitmap("edit_uni", this)); bmp = m_bitmap_cache->insert(bitmap_key, bmps); } set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); @@ -753,8 +760,8 @@ void TabPresetComboBox::update_dirty() //------------------------------------------ -PhysicalPrinterDialog::PhysicalPrinterDialog() - : DPIDialog(NULL, wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +PhysicalPrinterDialog::PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset) + : DPIDialog(NULL, wxID_ANY, _L("PhysicalPrinter"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -762,8 +769,9 @@ PhysicalPrinterDialog::PhysicalPrinterDialog() int border = 10; int em = em_unit(); - printer_text = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); - printer_presets = new PlaterPresetComboBox(this, Preset::TYPE_PRINTER); + printer_text = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER, true); + printer_presets->update(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 3d9a13490f..38b98d6586 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -43,7 +43,6 @@ public: }; void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); - int em_unit() const { return m_em_unit; } virtual void update() {}; virtual void msw_rescale(); @@ -131,13 +130,13 @@ private: // --------------------------------- -// *** PlaterPresetComboBox *** +// *** TabPresetComboBox *** // --------------------------------- class TabPresetComboBox : public PresetComboBox { public: - TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); + TabPresetComboBox(wxWindow *parent, Preset::Type preset_type, bool is_from_physical_printer = false); ~TabPresetComboBox() {} void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; @@ -165,7 +164,7 @@ class PhysicalPrinterDialog : public DPIDialog PresetComboBox* printer_presets; public: - PhysicalPrinterDialog(); + PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset); ~PhysicalPrinterDialog() {} protected: diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 88c11030d6..bb19e139d2 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -815,7 +815,7 @@ void Tab::update_visibility() void Tab::msw_rescale() { - m_em_unit = wxGetApp().em_unit(); + m_em_unit = em_unit(m_parent); m_mode_sizer->msw_rescale(); m_presets_choice->msw_rescale(); @@ -827,6 +827,8 @@ void Tab::msw_rescale() btn->msw_rescale(); for (const auto bmp : m_scaled_bitmaps) bmp->msw_rescale(); + for (const auto ikon : m_blinking_ikons) + ikon.second->msw_rescale(); for (ScalableBitmap& bmp : m_mode_bitmap_cache) bmp.msw_rescale(); From eb215fe994be09ae3ccaddba044a52e4169b5906 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 19 Jun 2020 15:32:44 +0200 Subject: [PATCH 133/826] ENABLE_GCODE_VIEWER_AS_STATE -> Removed tabs from gcode viewer state --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/3DBed.cpp | 16 ++++++++++++++ src/slic3r/GUI/MainFrame.cpp | 40 +++++++++++++++++++++++++++++++--- src/slic3r/GUI/MainFrame.hpp | 3 +++ 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 41b204d654..b04e78c4ed 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -60,7 +60,7 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (1 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 450a538d04..16ab95d6c4 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -694,7 +694,11 @@ void Bed3D::render_default(bool bottom) const { // draw background glsafe(::glDepthMask(GL_FALSE)); +#if ENABLE_LAYOUT_NO_RESTART + glsafe(::glColor4fv(m_model_color.data())); +#else glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); +#endif // ENABLE_LAYOUT_NO_RESTART glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); @@ -702,11 +706,23 @@ void Bed3D::render_default(bool bottom) const } // draw grid +#if ENABLE_LAYOUT_NO_RESTART + glsafe(::glLineWidth(1.5f * m_scale_factor)); +#else glsafe(::glLineWidth(3.0f * m_scale_factor)); +#endif // ENABLE_LAYOUT_NO_RESTART if (has_model && !bottom) +#if ENABLE_LAYOUT_NO_RESTART + glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 1.0f)); +#else glsafe(::glColor4f(0.75f, 0.75f, 0.75f, 1.0f)); +#endif // ENABLE_LAYOUT_NO_RESTART else +#if ENABLE_LAYOUT_NO_RESTART + glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.6f)); +#else glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); +#endif //ENABLE_LAYOUT_NO_RESTART glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 00cc7d7bb2..7f591bedd8 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -323,9 +323,16 @@ void MainFrame::update_layout() Layout(); }; +#if ENABLE_GCODE_VIEWER_AS_STATE + ESettingsLayout layout = (m_mode == EMode::GCodeViewer) ? ESettingsLayout::GCodeViewer : + (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old); +#else ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old; +#endif // ENABLE_GCODE_VIEWER_AS_STATE if (m_layout == layout) return; @@ -377,6 +384,14 @@ void MainFrame::update_layout() m_plater->Show(); break; } +#if ENABLE_GCODE_VIEWER_AS_STATE + case ESettingsLayout::GCodeViewer: + { + GetSizer()->Add(m_plater, 1, wxEXPAND); + m_plater->Show(); + break; + } +#endif // ENABLE_GCODE_VIEWER_AS_STATE } //#ifdef __APPLE__ @@ -1082,15 +1097,16 @@ void MainFrame::init_menubar() #endif m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _L("(Re)Slice No&w") + "\tCtrl+R", _L("Start new slicing process"), [this](wxCommandEvent&) { reslice_now(); }, "re_slice", nullptr, - [this](){return m_plater != nullptr && can_reslice(); }, this); + [this]() { return m_plater != nullptr && can_reslice(); }, this); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, - [this]() {return true; }, this); + [this]() { return true; }, this); #if ENABLE_GCODE_VIEWER_AS_STATE fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), - [this](wxCommandEvent&) { set_mode(EMode::GCodeViewer); }); + [this](wxCommandEvent&) { set_mode(EMode::GCodeViewer); }, "", nullptr, + [this]() { return m_plater != nullptr && m_plater->printer_technology() != ptSLA; }, this); #endif // ENABLE_GCODE_VIEWER_AS_STATE fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), @@ -1381,16 +1397,30 @@ void MainFrame::init_gcodeviewer_menubar() void MainFrame::set_mode(EMode mode) { + if (m_mode == mode) + return; + + wxBusyCursor busy; + m_mode = mode; switch (m_mode) { default: case EMode::Editor: { +#if ENABLE_LAYOUT_NO_RESTART + update_layout(); + select_tab(0); +#endif // ENABLE_LAYOUT_NO_RESTART + m_plater->reset(); m_plater->Freeze(); + // reinitialize undo/redo stack + m_plater->clear_undo_redo_stack_main(); + m_plater->take_snapshot(_L("New Project")); + // switch view m_plater->select_view_3D("3D"); m_plater->select_view("iso"); @@ -1421,6 +1451,10 @@ void MainFrame::set_mode(EMode mode) } case EMode::GCodeViewer: { +#if ENABLE_LAYOUT_NO_RESTART + update_layout(); +#endif // ENABLE_LAYOUT_NO_RESTART + m_plater->reset(); m_plater->reset_last_loaded_gcode(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 9ec2d991a5..931dd87b28 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -138,6 +138,9 @@ class MainFrame : public DPIFrame Old, New, Dlg, +#if ENABLE_GCODE_VIEWER_AS_STATE + GCodeViewer +#endif // ENABLE_GCODE_VIEWER_AS_STATE }; ESettingsLayout m_layout{ ESettingsLayout::Unknown }; From 2a90cd2849b91111f1463d70e15fb755da38e193 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 22 Jun 2020 09:10:41 +0200 Subject: [PATCH 134/826] GCodeViewer -> Do not show modifier shells --- src/slic3r/GUI/GCodeViewer.cpp | 39 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 215faee594..cccb31969c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -282,7 +282,10 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& reset(); load_toolpaths(gcode_result); - load_shells(print, initialized); +#if ENABLE_GCODE_VIEWER_AS_STATE + if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) +#endif // ENABLE_GCODE_VIEWER_AS_STATE + load_shells(print, initialized); #if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { @@ -493,21 +496,21 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) return; // vertex data / bounding box -> extract from result - std::vector vertices_data(m_vertices.vertices_count * 3); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; + std::vector vertices_data(m_vertices.vertices_count * 3); + for (size_t i = 0; i < m_vertices.vertices_count; ++i) { + const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; #if ENABLE_GCODE_VIEWER_AS_STATE - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) + if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) + m_bounding_box.merge(move.position.cast()); + else { +#endif // ENABLE_GCODE_VIEWER_AS_STATE + if (move.type == GCodeProcessor::EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) m_bounding_box.merge(move.position.cast()); - else { -#endif // ENABLE_GCODE_VIEWER_AS_STATE - if (move.type == GCodeProcessor::EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) - m_bounding_box.merge(move.position.cast()); #if ENABLE_GCODE_VIEWER_AS_STATE - } -#endif // ENABLE_GCODE_VIEWER_AS_STATE - ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); } +#endif // ENABLE_GCODE_VIEWER_AS_STATE + ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); + } m_bounding_box.merge(m_bounding_box.max + m_sequential_view.marker.get_bounding_box().max[2] * Vec3d::UnitZ()); @@ -684,6 +687,18 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } + // remove modifiers + while (true) { + GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); + if (it != m_shells.volumes.volumes.end()) + { + delete (*it); + m_shells.volumes.volumes.erase(it); + } + else + break; + } + for (GLVolume* volume : m_shells.volumes.volumes) { volume->zoom_to_volumes = false; From dc6f97a6ad473c9f137ebf30b753b298b64a9f56 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 22 Jun 2020 11:49:58 +0200 Subject: [PATCH 135/826] ENABLE_GCODE_VIEWER_AS_STATE -> Fixed toolpaths visualization when switching between states and when exporting g-code --- src/slic3r/GUI/GUI_Preview.cpp | 6 ++---- src/slic3r/GUI/MainFrame.cpp | 2 ++ src/slic3r/GUI/Plater.cpp | 26 ++++++++++++++++++++++++++ src/slic3r/GUI/Plater.hpp | 2 ++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 05168fc333..6ba3835075 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -510,11 +510,9 @@ void Preview::reload_print(bool keep_volumes) !keep_volumes) { m_canvas->reset_volumes(); -#if ENABLE_GCODE_VIEWER - m_canvas->reset_gcode_toolpaths(); -#else +#if !ENABLE_GCODE_VIEWER m_canvas->reset_legend_texture(); -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER m_loaded = false; #ifdef __linux__ m_volumes_cleanup_required = false; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7f591bedd8..2e25752a1a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1414,6 +1414,7 @@ void MainFrame::set_mode(EMode mode) #endif // ENABLE_LAYOUT_NO_RESTART m_plater->reset(); + m_plater->reset_gcode_toolpaths(); m_plater->Freeze(); @@ -1457,6 +1458,7 @@ void MainFrame::set_mode(EMode mode) m_plater->reset(); m_plater->reset_last_loaded_gcode(); + m_plater->reset_gcode_toolpaths(); m_plater->Freeze(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ae4c4ba000..18c185551d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1701,6 +1701,10 @@ struct Plater::priv void update_preview_moves_slider(); #endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + void reset_gcode_toolpaths(); +#endif // ENABLE_GCODE_VIEWER + void reset_all_gizmos(); void update_ui_from_settings(); void update_main_toolbar_tooltips(); @@ -4008,6 +4012,13 @@ void Plater::priv::update_preview_moves_slider() } #endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER +void Plater::priv::reset_gcode_toolpaths() +{ + preview->get_canvas3d()->reset_gcode_toolpaths(); +} +#endif // ENABLE_GCODE_VIEWER + bool Plater::priv::can_set_instance_to_object() const { const int obj_idx = get_selected_object_idx(); @@ -5060,6 +5071,9 @@ void Plater::reslice() if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; +#if ENABLE_GCODE_VIEWER + bool clean_gcode_toolpaths = true; +#endif // ENABLE_GCODE_VIEWER if (p->background_process.running()) { if (wxGetApp().get_mode() == comSimple) @@ -5072,6 +5086,13 @@ void Plater::reslice() } else if (!p->background_process.empty() && !p->background_process.idle()) p->show_action_buttons(true); +#if ENABLE_GCODE_VIEWER + else + clean_gcode_toolpaths = false; + + if (clean_gcode_toolpaths) + reset_gcode_toolpaths(); +#endif // ENABLE_GCODE_VIEWER // update type of preview p->preview->update_view_type(true); @@ -5750,6 +5771,11 @@ void Plater::update_preview_moves_slider() { p->update_preview_moves_slider(); } + +void Plater::reset_gcode_toolpaths() +{ + p->reset_gcode_toolpaths(); +} #endif // ENABLE_GCODE_VIEWER const Mouse3DController& Plater::get_mouse3d_controller() const diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9759a7ffc3..8d0c935a4c 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -350,6 +350,8 @@ public: #if ENABLE_GCODE_VIEWER void update_preview_bottom_toolbar(); void update_preview_moves_slider(); + + void reset_gcode_toolpaths(); #endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER_AS_STATE From 88670b48fd66b8f7561d7a4b54ba71460e4fbf19 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 22 Jun 2020 12:10:18 +0200 Subject: [PATCH 136/826] ENABLE_GCODE_VIEWER_AS_STATE -> Added dialog informing user that all objects will be removed when switching to g-code viewer mode --- src/slic3r/GUI/MainFrame.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 2e25752a1a..366dc44711 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1105,7 +1105,10 @@ void MainFrame::init_menubar() #if ENABLE_GCODE_VIEWER_AS_STATE fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), - [this](wxCommandEvent&) { set_mode(EMode::GCodeViewer); }, "", nullptr, + [this](wxCommandEvent&) { + if (m_plater->model().objects.empty() || wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) + set_mode(EMode::GCodeViewer); + }, "", nullptr, [this]() { return m_plater != nullptr && m_plater->printer_technology() != ptSLA; }, this); #endif // ENABLE_GCODE_VIEWER_AS_STATE fileMenu->AppendSeparator(); From 7207f215e98289bdc39c157193698b987e720a97 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 22 Jun 2020 12:43:52 +0200 Subject: [PATCH 137/826] ENABLE_GCODE_VIEWER_AS_STATE -> Do not show warning texture in gcode viewer mode --- src/slic3r/GUI/GLCanvas3D.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e7be2c4019..5c72ae0fa7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7110,8 +7110,18 @@ void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning show = _is_any_volume_outside(); else { - BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - show = (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; +#if ENABLE_GCODE_VIEWER_AS_STATE + if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) + { + BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); + const BoundingBoxf3& paths_volume = m_gcode_viewer.get_bounding_box(); + if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) + show = !test_volume.contains(paths_volume); + } +#else + BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); + show = (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; +#endif // ENABLE_GCODE_VIEWER_AS_STATE } _set_warning_texture(warning, show); #else From 289f7a14a016b102c91dc7bb15dbaccc07683892 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 22 Jun 2020 14:06:41 +0200 Subject: [PATCH 138/826] Follow-up of dc6f97a6ad473c9f137ebf30b753b298b64a9f56 -> Fixed toolpaths visualization when new slicing is required --- src/slic3r/GUI/GUI_Preview.cpp | 12 +++++++++--- src/slic3r/GUI/GUI_Preview.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 5 ++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6ba3835075..48d6e930b0 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -510,9 +510,11 @@ void Preview::reload_print(bool keep_volumes) !keep_volumes) { m_canvas->reset_volumes(); -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + m_canvas->reset_gcode_toolpaths(); +#else m_canvas->reset_legend_texture(); -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER m_loaded = false; #ifdef __linux__ m_volumes_cleanup_required = false; @@ -761,7 +763,7 @@ void Preview::on_checkbox_legend(wxCommandEvent& evt) } #endif // ENABLE_GCODE_VIEWER -void Preview::update_view_type(bool slice_completed) +void Preview::update_view_type(bool keep_volumes) { const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; @@ -785,7 +787,11 @@ void Preview::update_view_type(bool slice_completed) m_preferred_color_mode = "feature"; } +#if ENABLE_GCODE_VIEWER + reload_print(keep_volumes); +#else reload_print(); +#endif // ENABLE_GCODE_VIEWER } #if ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 291ee4156a..bf174c2e09 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -186,7 +186,7 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, void edit_double_slider(wxKeyEvent& evt); #endif // ENABLE_GCODE_VIEWER - void update_view_type(bool slice_completed); + void update_view_type(bool keep_volumes); bool is_loaded() const { return m_loaded; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 18c185551d..70ee1a7589 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5092,10 +5092,13 @@ void Plater::reslice() if (clean_gcode_toolpaths) reset_gcode_toolpaths(); -#endif // ENABLE_GCODE_VIEWER + // update type of preview + p->preview->update_view_type(!clean_gcode_toolpaths); +#else // update type of preview p->preview->update_view_type(true); +#endif // ENABLE_GCODE_VIEWER } void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages) From ca7dce9f0296da29f1d52009526d5019903d474a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 22 Jun 2020 15:42:27 +0200 Subject: [PATCH 139/826] Follow-up of dc6f97a6ad473c9f137ebf30b753b298b64a9f56 -> Fixed toolpaths visualization when editing config data --- src/slic3r/GUI/GUI_Preview.cpp | 6 ++---- src/slic3r/GUI/Plater.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 48d6e930b0..2ddb59b863 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -510,11 +510,9 @@ void Preview::reload_print(bool keep_volumes) !keep_volumes) { m_canvas->reset_volumes(); -#if ENABLE_GCODE_VIEWER - m_canvas->reset_gcode_toolpaths(); -#else +#if !ENABLE_GCODE_VIEWER m_canvas->reset_legend_texture(); -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER m_loaded = false; #ifdef __linux__ m_volumes_cleanup_required = false; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 70ee1a7589..bd19c94c79 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2861,10 +2861,20 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool this->sidebar->show_sliced_info_sizer(false); // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. // Otherwise they will be just refreshed. +#if ENABLE_GCODE_VIEWER + if (this->preview != nullptr) + { + // If the preview is not visible, the following line just invalidates the preview, + // but the G-code paths or SLA preview are calculated first once the preview is made visible. + this->preview->get_canvas3d()->reset_gcode_toolpaths(); + this->preview->reload_print(); + } +#else if (this->preview != nullptr) // If the preview is not visible, the following line just invalidates the preview, // but the G-code paths or SLA preview are calculated first once the preview is made visible. this->preview->reload_print(); +#endif // ENABLE_GCODE_VIEWER // In FDM mode, we need to reload the 3D scene because of the wipe tower preview box. // In SLA mode, we need to reload the 3D scene every time to show the support structures. if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower"))) From 779dcd58c8687725cff75ec7b3540679c2c9631f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 23 Jun 2020 09:01:28 +0200 Subject: [PATCH 140/826] GCodeViewer -> Line width of toolpaths dependent on zoom --- src/slic3r/GUI/GCodeViewer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index cccb31969c..3bba4a7723 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -870,8 +870,12 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; + auto line_width = [zoom]() { + return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); + }; + glsafe(::glCullFace(GL_BACK)); - glsafe(::glLineWidth(3.0f)); + glsafe(::glLineWidth(static_cast(line_width()))); unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); From 7e815b47277fbdea481ce5d807c3eb76d588eb6b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 23 Jun 2020 14:31:08 +0200 Subject: [PATCH 141/826] GCodeViewer -> Fixed sequential view endpoints when moving the vertical slider thumb --- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2ddb59b863..530165001f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1440,7 +1440,7 @@ void Preview::on_sliders_scroll_changed(wxCommandEvent& event) #if ENABLE_GCODE_VIEWER void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event) { - m_canvas->update_gcode_sequential_view_current(static_cast(m_moves_slider->GetLowerValueD()), static_cast(m_moves_slider->GetHigherValueD())); + m_canvas->update_gcode_sequential_view_current(static_cast(m_moves_slider->GetLowerValueD() - 1.0), static_cast(m_moves_slider->GetHigherValueD() - 1.0)); m_canvas->render(); } From 81a7b7782b3b9c7cc371011a60d45afcea4983ce Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 23 Jun 2020 15:22:52 +0200 Subject: [PATCH 142/826] GCodeViewer -> Some refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 149 ++++++++++----------------------- 1 file changed, 46 insertions(+), 103 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3bba4a7723..415b61aeb9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -44,8 +44,7 @@ std::vector> decode_colors(const std::vector & static const float INV_255 = 1.0f / 255.0f; std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f }); - for (size_t i = 0; i < colors.size(); ++i) - { + for (size_t i = 0; i < colors.size(); ++i) { const std::string& color = colors[i]; const char* c = color.data() + 1; if ((color.size() == 7) && (color.front() == '#')) { @@ -293,11 +292,10 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& const double margin = 10.0; Vec2d min(m_bounding_box.min(0) - margin, m_bounding_box.min(1) - margin); Vec2d max(m_bounding_box.max(0) + margin, m_bounding_box.max(1) + margin); - Pointfs bed_shape = { - { min(0), min(1) }, - { max(0), min(1) }, - { max(0), max(1) }, - { min(0), max(1) } }; + Pointfs bed_shape = { { min(0), min(1) }, + { max(0), min(1) }, + { max(0), max(1) }, + { min(0), max(1) } }; wxGetApp().plater()->set_bed_shape(bed_shape, "", ""); } #endif // ENABLE_GCODE_VIEWER_AS_STATE @@ -317,8 +315,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update ranges for coloring / legend m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) - { + for (size_t i = 0; i < m_vertices.vertices_count; ++i) { // skip first vertex if (i == 0) continue; @@ -466,8 +463,7 @@ void GCodeViewer::init_shaders() unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - for (unsigned char i = begin_id; i < end_id; ++i) - { + for (unsigned char i = begin_id; i < end_id; ++i) { switch (buffer_type(i)) { case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } @@ -530,8 +526,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // indices data -> extract from result std::vector> indices(m_buffers.size()); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) - { + for (size_t i = 0; i < m_vertices.vertices_count; ++i) { // skip first vertex if (i == 0) continue; @@ -560,10 +555,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Travel: { if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(i)); + buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(i - 1)); Path& last_path = buffer.paths.back(); last_path.first.position = prev.position; - last_path.first.s_id = static_cast(i - 1); buffer_indices.push_back(static_cast(i - 1)); } @@ -571,23 +565,18 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer_indices.push_back(static_cast(i)); break; } - default: - { - break; - } + default: { break; } } } #if ENABLE_GCODE_VIEWER_STATISTICS - for (IBuffer& buffer : m_buffers) - { + for (IBuffer& buffer : m_buffers) { m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } #endif // ENABLE_GCODE_VIEWER_STATISTICS // indices data -> send data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) - { + for (size_t i = 0; i < m_buffers.size(); ++i) { IBuffer& buffer = m_buffers[i]; std::vector& buffer_indices = indices[i]; buffer.indices_count = buffer_indices.size(); @@ -605,8 +594,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } // layers zs / roles / extruder ids / cp color ids -> extract from result - for (size_t i = 0; i < m_vertices.vertices_count; ++i) - { + for (size_t i = 0; i < m_vertices.vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (move.type == GCodeProcessor::EMoveType::Extrude) m_layers_zs.emplace_back(static_cast(move.position[2])); @@ -655,8 +643,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) // adds objects' volumes int object_id = 0; - for (const PrintObject* obj : print.objects()) - { + for (const PrintObject* obj : print.objects()) { const ModelObject* model_obj = obj->model_object(); std::vector instance_ids(model_obj->instances.size()); @@ -690,8 +677,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) // remove modifiers while (true) { GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; }); - if (it != m_shells.volumes.volumes.end()) - { + if (it != m_shells.volumes.volumes.end()) { delete (*it); m_shells.volumes.volumes.erase(it); } @@ -699,8 +685,7 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) break; } - for (GLVolume* volume : m_shells.volumes.volumes) - { + for (GLVolume* volume : m_shells.volumes.volumes) { volume->zoom_to_volumes = false; volume->color[3] = 0.25f; volume->force_native_color = true; @@ -841,8 +826,8 @@ void GCodeViewer::render_toolpaths() const Transform3d inv_proj = camera.get_projection_matrix().inverse(); - auto render_options = [this, zoom, inv_proj, viewport, point_size, near_plane_height](const IBuffer& buffer, EOptionsColors colors_id, GLShaderProgram& shader) { - shader.set_uniform("uniform_color", Options_Colors[static_cast(colors_id)]); + auto render_as_points = [this, zoom, inv_proj, viewport, point_size, near_plane_height](const IBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { + shader.set_uniform("uniform_color", Options_Colors[static_cast(color_id)]); shader.set_uniform("zoom", zoom); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.percent_outline)); @@ -870,6 +855,18 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; + auto render_as_lines = [this](const IBuffer& buffer, GLShaderProgram& shader) { + for (const RenderPath& path : buffer.render_paths) + { + shader.set_uniform("uniform_color", path.color); +// glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_line_strip_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + }; + auto line_width = [zoom]() { return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); }; @@ -902,62 +899,14 @@ void GCodeViewer::render_toolpaths() const switch (type) { - case GCodeProcessor::EMoveType::Tool_change: - { - render_options(buffer, EOptionsColors::ToolChanges, *shader); - break; - } - case GCodeProcessor::EMoveType::Color_change: - { - render_options(buffer, EOptionsColors::ColorChanges, *shader); - break; - } - case GCodeProcessor::EMoveType::Pause_Print: - { - render_options(buffer, EOptionsColors::PausePrints, *shader); - break; - } - case GCodeProcessor::EMoveType::Custom_GCode: - { - render_options(buffer, EOptionsColors::CustomGCodes, *shader); - break; - } - case GCodeProcessor::EMoveType::Retract: - { - render_options(buffer, EOptionsColors::Retractions, *shader); - break; - } - case GCodeProcessor::EMoveType::Unretract: - { - render_options(buffer, EOptionsColors::Unretractions, *shader); - break; - } + case GCodeProcessor::EMoveType::Tool_change: { render_as_points(buffer, EOptionsColors::ToolChanges, *shader); break; } + case GCodeProcessor::EMoveType::Color_change: { render_as_points(buffer, EOptionsColors::ColorChanges, *shader); break; } + case GCodeProcessor::EMoveType::Pause_Print: { render_as_points(buffer, EOptionsColors::PausePrints, *shader); break; } + case GCodeProcessor::EMoveType::Custom_GCode: { render_as_points(buffer, EOptionsColors::CustomGCodes, *shader); break; } + case GCodeProcessor::EMoveType::Retract: { render_as_points(buffer, EOptionsColors::Retractions, *shader); break; } + case GCodeProcessor::EMoveType::Unretract: { render_as_points(buffer, EOptionsColors::Unretractions, *shader); break; } case GCodeProcessor::EMoveType::Extrude: - { - for (const RenderPath& path : buffer.render_paths) - { - shader->set_uniform("uniform_color", path.color); - glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_line_strip_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - } - break; - } - case GCodeProcessor::EMoveType::Travel: - { - for (const RenderPath& path : buffer.render_paths) - { - shader->set_uniform("uniform_color", path.color); - glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_line_strip_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - } - break; - } + case GCodeProcessor::EMoveType::Travel: { render_as_lines(buffer, *shader); break; } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -1090,8 +1039,7 @@ void GCodeViewer::render_legend() const // draw text ImGui::Dummy({ icon_size, icon_size }); ImGui::SameLine(); - if (callback != nullptr) - { + if (callback != nullptr) { if (ImGui::MenuItem(label.c_str())) callback(); } @@ -1114,8 +1062,7 @@ void GCodeViewer::render_legend() const if (step_size == 0.0f) // single item use case add_range_item(0, range.min, decimals); - else - { + else { for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { add_range_item(i, range.min + static_cast(i) * step_size, decimals); } @@ -1297,8 +1244,7 @@ void GCodeViewer::render_legend() const } // travel paths - if (m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)].visible) - { + if (m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)].visible) { switch (m_view_type) { case EViewType::Feedrate: @@ -1347,8 +1293,7 @@ void GCodeViewer::render_legend() const }; // options - if (any_option_visible()) - { + if (any_option_visible()) { // title ImGui::Spacing(); ImGui::Spacing(); @@ -1386,19 +1331,19 @@ void GCodeViewer::render_statistics() const imgui.text(std::string("Load time:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.load_time) + "ms"); + imgui.text(std::to_string(m_statistics.load_time) + " ms"); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Resfresh time:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_time) + "ms"); + imgui.text(std::to_string(m_statistics.refresh_time) + " ms"); ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Resfresh paths time:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_paths_time) + "ms"); + imgui.text(std::to_string(m_statistics.refresh_paths_time) + " ms"); ImGui::Separator(); @@ -1496,11 +1441,9 @@ void GCodeViewer::render_shaders_editor() const case 2: { set_shader("options_120_solid"); break; } } - if (ImGui::CollapsingHeader("Options", ImGuiTreeNodeFlags_DefaultOpen)) - { + if (ImGui::CollapsingHeader("Options", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::SliderFloat("point size", &m_shaders_editor.point_size, 0.5f, 3.0f, "%.1f"); - if (m_shaders_editor.shader_version == 1) - { + if (m_shaders_editor.shader_version == 1) { ImGui::SliderInt("percent outline", &m_shaders_editor.percent_outline, 0, 50); ImGui::SliderInt("percent center", &m_shaders_editor.percent_center, 0, 50); } From 02624689ce345dc20d24194d4c6b4d8c438d7907 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jun 2020 08:50:01 +0200 Subject: [PATCH 143/826] Physical Printers. - save/load printers - consistency between selection on Tab and Plater --- src/libslic3r/Preset.cpp | 250 ++++++++++++-------- src/libslic3r/Preset.hpp | 109 ++++----- src/libslic3r/PresetBundle.cpp | 24 +- src/libslic3r/PresetBundle.hpp | 1 + src/libslic3r/PrintConfig.cpp | 20 ++ src/slic3r/GUI/Plater.cpp | 30 ++- src/slic3r/GUI/PresetComboBoxes.cpp | 342 ++++++++++++++++++++++++---- src/slic3r/GUI/PresetComboBoxes.hpp | 37 ++- src/slic3r/GUI/Tab.cpp | 10 + 9 files changed, 600 insertions(+), 223 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e46cd6c822..94c9577dfb 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1339,108 +1339,178 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model return it != cend() ? &*it : nullptr; } -/* -PhysicalPrinter& PhysicalPrinterCollection::load_external_printer( - // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) - const std::string& path, - // Name of the profile, derived from the source file name. - const std::string& name, - // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. - const std::string& original_name, - // Config to initialize the preset from. - const DynamicPrintConfig& config, - // Select the preset after loading? - bool select) -{ - // Load the preset over a default preset, so that the missing fields are filled in from the default preset. - DynamicPrintConfig cfg(this->default_printer().config); - cfg.apply_only(config, cfg.keys(), true); - // Is there a preset already loaded with the name stored inside the config? - std::deque::iterator it = this->find_printer_internal(original_name); - bool found = it != m_printers.end() && it->name == original_name; - if (!found) { - // Try to match the original_name against the "renamed_from" profile names of loaded system profiles. - / * - it = this->find_preset_renamed(original_name); - found = it != m_presets.end(); - * / - } - if (found) { - if (profile_print_params_same(it->config, cfg)) { - // The preset exists and it matches the values stored inside config. - if (select) - this->select_printer(it - m_printers.begin()); - return *it; - } - if (profile_host_params_same_or_anonymized(it->config, cfg) == ProfileHostParams::Anonymized) { - // The project being loaded is anonymized. Replace the empty host keys of the loaded profile with the data from the original profile. - // See "Octoprint Settings when Opening a .3MF file" GH issue #3244 - auto opt_update = [it, &cfg](const std::string& opt_key) { - auto opt = it->config.option(opt_key); - if (opt != nullptr) - cfg.set_key_value(opt_key, opt->clone()); - }; - opt_update("print_host"); - opt_update("printhost_apikey"); - opt_update("printhost_cafile"); - } - } - // The external preset does not match an internal preset, load the external preset. - std::string new_name; - for (size_t idx = 0;; ++idx) { - std::string suffix; - if (original_name.empty()) { - if (idx > 0) - suffix = " (" + std::to_string(idx) + ")"; - } - else { - if (idx == 0) - suffix = " (" + original_name + ")"; - else - suffix = " (" + original_name + "-" + std::to_string(idx) + ")"; - } - new_name = name + suffix; - it = this->find_printer_internal(new_name); - if (it == m_printers.end() || it->name != new_name) - // Unique profile name. Insert a new profile. - break; - if (profile_print_params_same(it->config, cfg)) { - // The preset exists and it matches the values stored inside config. - if (select) - this->select_printer(it - m_printers.begin()); - return *it; - } - // Form another profile name. - } - // Insert a new profile. - PhysicalPrinter& printer = this->load_printer(path, new_name, std::move(cfg), select); - return printer; +// ------------------------- +// *** PhysicalPrinter *** +// ------------------------- + +const std::vector& PhysicalPrinter::printer_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "preset_name", + "printer_technology", + "host_type", + "print_host", + "printhost_apikey", + "printhost_cafile", + "login", + "password" + }; + } + return s_opts; } -void PhysicalPrinterCollection::save_printer(const std::string& new_name) +const std::string& PhysicalPrinter::get_preset_name() +{ + return config.opt_string("preset_name"); +} + +void PhysicalPrinter::update_from_preset(const Preset& preset) +{ + config.apply_only(preset.config, printer_options(), false); + // add preset name to the options list + config.set_key_value("preset_name", new ConfigOptionString(preset.name)); +} + +void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) +{ + config.apply_only(new_config, printer_options(), false); +} + +PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : + name(name) +{ + update_from_preset(preset); +} + + +// ----------------------------------- +// *** PhysicalPrinterCollection *** +// ----------------------------------- + +PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector& keys) +{ +} + +// Load all presets found in dir_path. +// Throws an exception on error. +void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir) +{ + boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); + m_dir_path = dir.string(); + std::string errors_cummulative; + // Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken. + std::deque printers_loaded; + for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) + if (Slic3r::is_ini_file(dir_entry)) { + std::string name = dir_entry.path().filename().string(); + // Remove the .ini suffix. + name.erase(name.size() - 4); + if (this->find_printer(name, false)) { + // This happens when there's is a preset (most likely legacy one) with the same name as a system preset + // that's already been loaded from a bundle. + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; + continue; + } + try { + PhysicalPrinter printer(name); + printer.file = dir_entry.path().string(); + // Load the preset file, apply preset values on top of defaults. + try { + DynamicPrintConfig config; + config.load_from_ini(printer.file); + printer.update_from_config(config); + printer.loaded = true; + } + catch (const std::ifstream::failure& err) { + throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); + } + catch (const std::runtime_error& err) { + throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); + } + printers_loaded.emplace_back(printer); + } + catch (const std::runtime_error& err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + } + } + m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); + std::sort(m_printers.begin(), m_printers.end()); +//! this->select_preset(first_visible_idx()); + if (!errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); +} + +PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found) +{ + PhysicalPrinter key(name); + auto it = this->find_printer_internal(name); + // Ensure that a temporary copy is returned if the preset found is currently selected. + return (it != m_printers.end() && it->name == key.name) ? &this->printer(it - m_printers.begin()) : + first_visible_if_not_found ? &this->printer(0) : nullptr; +} + +// Generate a file path from a profile name. Add the ".ini" suffix if it is missing. +std::string PhysicalPrinterCollection::path_from_name(const std::string& new_name) const +{ + std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); + return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); +} + +void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer) { // 1) Find the printer with a new_name or create a new one, // initialize it with the edited config. - auto it = this->find_printer_internal(new_name); - if (it != m_printers.end() && it->name == new_name) { - // Preset with the same name found. - PhysicalPrinter& printer = *it; + auto it = this->find_printer_internal(edited_printer.name); + if (it != m_printers.end() && it->name == edited_printer.name) { + // Printer with the same name found. // Overwriting an existing preset. - printer.config = std::move(m_edited_printer.config); + it->config = std::move(edited_printer.config); } else { // Creating a new printer. - PhysicalPrinter& printer = *m_printers.insert(it, m_edited_printer); - std::string old_name = printer.name; - printer.name = new_name; + it = m_printers.insert(it, edited_printer); } - // 2) Activate the saved preset. - this->select_printer_by_name(new_name, true); - // 3) Store the active preset to disk. - this->get_selected_preset().save(); + assert(it != m_printers.end()); + + // 2) Save printer + PhysicalPrinter& printer = *it; + if (printer.file.empty()) + printer.file = this->path_from_name(printer.name); + printer.save(); + + // update idx_selected + m_idx_selected = it - m_printers.begin(); } -*/ + +bool PhysicalPrinterCollection::delete_printer(const std::string& name) +{ + auto it = this->find_printer_internal(name); + + const PhysicalPrinter& printer = *it; + if (it == m_printers.end()) + return false; + + // Erase the preset file. + boost::nowide::remove(printer.file.c_str()); + m_printers.erase(it); + return true; +} + +PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& name) +{ + auto it = this->find_printer_internal(name); + assert(it != m_printers.end()); + + // update idx_selected + m_idx_selected = it - m_printers.begin(); + return *it; +} + + namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index b0af2f142b..c08a1a0fbc 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -535,14 +535,14 @@ namespace PresetUtils { class PhysicalPrinter { public: - PhysicalPrinter(const std::string& name) : name(name) {} + PhysicalPrinter() {} + PhysicalPrinter(const std::string& name) : name(name){} + PhysicalPrinter(const std::string& name, const Preset& preset); // Name of the Physical Printer, usually derived form the file name. std::string name; // File name of the Physical Printer. std::string file; - // Name of the related Printer preset - std::string preset_name; // Has this profile been loaded? bool loaded = false; @@ -550,7 +550,13 @@ public: // Configuration data, loaded from a file, or set from the defaults. DynamicPrintConfig config; + static const std::vector& printer_options(); + const std::string& get_preset_name(); + void save() { this->config.save(this->file); } + void save_to(const std::string& file_name) const { this->config.save(file_name); } + void update_from_preset(const Preset& preset); + void update_from_config(const DynamicPrintConfig &new_config); // Return a printer technology, return ptFFF if the printer technology is not set. static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) { @@ -562,18 +568,22 @@ public: PrinterTechnology printer_technology() const { return printer_technology(this->config); } // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. - bool operator<(const Preset& other) const { return this->name < other.name; } + bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; } protected: friend class PhysicalPrinterCollection; }; -/* -// Collections of presets of the same type (one of the Print, Filament or Printer type). + + +// --------------------------------- +// *** PhysicalPrinterCollection *** +// --------------------------------- + +// Collections of physical printers class PhysicalPrinterCollection { public: - // Initialize the PresetCollection with the "- default -" preset. - PhysicalPrinterCollection(const std::vector& keys) : m_idx_selected(0) {} + PhysicalPrinterCollection(const std::vector& keys); ~PhysicalPrinterCollection() {} typedef std::deque::iterator Iterator; @@ -585,63 +595,39 @@ public: ConstIterator end() const { return m_printers.cend(); } ConstIterator cend() const { return m_printers.cend(); } + bool empty() const {return m_printers.empty(); } + void reset(bool delete_files) {}; const std::deque& operator()() const { return m_printers; } // Load ini files of the particular type from the provided directory path. - void load_printers(const std::string& dir_path, const std::string& subdir){}; - - // Load a preset from an already parsed config file, insert it into the sorted sequence of presets - // and select it, losing previous modifications. - PhysicalPrinter& load_printer(const std::string& path, const std::string& name, const DynamicPrintConfig& config, bool select = true); - PhysicalPrinter& load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select = true); - - PhysicalPrinter& load_external_printer( - // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) - const std::string& path, - // Name of the profile, derived from the source file name. - const std::string& name, - // Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored. - const std::string& original_name, - // Config to initialize the preset from. - const DynamicPrintConfig& config, - // Select the preset after loading? - bool select = true); + void load_printers(const std::string& dir_path, const std::string& subdir); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. - // ? New printer is activated. - void save_printer(const std::string& new_name); + // New printer is activated. + void save_printer(const PhysicalPrinter& printer); // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. - bool delete_current_printer() {return true;} - // Delete the current preset, activate the first visible preset. - // returns true if the preset was deleted successfully. - bool delete_printer(const std::string& name) { return true; } + bool delete_printer(const std::string& name); - // Select a printer. If an invalid index is provided, the first visible printer is selected. - PhysicalPrinter& select_printer(size_t idx); // Return the selected preset, without the user modifications applied. - PhysicalPrinter& get_selected_preset() { return m_printers[m_idx_selected]; } - const PhysicalPrinter& get_selected_preset() const { return m_printers[m_idx_selected]; } + PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } + const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; } size_t get_selected_idx() const { return m_idx_selected; } // Returns the name of the selected preset, or an empty string if no preset is selected. - std::string get_selected_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_preset().name; } - PhysicalPrinter& get_edited_preset() { return m_edited_printer; } - const PhysicalPrinter& get_edited_preset() const { return m_edited_printer; } + std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } + DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } - // Return a preset possibly with modifications. - PhysicalPrinter& default_printer(size_t idx = 0) { return m_printers[idx]; } - const PhysicalPrinter& default_printer(size_t idx = 0) const { return m_printers[idx]; } + // select printer with name and return reference on it + PhysicalPrinter& select_printer_by_name(const std::string& name); + bool has_selection() const { return m_idx_selected != size_t(-1); } + void unselect_printer() { m_idx_selected = size_t(-1); } - // used to update preset_choice from Tab - const std::deque& get_presets() const { return m_printers; } - size_t get_idx_selected() { return m_idx_selected; } - - // Return a preset by an index. If the preset is active, a temporary copy is returned. - PhysicalPrinter& printer(size_t idx) { return (idx == m_idx_selected) ? m_edited_printer : m_printers[idx]; } + // Return a printer by an index. If the printer is active, a temporary copy is returned. + PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; } const PhysicalPrinter& printer(size_t idx) const { return const_cast(this)->printer(idx); } // Return a preset by its name. If the preset is active, a temporary copy is returned. @@ -652,20 +638,10 @@ public: return const_cast(this)->find_printer(name, first_visible_if_not_found); } - // Return number of presets including the "- default -" preset. - size_t size() const { return m_printers.size(); } - - // Select a profile by its name. Return true if the selection changed. - // Without force, the selection is only updated if the index changes. - // With force, the changes are reverted if the new index is the same as the old index. - bool select_printer_by_name(const std::string& name, bool force) {}; - // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string& new_name) const; private: -// PhysicalPrinterCollection(); - PhysicalPrinterCollection(const PhysicalPrinterCollection& other); PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); // Find a preset position in the sorted list of presets. @@ -674,8 +650,8 @@ private: // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. std::deque::iterator find_printer_internal(const std::string& name) { - PhysicalPrinter key(name); - auto it = std::lower_bound(m_printers.begin()+0, m_printers.end(), key); + PhysicalPrinter printer(name); + auto it = std::lower_bound(m_printers.begin(), m_printers.end(), printer); return it; } std::deque::const_iterator find_printer_internal(const std::string& name) const @@ -683,23 +659,18 @@ private: return const_cast(this)->find_printer_internal(name); } - static std::vector dirty_options(const Preset* edited, const Preset* reference, const bool is_printer_type = false); - - // List of presets, starting with the "- default -" preset. + // List of printers // Use deque to force the container to allocate an object per each entry, // so that the addresses of the presets don't change during resizing of the container. std::deque m_printers; - // Initially this printer contains a copy of the selected printer. Later on, this copy may be modified by the user. - PhysicalPrinter m_edited_printer; - // Selected preset. - size_t m_idx_selected; + + // Selected printer. + size_t m_idx_selected = size_t(-1); // Path to the directory to store the config files into. std::string m_dir_path; }; -////////////////////////////////////////////////////////////////////// -*/ } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 9da7731a45..7c4fa1cb25 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -41,7 +41,8 @@ PresetBundle::PresetBundle() : filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults())), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast(SLAFullPrintConfig::defaults())), sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast(SLAFullPrintConfig::defaults())), - printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -") + printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast(FullPrintConfig::defaults()), "- default FFF -"), + physical_printers(PhysicalPrinter::printer_options()) { // The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes, // therefore they need to be handled differently. As they have no counterpart in StaticPrintConfig, they are not being @@ -139,14 +140,16 @@ void PresetBundle::setup_directories() data_dir / "presets" / "filament", data_dir / "presets" / "sla_print", data_dir / "presets" / "sla_material", - data_dir / "presets" / "printer" + data_dir / "presets" / "printer", + data_dir / "presets" / "physical_printer" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. data_dir / "print", data_dir / "filament", data_dir / "sla_print", data_dir / "sla_material", - data_dir / "printer" + data_dir / "printer", + data_dir / "physical_printer" #endif }; for (const boost::filesystem::path &path : paths) { @@ -196,6 +199,11 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } + try { + this->physical_printers.load_printers(dir_user_presets, "physical_printer"); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + } this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); if (! errors_cummulative.empty()) @@ -422,6 +430,14 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr // exist. this->update_compatible(PresetSelectCompatibleType::Always); this->update_multi_material_filament_presets(); + + // Parse the initial physical printer name. + std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer")); + + // Activate physical printer from the config + const PhysicalPrinter* initial_physical_printer = physical_printers.find_printer(initial_physical_printer_name); + if (initial_physical_printer) + physical_printers.select_printer_by_name(initial_physical_printer_name); } // Export selections (current print, current filaments, current printer) into config.ini @@ -441,6 +457,8 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "sla_print", sla_prints.get_selected_preset_name()); config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); config.set("presets", "printer", printers.get_selected_preset_name()); + + config.set("extras", "physical_printer", physical_printers.get_selected_printer_name()); } DynamicPrintConfig PresetBundle::full_config() const diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 19d4093d63..2906584d33 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -39,6 +39,7 @@ public: PresetCollection& materials(PrinterTechnology pt) { return pt == ptFFF ? this->filaments : this->sla_materials; } const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; } PrinterPresetCollection printers; + PhysicalPrinterCollection physical_printers; // Filament preset names for a multi-extruder or multi-material print. // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d6a23d75d3..93ef170fb3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -130,6 +130,26 @@ void PrintConfigDef::init_common_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); + + // Options used by physical printers + + def = this->add("login", coString); + def->label = L("Login"); +// def->tooltip = L(""); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("password", coString); + def->label = L("Password"); +// def->tooltip = L(""); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + + def = this->add("preset_name", coString); + def->label = L("Printer preset name"); + def->tooltip = L("Related printer preset name"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); } void PrintConfigDef::init_fff_params() diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a78683bd4e..048e17926e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -942,8 +942,6 @@ void Sidebar::msw_rescale() void Sidebar::sys_color_changed() { - // Update preset comboboxes in respect to the system color ... - // combo->msw_rescale() updates icon on button, so use it for (PlaterPresetComboBox* combo : std::vector{ p->combo_print, p->combo_sla_print, p->combo_sla_material, @@ -952,12 +950,8 @@ void Sidebar::sys_color_changed() for (PlaterPresetComboBox* combo : p->combos_filament) combo->msw_rescale(); - // ... then refill them and set min size to correct layout of the sidebar - update_all_preset_comboboxes(); - p->object_list->sys_color_changed(); p->object_manipulation->sys_color_changed(); -// p->object_settings->msw_rescale(); p->object_layers->sys_color_changed(); // btn...->msw_rescale() updates icon on button, so use it @@ -3208,12 +3202,23 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) //! instead of //! combo->GetStringSelection().ToUTF8().data()); - const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, + std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data())); if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); } + + if (preset_type == Preset::TYPE_PRINTER) { + if(combo->is_selected_physical_printer()) { + // Select related printer preset on the Printer Settings Tab + const std::string printer_name = combo->GetString(selection).ToUTF8().data(); + PhysicalPrinter& printer = wxGetApp().preset_bundle->physical_printers.select_printer_by_name(printer_name); + preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, printer.get_preset_name()); + } + else + wxGetApp().preset_bundle->physical_printers.unselect_printer(); + } // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { @@ -3974,7 +3979,12 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const this->ready_to_slice = ready_to_slice; wxWindowUpdateLocker noUpdater(sidebar); - const auto prin_host_opt = config->option("print_host"); + + DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); + if (!selected_printer_config) + selected_printer_config = config; + + const auto prin_host_opt = selected_printer_config->option("print_host"); const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); // when a background processing is ON, export_btn and/or send_btn are showing @@ -4893,7 +4903,9 @@ void Plater::send_gcode() { if (p->model.objects.empty()) { return; } - PrintHostJob upload_job(p->config); + // if physical_printer is selected, send gcode for this printer + DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config(); + PrintHostJob upload_job(physical_printer_config ? physical_printer_config : p->config); if (upload_job.empty()) { return; } // Obtain default output path diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 6c38c866d1..ce25d56901 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -31,6 +31,7 @@ #include "../Utils/UndoRedo.hpp" #include "RemovableDriveManager.hpp" #include "BitmapCache.hpp" +#include "BonjourDialog.hpp" using Slic3r::GUI::format_wxstr; @@ -241,8 +242,9 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset evt.StopPropagation(); if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) { - PhysicalPrinterDialog dlg(_L("New Physical Printer"), this->m_last_selected); - dlg.ShowModal(); + PhysicalPrinterDialog dlg(wxEmptyString); + if (dlg.ShowModal() == wxID_OK) + this->update(); return; } if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { @@ -255,7 +257,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset } wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); } - } else if ( this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { + } else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { this->m_last_selected = selected_item; evt.SetInt(this->m_type); evt.Skip(); @@ -319,6 +321,16 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) { + // In a case of a physical printer, for its editing open PhysicalPrinterDialog + if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) + { + PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); + if (dlg.ShowModal() == wxID_OK) { + update(); + return; + } + } + Tab* tab = wxGetApp().get_tab(m_type); if (!tab) return; @@ -355,6 +367,13 @@ PlaterPresetComboBox::~PlaterPresetComboBox() edit_btn->Destroy(); } +bool PlaterPresetComboBox::is_selected_physical_printer() +{ + auto selected_item = this->GetSelection(); + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + return marker == LABEL_ITEM_PHYSICAL_PRINTER; +} + // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void PlaterPresetComboBox::update() @@ -388,7 +407,6 @@ void PlaterPresetComboBox::update() bool wide_icons = !selected_preset.is_compatible; std::map nonsys_presets; - std::map physical_printers; wxString selected = ""; wxString tooltip = ""; @@ -400,9 +418,11 @@ void PlaterPresetComboBox::update() for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; - bool is_selected = m_type == Preset::TYPE_FILAMENT ? - m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : - i == m_collection->get_selected_idx(); + bool is_selected = m_type == Preset::TYPE_FILAMENT ? + m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : + // The case, when some physical printer is selected + m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false : + i == m_collection->get_selected_idx(); if (!preset.is_visible || (!preset.is_compatible && !is_selected)) continue; @@ -495,17 +515,6 @@ void PlaterPresetComboBox::update() selected_preset_item = GetCount() - 1; } } - if (!physical_printers.empty()) - { - set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); - for (std::map::iterator it = physical_printers.begin(); it != physical_printers.end(); ++it) { - Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - } - } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { std::string bitmap_key = ""; @@ -536,8 +545,45 @@ void PlaterPresetComboBox::update() else set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } - if (m_type == Preset::TYPE_PRINTER) { - std::string bitmap_key = ""; + + if (m_type == Preset::TYPE_PRINTER) + { + // add Physical printers, if any exists + if (!m_preset_bundle->physical_printers.empty()) { + set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; + + for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { + std::string bitmap_key = it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name)); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width+norm_icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + + set_label_marker(Append(wxString::FromUTF8((it->name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + if (ph_printers.has_selection() && it->name == ph_printers.get_selected_printer_name() || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + + // add LABEL_ITEM_PHYSICAL_PRINTERS + std::string bitmap_key; if (wide_icons) bitmap_key += "wide,"; bitmap_key += "edit_preset_list"; @@ -589,10 +635,10 @@ void PlaterPresetComboBox::msw_rescale() // *** TabPresetComboBox *** // --------------------------------- -TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type, bool is_from_physical_printer/* = false*/) : +TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) : PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1)) { - Bind(wxEVT_COMBOBOX, [this, is_from_physical_printer](wxCommandEvent& evt) { + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { // see https://github.com/prusa3d/PrusaSlicer/issues/3889 // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. @@ -603,21 +649,17 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type, if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); if (marker == LABEL_ITEM_WIZARD_PRINTERS) - wxTheApp->CallAfter([this, is_from_physical_printer]() { + wxTheApp->CallAfter([this]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); - if (is_from_physical_printer) + + // update combobox if its parent is a PhysicalPrinterDialog + PhysicalPrinterDialog* parent = dynamic_cast(this->GetParent()); + if (parent != nullptr) update(); }); } - else if ( is_from_physical_printer) { - // do nothing - } - else if (m_last_selected != selected_item || m_collection->current_is_dirty() ) { - std::string selected_string = this->GetString(selected_item).ToUTF8().data(); - Tab* tab = wxGetApp().get_tab(this->m_type); - assert (tab); - tab->select_preset(selected_string); - } + else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty()) ) + on_selection_changed(selected_item); evt.StopPropagation(); }); @@ -760,35 +802,212 @@ void TabPresetComboBox::update_dirty() //------------------------------------------ -PhysicalPrinterDialog::PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset) +PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : DPIDialog(NULL, wxID_ANY, _L("PhysicalPrinter"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); int border = 10; - int em = em_unit(); - printer_text = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); - printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER, true); - printer_presets->update(); + m_printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); + m_printer_presets->set_selection_changed_function([this](int selection) { + std::string selected_string = Preset::remove_suffix_modified(m_printer_presets->GetString(selection).ToUTF8().data()); + Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); + assert(preset); + Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + m_printer.update_from_preset(*preset); + + // update values + m_optgroup->reload_config(); + update_octoprint_visible(); + }); + m_printer_presets->update(); + + wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + + if (printer_name.IsEmpty()) + printer_name = preset_name + " - "+_L("Physical Printer"); + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); + if (!printer) { + const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + printer = new PhysicalPrinter(into_u8(printer_name), preset); + } + assert(printer); + m_printer = *printer; + + m_config = &m_printer.config; + + m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); + build_printhost_settings(m_optgroup); + m_optgroup->reload_config(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(printer_text , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(btns , 0, wxEXPAND | wxALL, border); + topSizer->Add(m_printer_name , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(btns , 0, wxEXPAND | wxALL, border); SetSizer(topSizer); topSizer->SetSizeHints(this); } +void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) +{ + m_optgroup->append_single_option_line("host_type"); + + auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + (*btn)->SetFont(wxGetApp().normal_font()); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(*btn); + return sizer; + }; + + auto printhost_browse = [=](wxWindow* parent) + { + auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { + BonjourDialog dialog(this, Preset::printer_technology(m_printer.config)); + if (dialog.show_and_lookup()) { + m_optgroup->set_value("print_host", std::move(dialog.get_selected()), true); + m_optgroup->get_field("print_host")->field_changed(); + } + }); + + return sizer; + }; + + auto print_host_test = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); + + m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (!host) { + const wxString text = _L("Could not get a valid Printer Host reference"); + show_error(this, text); + return; + } + wxString msg; + if (host->test(msg)) { + show_info(this, host->get_test_ok_msg(), _L("Success!")); + } + else { + show_error(this, host->get_test_failed_msg(msg)); + } + }); + + return sizer; + }; + + // Set a wider width for a better alignment + Option option = m_optgroup->get_option("print_host"); + option.opt.width = Field::def_width_wider(); + Line host_line = m_optgroup->create_single_option_line(option); + host_line.append_widget(printhost_browse); + host_line.append_widget(print_host_test); + m_optgroup->append_line(host_line); + option = m_optgroup->get_option("printhost_apikey"); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + + const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."); + + if (Http::ca_file_supported()) { + option = m_optgroup->get_option("printhost_cafile"); + option.opt.width = Field::def_width_wider(); + Line cafile_line = m_optgroup->create_single_option_line(option); + + auto printhost_cafile_browse = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) { + static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); + wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) { + m_optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); + m_optgroup->get_field("printhost_cafile")->field_changed(); + } + }); + + return sizer; + }; + + cafile_line.append_widget(printhost_cafile_browse); + m_optgroup->append_line(cafile_line); + + Line cafile_hint{ "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + m_optgroup->append_line(cafile_hint); + } + else { + Line line{ "", "" }; + line.full_width = 1; + + line.widget = [ca_file_hint](wxWindow* parent) { + std::string info = _u8L("HTTPS CA File") + ":\n\t" + + (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + + "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); + + auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + txt->SetFont(wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt, 1, wxEXPAND); + return sizer; + }; + + m_optgroup->append_line(line); + } + + for (const std::string& opt_key : std::vector{ "login", "password" }) { + option = m_optgroup->get_option(opt_key); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + } + + update_octoprint_visible(); +} + +void PhysicalPrinterDialog::update_octoprint_visible() +{ + const PrinterTechnology tech = Preset::printer_technology(m_printer.config); + // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) + Field* host_type = m_optgroup->get_field("host_type"); + if (tech == ptFFF) + host_type->enable(); + else { + host_type->set_value(int(PrintHostType::htOctoPrint), false); + host_type->disable(); + } +} + void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); + m_printhost_browse_btn->msw_rescale(); + m_printhost_test_btn->msw_rescale(); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->msw_rescale(); + + m_optgroup->msw_rescale(); + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); const wxSize& size = wxSize(40 * em, 30 * em); @@ -798,5 +1017,46 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } +void PhysicalPrinterDialog::OnOK(wxEvent& event) +{ + wxString printer_name = m_printer_name->GetValue(); + if (printer_name.IsEmpty()) { + show_error(this, _L("The supplied name is empty. It can't be saved.")); + return; + } + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name)); + if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) + { + wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str()); + msg_text += "\n" + _L("Replace?"); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_NO) + return; + + // Remove the printer from the list. + printers.delete_printer(into_u8(printer_name)); + } + + //upadte printer name, if it was changed + m_printer.name = into_u8(printer_name); + + // save new physical printer + printers.save_printer(m_printer); + + // update selection on the tab only when it was changed + if (m_printer.get_preset_name() != wxGetApp().preset_bundle->printers.get_selected_preset_name()) { + Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); + if (tab) { + wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + tab->select_preset(into_u8(preset_name)); + } + } + + event.Skip(); +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 38b98d6586..e1597bcfc6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,6 +13,7 @@ class wxString; class wxTextCtrl; +class ScalableButton; namespace Slic3r { @@ -33,7 +34,8 @@ public: ~PresetComboBox(); enum LabelItemType { - LABEL_ITEM_MARKER = 0xffffff01, + LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01, + LABEL_ITEM_MARKER, LABEL_ITEM_PHYSICAL_PRINTERS, LABEL_ITEM_WIZARD_PRINTERS, LABEL_ITEM_WIZARD_FILAMENTS, @@ -121,11 +123,13 @@ public: void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; } int get_extruder_idx() const { return m_extruder_idx; } + bool is_selected_physical_printer(); + void update() override; void msw_rescale() override; private: - int m_extruder_idx = -1; + int m_extruder_idx = -1; }; @@ -135,8 +139,11 @@ private: class TabPresetComboBox : public PresetComboBox { + bool show_incompatible {false}; + std::function on_selection_changed { nullptr }; + public: - TabPresetComboBox(wxWindow *parent, Preset::Type preset_type, bool is_from_physical_printer = false); + TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); ~TabPresetComboBox() {} void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; @@ -146,25 +153,33 @@ public: void update_dirty(); void msw_rescale() override; -private: - bool show_incompatible{false}; + void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } }; //------------------------------------------ // PhysicalPrinterDialog //------------------------------------------ - +class ConfigOptionsGroup; class PhysicalPrinterDialog : public DPIDialog { - std::string printer_name; - std::string preset_name; + PhysicalPrinter m_printer; + DynamicPrintConfig* m_config { nullptr }; - wxTextCtrl* printer_text { nullptr }; - PresetComboBox* printer_presets; + wxTextCtrl* m_printer_name { nullptr }; + TabPresetComboBox* m_printer_presets { nullptr }; + ConfigOptionsGroup* m_optgroup { nullptr }; + + ScalableButton* m_printhost_browse_btn; + ScalableButton* m_printhost_test_btn; + ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + + void build_printhost_settings(ConfigOptionsGroup* optgroup); + void update_octoprint_visible(); + void OnOK(wxEvent& event); public: - PhysicalPrinterDialog(const wxString& printer_name, int last_selected_preset); + PhysicalPrinterDialog(wxString printer_name); ~PhysicalPrinterDialog() {} protected: diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bb19e139d2..da5d51dc54 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -161,6 +161,13 @@ void Tab::create_preset_tab() // preset chooser m_presets_choice = new TabPresetComboBox(panel, m_type); + m_presets_choice->set_selection_changed_function([this](int selection) { + // unselect pthysical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); + // select preset + std::string selected_string = m_presets_choice->GetString(selection).ToUTF8().data(); + select_preset(selected_string); + }); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -3022,6 +3029,9 @@ void Tab::select_preset(std::string preset_name, bool delete_current) if (canceled) { update_tab_ui(); + // unselect physical printer selection to the correct synchronization of the printer presets between Tab and Plater + if (m_type == Preset::TYPE_PRINTER) + m_preset_bundle->physical_printers.unselect_printer(); // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. on_presets_changed(); From 89035febfaf926394eaef82db007694dc87c9713 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jun 2020 09:20:04 +0200 Subject: [PATCH 144/826] Fixed includes --- src/slic3r/Utils/FixModelByWin10.cpp | 2 +- xs/xsp/Model.xsp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 0de526432b..a3683a84d3 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -30,10 +30,10 @@ #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" +#include "libslic3r/PresetBundle.hpp" #include "libslic3r/Format/3mf.hpp" #include "../GUI/GUI.hpp" #include "../GUI/I18N.hpp" -#include "../GUI/PresetBundle.hpp" #include #include diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 4fb35578db..844b7c95ec 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -12,7 +12,7 @@ #include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Format/PRUS.hpp" #include "libslic3r/Format/STL.hpp" -#include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" %} %name{Slic3r::Model} class Model { From 8ac839f427ef2c0d1470de51e8d9515689296e83 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jun 2020 12:28:00 +0200 Subject: [PATCH 145/826] Physical printers: Delete selected printer + Added context menu for the cog-button near the printer presets --- src/libslic3r/Preset.cpp | 21 +++++++-- src/libslic3r/Preset.hpp | 3 ++ src/slic3r/GUI/PresetComboBoxes.cpp | 72 +++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 2 + 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 94c9577dfb..e17130f69c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1489,17 +1489,32 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print bool PhysicalPrinterCollection::delete_printer(const std::string& name) { auto it = this->find_printer_internal(name); + if (it == m_printers.end()) + return false; const PhysicalPrinter& printer = *it; - if (it == m_printers.end()) - return false; - // Erase the preset file. boost::nowide::remove(printer.file.c_str()); m_printers.erase(it); return true; } +bool PhysicalPrinterCollection::delete_selected_printer() +{ + if (!has_selection()) + return false; + const PhysicalPrinter& printer = this->get_selected_printer(); + + // Erase the preset file. + boost::nowide::remove(printer.file.c_str()); + // Remove the preset from the list. + m_printers.erase(m_printers.begin() + m_idx_selected); + // unselect all printers + unselect_printer(); + + return true; +} + PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& name) { auto it = this->find_printer_internal(name); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index c08a1a0fbc..a5837a9fe0 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -612,6 +612,9 @@ public: // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. bool delete_printer(const std::string& name); + // Delete the selected preset + // returns true if the preset was deleted successfully. + bool delete_selected_printer(); // Return the selected preset, without the user modifications applied. PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index ce25d56901..e9da7dc9d8 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" @@ -322,28 +323,14 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) { // In a case of a physical printer, for its editing open PhysicalPrinterDialog - if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) - { - PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); - if (dlg.ShowModal() == wxID_OK) { - update(); - return; - } + if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) { + this->show_edit_menu(); + return; } - Tab* tab = wxGetApp().get_tab(m_type); - if (!tab) + if (!switch_to_tab()) return; - int page_id = wxGetApp().tab_panel()->FindPage(tab); - if (page_id == wxNOT_FOUND) - return; - - wxGetApp().tab_panel()->SetSelection(page_id); - - // Switch to Settings NotePad - wxGetApp().mainframe->select_tab(); - /* In a case of a multi-material printing, for editing another Filament Preset * it's needed to select this preset for the "Filament settings" Tab */ @@ -355,7 +342,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) { const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset); - tab->select_preset(preset_name); + wxGetApp().get_tab(m_type)->select_preset(preset_name); } } }); @@ -374,6 +361,53 @@ bool PlaterPresetComboBox::is_selected_physical_printer() return marker == LABEL_ITEM_PHYSICAL_PRINTER; } +bool PlaterPresetComboBox::switch_to_tab() +{ + Tab* tab = wxGetApp().get_tab(m_type); + if (!tab) + return false; + + int page_id = wxGetApp().tab_panel()->FindPage(tab); + if (page_id == wxNOT_FOUND) + return false; + + wxGetApp().tab_panel()->SetSelection(page_id); + // Switch to Settings NotePad + wxGetApp().mainframe->select_tab(); + return true; +} + +void PlaterPresetComboBox::show_edit_menu() +{ + wxMenu* menu = new wxMenu(); + + append_menu_item(menu, wxID_ANY, _L("Edit related printer profile"), "", + [this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater()); + + append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "", + [this](wxCommandEvent&) { + PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); + if (dlg.ShowModal() == wxID_OK) + update(); + }, "cog", menu, []() { return true; }, wxGetApp().plater()); + + append_menu_item(menu, wxID_ANY, _L("Delete physical printer"), "", + [this](wxCommandEvent&) { + const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_printer_name(); + if (printer_name.empty()) + return; + + const wxString msg = from_u8((boost::format(_u8L("Are you sure you want to delete \"%1%\" printer?")) % printer_name).str()); + if (wxMessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES) + return; + + m_preset_bundle->physical_printers.delete_selected_printer(); + update(); + }, "cross", menu, []() { return true; }, wxGetApp().plater()); + + wxGetApp().plater()->PopupMenu(menu); +} + // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void PlaterPresetComboBox::update() diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index e1597bcfc6..9261e92ef4 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -124,6 +124,8 @@ public: int get_extruder_idx() const { return m_extruder_idx; } bool is_selected_physical_printer(); + bool switch_to_tab(); + void show_edit_menu(); void update() override; void msw_rescale() override; From 648ecb47c23ac44cb10155796c8c979d1019a0eb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 24 Jun 2020 16:57:09 +0200 Subject: [PATCH 146/826] GCodeViewer -> Fixed incorrect detection of out of printbed for toolpaths --- src/slic3r/GUI/GCodeViewer.cpp | 26 +++++++++++++------------- src/slic3r/GUI/GCodeViewer.hpp | 18 ++++++++++++------ src/slic3r/GUI/GLCanvas3D.cpp | 10 +++++----- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 415b61aeb9..4b308e7030 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -148,7 +148,6 @@ void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& positi { m_world_position = position; m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size()[2] * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); - m_world_bounding_box = m_model.get_bounding_box().transformed(m_world_transform.cast()); } void GCodeViewer::SequentialView::Marker::render() const @@ -288,10 +287,10 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& #if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { - // adjust printbed size + // adjust printbed size in dependence of toolpaths bbox const double margin = 10.0; - Vec2d min(m_bounding_box.min(0) - margin, m_bounding_box.min(1) - margin); - Vec2d max(m_bounding_box.max(0) + margin, m_bounding_box.max(1) + margin); + Vec2d min(m_paths_bounding_box.min(0) - margin, m_paths_bounding_box.min(1) - margin); + Vec2d max(m_paths_bounding_box.max(0) + margin, m_paths_bounding_box.max(1) + margin); Pointfs bed_shape = { { min(0), min(1) }, { max(0), min(1) }, { max(0), max(1) }, @@ -359,7 +358,8 @@ void GCodeViewer::reset() buffer.reset(); } - m_bounding_box = BoundingBoxf3(); + m_paths_bounding_box = BoundingBoxf3(); + m_max_bounding_box = BoundingBoxf3(); m_tool_colors = std::vector(); m_extruder_ids = std::vector(); m_extrusions.reset_role_visibility_flags(); @@ -497,18 +497,19 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; #if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) - m_bounding_box.merge(move.position.cast()); + // for the gcode viewer we need all moves to correctly size the printbed + m_paths_bounding_box.merge(move.position.cast()); else { #endif // ENABLE_GCODE_VIEWER_AS_STATE if (move.type == GCodeProcessor::EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) - m_bounding_box.merge(move.position.cast()); + m_paths_bounding_box.merge(move.position.cast()); #if ENABLE_GCODE_VIEWER_AS_STATE } #endif // ENABLE_GCODE_VIEWER_AS_STATE ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); } - m_bounding_box.merge(m_bounding_box.max + m_sequential_view.marker.get_bounding_box().max[2] * Vec3d::UnitZ()); + m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().max[2] * Vec3d::UnitZ()); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.vertices_size = SLIC3R_STDVEC_MEMSIZE(vertices_data, float); @@ -578,7 +579,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // indices data -> send data to gpu for (size_t i = 0; i < m_buffers.size(); ++i) { IBuffer& buffer = m_buffers[i]; - std::vector& buffer_indices = indices[i]; + const std::vector& buffer_indices = indices[i]; buffer.indices_count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer_indices, unsigned int); @@ -859,7 +860,6 @@ void GCodeViewer::render_toolpaths() const for (const RenderPath& path : buffer.render_paths) { shader.set_uniform("uniform_color", path.color); -// glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; @@ -878,8 +878,8 @@ void GCodeViewer::render_toolpaths() const unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, VBuffer::vertex_size_bytes(), (const void*)0)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, VBuffer::vertex_size_bytes(), (const void*)0)); + glsafe(::glEnableVertexAttribArray(0)); for (unsigned char i = begin_id; i < end_id; ++i) { const IBuffer& buffer = m_buffers[i]; @@ -914,7 +914,7 @@ void GCodeViewer::render_toolpaths() const } } - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glDisableVertexAttribArray(0)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 413f4ef4ad..5c60863058 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -43,8 +43,8 @@ class GCodeViewer void reset(); - static size_t vertex_size() { return 3; } - static size_t vertex_size_bytes() { return vertex_size() * sizeof(float); } + static size_t vertex_size_floats() { return 3; } + static size_t vertex_size_bytes() { return vertex_size_floats() * sizeof(float); } }; // Used to identify different toolpath sub-types inside a IBuffer @@ -160,11 +160,14 @@ class GCodeViewer #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics { + // times long long load_time{ 0 }; long long refresh_time{ 0 }; long long refresh_paths_time{ 0 }; + // opengl calls long long gl_multi_points_calls_count{ 0 }; long long gl_multi_line_strip_calls_count{ 0 }; + // memory long long results_size{ 0 }; long long vertices_size{ 0 }; long long vertices_gpu_size{ 0 }; @@ -220,7 +223,6 @@ public: GLModel m_model; Vec3f m_world_position; Transform3f m_world_transform; - BoundingBoxf3 m_world_bounding_box; float m_z_offset{ 0.5f }; std::array m_color{ 1.0f, 1.0f, 1.0f, 1.0f }; bool m_visible{ false }; @@ -228,7 +230,7 @@ public: public: void init(); - const BoundingBoxf3& get_bounding_box() const { return m_world_bounding_box; } + const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } void set_world_position(const Vec3f& position); void set_color(const std::array& color) { m_color = color; } @@ -268,7 +270,10 @@ private: unsigned int m_last_result_id{ 0 }; VBuffer m_vertices; mutable std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; - BoundingBoxf3 m_bounding_box; + // bounding box of toolpaths + BoundingBoxf3 m_paths_bounding_box; + // bounding box of toolpaths + marker tools + BoundingBoxf3 m_max_bounding_box; std::vector m_tool_colors; std::vector m_layers_zs; std::array m_layers_z_range; @@ -303,7 +308,8 @@ public: bool has_data() const { return !m_roles.empty(); } - const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } + const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; } + const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; } const std::vector& get_layers_zs() const { return m_layers_zs; }; const SequentialView& get_sequential_view() const { return m_sequential_view; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5c72ae0fa7..0ee30a8084 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1933,7 +1933,7 @@ void GLCanvas3D::zoom_to_selection() #if ENABLE_GCODE_VIEWER_AS_STATE void GLCanvas3D::zoom_to_gcode() { - _zoom_to_box(m_gcode_viewer.get_bounding_box(), 1.05); + _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05); } #endif // ENABLE_GCODE_VIEWER_AS_STATE @@ -3108,7 +3108,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) if (!m_volumes.empty()) zoom_to_volumes(); else - _zoom_to_box(m_gcode_viewer.get_bounding_box()); + _zoom_to_box(m_gcode_viewer.get_paths_bounding_box()); } break; @@ -5189,7 +5189,7 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be #if ENABLE_GCODE_VIEWER if (!m_main_toolbar.is_enabled()) - bb.merge(m_gcode_viewer.get_bounding_box()); + bb.merge(m_gcode_viewer.get_max_bounding_box()); #endif // ENABLE_GCODE_VIEWER return bb; @@ -5385,7 +5385,7 @@ void GLCanvas3D::_render_background() const use_error_color &= _is_any_volume_outside(); else { BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; + use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_paths_bounding_box()) : false; } #if ENABLE_GCODE_VIEWER_AS_STATE } @@ -7114,7 +7114,7 @@ void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - const BoundingBoxf3& paths_volume = m_gcode_viewer.get_bounding_box(); + const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) show = !test_volume.contains(paths_volume); } From eb683616193f03991cff08dbaf85bb1fe348cd09 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 25 Jun 2020 08:14:45 +0200 Subject: [PATCH 147/826] Follow-up of 648ecb47c23ac44cb10155796c8c979d1019a0eb -> Fixed calculation of max bounding box --- src/slic3r/GUI/GCodeViewer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 4b308e7030..05a8c20bba 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -509,7 +509,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); } - m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().max[2] * Vec3d::UnitZ()); + m_max_bounding_box = m_paths_bounding_box; + m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.vertices_size = SLIC3R_STDVEC_MEMSIZE(vertices_data, float); From 1a2926050fee0b5ab118054aaa4ccb65614d2063 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 25 Jun 2020 12:58:59 +0200 Subject: [PATCH 148/826] PhysicalPrinter. PhysicalPrinterDialog improvements --- src/libslic3r/Preset.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 11 +++++++ src/libslic3r/PrintConfig.hpp | 13 ++++++++ src/slic3r/GUI/Field.cpp | 2 ++ src/slic3r/GUI/Field.hpp | 2 ++ src/slic3r/GUI/GUI.cpp | 2 ++ src/slic3r/GUI/OptionsGroup.cpp | 19 +++++++----- src/slic3r/GUI/OptionsGroup.hpp | 7 +++++ src/slic3r/GUI/PresetComboBoxes.cpp | 46 +++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- 10 files changed, 85 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e17130f69c..abc508b489 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1354,7 +1354,8 @@ const std::vector& PhysicalPrinter::printer_options() "host_type", "print_host", "printhost_apikey", - "printhost_cafile", + "printhost_cafile", + "authorization_type", "login", "password" }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 93ef170fb3..01b17ec0de 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -150,6 +150,17 @@ void PrintConfigDef::init_common_params() def->tooltip = L("Related printer preset name"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); + + def = this->add("authorization_type", coEnum); + def->label = L("Authorization Type"); +// def->tooltip = L(""); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("key"); + def->enum_values.push_back("user"); + def->enum_labels.push_back("KeyPassword"); + def->enum_labels.push_back("UserPassword"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(atKeyPassword)); } void PrintConfigDef::init_fff_params() diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f28ef2a228..9b5c47512e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -33,6 +33,10 @@ enum PrintHostType { htOctoPrint, htDuet, htFlashAir, htAstroBox }; +enum AuthorizationType { + atKeyPassword, atUserPassword +}; + enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount, @@ -109,6 +113,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g return keys_map; } +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["key"] = atKeyPassword; + keys_map["user"] = atUserPassword; + } + return keys_map; +} + template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 8ab82e20d8..9cb3d726de 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1080,6 +1080,8 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("support_pillar_connection_mode") == 0) m_value = static_cast(ret_enum); + else if (m_opt_id == "authorization_type") + m_value = static_cast(ret_enum); } else if (m_opt.gui_type == "f_enum_open") { const int ret_enum = field->GetSelection(); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 484b2059f0..1a49977565 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -151,6 +151,8 @@ public: virtual wxSizer* getSizer() { return nullptr; } virtual wxWindow* getWindow() { return nullptr; } + wxStaticText* getLabel() { return m_Label; } + bool is_matched(const std::string& string, const std::string& pattern); void get_value_by_opt_type(wxString& str, const bool check_value = true); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index b9516b12f2..88c4576681 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -194,6 +194,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if(opt_key.compare("support_pillar_connection_mode") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); + else if(opt_key == "authorization_type") + config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); } break; case coPoints:{ diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 819c214a85..1bebb8827a 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -729,31 +729,34 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config opt_key == "fill_pattern" ) { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("ironing_type") == 0 ) { + else if (opt_key == "ironing_type") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("gcode_flavor") == 0 ) { + else if (opt_key == "gcode_flavor") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("support_material_pattern") == 0) { + else if (opt_key == "support_material_pattern") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("seam_position") == 0) { + else if (opt_key == "seam_position") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("host_type") == 0) { + else if (opt_key == "host_type") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("display_orientation") == 0) { + else if (opt_key == "display_orientation") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key.compare("support_pillar_connection_mode") == 0) { + else if (opt_key == "support_pillar_connection_mode") { ret = static_cast(config.option>(opt_key)->value); } + else if (opt_key == "authorization_type") { + ret = static_cast(config.option>(opt_key)->value); + } } break; case coPoints: - if (opt_key.compare("bed_shape") == 0) + if (opt_key == "bed_shape") ret = config.option(opt_key)->values; else ret = config.option(opt_key)->get_at(idx); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 2e6f9aa0f4..edd4a15bc9 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -149,6 +149,13 @@ public: return true; } + void show_field(const t_config_option_key& opt_key, bool show = true) { + Field* field = get_field(opt_key); + field->getWindow()->Show(show); + field->getLabel()->Show(show); + } + void hide_field(const t_config_option_key& opt_key) { show_field(opt_key, false); } + void set_name(const wxString& new_name) { stb->SetLabel(new_name); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index e9da7dc9d8..b39582ee0f 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -837,7 +837,7 @@ void TabPresetComboBox::update_dirty() PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) - : DPIDialog(NULL, wxID_ANY, _L("PhysicalPrinter"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -856,7 +856,7 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) // update values m_optgroup->reload_config(); - update_octoprint_visible(); + update(); }); m_printer_presets->update(); @@ -898,6 +898,11 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { + m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (opt_key == "authorization_type") + this->update(); + }; + m_optgroup->append_single_option_line("host_type"); auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { @@ -952,6 +957,9 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr host_line.append_widget(printhost_browse); host_line.append_widget(print_host_test); m_optgroup->append_line(host_line); + + m_optgroup->append_single_option_line("authorization_type"); + option = m_optgroup->get_option("printhost_apikey"); option.opt.width = Field::def_width_wider(); m_optgroup->append_single_option_line(option); @@ -999,7 +1007,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); - auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); txt->SetFont(wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(txt, 1, wxEXPAND); @@ -1015,20 +1024,33 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line(option); } - update_octoprint_visible(); + update(); } -void PhysicalPrinterDialog::update_octoprint_visible() +void PhysicalPrinterDialog::update() { const PrinterTechnology tech = Preset::printer_technology(m_printer.config); // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - Field* host_type = m_optgroup->get_field("host_type"); - if (tech == ptFFF) - host_type->enable(); - else { - host_type->set_value(int(PrintHostType::htOctoPrint), false); - host_type->disable(); + if (tech == ptFFF) { + m_optgroup->show_field("host_type"); + m_optgroup->hide_field("authorization_type"); + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->hide_field(opt_key); } + else { + m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); + m_optgroup->hide_field("host_type"); + + m_optgroup->show_field("authorization_type"); + + AuthorizationType auth_type = m_config->option>("authorization_type")->value; + m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); + + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); + } + + this->Layout(); } void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) @@ -1044,7 +1066,7 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - const wxSize& size = wxSize(40 * em, 30 * em); + const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(size); Fit(); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 9261e92ef4..196c4368e8 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -177,7 +177,7 @@ class PhysicalPrinterDialog : public DPIDialog ScalableButton* m_printhost_cafile_browse_btn {nullptr}; void build_printhost_settings(ConfigOptionsGroup* optgroup); - void update_octoprint_visible(); + void update(); void OnOK(wxEvent& event); public: From d96b5f360661feaa2f964d9f4b18d1e586d2607b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 26 Jun 2020 09:58:39 +0200 Subject: [PATCH 149/826] PhysicalPrinter : Next improvements: * Create full printer name as a PrinterName + RelatedPresetName * Added printer model to the PhysicalPrinter.config => Enable to select just between presets with same printer model * When physical printer is selected and create new preset ask if should we use this preset for selected ph_printer or just to switch for it --- src/libslic3r/Preset.cpp | 33 +++++- src/libslic3r/Preset.hpp | 24 +++- src/slic3r/GUI/PresetComboBoxes.cpp | 175 ++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 9 ++ src/slic3r/GUI/Tab.cpp | 33 +++++- src/slic3r/GUI/Tab.hpp | 1 + src/slic3r/GUI/wxExtensions.cpp | 7 +- src/slic3r/GUI/wxExtensions.hpp | 4 +- 8 files changed, 225 insertions(+), 61 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index abc508b489..9af3dacf0a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -1351,6 +1352,7 @@ const std::vector& PhysicalPrinter::printer_options() s_opts = { "preset_name", "printer_technology", + "printer_model", "host_type", "print_host", "printhost_apikey", @@ -1363,21 +1365,28 @@ const std::vector& PhysicalPrinter::printer_options() return s_opts; } -const std::string& PhysicalPrinter::get_preset_name() +const std::string& PhysicalPrinter::get_preset_name() const { return config.opt_string("preset_name"); } +const std::string& PhysicalPrinter::get_printer_model() const +{ + return config.opt_string("printer_model"); +} + void PhysicalPrinter::update_from_preset(const Preset& preset) { config.apply_only(preset.config, printer_options(), false); // add preset name to the options list config.set_key_value("preset_name", new ConfigOptionString(preset.name)); + update_full_name(); } void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) { config.apply_only(new_config, printer_options(), false); + update_full_name(); } PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : @@ -1386,6 +1395,24 @@ PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) update_from_preset(preset); } +void PhysicalPrinter::set_name(const std::string& name) +{ + this->name = name; + update_full_name(); +} + +void PhysicalPrinter::update_full_name() +{ + full_name = name + " * " + get_preset_name(); +} + +std::string PhysicalPrinter::get_short_name(std::string full_name) +{ + int pos = full_name.find_first_of(" * "); + boost::erase_tail(full_name, full_name.length() - pos); + return full_name; +} + // ----------------------------------- // *** PhysicalPrinterCollection *** @@ -1470,6 +1497,7 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print // Printer with the same name found. // Overwriting an existing preset. it->config = std::move(edited_printer.config); + it->full_name = edited_printer.full_name; } else { // Creating a new printer. @@ -1516,8 +1544,9 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } -PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& name) +PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(std::string name) { + name = PhysicalPrinter::get_short_name(name); auto it = this->find_printer_internal(name); assert(it != m_printers.end()); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index a5837a9fe0..6eb1fd2db3 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -538,20 +538,24 @@ public: PhysicalPrinter() {} PhysicalPrinter(const std::string& name) : name(name){} PhysicalPrinter(const std::string& name, const Preset& preset); + void set_name(const std::string &name); + void update_full_name(); // Name of the Physical Printer, usually derived form the file name. std::string name; + // Full name of the Physical Printer, included related preset name + std::string full_name; // File name of the Physical Printer. std::string file; + // Configuration data, loaded from a file, or set from the defaults. + DynamicPrintConfig config; // Has this profile been loaded? bool loaded = false; - // Configuration data, loaded from a file, or set from the defaults. - DynamicPrintConfig config; - static const std::vector& printer_options(); - const std::string& get_preset_name(); + const std::string& get_preset_name() const; + const std::string& get_printer_model() const; void save() { this->config.save(this->file); } void save_to(const std::string& file_name) const { this->config.save(file_name); } @@ -570,6 +574,9 @@ public: // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; } + // get printer name from the full name uncluded preset name + static std::string get_short_name(std::string full_name); + protected: friend class PhysicalPrinterCollection; }; @@ -622,10 +629,17 @@ public: size_t get_selected_idx() const { return m_idx_selected; } // Returns the name of the selected preset, or an empty string if no preset is selected. std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } + // Returns the full name of the selected preset, or an empty string if no preset is selected. + std::string get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().full_name; } + // Returns the printer model of the selected preset, or an empty string if no preset is selected. + std::string get_selected_printer_model() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_printer_model(); } + // Returns the printer model of the selected preset, or an empty string if no preset is selected. + std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_preset_name(); } + // Returns the config of the selected preset, or nullptr if no preset is selected. DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } // select printer with name and return reference on it - PhysicalPrinter& select_printer_by_name(const std::string& name); + PhysicalPrinter& select_printer_by_name(std::string name); bool has_selection() const { return m_idx_selected != size_t(-1); } void unselect_printer() { m_idx_selected = size_t(-1); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index b39582ee0f..88dd4b739a 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -104,6 +104,7 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const m_bitmapCompatible = ScalableBitmap(this, "flag_green"); m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); m_bitmapLock = ScalableBitmap(this, "lock_closed"); + m_bitmapLockDisabled = ScalableBitmap(this, "lock_closed", 16, true); // parameters for an icon's drawing fill_width_height(); @@ -125,6 +126,7 @@ void PresetComboBox::msw_rescale() m_em_unit = em_unit(this); m_bitmapLock.msw_rescale(); + m_bitmapLockDisabled.msw_rescale(); m_bitmapIncompatible.msw_rescale(); m_bitmapCompatible.msw_rescale(); @@ -241,6 +243,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); evt.StopPropagation(); + /* if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) { PhysicalPrinterDialog dlg(wxEmptyString); @@ -258,6 +261,19 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset } wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); } + */ + if (marker == LABEL_ITEM_WIZARD_PRINTERS) + show_add_menu(); + else + { + ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; + switch (marker) { + case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; + case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; + default: break; + } + wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); + } } else if (marker == LABEL_ITEM_PHYSICAL_PRINTER || this->m_last_selected != selected_item || m_collection->current_is_dirty() ) { this->m_last_selected = selected_item; evt.SetInt(this->m_type); @@ -377,6 +393,25 @@ bool PlaterPresetComboBox::switch_to_tab() return true; } +void PlaterPresetComboBox::show_add_menu() +{ + wxMenu* menu = new wxMenu(); + + append_menu_item(menu, wxID_ANY, _L("Add/Remove logical printers"), "", + [this](wxCommandEvent&) { + wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + + append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "", + [this](wxCommandEvent&) { + PhysicalPrinterDialog dlg(wxEmptyString); + if (dlg.ShowModal() == wxID_OK) + update(); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + + wxGetApp().plater()->PopupMenu(menu); +} + void PlaterPresetComboBox::show_edit_menu() { wxMenu* menu = new wxMenu(); @@ -393,7 +428,7 @@ void PlaterPresetComboBox::show_edit_menu() append_menu_item(menu, wxID_ANY, _L("Delete physical printer"), "", [this](wxCommandEvent&) { - const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_printer_name(); + const std::string& printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name(); if (printer_name.empty()) return; @@ -550,36 +585,6 @@ void PlaterPresetComboBox::update() } } - if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars.update_plater_ui - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - if (m_type == Preset::TYPE_SLA_MATERIAL) - set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); - else - set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); - } - if (m_type == Preset::TYPE_PRINTER) { // add Physical printers, if any exists @@ -608,7 +613,7 @@ void PlaterPresetComboBox::update() bmp = m_bitmap_cache->insert(bitmap_key, bmps); } - set_label_marker(Append(wxString::FromUTF8((it->name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + set_label_marker(Append(wxString::FromUTF8((it->full_name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); if (ph_printers.has_selection() && it->name == ph_printers.get_selected_printer_name() || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) @@ -616,6 +621,7 @@ void PlaterPresetComboBox::update() } } +/* // add LABEL_ITEM_PHYSICAL_PRINTERS std::string bitmap_key; if (wide_icons) @@ -639,6 +645,37 @@ void PlaterPresetComboBox::update() bmp = m_bitmap_cache->insert(bitmap_key, bmps); } set_label_marker(Append(separator(L("Add physical printer")), *bmp), LABEL_ITEM_PHYSICAL_PRINTERS); +*/ + } + + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "edit_preset_list"; + bitmap_key += "-h" + std::to_string(icon_height); + + wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars.update_plater_ui + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap("edit_uni")); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + if (m_type == Preset::TYPE_SLA_MATERIAL) + set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); + else + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } /* But, if selected_preset_item is still equal to INT_MAX, it means that @@ -680,7 +717,7 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) auto selected_item = evt.GetSelection(); auto marker = reinterpret_cast(this->GetClientData(selected_item)); - if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { + if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); if (marker == LABEL_ITEM_WIZARD_PRINTERS) wxTheApp->CallAfter([this]() { @@ -710,7 +747,7 @@ void TabPresetComboBox::update() const std::deque& presets = m_collection->get_presets(); - std::map nonsys_presets; + std::map> nonsys_presets; wxString selected = ""; if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); @@ -719,13 +756,24 @@ void TabPresetComboBox::update() const Preset& preset = presets[i]; if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) continue; + + // marker used for disable incompatible printer models for the selected physical printer + bool is_enabled = true; + // check this value just for printer presets, when physical printer is selected + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { + is_enabled = m_enable_all ? true : + preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name() || + preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model(); + } std::string bitmap_key = "tab"; - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this); + wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this, 16, !is_enabled); if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) bitmap_key += "_sla"; + if (!is_enabled) + bitmap_key += "_disabled"; } bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; @@ -737,13 +785,14 @@ void TabPresetComboBox::update() std::vector bmps; bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? main_bmp : preset.is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); + bmps.emplace_back((preset.is_system || preset.is_default) ? (is_enabled ? m_bitmapLock.bmp() : m_bitmapLockDisabled.bmp()) : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); bmp = m_bitmap_cache->insert(bitmap_key, bmps); } if (preset.is_default || preset.is_system) { - Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - (bmp == 0) ? main_bmp : *bmp); + int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), !bmp ? main_bmp : *bmp); + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); if (i == idx_selected || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) @@ -751,7 +800,8 @@ void TabPresetComboBox::update() } else { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + std::pair pair(bmp, is_enabled); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); if (i == idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } @@ -761,8 +811,11 @@ void TabPresetComboBox::update() if (!nonsys_presets.empty()) { set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - Append(it->first, *it->second); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); if (it->first == selected || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) @@ -845,6 +898,17 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) int border = 10; m_printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); + + if (printer_name.IsEmpty()) { + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_printer_presets->set_enable_all(); + printer_name = _L("My Printer Device"); + } + else { + std::string full_name = into_u8(printer_name); + printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); + } + m_printer_presets->set_selection_changed_function([this](int selection) { std::string selected_string = Preset::remove_suffix_modified(m_printer_presets->GetString(selection).ToUTF8().data()); Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); @@ -854,17 +918,22 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) preset = &edited_preset; m_printer.update_from_preset(*preset); + update_printer_name(); + // update values m_optgroup->reload_config(); update(); }); m_printer_presets->update(); - wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _("Descriptive name for the printer device") + ":"); + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); + m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_printer_name(); }); - if (printer_name.IsEmpty()) - printer_name = preset_name + " - "+_L("Physical Printer"); - m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + wxStaticText* label_bottom = new wxStaticText(this, wxID_ANY, _("This printer name will be shown in the presets list") + ":"); + m_full_printer_name = new wxStaticText(this, wxID_ANY, ""); + + update_printer_name(); PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); @@ -887,8 +956,11 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_printer_name , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(label_bottom , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_full_printer_name , 0, wxEXPAND | wxLEFT | wxRIGHT, border); topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(btns , 0, wxEXPAND | wxALL, border); @@ -1053,6 +1125,15 @@ void PhysicalPrinterDialog::update() this->Layout(); } +void PhysicalPrinterDialog::update_printer_name() +{ + wxString printer_name = m_printer_name->GetValue(); + wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); + + m_full_printer_name->SetLabelText("\t" + printer_name + " * " + preset_name); + this->Layout(); +} + void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -1096,8 +1177,8 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) printers.delete_printer(into_u8(printer_name)); } - //upadte printer name, if it was changed - m_printer.name = into_u8(printer_name); + //update printer name, if it was changed + m_printer.set_name(into_u8(printer_name)); // save new physical printer printers.save_printer(m_printer); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 196c4368e8..653c0e540c 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,6 +13,7 @@ class wxString; class wxTextCtrl; +class wxStatictext; class ScalableButton; namespace Slic3r { @@ -35,6 +36,7 @@ public: enum LabelItemType { LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01, + LABEL_ITEM_DISABLED, LABEL_ITEM_MARKER, LABEL_ITEM_PHYSICAL_PRINTERS, LABEL_ITEM_WIZARD_PRINTERS, @@ -66,6 +68,8 @@ protected: ScalableBitmap m_bitmapIncompatible; // Indicator, that the preset is system and not modified. ScalableBitmap m_bitmapLock; + // Disabled analogue of the m_bitmapLock . + ScalableBitmap m_bitmapLockDisabled; int m_last_selected; int m_em_unit; @@ -125,6 +129,7 @@ public: bool is_selected_physical_printer(); bool switch_to_tab(); + void show_add_menu(); void show_edit_menu(); void update() override; @@ -142,6 +147,7 @@ private: class TabPresetComboBox : public PresetComboBox { bool show_incompatible {false}; + bool m_enable_all {false}; std::function on_selection_changed { nullptr }; public: @@ -156,6 +162,7 @@ public: void msw_rescale() override; void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } + void set_enable_all(bool enable=true) { m_enable_all = enable; } }; @@ -169,6 +176,7 @@ class PhysicalPrinterDialog : public DPIDialog DynamicPrintConfig* m_config { nullptr }; wxTextCtrl* m_printer_name { nullptr }; + wxStaticText* m_full_printer_name { nullptr }; TabPresetComboBox* m_printer_presets { nullptr }; ConfigOptionsGroup* m_optgroup { nullptr }; @@ -178,6 +186,7 @@ class PhysicalPrinterDialog : public DPIDialog void build_printhost_settings(ConfigOptionsGroup* optgroup); void update(); + void update_printer_name(); void OnOK(wxEvent& event); public: diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index da5d51dc54..595283e983 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -162,10 +162,9 @@ void Tab::create_preset_tab() // preset chooser m_presets_choice = new TabPresetComboBox(panel, m_type); m_presets_choice->set_selection_changed_function([this](int selection) { - // unselect pthysical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); - // select preset std::string selected_string = m_presets_choice->GetString(selection).ToUTF8().data(); + update_physical_printers(selected_string); + // select preset select_preset(selected_string); }); @@ -763,6 +762,32 @@ void Tab::update_tab_ui() m_presets_choice->update(); } +void Tab::update_physical_printers(std::string preset_name) +{ + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) + { + std::string printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name(); + wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\".")) % printer_name).str()); + msg_text += "\n\n" + _L("Would you like to change related preset for this printer?") + "\n\n" + + _L("Select YES if you want to change related preset for this printer \n" + "or NO to switch to the another preset (logical printer)."); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_YES) { + preset_name = Preset::remove_suffix_modified(preset_name); + Preset* preset = m_presets->find_preset(preset_name); + assert(preset); + Preset& edited_preset = m_presets->get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + m_preset_bundle->physical_printers.get_selected_printer().update_from_preset(*preset); + } + else + // unselect physical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); + } +} + // Load a provied DynamicConfig into the tab, modifying the active preset. // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) @@ -3269,6 +3294,8 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + //update physical printer's related printer preset if it's needed + update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); // Update the selection boxes at the plater. diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index bc15efa359..69720ff65e 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -306,6 +306,7 @@ public: void load_initial_data(); void update_dirty(); void update_tab_ui(); + void update_physical_printers(std::string preset_name); void load_config(const DynamicPrintConfig& config); virtual void reload_config(); void update_mode(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 39b3e154b4..67b5a18f79 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -731,11 +731,12 @@ void MenuWithSeparators::SetSecondSeparator() // ---------------------------------------------------------------------------- ScalableBitmap::ScalableBitmap( wxWindow *parent, const std::string& icon_name/* = ""*/, - const int px_cnt/* = 16*/): + const int px_cnt/* = 16*/, + const bool grayscale/* = false*/): m_parent(parent), m_icon_name(icon_name), m_px_cnt(px_cnt) { - m_bmp = create_scaled_bitmap(icon_name, parent, px_cnt); + m_bmp = create_scaled_bitmap(icon_name, parent, px_cnt, grayscale); } wxSize ScalableBitmap::GetBmpSize() const @@ -768,7 +769,7 @@ int ScalableBitmap::GetBmpHeight() const void ScalableBitmap::msw_rescale() { - m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt); + m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt, m_grayscale); } // ---------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 17fe8992c5..9be3361bda 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -130,7 +130,8 @@ public: ScalableBitmap() {}; ScalableBitmap( wxWindow *parent, const std::string& icon_name = "", - const int px_cnt = 16); + const int px_cnt = 16, + const bool grayscale = false); ~ScalableBitmap() {} @@ -151,6 +152,7 @@ private: wxBitmap m_bmp = wxBitmap(); std::string m_icon_name = ""; int m_px_cnt {16}; + bool m_grayscale {false}; }; From 6d4a0d91fce7bc8e85302d9b0f8d14b882c0cb9c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 26 Jun 2020 16:58:53 +0200 Subject: [PATCH 150/826] Fixed typo in PresetComboBox.hpp and added missed include in libslic3r.h --- src/libslic3r/libslic3r.h | 1 + src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index db375ec14f..3ea70d9985 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -5,6 +5,7 @@ // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include +#include #include #include #include diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 653c0e540c..dce22bc82b 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,7 +13,7 @@ class wxString; class wxTextCtrl; -class wxStatictext; +class wxStaticText; class ScalableButton; namespace Slic3r { From 1da6a2c20fd96816291b3105b04f383f9b3f4f23 Mon Sep 17 00:00:00 2001 From: Manuel Coenen Date: Mon, 29 Jun 2020 12:14:53 +0200 Subject: [PATCH 151/826] Fix compiler error with VS2019 (hopefully) --- src/slic3r/Utils/Duet.cpp | 24 ++++++++++++------------ src/slic3r/Utils/Duet.hpp | 10 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index cc6b0ebd94..4a510bb04b 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -39,7 +39,7 @@ bool Duet::test(wxString &msg) const auto connectionType = connect(msg); disconnect(connectionType); - return connectionType != Duet::ConnectionType::ERROR; + return connectionType != ConnectionType::error; } wxString Duet::get_test_ok_msg () const @@ -58,13 +58,13 @@ bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn e { wxString connect_msg; auto connectionType = connect(connect_msg); - if (connectionType == Duet::ConnectionType::ERROR) { + if (connectionType == ConnectionType::error) { error_fn(std::move(connect_msg)); return false; } bool res = true; - bool dsf = (connectionType == Duet::ConnectionType::DSF); + bool dsf = (connectionType == ConnectionType::dsf); auto upload_cmd = get_upload_url(upload_data.upload_path.string(), connectionType); BOOST_LOG_TRIVIAL(info) << boost::format("Duet: Uploading file %1%, filepath: %2%, print: %3%, command: %4%") @@ -117,7 +117,7 @@ bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn e Duet::ConnectionType Duet::connect(wxString &msg) const { - auto res = Duet::ConnectionType::ERROR; + auto res = ConnectionType::error; auto url = get_connect_url(false); auto http = Http::get(std::move(url)); @@ -129,7 +129,7 @@ Duet::ConnectionType Duet::connect(wxString &msg) const msg = format_error(body, error, status); }) .on_complete([&](std::string body, unsigned) { - res = Duet::ConnectionType::DSF; + res = ConnectionType::dsf; }) .perform_sync(); }) @@ -139,7 +139,7 @@ Duet::ConnectionType Duet::connect(wxString &msg) const int err_code = get_err_code_from_body(body); switch (err_code) { case 0: - res = Duet::ConnectionType::RRF; + res = ConnectionType::rrf; break; case 1: msg = format_error(body, L("Wrong password"), 0); @@ -158,10 +158,10 @@ Duet::ConnectionType Duet::connect(wxString &msg) const return res; } -void Duet::disconnect(Duet::ConnectionType connectionType) const +void Duet::disconnect(ConnectionType connectionType) const { // we don't need to disconnect from DSF or if it failed anyway - if (connectionType != Duet::ConnectionType::RRF) { + if (connectionType != ConnectionType::rrf) { return; } auto url = (boost::format("%1%rr_disconnect") @@ -175,9 +175,9 @@ void Duet::disconnect(Duet::ConnectionType connectionType) const .perform_sync(); } -std::string Duet::get_upload_url(const std::string &filename, Duet::ConnectionType connectionType) const +std::string Duet::get_upload_url(const std::string &filename, ConnectionType connectionType) const { - if (connectionType == Duet::ConnectionType::DSF) { + if (connectionType == ConnectionType::dsf) { return (boost::format("%1%machine/file/gcodes/%2%") % get_base_url() % Http::url_encode(filename)).str(); @@ -228,10 +228,10 @@ std::string Duet::timestamp_str() const return std::string(buffer); } -bool Duet::start_print(wxString &msg, const std::string &filename, Duet::ConnectionType connectionType) const +bool Duet::start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const { bool res = false; - bool dsf = (connectionType == Duet::ConnectionType::DSF); + bool dsf = (connectionType == ConnectionType::dsf); auto url = dsf ? (boost::format("%1%machine/code") diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index 41c6b1d755..597df02b35 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -29,17 +29,17 @@ public: std::string get_host() const override { return host; } private: - enum ConnectionType { RRF, DSF, ERROR }; + enum class ConnectionType { rrf, dsf, error }; std::string host; std::string password; - std::string get_upload_url(const std::string &filename, Duet::ConnectionType connectionType) const; + std::string get_upload_url(const std::string &filename, ConnectionType connectionType) const; std::string get_connect_url(const bool dsfUrl) const; std::string get_base_url() const; std::string timestamp_str() const; - Duet::ConnectionType connect(wxString &msg) const; - void disconnect(Duet::ConnectionType connectionType) const; - bool start_print(wxString &msg, const std::string &filename, Duet::ConnectionType connectionType) const; + ConnectionType connect(wxString &msg) const; + void disconnect(ConnectionType connectionType) const; + bool start_print(wxString &msg, const std::string &filename, ConnectionType connectionType) const; int get_err_code_from_body(const std::string &body) const; }; From 69de5c8c9fdb4e51e3b6d489f36b0b6227360770 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 29 Jun 2020 14:00:08 +0200 Subject: [PATCH 152/826] GCodeViewer -> Pass vertex normal to shaders for toolpaths --- resources/shaders/extrusions.fs | 40 --- resources/shaders/extrusions.vs | 11 - resources/shaders/options_120_solid.fs | 2 +- resources/shaders/toolpaths.fs | 31 ++ resources/shaders/toolpaths.vs | 21 ++ resources/shaders/travels.fs | 40 --- resources/shaders/travels.vs | 11 - src/libslic3r/GCode/GCodeProcessor.cpp | 12 + src/libslic3r/GCode/GCodeProcessor.hpp | 5 + src/libslic3r/Technologies.hpp | 4 +- src/slic3r/GUI/GCodeViewer.cpp | 410 +++++++++++++++++-------- src/slic3r/GUI/GCodeViewer.hpp | 100 ++++-- src/slic3r/GUI/GLShadersManager.cpp | 9 +- 13 files changed, 431 insertions(+), 265 deletions(-) delete mode 100644 resources/shaders/extrusions.fs delete mode 100644 resources/shaders/extrusions.vs create mode 100644 resources/shaders/toolpaths.fs create mode 100644 resources/shaders/toolpaths.vs delete mode 100644 resources/shaders/travels.fs delete mode 100644 resources/shaders/travels.vs diff --git a/resources/shaders/extrusions.fs b/resources/shaders/extrusions.fs deleted file mode 100644 index 65db0667a9..0000000000 --- a/resources/shaders/extrusions.fs +++ /dev/null @@ -1,40 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/extrusions.vs b/resources/shaders/extrusions.vs deleted file mode 100644 index 8980f3944b..0000000000 --- a/resources/shaders/extrusions.vs +++ /dev/null @@ -1,11 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); - gl_Position = ftransform(); -} diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs index 4719ff96a6..4480d7b147 100644 --- a/resources/shaders/options_120_solid.fs +++ b/resources/shaders/options_120_solid.fs @@ -84,6 +84,6 @@ void main() vec3 eye_on_sphere_position = eye_position_on_sphere(eye_position_from_fragment()); - gl_FragDepth = fragment_depth(eye_on_sphere_position); +// gl_FragDepth = fragment_depth(eye_on_sphere_position); gl_FragColor = on_sphere_color(eye_on_sphere_position); } diff --git a/resources/shaders/toolpaths.fs b/resources/shaders/toolpaths.fs new file mode 100644 index 0000000000..13f60c0a82 --- /dev/null +++ b/resources/shaders/toolpaths.fs @@ -0,0 +1,31 @@ +#version 110 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); + +// x = ambient, y = top diffuse, z = front diffuse, w = global +uniform vec4 light_intensity; +uniform vec3 uniform_color; + +varying vec3 eye_position; +varying vec3 eye_normal; + +float intensity; + +void main() +{ + vec3 normal = normalize(eye_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. Take the abs value to light the lines no matter in which direction the normal points. + float NdotL = abs(dot(normal, LIGHT_TOP_DIR)); + + intensity = light_intensity.x + NdotL * light_intensity.y; + + // Perform the same lighting calculation for the 2nd light source. + NdotL = abs(dot(normal, LIGHT_FRONT_DIR)); + intensity += NdotL * light_intensity.z; + + gl_FragColor = vec4(uniform_color * light_intensity.w * intensity, 1.0); +} diff --git a/resources/shaders/toolpaths.vs b/resources/shaders/toolpaths.vs new file mode 100644 index 0000000000..34d141bfe1 --- /dev/null +++ b/resources/shaders/toolpaths.vs @@ -0,0 +1,21 @@ +#version 110 + +varying vec3 eye_position; +varying vec3 eye_normal; + +vec3 world_normal() +{ + // the world normal is always parallel to the world XY plane + // the x component is stored into gl_Vertex.w + float x = gl_Vertex.w; + float y = sqrt(1.0 - x * x); + return vec3(x, y, 0.0); +} + +void main() +{ + vec4 world_position = vec4(gl_Vertex.xyz, 1.0); + gl_Position = gl_ModelViewProjectionMatrix * world_position; + eye_position = (gl_ModelViewMatrix * world_position).xyz; + eye_normal = gl_NormalMatrix * world_normal(); +} diff --git a/resources/shaders/travels.fs b/resources/shaders/travels.fs deleted file mode 100644 index 65db0667a9..0000000000 --- a/resources/shaders/travels.fs +++ /dev/null @@ -1,40 +0,0 @@ -#version 110 - -#define INTENSITY_AMBIENT 0.3 -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -uniform vec3 uniform_color; - -varying vec3 eye_position; -varying vec3 eye_normal; - -// x = tainted, y = specular; -vec2 intensity; - -void main() -{ - vec3 normal = normalize(eye_normal); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + uniform_color * intensity.x, 1.0); -} diff --git a/resources/shaders/travels.vs b/resources/shaders/travels.vs deleted file mode 100644 index 8980f3944b..0000000000 --- a/resources/shaders/travels.vs +++ /dev/null @@ -1,11 +0,0 @@ -#version 110 - -varying vec3 eye_position; -varying vec3 eye_normal; - -void main() -{ - eye_position = (gl_ModelViewMatrix * gl_Vertex).xyz; - eye_normal = gl_NormalMatrix * vec3(0.0, 0.0, 1.0); - gl_Position = ftransform(); -} diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index d561647262..14f29b56b6 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -7,6 +7,10 @@ #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_STATISTICS +#include +#endif // ENABLE_GCODE_VIEWER_STATISTICS + static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; @@ -89,9 +93,17 @@ void GCodeProcessor::reset() void GCodeProcessor::process_file(const std::string& filename) { +#if ENABLE_GCODE_VIEWER_STATISTICS + auto start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + m_result.id = ++s_result_id; m_result.moves.emplace_back(MoveVertex()); m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS } void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index bc49245848..3f596c9c2b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -106,7 +106,12 @@ namespace Slic3r { { unsigned int id; std::vector moves; +#if ENABLE_GCODE_VIEWER_STATISTICS + long long time{ 0 }; + void reset() { time = 0; moves = std::vector(); } +#else void reset() { moves = std::vector(); } +#endif // ENABLE_GCODE_VIEWER_STATISTICS }; private: diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index b04e78c4ed..fb84efe5ab 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,8 +59,8 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_STATISTICS (1 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (1 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 05a8c20bba..6d6ba557e2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -64,12 +64,23 @@ std::vector> decode_colors(const std::vector & void GCodeViewer::VBuffer::reset() { // release gpu memory - if (vbo_id > 0) { - glsafe(::glDeleteBuffers(1, &vbo_id)); - vbo_id = 0; + if (id > 0) { + glsafe(::glDeleteBuffers(1, &id)); + id = 0; } - vertices_count = 0; + count = 0; +} + +void GCodeViewer::IBuffer::reset() +{ + // release gpu memory + if (id > 0) { + glsafe(::glDeleteBuffers(1, &id)); + id = 0; + } + + count = 0; } bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const @@ -96,21 +107,18 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const } } -void GCodeViewer::IBuffer::reset() +void GCodeViewer::TBuffer::reset() { // release gpu memory - if (ibo_id > 0) { - glsafe(::glDeleteBuffers(1, &ibo_id)); - ibo_id = 0; - } + vertices.reset(); + indices.reset(); // release cpu memory - indices_count = 0; paths = std::vector(); render_paths = std::vector(); } -void GCodeViewer::IBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) +void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) { Path::Endpoint endpoint = { i_id, s_id, move.position }; paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); @@ -209,7 +217,7 @@ void GCodeViewer::SequentialView::Marker::render() const } const std::vector GCodeViewer::Extrusion_Role_Colors {{ - { 0.50f, 0.50f, 0.50f }, // erNone + { 0.75f, 0.75f, 0.75f }, // erNone { 1.00f, 1.00f, 0.40f }, // erPerimeter { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter { 0.00f, 0.00f, 1.00f }, // erOverhangPerimeter @@ -257,6 +265,29 @@ const std::vector GCodeViewer::Range_Colors {{ bool GCodeViewer::init() { + for (size_t i = 0; i < m_buffers.size(); ++i) + { + switch (buffer_type(i)) + { + case GCodeProcessor::EMoveType::Tool_change: + case GCodeProcessor::EMoveType::Color_change: + case GCodeProcessor::EMoveType::Pause_Print: + case GCodeProcessor::EMoveType::Custom_GCode: + case GCodeProcessor::EMoveType::Retract: + case GCodeProcessor::EMoveType::Unretract: + { + m_buffers[i].vertices.format = VBuffer::EFormat::Position; + break; + } + case GCodeProcessor::EMoveType::Extrude: + case GCodeProcessor::EMoveType::Travel: + { + m_buffers[i].vertices.format = VBuffer::EFormat::PositionNormal; + break; + } + } + } + set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); m_sequential_view.marker.init(); init_shaders(); @@ -306,7 +337,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (m_vertices.vertices_count == 0) + if (m_vertices_count == 0) return; // update tool colors @@ -314,7 +345,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update ranges for coloring / legend m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) { + for (size_t i = 0; i < m_vertices_count; ++i) { // skip first vertex if (i == 0) continue; @@ -342,19 +373,18 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: } } - // update buffers' render paths - refresh_render_paths(false, false); - #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.refresh_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS + + // update buffers' render paths + refresh_render_paths(false, false); } void GCodeViewer::reset() { - m_vertices.reset(); - - for (IBuffer& buffer : m_buffers) { + m_vertices_count = 0; + for (TBuffer& buffer : m_buffers) { buffer.reset(); } @@ -472,8 +502,8 @@ void GCodeViewer::init_shaders() case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "extrusions"; break; } - case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "travels"; break; } + case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "toolpaths"; break; } + case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "toolpaths"; break; } default: { break; } } } @@ -484,16 +514,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex); + m_statistics.results_time = gcode_result.time; #endif // ENABLE_GCODE_VIEWER_STATISTICS - // vertex data - m_vertices.vertices_count = gcode_result.moves.size(); - if (m_vertices.vertices_count == 0) + // vertices data + m_vertices_count = gcode_result.moves.size(); + if (m_vertices_count == 0) return; - // vertex data / bounding box -> extract from result - std::vector vertices_data(m_vertices.vertices_count * 3); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) { + for (size_t i = 0; i < m_vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; #if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) @@ -506,29 +535,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_GCODE_VIEWER_AS_STATE } #endif // ENABLE_GCODE_VIEWER_AS_STATE - ::memcpy(static_cast(&vertices_data[i * 3]), static_cast(move.position.data()), 3 * sizeof(float)); } + // max bounding box m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.vertices_size = SLIC3R_STDVEC_MEMSIZE(vertices_data, float); - m_statistics.vertices_gpu_size = vertices_data.size() * sizeof(float); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // vertex data -> send to gpu - glsafe(::glGenBuffers(1, &m_vertices.vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices_data.size() * sizeof(float), vertices_data.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - // vertex data -> no more needed, free ram - vertices_data = std::vector(); - - // indices data -> extract from result + // toolpaths data -> extract from result + std::vector> vertices(m_buffers.size()); std::vector> indices(m_buffers.size()); - for (size_t i = 0; i < m_vertices.vertices_count; ++i) { + for (size_t i = 0; i < m_vertices_count; ++i) { // skip first vertex if (i == 0) continue; @@ -537,7 +553,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; unsigned char id = buffer_id(curr.type); - IBuffer& buffer = m_buffers[id]; + TBuffer& buffer = m_buffers[id]; + std::vector& buffer_vertices = vertices[id]; std::vector& buffer_indices = indices[id]; switch (curr.type) @@ -549,54 +566,103 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case GCodeProcessor::EMoveType::Retract: case GCodeProcessor::EMoveType::Unretract: { + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(curr.position[j]); + } buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(i)); - buffer_indices.push_back(static_cast(i)); + buffer_indices.push_back(static_cast(buffer_indices.size())); break; } case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(i - 1)); + // add starting vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(prev.position[j]); + } + // add starting vertex normal x component + buffer_vertices.push_back(normal_x); + // add starting index + buffer_indices.push_back(buffer_indices.size()); + buffer.add_path(curr, static_cast(buffer_indices.size() - 1), static_cast(i - 1)); Path& last_path = buffer.paths.back(); last_path.first.position = prev.position; - buffer_indices.push_back(static_cast(i - 1)); } - - buffer.paths.back().last = { static_cast(buffer_indices.size()), static_cast(i), curr.position }; - buffer_indices.push_back(static_cast(i)); + + Path& last_path = buffer.paths.back(); + if (last_path.first.i_id != last_path.last.i_id) + { + // add previous vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(prev.position[j]); + } + // add previous vertex normal x component + buffer_vertices.push_back(normal_x); + // add previous index + buffer_indices.push_back(buffer_indices.size()); + } + + // add current vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(curr.position[j]); + } + // add current vertex normal x component + buffer_vertices.push_back(normal_x); + // add current index + buffer_indices.push_back(buffer_indices.size()); + last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(i), curr.position }; break; } default: { break; } } } + // toolpaths data -> send data to gpu + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& buffer = m_buffers[i]; + + // vertices + const std::vector& buffer_vertices = vertices[i]; + buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); #if ENABLE_GCODE_VIEWER_STATISTICS - for (IBuffer& buffer : m_buffers) { - m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } + m_statistics.vertices_gpu_size = buffer_vertices.size() * sizeof(float); #endif // ENABLE_GCODE_VIEWER_STATISTICS - // indices data -> send data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - IBuffer& buffer = m_buffers[i]; + glsafe(::glGenBuffers(1, &buffer.vertices.id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + // indices const std::vector& buffer_indices = indices[i]; - buffer.indices_count = buffer_indices.size(); + buffer.indices.count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer_indices, unsigned int); - m_statistics.indices_gpu_size += buffer.indices_count * sizeof(unsigned int); + m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (buffer.indices_count > 0) { - glsafe(::glGenBuffers(1, &buffer.ibo_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.indices_count * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); + if (buffer.indices.count > 0) { + glsafe(::glGenBuffers(1, &buffer.indices.id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.count * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } } +#if ENABLE_GCODE_VIEWER_STATISTICS + for (const TBuffer& buffer : m_buffers) { + m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + } + m_statistics.travel_segments_count = indices[buffer_id(GCodeProcessor::EMoveType::Travel)].size() / 2; + m_statistics.extrude_segments_count = indices[buffer_id(GCodeProcessor::EMoveType::Extrude)].size() / 2; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + // layers zs / roles / extruder ids / cp color ids -> extract from result - for (size_t i = 0; i < m_vertices.vertices_count; ++i) { + for (size_t i = 0; i < m_vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (move.type == GCodeProcessor::EMoveType::Extrude) m_layers_zs.emplace_back(static_cast(move.position[2])); @@ -728,18 +794,18 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool m_statistics.render_paths_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS - m_sequential_view.endpoints.first = m_vertices.vertices_count; + m_sequential_view.endpoints.first = m_vertices_count; m_sequential_view.endpoints.last = 0; if (!keep_sequential_current_first) m_sequential_view.current.first = 0; if (!keep_sequential_current_last) - m_sequential_view.current.last = m_vertices.vertices_count; + m_sequential_view.current.last = m_vertices_count; // first pass: collect visible paths and update sequential view data - std::vector> paths; - for (IBuffer& buffer : m_buffers) { + std::vector> paths; + for (TBuffer& buffer : m_buffers) { // reset render paths - buffer.render_paths = std::vector(); + buffer.render_paths.clear(); if (!buffer.visible) continue; @@ -767,23 +833,43 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // update current sequential position m_sequential_view.current.first = keep_sequential_current_first ? std::clamp(m_sequential_view.current.first, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.first; m_sequential_view.current.last = keep_sequential_current_last ? std::clamp(m_sequential_view.current.last, m_sequential_view.endpoints.first, m_sequential_view.endpoints.last) : m_sequential_view.endpoints.last; - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); - size_t v_size = VBuffer::vertex_size_bytes(); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(m_sequential_view.current.last * v_size), static_cast(v_size), static_cast(m_sequential_view.current_position.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - // second pass: filter paths by sequential data + // get the world position from gpu + bool found = false; + for (const TBuffer& buffer : m_buffers) { + // searches the path containing the current position + for (const Path& path : buffer.paths) { + if (path.first.s_id <= m_sequential_view.current.last && m_sequential_view.current.last <= path.last.s_id) { + size_t offset = m_sequential_view.current.last - path.first.s_id; + if (offset > 0 && (path.type == GCodeProcessor::EMoveType::Travel || path.type == GCodeProcessor::EMoveType::Extrude)) + offset = 1 + 2 * (offset - 1); + + offset += path.first.i_id; + + // gets the position from the vertices buffer on gpu + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(offset * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(m_sequential_view.current_position.data()))); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + found = true; + break; + } + } + if (found) + break; + } + + // second pass: filter paths by sequential data and collect them by color for (auto&& [buffer, id] : paths) { const Path& path = buffer->paths[id]; - if ((m_sequential_view.current.last <= path.first.s_id) || (path.last.s_id <= m_sequential_view.current.first)) + if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) continue; Color color; switch (path.type) { case GCodeProcessor::EMoveType::Extrude: { color = extrusion_color(path); break; } - case GCodeProcessor::EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } - default: { color = { 0.0f, 0.0f, 0.0f }; break; } + case GCodeProcessor::EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } + default: { color = { 0.0f, 0.0f, 0.0f }; break; } } auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); @@ -792,7 +878,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool it->color = color; } - it->sizes.push_back(std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1); + unsigned int size = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; + if (path.type == GCodeProcessor::EMoveType::Extrude || path.type == GCodeProcessor::EMoveType::Travel) + size = 2 * (size - 1); + + it->sizes.push_back(size); unsigned int delta_1st = 0; if ((path.first.s_id < m_sequential_view.current.first) && (m_sequential_view.current.first <= path.last.s_id)) delta_1st = m_sequential_view.current.first - path.first.s_id; @@ -801,14 +891,13 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } #if ENABLE_GCODE_VIEWER_STATISTICS - for (const IBuffer& buffer : m_buffers) { + for (const TBuffer& buffer : m_buffers) { m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath); for (const RenderPath& path : buffer.render_paths) { m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); m_statistics.render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); } } - m_statistics.refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } @@ -816,9 +905,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - float point_size = m_shaders_editor.point_size; + float point_size = m_shaders_editor.points.point_size; #else - float point_size = 1.0f; + float point_size = 0.8f; #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR const Camera& camera = wxGetApp().plater()->get_camera(); double zoom = camera.get_zoom(); @@ -828,12 +917,12 @@ void GCodeViewer::render_toolpaths() const Transform3d inv_proj = camera.get_projection_matrix().inverse(); - auto render_as_points = [this, zoom, inv_proj, viewport, point_size, near_plane_height](const IBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { + auto render_as_points = [this, zoom, inv_proj, viewport, point_size, near_plane_height](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { shader.set_uniform("uniform_color", Options_Colors[static_cast(color_id)]); shader.set_uniform("zoom", zoom); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.percent_outline)); - shader.set_uniform("percent_center_radius", 0.01f * static_cast(m_shaders_editor.percent_center)); + shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.points.percent_outline)); + shader.set_uniform("percent_center_radius", 0.01f * static_cast(m_shaders_editor.points.percent_center)); #else shader.set_uniform("percent_outline_radius", 0.15f); shader.set_uniform("percent_center_radius", 0.15f); @@ -852,16 +941,16 @@ void GCodeViewer::render_toolpaths() const ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } - + glsafe(::glDisable(GL_POINT_SPRITE)); glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [this](const IBuffer& buffer, GLShaderProgram& shader) { + auto render_as_lines = [this](const TBuffer& buffer, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { shader.set_uniform("uniform_color", path.color); - glsafe(::glMultiDrawElements(GL_LINE_STRIP, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -878,27 +967,25 @@ void GCodeViewer::render_toolpaths() const unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vertices.vbo_id)); - glsafe(::glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, VBuffer::vertex_size_bytes(), (const void*)0)); - glsafe(::glEnableVertexAttribArray(0)); - for (unsigned char i = begin_id; i < end_id; ++i) { - const IBuffer& buffer = m_buffers[i]; - if (buffer.ibo_id == 0) - continue; - + const TBuffer& buffer = m_buffers[i]; if (!buffer.visible) continue; + if (buffer.vertices.id == 0 || buffer.indices.id == 0) + continue; + GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); if (shader != nullptr) { shader->start_using(); - GCodeProcessor::EMoveType type = buffer_type(i); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); + glsafe(::glVertexAttribPointer(0, buffer.vertices.vertex_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)0)); + glsafe(::glEnableVertexAttribArray(0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - switch (type) + switch (buffer_type(i)) { case GCodeProcessor::EMoveType::Tool_change: { render_as_points(buffer, EOptionsColors::ToolChanges, *shader); break; } case GCodeProcessor::EMoveType::Color_change: { render_as_points(buffer, EOptionsColors::ColorChanges, *shader); break; } @@ -907,16 +994,33 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Retract: { render_as_points(buffer, EOptionsColors::Retractions, *shader); break; } case GCodeProcessor::EMoveType::Unretract: { render_as_points(buffer, EOptionsColors::Unretractions, *shader); break; } case GCodeProcessor::EMoveType::Extrude: - case GCodeProcessor::EMoveType::Travel: { render_as_lines(buffer, *shader); break; } + case GCodeProcessor::EMoveType::Travel: + { + std::array light_intensity; +#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR + light_intensity[0] = m_shaders_editor.lines.lights.ambient; + light_intensity[1] = m_shaders_editor.lines.lights.top_diffuse; + light_intensity[2] = m_shaders_editor.lines.lights.front_diffuse; + light_intensity[3] = m_shaders_editor.lines.lights.global; +#else + light_intensity[0] = 0.25f; + light_intensity[1] = 0.7f; + light_intensity[2] = 0.75f; + light_intensity[3] = 0.75f; +#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR + shader->set_uniform("light_intensity", light_intensity); + render_as_lines(buffer, *shader); break; + } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + glsafe(::glDisableVertexAttribArray(0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + shader->stop_using(); } } - - glsafe(::glDisableVertexAttribArray(0)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } void GCodeViewer::render_shells() const @@ -985,13 +1089,13 @@ void GCodeViewer::render_legend() const { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_shaders_editor.shader_version == 1) { + if (m_shaders_editor.points.shader_version == 1) { draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); + float radius = 0.5f * icon_size * (1.0f - 0.01f * static_cast(m_shaders_editor.points.percent_outline)); draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - if (m_shaders_editor.percent_center > 0) { - radius = 0.5f * icon_size * 0.01f * static_cast(m_shaders_editor.percent_center); + if (m_shaders_editor.points.percent_center > 0) { + radius = 0.5f * icon_size * 0.01f * static_cast(m_shaders_editor.points.percent_center); draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); } } @@ -1284,10 +1388,10 @@ void GCodeViewer::render_legend() const }; auto add_option = [this, add_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { - const IBuffer& buffer = m_buffers[buffer_id(move_type)]; - if (buffer.visible && buffer.indices_count > 0) + const TBuffer& buffer = m_buffers[buffer_id(move_type)]; + if (buffer.visible && buffer.indices.count > 0) #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - add_item((m_shaders_editor.shader_version == 0) ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); + add_item((m_shaders_editor.points.shader_version == 0) ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); #else add_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR @@ -1320,14 +1424,22 @@ void GCodeViewer::render_legend() const void GCodeViewer::render_statistics() const { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - static const float offset = 250.0f; + static const float offset = 230.0f; ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); - imgui.begin(std::string("Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("GCodeProcessor time:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.results_time) + " ms"); + + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Load time:")); ImGui::PopStyleColor(); @@ -1370,12 +1482,6 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("Vertices CPU:")); - ImGui::PopStyleColor(); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.vertices_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Indices CPU:")); ImGui::PopStyleColor(); @@ -1408,6 +1514,20 @@ void GCodeViewer::render_statistics() const ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); + ImGui::Separator(); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Travel segments:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.travel_segments_count)); + + ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + imgui.text(std::string("Extrude segments:")); + ImGui::PopStyleColor(); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.extrude_segments_count)); + imgui.end(); } #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1429,34 +1549,58 @@ void GCodeViewer::render_shaders_editor() const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); imgui.set_next_window_pos(static_cast(cnv_size.get_width()), 0.5f * static_cast(cnv_size.get_height()), ImGuiCond_Once, 1.0f, 0.5f); + imgui.begin(std::string("Shaders editor (DEV only)"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); - ImGui::RadioButton("glsl version 1.10 (low end PCs)", &m_shaders_editor.shader_version, 0); - ImGui::RadioButton("glsl version 1.20 flat (billboards)", &m_shaders_editor.shader_version, 1); - ImGui::RadioButton("glsl version 1.20 solid (spheres default)", &m_shaders_editor.shader_version, 2); + if (ImGui::CollapsingHeader("Points", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::TreeNode("GLSL version")) { + ImGui::RadioButton("1.10 (low end PCs)", &m_shaders_editor.points.shader_version, 0); + ImGui::RadioButton("1.20 flat (billboards)", &m_shaders_editor.points.shader_version, 1); + ImGui::RadioButton("1.20 solid (spheres default)", &m_shaders_editor.points.shader_version, 2); + ImGui::TreePop(); + } - switch (m_shaders_editor.shader_version) - { - case 0: { set_shader("options_110"); break; } - case 1: { set_shader("options_120_flat"); break; } - case 2: { set_shader("options_120_solid"); break; } + switch (m_shaders_editor.points.shader_version) + { + case 0: { set_shader("options_110"); break; } + case 1: { set_shader("options_120_flat"); break; } + case 2: { set_shader("options_120_solid"); break; } + } + + if (ImGui::TreeNode("Options")) { + ImGui::SliderFloat("point size", &m_shaders_editor.points.point_size, 0.5f, 3.0f, "%.2f"); + if (m_shaders_editor.points.shader_version == 1) { + ImGui::SliderInt("% outline", &m_shaders_editor.points.percent_outline, 0, 50); + ImGui::SliderInt("% center", &m_shaders_editor.points.percent_center, 0, 50); + } + ImGui::TreePop(); + } } - if (ImGui::CollapsingHeader("Options", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderFloat("point size", &m_shaders_editor.point_size, 0.5f, 3.0f, "%.1f"); - if (m_shaders_editor.shader_version == 1) { - ImGui::SliderInt("percent outline", &m_shaders_editor.percent_outline, 0, 50); - ImGui::SliderInt("percent center", &m_shaders_editor.percent_center, 0, 50); + if (ImGui::CollapsingHeader("Lines", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::TreeNode("Lights")) { + ImGui::SliderFloat("ambient", &m_shaders_editor.lines.lights.ambient, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat("top diffuse", &m_shaders_editor.lines.lights.top_diffuse, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat("front diffuse", &m_shaders_editor.lines.lights.front_diffuse, 0.0f, 1.0f, "%.2f"); + ImGui::SliderFloat("global", &m_shaders_editor.lines.lights.global, 0.0f, 1.0f, "%.2f"); + ImGui::TreePop(); } } + ImGui::SetWindowSize(ImVec2(std::max(ImGui::GetWindowWidth(), 600.0f), -1.0f), ImGuiCond_Always); + if (ImGui::GetWindowPos().x + ImGui::GetWindowWidth() > static_cast(cnv_size.get_width())) { + ImGui::SetWindowPos(ImVec2(static_cast(cnv_size.get_width()) - ImGui::GetWindowWidth(), ImGui::GetWindowPos().y), ImGuiCond_Always); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + imgui.end(); } #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR bool GCodeViewer::is_travel_in_z_range(size_t id) const { - const IBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)]; + const TBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)]; if (id >= buffer.paths.size()) return false; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5c60863058..5cddeaa75a 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -33,18 +33,47 @@ class GCodeViewer CustomGCodes }; - // buffer containing vertices data + // vbo buffer containing vertices data for a specific toolpath type struct VBuffer { - unsigned int vbo_id{ 0 }; - size_t vertices_count{ 0 }; + enum class EFormat : unsigned char + { + Position, + PositionNormal + }; - size_t data_size_bytes() { return vertices_count * vertex_size_bytes(); } + EFormat format{ EFormat::Position }; + // vbo id + unsigned int id{ 0 }; + // count of vertices, updated after data are sent to gpu + size_t count{ 0 }; + + size_t data_size_bytes() const { return count * vertex_size_bytes(); } + size_t vertex_size_floats() const + { + switch (format) + { + // vertex format: 3 floats -> position.x|position.y|position.z + case EFormat::Position: { return 3; } + // vertex format: 4 floats -> position.x|position.y|position.z|normal.x + case EFormat::PositionNormal: { return 4; } + default: { return 0; } + } + } + size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } void reset(); + }; - static size_t vertex_size_floats() { return 3; } - static size_t vertex_size_bytes() { return vertex_size_floats() * sizeof(float); } + // ibo buffer containing indices data for a specific toolpath type + struct IBuffer + { + // ibo id + unsigned int id{ 0 }; + // count of indices, updated after data are sent to gpu + size_t count{ 0 }; + + void reset(); }; // Used to identify different toolpath sub-types inside a IBuffer @@ -52,9 +81,9 @@ class GCodeViewer { struct Endpoint { - // index into the buffer indices ibo + // index into the indices buffer unsigned int i_id{ 0u }; - // sequential id (same as index into the vertices vbo) + // sequential id unsigned int s_id{ 0u }; Vec3f position{ Vec3f::Zero() }; }; @@ -83,11 +112,12 @@ class GCodeViewer std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) }; - // buffer containing indices data and shader for a specific toolpath type - struct IBuffer + // buffer containing data for rendering a specific toolpath type + struct TBuffer { - unsigned int ibo_id{ 0 }; - size_t indices_count{ 0 }; + VBuffer vertices; + IBuffer indices; + std::string shader; std::vector paths; std::vector render_paths; @@ -161,6 +191,7 @@ class GCodeViewer struct Statistics { // times + long long results_time{ 0 }; long long load_time{ 0 }; long long refresh_time{ 0 }; long long refresh_paths_time{ 0 }; @@ -169,20 +200,24 @@ class GCodeViewer long long gl_multi_line_strip_calls_count{ 0 }; // memory long long results_size{ 0 }; - long long vertices_size{ 0 }; long long vertices_gpu_size{ 0 }; long long indices_size{ 0 }; long long indices_gpu_size{ 0 }; long long paths_size{ 0 }; long long render_paths_size{ 0 }; + // others + long long travel_segments_count{ 0 }; + long long extrude_segments_count{ 0 }; void reset_all() { reset_times(); reset_opengl(); reset_sizes(); + reset_counters(); } void reset_times() { + results_time = 0; load_time = 0; refresh_time = 0; refresh_paths_time = 0; @@ -195,23 +230,46 @@ class GCodeViewer void reset_sizes() { results_size = 0; - vertices_size = 0; vertices_gpu_size = 0; indices_size = 0; indices_gpu_size = 0; - paths_size = 0; + paths_size = 0; render_paths_size = 0; } + + void reset_counters() { + travel_segments_count = 0; + extrude_segments_count = 0; + } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR struct ShadersEditor { - int shader_version{ 2 }; - float point_size{ 1.0f }; - int percent_outline{ 0 }; - int percent_center{ 33 }; + struct Points + { + int shader_version{ 2 }; + float point_size{ 0.8f }; + int percent_outline{ 0 }; + int percent_center{ 33 }; + }; + + struct Lines + { + struct Lights + { + float ambient{ 0.25f }; + float top_diffuse{ 0.7f }; + float front_diffuse{ 0.75f }; + float global{ 0.75f }; + }; + + Lights lights; + }; + + Points points; + Lines lines; }; #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR @@ -268,8 +326,8 @@ public: private: unsigned int m_last_result_id{ 0 }; - VBuffer m_vertices; - mutable std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + size_t m_vertices_count{ 0 }; + mutable std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; // bounding box of toolpaths + marker tools diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 4bebf7b984..cb47d79618 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -35,15 +35,12 @@ std::pair GLShadersManager::init() valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); // used to render options in gcode preview valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); - if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) - { + if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) { valid &= append_shader("options_120_flat", { "options_120_flat.vs", "options_120_flat.fs" }); valid &= append_shader("options_120_solid", { "options_120_solid.vs", "options_120_solid.fs" }); } - // used to render extrusion paths in gcode preview - valid &= append_shader("extrusions", { "extrusions.vs", "extrusions.fs" }); - // used to render travel paths in gcode preview - valid &= append_shader("travels", { "travels.vs", "travels.fs" }); + // used to render extrusion and travel paths in gcode preview + valid &= append_shader("toolpaths", { "toolpaths.vs", "toolpaths.fs" }); // used to render objects in 3d editor valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }); // used to render variable layers heights in 3d editor From feb4857cf8d83829b844d30b86c6eeb9170454be Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 30 Jun 2020 12:53:42 +0200 Subject: [PATCH 153/826] Fixed height of features type combo popup when building against wxWidgets 3.1.3 --- src/slic3r/GUI/wxExtensions.cpp | 12 ++++-------- src/slic3r/GUI/wxExtensions.hpp | 1 - 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 74d790df86..c42933b9b3 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -174,7 +174,6 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; -const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18; bool wxCheckListBoxComboPopup::Create(wxWindow* parent) { @@ -202,20 +201,17 @@ wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, i // and set height dinamically in dependence of items count wxComboCtrl* cmb = GetComboCtrl(); - if (cmb != nullptr) - { + if (cmb != nullptr) { wxSize size = GetComboCtrl()->GetSize(); unsigned int count = GetCount(); - if (count > 0) - { + if (count > 0) { int max_width = size.x; - for (unsigned int i = 0; i < count; ++i) - { + for (unsigned int i = 0; i < count; ++i) { max_width = std::max(max_width, 60 + GetTextExtent(GetString(i)).x); } size.SetWidth(max_width); - size.SetHeight(4 + count * (2 + GetTextExtent(GetString(0)).y)); + size.SetHeight(count * cmb->GetCharHeight()); } else size.SetHeight(DefaultHeight); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 569257e1b4..254dbfad3a 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -63,7 +63,6 @@ class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup { static const unsigned int DefaultWidth; static const unsigned int DefaultHeight; - static const unsigned int DefaultItemHeight; wxString m_text; From 0b88e86634bd291f524c24912061c9c1e9e71ef3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 30 Jun 2020 14:12:47 +0200 Subject: [PATCH 154/826] PhysicalPrinter improvements: * implemented PresetForPrinter class --- src/libslic3r/Preset.cpp | 9 ++ src/libslic3r/Preset.hpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 154 ++++++++++++++++++++++------ src/slic3r/GUI/PresetComboBoxes.hpp | 51 +++++++-- 4 files changed, 175 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9af3dacf0a..ec3e933384 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1375,6 +1375,15 @@ const std::string& PhysicalPrinter::get_printer_model() const return config.opt_string("printer_model"); } +bool PhysicalPrinter::has_empty_config() const +{ + return config.opt_string("print_host" ).empty() && + config.opt_string("printhost_apikey").empty() && + config.opt_string("printhost_cafile").empty() && + config.opt_string("login" ).empty() && + config.opt_string("password" ).empty(); +} + void PhysicalPrinter::update_from_preset(const Preset& preset) { config.apply_only(preset.config, printer_options(), false); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6eb1fd2db3..a076a9a217 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -556,6 +556,7 @@ public: static const std::vector& printer_options(); const std::string& get_preset_name() const; const std::string& get_printer_model() const; + bool has_empty_config() const; void save() { this->config.save(this->file); } void save_to(const std::string& file_name) const { this->config.save(file_name); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 88dd4b739a..dc5365a139 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -884,6 +884,76 @@ void TabPresetComboBox::update_dirty() } +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ + +PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable) : + m_parent(parent) +{ + m_sizer = new wxBoxSizer(wxVERTICAL); + + m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + m_delete_preset_btn->SetFont(wxGetApp().normal_font()); + m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); + m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); + + m_presets_list = new TabPresetComboBox(parent, Preset::TYPE_PRINTER); + + if (is_all_enable) + m_presets_list->set_enable_all(); + + m_presets_list->set_selection_changed_function([this](int selection) { + std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); + Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); + assert(preset); + Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + + // if created physical printer doesn't have any settings, use the settings from the selected preset + if (m_parent->get_printer()->has_empty_config()) { + // update Print Host upload from the selected preset + m_parent->get_printer()->update_from_preset(*preset); + // update values in parent (PhysicalPrinterDialog) + m_parent->update(); + } + + update_full_printer_name(); + }); + + m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); + + m_presets_list->update(); +} + +PresetForPrinter::~PresetForPrinter() +{ + m_presets_list->Destroy(); + m_delete_preset_btn->Destroy(); + m_full_printer_name->Destroy(); +} + +void PresetForPrinter::DeletePreset(wxEvent& event) +{ + +} + +void PresetForPrinter::update_full_printer_name() +{ + wxString printer_name = m_parent->get_printer_name(); + wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection()); + + m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); +} + +void PresetForPrinter::msw_rescale() +{ + m_presets_list->msw_rescale(); + m_delete_preset_btn->msw_rescale(); +} + + //------------------------------------------ // PhysicalPrinterDialog //------------------------------------------ @@ -896,44 +966,31 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); int border = 10; + m_info_string = _("This printer name will be shown in the presets list") + ":\n"; - m_printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); + TabPresetComboBox* printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); if (printer_name.IsEmpty()) { - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_printer_presets->set_enable_all(); printer_name = _L("My Printer Device"); + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, true)); } else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); } - m_printer_presets->set_selection_changed_function([this](int selection) { - std::string selected_string = Preset::remove_suffix_modified(m_printer_presets->GetString(selection).ToUTF8().data()); - Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); - assert(preset); - Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - if (preset->name == edited_preset.name) - preset = &edited_preset; - m_printer.update_from_preset(*preset); - - update_printer_name(); - - // update values - m_optgroup->reload_config(); - update(); - }); - m_printer_presets->update(); - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _("Descriptive name for the printer device") + ":"); + + m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + m_add_preset_btn->SetFont(wxGetApp().normal_font()); + m_add_preset_btn->SetToolTip(_L("Add preset for this printer device")); + m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this); + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); - m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_printer_name(); }); + m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); - wxStaticText* label_bottom = new wxStaticText(this, wxID_ANY, _("This printer name will be shown in the presets list") + ":"); - m_full_printer_name = new wxStaticText(this, wxID_ANY, ""); - - update_printer_name(); + update_full_printer_names(); PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); @@ -948,19 +1005,27 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); build_printhost_settings(m_optgroup); - m_optgroup->reload_config(); + //m_optgroup->reload_config(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); + wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); + nameSizer->Add(m_printer_name, 1, wxEXPAND); + nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, border); + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_printer_name , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + for (PresetForPrinter* preset : m_presets) + topSizer->Add(preset->sizer(), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);; + /* topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(label_bottom , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_full_printer_name , 0, wxEXPAND | wxLEFT | wxRIGHT, border); + */ topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(btns , 0, wxEXPAND | wxALL, border); @@ -968,6 +1033,14 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) topSizer->SetSizeHints(this); } +PhysicalPrinterDialog::~PhysicalPrinterDialog() +{ + for (PresetForPrinter* preset : m_presets) { + delete preset; + preset = nullptr; + } +} + void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -1101,6 +1174,8 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr void PhysicalPrinterDialog::update() { + m_optgroup->reload_config(); + const PrinterTechnology tech = Preset::printer_technology(m_printer.config); // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) if (tech == ptFFF) { @@ -1125,12 +1200,17 @@ void PhysicalPrinterDialog::update() this->Layout(); } -void PhysicalPrinterDialog::update_printer_name() -{ - wxString printer_name = m_printer_name->GetValue(); - wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); - m_full_printer_name->SetLabelText("\t" + printer_name + " * " + preset_name); +wxString PhysicalPrinterDialog::get_printer_name() +{ + return m_info_string + m_printer_name->GetValue() + "\t"; +} + +void PhysicalPrinterDialog::update_full_printer_names() +{ + for (PresetForPrinter* preset : m_presets) + preset->update_full_printer_name(); + this->Layout(); } @@ -1147,6 +1227,9 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + for (PresetForPrinter* preset : m_presets) + preset->msw_rescale(); + const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(size); @@ -1184,6 +1267,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) printers.save_printer(m_printer); // update selection on the tab only when it was changed + /* if (m_printer.get_preset_name() != wxGetApp().preset_bundle->printers.get_selected_preset_name()) { Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); if (tab) { @@ -1191,9 +1275,15 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) tab->select_preset(into_u8(preset_name)); } } + */ event.Skip(); } +void PhysicalPrinterDialog::AddPreset(wxEvent& event) +{ + +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index dce22bc82b..3d5ae298f0 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -166,32 +166,67 @@ public: }; +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ +class PhysicalPrinterDialog; +class PresetForPrinter +{ + PhysicalPrinterDialog* m_parent { nullptr }; + + TabPresetComboBox* m_presets_list { nullptr }; + ScalableButton* m_delete_preset_btn { nullptr }; + wxStaticText* m_full_printer_name { nullptr }; + + wxBoxSizer* m_sizer { nullptr }; + + void DeletePreset(wxEvent& event); + +public: + PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable); + ~PresetForPrinter(); + + wxBoxSizer* sizer() { return m_sizer; } + void update_full_printer_name(); + + void msw_rescale(); + void on_sys_color_changed() {}; +}; + + //------------------------------------------ // PhysicalPrinterDialog //------------------------------------------ + class ConfigOptionsGroup; class PhysicalPrinterDialog : public DPIDialog { PhysicalPrinter m_printer; DynamicPrintConfig* m_config { nullptr }; + wxString m_info_string; wxTextCtrl* m_printer_name { nullptr }; - wxStaticText* m_full_printer_name { nullptr }; - TabPresetComboBox* m_printer_presets { nullptr }; + std::vector m_presets; + ConfigOptionsGroup* m_optgroup { nullptr }; - ScalableButton* m_printhost_browse_btn; - ScalableButton* m_printhost_test_btn; - ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + ScalableButton* m_add_preset_btn {nullptr}; + ScalableButton* m_printhost_browse_btn {nullptr}; + ScalableButton* m_printhost_test_btn {nullptr}; + ScalableButton* m_printhost_cafile_browse_btn {nullptr}; void build_printhost_settings(ConfigOptionsGroup* optgroup); - void update(); - void update_printer_name(); void OnOK(wxEvent& event); + void AddPreset(wxEvent& event); public: PhysicalPrinterDialog(wxString printer_name); - ~PhysicalPrinterDialog() {} + ~PhysicalPrinterDialog(); + + void update(); + wxString get_printer_name(); + void update_full_printer_names(); + PhysicalPrinter* get_printer() {return &m_printer; } protected: void on_dpi_changed(const wxRect& suggested_rect) override; From 0b1086f390bbc534f777c9e28480a6b463337be8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 3 Jul 2020 12:17:12 +0200 Subject: [PATCH 155/826] GCodeViewer -> Export of extrude toolpaths to obj files --- src/slic3r/GUI/3DScene.cpp | 2 + src/slic3r/GUI/3DScene.hpp | 2 + src/slic3r/GUI/GCodeViewer.cpp | 287 ++++++++++++++++++++++++++++++++- src/slic3r/GUI/GCodeViewer.hpp | 5 +- src/slic3r/GUI/GLCanvas3D.cpp | 8 + src/slic3r/GUI/MainFrame.cpp | 6 +- 6 files changed, 300 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f53e4a55c2..c9c7b72f37 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1017,6 +1017,7 @@ bool GLVolumeCollection::has_toolpaths_to_export() const return false; } +#if !ENABLE_GCODE_VIEWER void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const { if (filename == nullptr) @@ -1298,6 +1299,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const fclose(fp); } +#endif // !ENABLE_GCODE_VIEWER // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 1b4d0fc4f2..7e8ae6fe33 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -597,8 +597,10 @@ public: std::string log_memory_info() const; bool has_toolpaths_to_export() const; +#if !ENABLE_GCODE_VIEWER // Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format void export_toolpaths_to_obj(const char* filename) const; +#endif // !ENABLE_GCODE_VIEWER private: GLVolumeCollection(const GLVolumeCollection &other); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6d6ba557e2..688134f7c9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -487,6 +488,284 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range wxGetApp().plater()->update_preview_moves_slider(); } +void GCodeViewer::export_toolpaths_to_obj(const char* filename) const +{ + if (filename == nullptr) + return; + + if (!has_data()) + return; + + wxBusyCursor busy; + + // the data needed is contained into the Extrude TBuffer + const TBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Extrude)]; + if (buffer.vertices.id == 0 || buffer.indices.id == 0) + return; + + // collect color information to generate materials + std::vector colors; + for (const RenderPath& path : buffer.render_paths) { + colors.push_back(path.color); + } + + // save materials file + boost::filesystem::path mat_filename(filename); + mat_filename.replace_extension("mtl"); + FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths Materials\n"); + fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID); + + unsigned int colors_count = 1; + for (const Color& color : colors) + { + fprintf(fp, "\nnewmtl material_%d\n", colors_count++); + fprintf(fp, "Ka 1 1 1\n"); + fprintf(fp, "Kd %f %f %f\n", color[0], color[1], color[2]); + fprintf(fp, "Ks 0 0 0\n"); + } + + fclose(fp); + + // save geometry file + fp = boost::nowide::fopen(filename, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths\n"); + fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID); + fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); + + // get vertices data from vertex buffer on gpu + size_t floats_per_vertex = buffer.vertices.vertex_size_floats(); + std::vector vertices = std::vector(buffer.vertices.count * floats_per_vertex); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer.vertices.data_size_bytes(), vertices.data())); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + auto get_vertex = [&vertices, floats_per_vertex](size_t id) { + // extract vertex from vector of floats + size_t base_id = id * floats_per_vertex; + return Vec3f(vertices[base_id + 0], vertices[base_id + 1], vertices[base_id + 2]); + }; + + struct Segment + { + Vec3f v1; + Vec3f v2; + Vec3f dir; + Vec3f right; + Vec3f up; + Vec3f rl_displacement; + Vec3f tb_displacement; + float length; + }; + + auto generate_segment = [get_vertex](size_t start_id, float half_width, float half_height) { + auto local_basis = [](const Vec3f& dir) { + // calculate local basis (dir, right, up) on given segment + std::array ret; + ret[0] = dir.normalized(); + if (std::abs(ret[0][2]) < EPSILON) { + // segment parallel to XY plane + ret[1] = { ret[0][1], -ret[0][0], 0.0f }; + ret[2] = Vec3f::UnitZ(); + } + else if (std::abs(std::abs(ret[0].dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) { + // segment parallel to Z axis + ret[1] = Vec3f::UnitX(); + ret[2] = Vec3f::UnitY(); + } + else { + ret[0] = dir.normalized(); + ret[1] = ret[0].cross(Vec3f::UnitZ()).normalized(); + ret[2] = ret[1].cross(ret[0]); + } + return ret; + }; + + Vec3f v1 = get_vertex(start_id); + Vec3f v2 = get_vertex(start_id + 1); + float length = (v2 - v1).norm(); + const auto&& [dir, right, up] = local_basis(v2 - v1); + return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length }); + }; + + size_t out_vertices_count = 0; + + for (size_t i = 0; i < buffer.render_paths.size(); ++i) { + // get paths segments from buffer paths + const RenderPath& render_path = buffer.render_paths[i]; + const Path& path = buffer.paths[render_path.path_id]; + float half_width = 0.5f * path.width; + float half_height = 0.5f * path.height; + + // generates vertices/normals/triangles + std::vector out_vertices; + std::vector out_normals; + using Triangle = std::array; + std::vector out_triangles; + for (size_t j = 0; j < render_path.offsets.size(); ++j) { + unsigned int start = static_cast(render_path.offsets[j] / sizeof(unsigned int)); + unsigned int end = start + render_path.sizes[j]; + + for (size_t k = start; k < end; k += 2) { + Segment curr = generate_segment(k, half_width, half_height); + + if (k == start) { + // starting endpoint vertices/normals + out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right + out_vertices.push_back(curr.v1 + curr.tb_displacement); out_normals.push_back(curr.up); // top + out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left + out_vertices.push_back(curr.v1 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom + out_vertices_count += 4; + + // starting cap triangles + size_t base_id = out_vertices_count - 4 + 1; + out_triangles.push_back({ base_id + 0, base_id + 1, base_id + 2 }); + out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 3 }); + } + else { + // for the endpoint shared by the current and the previous segments + // we keep the top and bottom vertices of the previous vertices + // and add new left/right vertices for the current segment + out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right + out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left + out_vertices_count += 2; + + Segment prev = generate_segment(k - 2, half_width, half_height); + Vec3f med_dir = (prev.dir + curr.dir).normalized(); + float disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); + Vec3f disp_vec = disp * prev.dir; + + bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f; + if (prev.dir.dot(curr.dir) < 0.7071068f) { + // if the angle between two consecutive segments is greater than 45 degrees + // we add a cap in the outside corner + // and displace the vertices in the inside corner to the same position, if possible + if (is_right_turn) { + // corner cap triangles (left) + size_t base_id = out_vertices_count - 6 + 1; + out_triangles.push_back({ base_id + 5, base_id + 2, base_id + 1 }); + out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 }); + + // update right vertices + if (disp < prev.length) { + base_id = out_vertices.size() - 6; + out_vertices[base_id + 0] -= disp_vec; + out_vertices[base_id + 4] = out_vertices[base_id + 0]; + } + } + else { + // corner cap triangles (right) + size_t base_id = out_vertices_count - 6 + 1; + out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 1 }); + out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 }); + + // update left vertices + if (disp < prev.length) { + base_id = out_vertices.size() - 6; + out_vertices[base_id + 2] -= disp_vec; + out_vertices[base_id + 5] = out_vertices[base_id + 2]; + } + } + } + else { + // if the angle between two consecutive segments is lesser than 45 degrees + // displace the vertices to the same position + if (is_right_turn) { + size_t base_id = out_vertices.size() - 6; + // right + out_vertices[base_id + 0] -= disp_vec; + out_vertices[base_id + 4] = out_vertices[base_id + 0]; + // left + out_vertices[base_id + 2] += disp_vec; + out_vertices[base_id + 5] = out_vertices[base_id + 2]; + } + else { + size_t base_id = out_vertices.size() - 6; + // right + out_vertices[base_id + 0] += disp_vec; + out_vertices[base_id + 4] = out_vertices[base_id + 0]; + // left + out_vertices[base_id + 2] -= disp_vec; + out_vertices[base_id + 5] = out_vertices[base_id + 2]; + } + } + } + + // current second endpoint vertices/normals + out_vertices.push_back(curr.v2 + curr.rl_displacement); out_normals.push_back(curr.right); // right + out_vertices.push_back(curr.v2 + curr.tb_displacement); out_normals.push_back(curr.up); // top + out_vertices.push_back(curr.v2 - curr.rl_displacement); out_normals.push_back(-curr.right); // left + out_vertices.push_back(curr.v2 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom + out_vertices_count += 4; + + // sides triangles + if (k == start) { + size_t base_id = out_vertices_count - 8 + 1; + out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 5 }); + out_triangles.push_back({ base_id + 0, base_id + 5, base_id + 1 }); + out_triangles.push_back({ base_id + 1, base_id + 5, base_id + 6 }); + out_triangles.push_back({ base_id + 1, base_id + 6, base_id + 2 }); + out_triangles.push_back({ base_id + 2, base_id + 6, base_id + 7 }); + out_triangles.push_back({ base_id + 2, base_id + 7, base_id + 3 }); + out_triangles.push_back({ base_id + 3, base_id + 7, base_id + 4 }); + out_triangles.push_back({ base_id + 3, base_id + 4, base_id + 0 }); + } + else { + size_t base_id = out_vertices_count - 10 + 1; + out_triangles.push_back({ base_id + 4, base_id + 6, base_id + 7 }); + out_triangles.push_back({ base_id + 4, base_id + 7, base_id + 1 }); + out_triangles.push_back({ base_id + 1, base_id + 7, base_id + 8 }); + out_triangles.push_back({ base_id + 1, base_id + 8, base_id + 5 }); + out_triangles.push_back({ base_id + 5, base_id + 8, base_id + 9 }); + out_triangles.push_back({ base_id + 5, base_id + 9, base_id + 3 }); + out_triangles.push_back({ base_id + 3, base_id + 9, base_id + 6 }); + out_triangles.push_back({ base_id + 3, base_id + 6, base_id + 4 }); + } + + if (k + 2 == end) { + // ending cap triangles + size_t base_id = out_vertices_count - 4 + 1; + out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 1 }); + out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 2 }); + } + } + } + + // save to file + fprintf(fp, "\n# vertices path %lld\n", i + 1); + for (const Vec3f& v : out_vertices) { + fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]); + } + + fprintf(fp, "\n# normals path %lld\n", i + 1); + for (const Vec3f& n : out_normals) { + fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]); + } + + fprintf(fp, "\n# material path %lld\n", i + 1); + fprintf(fp, "usemtl material_%lld\n", i + 1); + + fprintf(fp, "\n# triangles path %lld\n", i + 1); + for (const Triangle& t : out_triangles) { + fprintf(fp, "f %lld//%lld %lld//%lld %lld//%lld\n", t[0], t[0], t[1], t[1], t[2], t[2]); + } + + } + +// fprintf(fp, "\n#vertices count %lld\n", out_vertices_count); + fclose(fp); +} + void GCodeViewer::init_shaders() { unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); @@ -641,7 +920,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) const std::vector& buffer_indices = indices[i]; buffer.indices.count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.indices_size += SLIC3R_STDVEC_MEMSIZE(buffer_indices, unsigned int); m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -876,6 +1154,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (it == buffer->render_paths.end()) { it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath()); it->color = color; + it->path_id = id; } unsigned int size = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; @@ -1482,12 +1761,6 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(std::string("Indices CPU:")); - ImGui::PopStyleColor(); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.indices_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); imgui.text(std::string("Paths CPU:")); ImGui::PopStyleColor(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 5cddeaa75a..137ae89af7 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -108,6 +108,7 @@ class GCodeViewer struct RenderPath { Color color; + size_t path_id; std::vector sizes; std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) }; @@ -201,7 +202,6 @@ class GCodeViewer // memory long long results_size{ 0 }; long long vertices_gpu_size{ 0 }; - long long indices_size{ 0 }; long long indices_gpu_size{ 0 }; long long paths_size{ 0 }; long long render_paths_size{ 0 }; @@ -231,7 +231,6 @@ class GCodeViewer void reset_sizes() { results_size = 0; vertices_gpu_size = 0; - indices_size = 0; indices_gpu_size = 0; paths_size = 0; render_paths_size = 0; @@ -397,6 +396,8 @@ public: bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } + void export_toolpaths_to_obj(const char* filename) const; + private: void init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0ee30a8084..f8ff9406f3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4349,12 +4349,20 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() bool GLCanvas3D::has_toolpaths_to_export() const { +#if ENABLE_GCODE_VIEWER + return m_gcode_viewer.has_data(); +#else return m_volumes.has_toolpaths_to_export(); +#endif // ENABLE_GCODE_VIEWER } void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const { +#if ENABLE_GCODE_VIEWER + m_gcode_viewer.export_toolpaths_to_obj(filename); +#else m_volumes.export_toolpaths_to_obj(filename); +#endif // ENABLE_GCODE_VIEWER } void GLCanvas3D::mouse_up_cleanup() diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 5e26979c3c..9c98f0f2e5 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1373,9 +1373,13 @@ void MainFrame::init_gcodeviewer_menubar() wxMenu* fileMenu = new wxMenu; { append_menu_item(fileMenu, wxID_ANY, _L("&Open G-code") + dots + "\tCtrl+O", _L("Open a G-code file"), - [this](wxCommandEvent&) { if (m_plater) m_plater->load_gcode(); }, "open", nullptr, + [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->load_gcode(); }, "open", nullptr, [this]() {return m_plater != nullptr; }, this); fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"), + [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, + [this]() {return can_export_toolpaths(); }, this); + fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"), [this](wxCommandEvent&) { set_mode(EMode::Editor); }); fileMenu->AppendSeparator(); From 2a78799f7e0dbdd2d381dbb4482b318264d7f81b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 3 Jul 2020 13:04:52 +0200 Subject: [PATCH 156/826] GCodeViewer -> Fixed layout when switching to/from gcode viewer state --- src/slic3r/GUI/MainFrame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 9c98f0f2e5..eba169dc96 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -389,7 +389,7 @@ void MainFrame::update_layout() #if ENABLE_GCODE_VIEWER_AS_STATE case ESettingsLayout::GCodeViewer: { - GetSizer()->Add(m_plater, 1, wxEXPAND); + m_main_sizer->Add(m_plater, 1, wxEXPAND); m_plater->Show(); break; } From 73b885fc3711e82ae4c2fabf2464a06714dda435 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 8 Jul 2020 13:33:50 +0200 Subject: [PATCH 157/826] GCodeViewer -> Added imgui dialog for estimated printing times --- src/libslic3r/GCode.cpp | 17 +- src/libslic3r/GCodeTimeEstimator.cpp | 33 ++ src/libslic3r/GCodeTimeEstimator.hpp | 8 + src/libslic3r/Print.hpp | 14 + src/libslic3r/Technologies.hpp | 4 +- src/slic3r/GUI/GCodeViewer.cpp | 379 +++++++++++++------ src/slic3r/GUI/GCodeViewer.hpp | 7 +- src/slic3r/GUI/GLCanvas3D.cpp | 68 ++-- src/slic3r/GUI/GLShadersManager.cpp | 2 +- src/slic3r/GUI/GUI_Preview.cpp | 5 +- src/slic3r/GUI/GUI_Preview.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 3 +- src/slic3r/GUI/ImGuiWrapper.cpp | 36 +- src/slic3r/GUI/ImGuiWrapper.hpp | 7 + src/slic3r/GUI/KBShortcutsDialog.cpp | 5 +- src/slic3r/GUI/MainFrame.cpp | 4 + src/slic3r/GUI/Plater.cpp | 26 +- 17 files changed, 438 insertions(+), 183 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8de64544c8..7bfb73aa37 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1050,10 +1050,19 @@ namespace DoExport { print_statistics.clear(); print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/(); print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; - print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true); - if (silent_time_estimator_enabled) - print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true); - print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); +#if ENABLE_GCODE_VIEWER + print_statistics.estimated_normal_custom_gcode_print_times_str = normal_time_estimator.get_custom_gcode_times_dhm(true); + print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times(true); + if (silent_time_estimator_enabled) { + print_statistics.estimated_silent_custom_gcode_print_times_str = silent_time_estimator.get_custom_gcode_times_dhm(true); + print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times(true); + } +#else + print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true); + if (silent_time_estimator_enabled) + print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true); +#endif // ENABLE_GCODE_VIEWER + print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); if (! extruders.empty()) { std::pair out_filament_used_mm ("; filament used [mm] = ", 0); std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 9e8137ef0e..d67db84819 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -678,6 +678,21 @@ namespace Slic3r { return _get_time_minutes(get_time()); } +#if ENABLE_GCODE_VIEWER + std::vector>> GCodeTimeEstimator::get_custom_gcode_times(bool include_remaining) const + { + std::vector>> ret; + + float total_time = 0.0f; + for (const auto& [type, time] : m_custom_gcode_times) { + float remaining = include_remaining ? m_time - total_time : 0.0f; + ret.push_back({ type, { time, remaining } }); + total_time += time; + } + + return ret; + } +#else std::vector> GCodeTimeEstimator::get_custom_gcode_times() const { return m_custom_gcode_times; @@ -721,7 +736,24 @@ namespace Slic3r { } return ret; } +#endif // ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + std::vector>> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const + { + std::vector>> ret; + + float total_time = 0.0f; + for (const auto& [type, time] : m_custom_gcode_times) { + std::string duration = _get_time_dhm(time); + std::string remaining = include_remaining ? _get_time_dhm(m_time - total_time) : ""; + ret.push_back({ type, { duration, remaining } }); + total_time += time; + } + + return ret; + } +#else std::vector> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const { std::vector> ret; @@ -742,6 +774,7 @@ namespace Slic3r { return ret; } +#endif // ENABLE_GCODE_VIEWER // Return an estimate of the memory consumed by the time estimator. size_t GCodeTimeEstimator::memory_used() const diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 63e11c4faa..cfa12b40be 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -358,6 +358,9 @@ namespace Slic3r { std::string get_time_minutes() const; // Returns the estimated time, in seconds, for each custom gcode +#if ENABLE_GCODE_VIEWER + std::vector>> get_custom_gcode_times(bool include_remaining) const; +#else std::vector> get_custom_gcode_times() const; // Returns the estimated time, in format DDd HHh MMm SSs, for each color @@ -367,10 +370,15 @@ namespace Slic3r { // Returns the estimated time, in minutes (integer), for each color // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" std::vector get_color_times_minutes(bool include_remaining) const; +#endif // ENABLE_GCODE_VIEWER // Returns the estimated time, in format DDd HHh MMm, for each custom_gcode // If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)" +#if ENABLE_GCODE_VIEWER + std::vector>> get_custom_gcode_times_dhm(bool include_remaining) const; +#else std::vector> get_custom_gcode_times_dhm(bool include_remaining) const; +#endif // ENABLE_GCODE_VIEWER // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index e4f4c60f57..eb9a4fb4b7 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -305,8 +305,15 @@ struct PrintStatistics PrintStatistics() { clear(); } std::string estimated_normal_print_time; std::string estimated_silent_print_time; +#if ENABLE_GCODE_VIEWER + std::vector>> estimated_normal_custom_gcode_print_times; + std::vector>> estimated_silent_custom_gcode_print_times; + std::vector>> estimated_normal_custom_gcode_print_times_str; + std::vector>> estimated_silent_custom_gcode_print_times_str; +#else std::vector> estimated_normal_custom_gcode_print_times; std::vector> estimated_silent_custom_gcode_print_times; +#endif // ENABLE_GCODE_VIEWER double total_used_filament; double total_extruded_volume; double total_cost; @@ -326,8 +333,15 @@ struct PrintStatistics void clear() { estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); +#if ENABLE_GCODE_VIEWER + estimated_normal_custom_gcode_print_times_str.clear(); + estimated_silent_custom_gcode_print_times_str.clear(); estimated_normal_custom_gcode_print_times.clear(); estimated_silent_custom_gcode_print_times.clear(); +#else + estimated_normal_custom_gcode_print_times.clear(); + estimated_silent_custom_gcode_print_times.clear(); +#endif //ENABLE_GCODE_VIEWER total_used_filament = 0.; total_extruded_volume = 0.; total_cost = 0.; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index fb84efe5ab..b04e78c4ed 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,8 +59,8 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -#define ENABLE_GCODE_VIEWER_STATISTICS (1 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (1 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 688134f7c9..26dc765db4 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -4,6 +4,8 @@ #if ENABLE_GCODE_VIEWER #include "libslic3r/Print.hpp" #include "libslic3r/Geometry.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Utils.hpp" #include "GUI_App.hpp" #if ENABLE_GCODE_VIEWER_AS_STATE #include "MainFrame.hpp" @@ -17,10 +19,7 @@ #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" -#include "libslic3r/Model.hpp" -#if ENABLE_GCODE_VIEWER_STATISTICS #include -#endif // ENABLE_GCODE_VIEWER_STATISTICS #include #include @@ -187,15 +186,14 @@ void GCodeViewer::SequentialView::Marker::render() const static float last_window_width = 0.0f; static size_t last_text_length = 0; - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); ImGuiWrapper& imgui = *wxGetApp().imgui(); Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); + imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.25f); imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Tool position") + ":"); ImGui::PopStyleColor(); ImGui::SameLine(); @@ -270,6 +268,7 @@ bool GCodeViewer::init() { switch (buffer_type(i)) { + default: { break; } case GCodeProcessor::EMoveType::Tool_change: case GCodeProcessor::EMoveType::Color_change: case GCodeProcessor::EMoveType::Pause_Print: @@ -420,6 +419,7 @@ void GCodeViewer::render() const m_sequential_view.marker.render(); render_shells(); render_legend(); + render_time_estimate(); #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -458,6 +458,7 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); + flags = set_flag(flags, static_cast(Preview::OptionType::TimeEstimate), is_time_estimate_enabled()); return flags; } @@ -477,6 +478,7 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); + enable_time_estimate(is_flag_set(static_cast(Preview::OptionType::TimeEstimate))); } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) @@ -591,8 +593,8 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const return ret; }; - Vec3f v1 = get_vertex(start_id); - Vec3f v2 = get_vertex(start_id + 1); + Vec3f v1 = get_vertex(start_id) - half_height * Vec3f::UnitZ(); + Vec3f v2 = get_vertex(start_id + 1) - half_height * Vec3f::UnitZ(); float length = (v2 - v1).norm(); const auto&& [dir, right, up] = local_basis(v2 - v1); return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length }); @@ -605,7 +607,8 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const const RenderPath& render_path = buffer.render_paths[i]; const Path& path = buffer.paths[render_path.path_id]; float half_width = 0.5f * path.width; - float half_height = 0.5f * path.height; + // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar + float half_height = std::max(0.5f * path.height, 0.005f); // generates vertices/normals/triangles std::vector out_vertices; @@ -657,7 +660,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 }); // update right vertices - if (disp < prev.length) { + if (disp < prev.length && disp < curr.length) { base_id = out_vertices.size() - 6; out_vertices[base_id + 0] -= disp_vec; out_vertices[base_id + 4] = out_vertices[base_id + 0]; @@ -670,7 +673,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 }); // update left vertices - if (disp < prev.length) { + if (disp < prev.length && disp < curr.length) { base_id = out_vertices.size() - 6; out_vertices[base_id + 2] -= disp_vec; out_vertices[base_id + 5] = out_vertices[base_id + 2]; @@ -742,27 +745,25 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const } // save to file - fprintf(fp, "\n# vertices path %lld\n", i + 1); + fprintf(fp, "\n# vertices path %zu\n", i + 1); for (const Vec3f& v : out_vertices) { fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]); } - fprintf(fp, "\n# normals path %lld\n", i + 1); + fprintf(fp, "\n# normals path %zu\n", i + 1); for (const Vec3f& n : out_normals) { fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]); } - fprintf(fp, "\n# material path %lld\n", i + 1); - fprintf(fp, "usemtl material_%lld\n", i + 1); + fprintf(fp, "\n# material path %zu\n", i + 1); + fprintf(fp, "usemtl material_%zu\n", i + 1); - fprintf(fp, "\n# triangles path %lld\n", i + 1); + fprintf(fp, "\n# triangles path %zu\n", i + 1); for (const Triangle& t : out_triangles) { - fprintf(fp, "f %lld//%lld %lld//%lld %lld//%lld\n", t[0], t[0], t[1], t[1], t[2], t[2]); + fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", t[0], t[0], t[1], t[1], t[2], t[2]); } - } -// fprintf(fp, "\n#vertices count %lld\n", out_vertices_count); fclose(fp); } @@ -775,12 +776,12 @@ void GCodeViewer::init_shaders() for (unsigned char i = begin_id; i < end_id; ++i) { switch (buffer_type(i)) { - case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } - case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120_solid" : "options_110"; break; } + case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "toolpaths"; break; } case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "toolpaths"; break; } default: { break; } @@ -1137,7 +1138,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } // second pass: filter paths by sequential data and collect them by color - for (auto&& [buffer, id] : paths) { + for (const auto& [buffer, id] : paths) { const Path& path = buffer->paths[id]; if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) continue; @@ -1203,8 +1204,8 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.points.percent_outline)); shader.set_uniform("percent_center_radius", 0.01f * static_cast(m_shaders_editor.points.percent_center)); #else - shader.set_uniform("percent_outline_radius", 0.15f); - shader.set_uniform("percent_center_radius", 0.15f); + shader.set_uniform("percent_outline_radius", 0.0f); + shader.set_uniform("percent_center_radius", 0.33f); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("viewport", viewport); shader.set_uniform("inv_proj_matrix", inv_proj); @@ -1266,6 +1267,7 @@ void GCodeViewer::render_toolpaths() const switch (buffer_type(i)) { + default: { break; } case GCodeProcessor::EMoveType::Tool_change: { render_as_points(buffer, EOptionsColors::ToolChanges, *shader); break; } case GCodeProcessor::EMoveType::Color_change: { render_as_points(buffer, EOptionsColors::ColorChanges, *shader); break; } case GCodeProcessor::EMoveType::Pause_Print: { render_as_points(buffer, EOptionsColors::PausePrints, *shader); break; } @@ -1320,18 +1322,15 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); } +#define USE_ICON_HEXAGON 1 + void GCodeViewer::render_legend() const { - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - static const ImU32 ICON_BORDER_COLOR = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); - if (!m_legend_enabled) return; ImGuiWrapper& imgui = *wxGetApp().imgui(); -#define USE_ICON_HEXAGON 1 - imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); @@ -1347,19 +1346,14 @@ void GCodeViewer::render_legend() const Line }; -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR auto add_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { -#else - auto add_item = [draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR float icon_size = ImGui::GetTextLineHeight(); - ImVec2 pos = ImGui::GetCursorPos(); + ImVec2 pos = ImGui::GetCursorScreenPos(); switch (type) { default: case EItemType::Rect: { - draw_list->AddRect({ pos.x, pos.y }, { pos.x + icon_size, pos.y + icon_size }, ICON_BORDER_COLOR, 0.0f, 0); draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); break; @@ -1380,37 +1374,26 @@ void GCodeViewer::render_legend() const } else draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - -// ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); -// draw_list->AddCircle(center, 0.5f * icon_size, ICON_BORDER_COLOR, 16); -// if (m_shaders_editor.shader_version == 1) { -// draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, -// ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); -// float radius = ((0.5f * icon_size) - 2.0f) * (1.0f - 0.01f * static_cast(m_shaders_editor.percent_outline)); -// draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); -// if (m_shaders_editor.percent_center > 0) { -// radius = ((0.5f * icon_size) - 2.0f) * 0.01f * static_cast(m_shaders_editor.percent_center); -// draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); -// } -// } else -// draw_list->AddCircleFilled(center, (0.5f * icon_size) - 2.0f, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); #else - draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - -// draw_list->AddCircle({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, 0.5f * icon_size, ICON_BORDER_COLOR, 16); -// draw_list->AddCircleFilled({ 0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size) }, (0.5f * icon_size) - 2.0f, -// ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(GCodeProcessor::EMoveType::Retract)].shader == "options_120_flat") { + draw_list->AddCircleFilled(center, 0.5f * icon_size, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = 0.5f * icon_size; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + radius = 0.5f * icon_size * 0.01f * 33.0f; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + } + else + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR + break; } case EItemType::Hexagon: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); -// draw_list->AddNgon(center, 0.5f * icon_size, ICON_BORDER_COLOR, 6); -// draw_list->AddNgonFilled(center, (0.5f * icon_size) - 2.0f, -// ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); break; } case EItemType::Line: @@ -1454,21 +1437,18 @@ void GCodeViewer::render_legend() const }; // extrusion paths -> title - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); switch (m_view_type) { - case EViewType::FeatureType: { imgui.text(_u8L("Feature type")); break; } - case EViewType::Height: { imgui.text(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.text(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.text(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.text(_u8L("Fan Speed (%%)")); break; } - case EViewType::VolumetricRate: { imgui.text(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: { imgui.text(_u8L("Tool")); break; } - case EViewType::ColorPrint: { imgui.text(_u8L("Color Print")); break; } - default: { break; } + case EViewType::FeatureType: { imgui.title(_u8L("Feature type")); break; } + case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } + case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } + case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%%)")); break; } + case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } + case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } + case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } + default: { break; } } - ImGui::PopStyleColor(); - ImGui::Separator(); // extrusion paths -> items switch (m_view_type) @@ -1566,28 +1546,26 @@ void GCodeViewer::render_legend() const else { for (int i = items_cnt; i >= 0; --i) { // create label for color change item - std::string id_str = " (" + std::to_string(i + 1) + ")"; - if (i == 0) { #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str() + id_str); + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str()); #else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str() + id_str); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str()); #endif // USE_ICON_HEXAGON break; } else if (i == items_cnt) { #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str() + id_str); + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str()); #else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str() + id_str); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str()); #endif // USE_ICON_HEXAGON continue; } #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second% cp_values[i].first).str() + id_str); + add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second % cp_values[i].first).str()); #else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second% cp_values[i].first).str() + id_str); + add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second % cp_values[i].first).str()); #endif // USE_ICON_HEXAGON } } @@ -1609,15 +1587,12 @@ void GCodeViewer::render_legend() const size_t last_color_id = m_tool_colors.size() - 1; for (int i = static_cast(custom_gcode_per_print_z.size()) - 1; i >= 0; --i) { if (custom_gcode_per_print_z[i].type == ColorChange) { - // create label for color change item - std::string id_str = " (" + std::to_string(color_change_idx--) + ")"; - #if USE_ICON_HEXAGON add_item(EItemType::Hexagon, m_tool_colors[last_color_id--], #else add_item(EItemType::Rect, m_tool_colors[last_color_id--], #endif // USE_ICON_HEXAGON - (boost::format(_u8L("Color change for Extruder %d at %.2f mm")) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str() + id_str); + (boost::format(_u8L("Color change for Extruder %d at %.2f mm")) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str()); } } } @@ -1641,11 +1616,7 @@ void GCodeViewer::render_legend() const { // title ImGui::Spacing(); - ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_u8L("Travel")); - ImGui::PopStyleColor(); - ImGui::Separator(); + imgui.title(_u8L("Travel")); // items add_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); @@ -1657,13 +1628,18 @@ void GCodeViewer::render_legend() const } } - auto any_option_visible = [this]() { - return m_buffers[buffer_id(GCodeProcessor::EMoveType::Color_change)].visible || - m_buffers[buffer_id(GCodeProcessor::EMoveType::Custom_GCode)].visible || - m_buffers[buffer_id(GCodeProcessor::EMoveType::Pause_Print)].visible || - m_buffers[buffer_id(GCodeProcessor::EMoveType::Retract)].visible || - m_buffers[buffer_id(GCodeProcessor::EMoveType::Tool_change)].visible || - m_buffers[buffer_id(GCodeProcessor::EMoveType::Unretract)].visible; + auto any_option_available = [this]() { + auto available = [this](GCodeProcessor::EMoveType type) { + const TBuffer& buffer = m_buffers[buffer_id(type)]; + return buffer.visible && buffer.indices.count > 0; + }; + + return available(GCodeProcessor::EMoveType::Color_change) || + available(GCodeProcessor::EMoveType::Custom_GCode) || + available(GCodeProcessor::EMoveType::Pause_Print) || + available(GCodeProcessor::EMoveType::Retract) || + available(GCodeProcessor::EMoveType::Tool_change) || + available(GCodeProcessor::EMoveType::Unretract); }; auto add_option = [this, add_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { @@ -1677,14 +1653,10 @@ void GCodeViewer::render_legend() const }; // options - if (any_option_visible()) { + if (any_option_available()) { // title ImGui::Spacing(); - ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_u8L("Options")); - ImGui::PopStyleColor(); - ImGui::Separator(); + imgui.title(_u8L("Options")); // items add_option(GCodeProcessor::EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); @@ -1699,10 +1671,175 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } +void GCodeViewer::render_time_estimate() const +{ + static const std::vector Columns_Headers = { + _u8L("Operation"), + _u8L("Remaining"), + _u8L("Duration") + }; + + if (!m_time_estimate_enabled) + return; + + const PrintStatistics& ps = wxGetApp().plater()->fff_print().print_statistics(); + if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") + return; + + int columns_count = 1; + if (ps.estimated_silent_print_time != "N/A") + ++columns_count; + + ImGuiWrapper& imgui = *wxGetApp().imgui(); + Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); + ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(-1.0f, 0.5f * static_cast(cnv_size.get_height()))); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + imgui.begin(std::string("Time_estimate"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + float icon_size = ImGui::GetTextLineHeight(); + + using Time = std::pair; + using TimesList = std::vector>; + using Headers = std::vector; + using Offsets = std::array; + + auto add_mode = [this, &imgui, icon_size, draw_list](const std::string& mode, const std::string& time, const TimesList& times, const Headers& headers) { + auto add_partial_times = [this, &imgui, icon_size, draw_list](const TimesList& times, const Headers& headers) { + auto add_color = [this, &imgui, icon_size, draw_list](int id, Offsets& offsets, const Time& time) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + std::string text = _u8L("Color"); + if (m_view_type != EViewType::ColorPrint) + text += " " + std::to_string(id); + imgui.text(text); + ImGui::PopStyleColor(); + ImGui::SameLine(); + + if (m_view_type == EViewType::ColorPrint) { + const Color& color = m_tool_colors[id - 1]; + ImVec2 pos = ImGui::GetCursorScreenPos(); +#if USE_ICON_HEXAGON + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); +#else + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ m_tool_colors[i][0], m_tool_colors[i][1], m_tool_colors[i][2], 1.0f })); +#endif // USE_ICON_HEXAGON + } + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(time.second))); + ImGui::SameLine(offsets[1]); + imgui.text(short_time(get_time_dhms(time.first))); + }; + auto calc_offsets = [this, icon_size](const TimesList& times, const Headers& headers, int color_change_count) { + Offsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; + for (const auto& [type, time] : times) { + std::string label; + switch (type) + { + case CustomGCode::PausePrint: + { + label = _u8L("Pause"); + break; + } + case CustomGCode::ColorChange: + { + label = _u8L("Color"); + if (m_view_type != EViewType::ColorPrint) + label += " " + std::to_string(color_change_count); + break; + } + default: { break; } + } + + ret[0] = std::max(ret[0], ImGui::CalcTextSize(label.c_str()).x); + ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time.second)).c_str()).x); + } + + const ImGuiStyle& style = ImGui::GetStyle(); + ret[0] += icon_size + style.ItemSpacing.x; + ret[1] += ret[0] + style.ItemSpacing.x; + return ret; + }; + + if (times.empty()) + return; + + int color_change_count = 0; + for (auto time : times) { + if (time.first == CustomGCode::ColorChange) + ++color_change_count; + } + + Offsets offsets = calc_offsets(times, headers, color_change_count); + + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(headers[0]); + ImGui::SameLine(offsets[0]); + imgui.text(headers[1]); + ImGui::SameLine(offsets[1]); + imgui.text(headers[2]); + ImGui::PopStyleColor(); + + int last_color_id = color_change_count; + + for (int i = static_cast(times.size()) - 1; i >= 0; --i) { + const auto& [type, time] = times[i]; + + switch (type) + { + case CustomGCode::PausePrint: + { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_u8L("Pause")); + ImGui::PopStyleColor(); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(time.second - time.first))); + + add_color(last_color_id, offsets, time); + break; + } + case CustomGCode::ColorChange: + { + add_color(color_change_count, offsets, time); + last_color_id = color_change_count--; + break; + } + default: { break; } + } + } + }; + + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(mode + ":"); + ImGui::PopStyleColor(); + ImGui::SameLine(); + imgui.text(time); + add_partial_times(times, headers); + }; + + // title + imgui.title(_u8L("Estimated printing time")); + + // times + if (ps.estimated_normal_print_time != "N/A") + add_mode(_u8L("Normal mode"), ps.estimated_normal_print_time, ps.estimated_normal_custom_gcode_print_times, Columns_Headers); + + if (ps.estimated_silent_print_time != "N/A") { + ImGui::Separator(); + add_mode(_u8L("Stealth mode"), ps.estimated_silent_print_time, ps.estimated_silent_custom_gcode_print_times, Columns_Headers); + } + + imgui.end(); + ImGui::PopStyleVar(); +} + #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const { - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); static const float offset = 230.0f; ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -1711,7 +1848,7 @@ void GCodeViewer::render_statistics() const imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("GCodeProcessor time:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); @@ -1719,19 +1856,19 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Load time:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.load_time) + " ms"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Resfresh time:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.refresh_time) + " ms"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Resfresh paths time:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); @@ -1739,13 +1876,13 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Multi GL_POINTS calls:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count)); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Multi GL_LINE_STRIP calls:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); @@ -1753,7 +1890,7 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("GCodeProcessor results:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); @@ -1761,13 +1898,13 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Paths CPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.paths_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Render paths CPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); @@ -1775,13 +1912,13 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Vertices GPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Indices GPU:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); @@ -1789,13 +1926,13 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Travel segments:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.travel_segments_count)); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(std::string("Extrude segments:")); ImGui::PopStyleColor(); ImGui::SameLine(offset); @@ -1816,8 +1953,6 @@ void GCodeViewer::render_shaders_editor() const } }; - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - ImGuiWrapper& imgui = *wxGetApp().imgui(); Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); @@ -1828,8 +1963,8 @@ void GCodeViewer::render_shaders_editor() const if (ImGui::CollapsingHeader("Points", ImGuiTreeNodeFlags_DefaultOpen)) { if (ImGui::TreeNode("GLSL version")) { ImGui::RadioButton("1.10 (low end PCs)", &m_shaders_editor.points.shader_version, 0); - ImGui::RadioButton("1.20 flat (billboards)", &m_shaders_editor.points.shader_version, 1); - ImGui::RadioButton("1.20 solid (spheres default)", &m_shaders_editor.points.shader_version, 2); + ImGui::RadioButton("1.20 flat (billboards) [default]", &m_shaders_editor.points.shader_version, 1); + ImGui::RadioButton("1.20 solid (spheres)", &m_shaders_editor.points.shader_version, 2); ImGui::TreePop(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 137ae89af7..90155c7281 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -248,7 +248,7 @@ class GCodeViewer { struct Points { - int shader_version{ 2 }; + int shader_version{ 1 }; float point_size{ 0.8f }; int percent_outline{ 0 }; int percent_center{ 33 }; @@ -341,6 +341,7 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; + bool m_time_estimate_enabled{ true }; #if ENABLE_GCODE_VIEWER_STATISTICS mutable Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -396,6 +397,9 @@ public: bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } + bool is_time_estimate_enabled() const { return m_time_estimate_enabled; } + void enable_time_estimate(bool enable) { m_time_estimate_enabled = enable; } + void export_toolpaths_to_obj(const char* filename) const; private: @@ -406,6 +410,7 @@ private: void render_toolpaths() const; void render_shells() const; void render_legend() const; + void render_time_estimate() const; #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f8ff9406f3..8c69142485 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -219,8 +219,6 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const if (!m_enabled) return; - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - const Size& cnv_size = canvas.get_canvas_size(); float canvas_w = (float)cnv_size.get_width(); float canvas_h = (float)cnv_size.get_height(); @@ -228,50 +226,50 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f); - imgui.begin(_(L("Variable layer height")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Left mouse button:"))); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_L("Left mouse button:")); ImGui::PopStyleColor(); ImGui::SameLine(); - imgui.text(_(L("Add detail"))); + imgui.text(_L("Add detail")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Right mouse button:"))); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_L("Right mouse button:")); ImGui::PopStyleColor(); ImGui::SameLine(); - imgui.text(_(L("Remove detail"))); + imgui.text(_L("Remove detail")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Shift + Left mouse button:"))); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_L("Shift + Left mouse button:")); ImGui::PopStyleColor(); ImGui::SameLine(); - imgui.text(_(L("Reset to base"))); + imgui.text(_L("Reset to base")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Shift + Right mouse button:"))); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_L("Shift + Right mouse button:")); ImGui::PopStyleColor(); ImGui::SameLine(); - imgui.text(_(L("Smoothing"))); + imgui.text(_L("Smoothing")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Mouse wheel:"))); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_L("Mouse wheel:")); ImGui::PopStyleColor(); ImGui::SameLine(); - imgui.text(_(L("Increase/decrease edit area"))); + imgui.text(_L("Increase/decrease edit area")); ImGui::Separator(); - if (imgui.button(_(L("Adaptive")))) + if (imgui.button(_L("Adaptive"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); ImGui::SameLine(); float text_align = ImGui::GetCursorPosX(); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Quality / Speed"))); + imgui.text(_L("Quality / Speed")); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::TextUnformatted(_(L("Higher print quality versus higher print speed.")).ToUTF8()); + ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); ImGui::EndTooltip(); } @@ -282,13 +280,13 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const ImGui::SliderFloat("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); ImGui::Separator(); - if (imgui.button(_(L("Smooth")))) + if (imgui.button(_L("Smooth"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); ImGui::SameLine(); ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Radius"))); + imgui.text(_L("Radius")); ImGui::SameLine(); ImGui::SetCursorPosX(widget_align); ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); @@ -298,7 +296,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Keep min"))); + imgui.text(_L("Keep min")); ImGui::SameLine(); if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization ImGui::SetCursorPosX(widget_align); @@ -307,7 +305,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const imgui.checkbox("##2", m_smooth_params.keep_min); ImGui::Separator(); - if (imgui.button(_(L("Reset")))) + if (imgui.button(_L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); imgui.end(); @@ -3078,8 +3076,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #if ENABLE_GCODE_VIEWER case 'L': case 'l': { - if (!m_main_toolbar.is_enabled()) - { + if (!m_main_toolbar.is_enabled()) { m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled()); m_dirty = true; wxGetApp().plater()->update_preview_bottom_toolbar(); @@ -3090,13 +3087,24 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'O': case 'o': { _update_camera_zoom(-1.0); break; } #if ENABLE_RENDER_PICKING_PASS - case 'T': - case 't': { + case 'P': + case 'p': { m_show_picking_texture = !m_show_picking_texture; - m_dirty = true; + m_dirty = true; break; } #endif // ENABLE_RENDER_PICKING_PASS +#if ENABLE_GCODE_VIEWER + case 'T': + case 't': { + if (!m_main_toolbar.is_enabled()) { + m_gcode_viewer.enable_time_estimate(!m_gcode_viewer.is_time_estimate_enabled()); + m_dirty = true; + wxGetApp().plater()->update_preview_bottom_toolbar(); + } + break; + } +#endif // ENABLE_GCODE_VIEWER case 'Z': #if ENABLE_GCODE_VIEWER case 'z': diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index cb47d79618..e62a81d39b 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -69,7 +69,7 @@ GLShaderProgram* GLShadersManager::get_current_shader() if (id == 0) return nullptr; - auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr& p) { return p->get_id() == id; }); + auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr& p) { return static_cast(p->get_id()) == id; }); return (it != m_shaders.end()) ? it->get() : nullptr; } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 530165001f..50a820cfaa 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -323,7 +323,8 @@ bool Preview::init(wxWindow* parent, Model* model) get_option_type_string(OptionType::CustomGCodes) + "|0|" + get_option_type_string(OptionType::Shells) + "|0|" + get_option_type_string(OptionType::ToolMarker) + "|0|" + - get_option_type_string(OptionType::Legend) + "|1" + get_option_type_string(OptionType::Legend) + "|1|" + + get_option_type_string(OptionType::TimeEstimate) + "|1" ); Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); #else @@ -1458,10 +1459,10 @@ wxString Preview::get_option_type_string(OptionType type) const case OptionType::Shells: { return _L("Shells"); } case OptionType::ToolMarker: { return _L("Tool marker"); } case OptionType::Legend: { return _L("Legend"); } + case OptionType::TimeEstimate: { return _L("Estimated printing time"); } default: { return ""; } } } - #endif // ENABLE_GCODE_VIEWER } // namespace GUI diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index bf174c2e09..ff3bf41371 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -149,7 +149,8 @@ public: CustomGCodes, Shells, ToolMarker, - Legend + Legend, + TimeEstimate }; Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cd42857247..9aaded6e3c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -650,8 +650,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l window_width = std::max(window_width, button_width); auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); m_imgui->text(caption); ImGui::PopStyleColor(); ImGui::SameLine(caption_max); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 51a9a6d4eb..2c463dc2a5 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -44,6 +44,12 @@ static const std::map font_icons = { {ImGui::MaterialIconMarker , "resin" } }; +const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROND = { 0.133f, 0.133f, 0.133f, 0.8f }; +const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f }; + ImGuiWrapper::ImGuiWrapper() : m_glyph_ranges(nullptr) , m_font_cjk(false) @@ -751,6 +757,22 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co check_box(_L("Search in English"), view_params.english); } +void ImGuiWrapper::title(const std::string& str) +{ + ImGuiWindow* window = ImGui::GetCurrentWindow(); + const float frame_height = ImGui::CalcTextSize(str.c_str(), nullptr, false).y; + + ImRect frame_bb; + frame_bb.Min = { window->WorkRect.Min.x, window->DC.CursorPos.y }; + frame_bb.Max = { window->WorkRect.Max.x, window->DC.CursorPos.y + frame_height }; + + frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); + frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); + + window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(COL_ORANGE_DARK), 0.0f, 0); + text(str); +} + void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); @@ -970,20 +992,10 @@ void ImGuiWrapper::init_style() { ImGuiStyle &style = ImGui::GetStyle(); - auto set_color = [&](ImGuiCol_ col, unsigned hex_color) { - style.Colors[col] = ImVec4( - ((hex_color >> 24) & 0xff) / 255.0f, - ((hex_color >> 16) & 0xff) / 255.0f, - ((hex_color >> 8) & 0xff) / 255.0f, - (hex_color & 0xff) / 255.0f); + auto set_color = [&](ImGuiCol_ entity, ImVec4 color) { + style.Colors[entity] = color; }; - static const unsigned COL_WINDOW_BACKGROND = 0x222222cc; - static const unsigned COL_GREY_DARK = 0x555555ff; - static const unsigned COL_GREY_LIGHT = 0x666666ff; - static const unsigned COL_ORANGE_DARK = 0xc16737ff; - static const unsigned COL_ORANGE_LIGHT = 0xff7d38ff; - // Window style.WindowRounding = 4.0f; set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROND); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index bf542e1381..f79bd3fbc8 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -80,6 +80,7 @@ public: bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel); void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str, Search::OptionViewParameters& view_params, int& selected, bool& edited, int& mouse_wheel, bool is_localized); + void title(const std::string& str); void disabled_begin(bool disabled); void disabled_end(); @@ -89,6 +90,12 @@ public: bool want_text_input() const; bool want_any_input() const; + static const ImVec4 COL_WINDOW_BACKGROND; + static const ImVec4 COL_GREY_DARK; + static const ImVec4 COL_GREY_LIGHT; + static const ImVec4 COL_ORANGE_DARK; + static const ImVec4 COL_ORANGE_LIGHT; + private: void init_font(bool compress); void init_input(); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 51ba06ba45..66e5ac4878 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -183,7 +183,7 @@ void KBShortcutsDialog::fill_shortcuts() #endif // __linux__ #if ENABLE_RENDER_PICKING_PASS // Don't localize debugging texts. - { "T", "Toggle picking pass texture rendering on/off" }, + { "P", "Toggle picking pass texture rendering on/off" }, #endif // ENABLE_RENDER_PICKING_PASS }; @@ -203,7 +203,8 @@ void KBShortcutsDialog::fill_shortcuts() { L("Arrow Down"), L("Lower Layer") }, { "U", L("Upper Layer") }, { "D", L("Lower Layer") }, - { "L", L("Show/Hide Legend") } + { "L", L("Show/Hide Legend") }, + { "T", L("Show/Hide Estimated printing time") } }; m_full_shortcuts.push_back(std::make_pair(_L("Preview"), preview_shortcuts)); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index eba169dc96..59b8e56f34 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -355,6 +355,10 @@ void MainFrame::update_layout() // Set new settings switch (m_layout) { + case ESettingsLayout::Unknown: + { + break; + } case ESettingsLayout::Old: { m_plater->Reparent(m_tabpanel); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 36fa83470a..d0b52426c2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1330,8 +1330,12 @@ void Sidebar::update_sliced_info_sizer() wxString str_color = _L("Color"); wxString str_pause = _L("Pause"); - auto fill_labels = [str_color, str_pause](const std::vector>& times, - wxString& new_label, wxString& info_text) +#if ENABLE_GCODE_VIEWER + auto fill_labels = [str_color, str_pause](const std::vector>>& times, +#else + auto fill_labels = [str_color, str_pause](const std::vector>& times, +#endif // ENABLE_GCODE_VIEWER + wxString& new_label, wxString& info_text) { int color_change_count = 0; for (auto time : times) @@ -1348,19 +1352,31 @@ void Sidebar::update_sliced_info_sizer() if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint) new_label += format_wxstr(" -> %1%", str_pause); +#if ENABLE_GCODE_VIEWER + info_text += format_wxstr("\n%1% (%2%)", times[i].second.first, times[i].second.second); +#else info_text += format_wxstr("\n%1%", times[i].second); +#endif // ENABLE_GCODE_VIEWER } }; if (ps.estimated_normal_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); +#if ENABLE_GCODE_VIEWER + fill_labels(ps.estimated_normal_custom_gcode_print_times_str, new_label, info_text); +#else fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); +#endif // ENABLE_GCODE_VIEWER } if (ps.estimated_silent_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("stealth mode")); info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time); +#if ENABLE_GCODE_VIEWER + fill_labels(ps.estimated_silent_custom_gcode_print_times_str, new_label, info_text); +#else fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text); +#endif // ENABLE_GCODE_VIEWER } p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); } @@ -2709,6 +2725,9 @@ void Plater::priv::reset() if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); +#if ENABLE_GCODE_VIEWER + reset_gcode_toolpaths(); +#endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER_AS_STATE gcode_result.reset(); #endif // ENABLE_GCODE_VIEWER_AS_STATE @@ -2859,8 +2878,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. // Otherwise they will be just refreshed. #if ENABLE_GCODE_VIEWER - if (this->preview != nullptr) - { + if (this->preview != nullptr) { // If the preview is not visible, the following line just invalidates the preview, // but the G-code paths or SLA preview are calculated first once the preview is made visible. this->preview->get_canvas3d()->reset_gcode_toolpaths(); From 6fbb3db79c1a2b87a873120606bdf2bdc8cacc65 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 8 Jul 2020 14:43:14 +0200 Subject: [PATCH 158/826] Fixed build when ENABLE_GCODE_VIEWER is disabled --- src/slic3r/GUI/3DBed.cpp | 5 ++--- src/slic3r/GUI/MainFrame.cpp | 2 ++ src/slic3r/GUI/Selection.cpp | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 16ab95d6c4..ca075fb372 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -369,7 +369,6 @@ void Bed3D::calc_bounding_boxes() const m_extended_bounding_box.merge(model_bb); } #else - m_extended_bounding_box.merge(m_axes.get_total_length() * Vec3d::Ones()); m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones()); // extend to contain model, if any if (!m_model.get_filename().empty()) @@ -694,11 +693,11 @@ void Bed3D::render_default(bool bottom) const { // draw background glsafe(::glDepthMask(GL_FALSE)); -#if ENABLE_LAYOUT_NO_RESTART +#if ENABLE_GCODE_VIEWER glsafe(::glColor4fv(m_model_color.data())); #else glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); -#endif // ENABLE_LAYOUT_NO_RESTART +#endif // ENABLE_GCODE_VIEWER glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 59b8e56f34..0d2c17dce7 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -482,9 +482,11 @@ void MainFrame::shutdown() #endif // ENABLE_LAYOUT_NO_RESTART if (m_plater != nullptr) { +#if ENABLE_GCODE_VIEWER_AS_STATE // restore sidebar if it was hidden when switching to gcode viewer mode if (m_restore_from_gcode_viewer.collapsed_sidebar) m_plater->collapse_sidebar(false); +#endif // ENABLE_GCODE_VIEWER_AS_STATE // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). m_plater->get_mouse3d_controller().shutdown(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index add09a59de..f429c6a958 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -2039,11 +2039,19 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con shader->set_uniform("uniform_color", uniform_scale ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4); glsafe(::glTranslated(0.0, 5.0, 0.0)); +#if ENABLE_GCODE_VIEWER m_arrow.render(); +#else + m_arrow->render(); +#endif // ENABLE_GCODE_VIEWER glsafe(::glTranslated(0.0, -10.0, 0.0)); glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); +#if ENABLE_GCODE_VIEWER m_arrow.render(); +#else + m_arrow->render(); +#endif // ENABLE_GCODE_VIEWER }; if (boost::ends_with(sidebar_field, "x") || uniform_scale) From 431cfcc671f2cf8b861814e24c5afd5c1c7ec9d7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 9 Jul 2020 15:57:35 +0200 Subject: [PATCH 159/826] GCodeViewer -> Reworked layout of imgui dialog for estimated printing times --- src/slic3r/GUI/GCodeViewer.cpp | 254 +++++++++++++++++++------------- src/slic3r/GUI/ImGuiWrapper.cpp | 7 + 2 files changed, 160 insertions(+), 101 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 26dc765db4..3ceaf86fa2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -40,23 +40,28 @@ static GCodeProcessor::EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); } -std::vector> decode_colors(const std::vector & colors) { +std::array decode_color(const std::string& color) { static const float INV_255 = 1.0f / 255.0f; + std::array ret; + const char* c = color.data() + 1; + if ((color.size() == 7) && (color.front() == '#')) { + for (size_t j = 0; j < 3; ++j) { + int digit1 = hex_digit_to_int(*c++); + int digit2 = hex_digit_to_int(*c++); + if ((digit1 == -1) || (digit2 == -1)) + break; + + ret[j] = float(digit1 * 16 + digit2) * INV_255; + } + } + return ret; +} + +std::vector> decode_colors(const std::vector& colors) { std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f }); for (size_t i = 0; i < colors.size(); ++i) { - const std::string& color = colors[i]; - const char* c = color.data() + 1; - if ((color.size() == 7) && (color.front() == '#')) { - for (size_t j = 0; j < 3; ++j) { - int digit1 = hex_digit_to_int(*c++); - int digit2 = hex_digit_to_int(*c++); - if ((digit1 == -1) || (digit2 == -1)) - break; - - output[i][j] = float(digit1 * 16 + digit2) * INV_255; - } - } + output[i] = decode_color(colors[i]); } return output; } @@ -1575,6 +1580,11 @@ void GCodeViewer::render_legend() const { // extruders for (unsigned int i = 0; i < (unsigned int)extruders_count; ++i) { + // shows only extruders actually used + auto it = std::find(m_extruder_ids.begin(), m_extruder_ids.end(), static_cast(i)); + if (it == m_extruder_ids.end()) + continue; + #if USE_ICON_HEXAGON add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); #else @@ -1671,14 +1681,9 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } + void GCodeViewer::render_time_estimate() const { - static const std::vector Columns_Headers = { - _u8L("Operation"), - _u8L("Remaining"), - _u8L("Duration") - }; - if (!m_time_estimate_enabled) return; @@ -1686,94 +1691,87 @@ void GCodeViewer::render_time_estimate() const if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") return; - int columns_count = 1; - if (ps.estimated_silent_print_time != "N/A") - ++columns_count; - ImGuiWrapper& imgui = *wxGetApp().imgui(); - Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(-1.0f, 0.5f * static_cast(cnv_size.get_height()))); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.6f); - imgui.begin(std::string("Time_estimate"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - float icon_size = ImGui::GetTextLineHeight(); using Time = std::pair; using TimesList = std::vector>; - using Headers = std::vector; - using Offsets = std::array; - auto add_mode = [this, &imgui, icon_size, draw_list](const std::string& mode, const std::string& time, const TimesList& times, const Headers& headers) { - auto add_partial_times = [this, &imgui, icon_size, draw_list](const TimesList& times, const Headers& headers) { - auto add_color = [this, &imgui, icon_size, draw_list](int id, Offsets& offsets, const Time& time) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - std::string text = _u8L("Color"); - if (m_view_type != EViewType::ColorPrint) - text += " " + std::to_string(id); - imgui.text(text); - ImGui::PopStyleColor(); - ImGui::SameLine(); + // helper structure containig the data needed to render the items + struct Item + { + CustomGCode::Type type; + int extruder_id; + Color color; + Time time; + }; + using Items = std::vector; - if (m_view_type == EViewType::ColorPrint) { - const Color& color = m_tool_colors[id - 1]; - ImVec2 pos = ImGui::GetCursorScreenPos(); -#if USE_ICON_HEXAGON - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); -#else - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ m_tool_colors[i][0], m_tool_colors[i][1], m_tool_colors[i][2], 1.0f })); -#endif // USE_ICON_HEXAGON - } - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(time.second))); - ImGui::SameLine(offsets[1]); - imgui.text(short_time(get_time_dhms(time.first))); + auto append_mode = [this, &imgui](const std::string& time_str, const Items& items) { + auto append_partial_times = [this, &imgui](const Items& items) { + using Headers = std::vector; + const Headers headers = { + _u8L("Event"), + _u8L("Remaining"), + _u8L("Duration") }; - auto calc_offsets = [this, icon_size](const TimesList& times, const Headers& headers, int color_change_count) { + using Offsets = std::array; + auto calc_offsets = [this, &headers](const Items& items) { Offsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; - for (const auto& [type, time] : times) { + for (const Item& item : items) { std::string label; - switch (type) + switch (item.type) { - case CustomGCode::PausePrint: - { - label = _u8L("Pause"); - break; - } + case CustomGCode::PausePrint: { label = _u8L("Pause"); break; } case CustomGCode::ColorChange: { - label = _u8L("Color"); - if (m_view_type != EViewType::ColorPrint) - label += " " + std::to_string(color_change_count); + int extruders_count = wxGetApp().extruders_edited_cnt(); + label = (extruders_count > 1) ? _u8L("[XX] Color") : _u8L("Color"); break; } default: { break; } } ret[0] = std::max(ret[0], ImGui::CalcTextSize(label.c_str()).x); - ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time.second)).c_str()).x); + ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(item.time.second)).c_str()).x); } const ImGuiStyle& style = ImGui::GetStyle(); - ret[0] += icon_size + style.ItemSpacing.x; + ret[0] += ImGui::GetTextLineHeight() + 2.0f * style.ItemSpacing.x; ret[1] += ret[0] + style.ItemSpacing.x; return ret; }; + auto append_color = [this, &imgui](int id, int extruder_id, const Color& color, Offsets& offsets, const Time& time) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + int extruders_count = wxGetApp().extruders_edited_cnt(); + std::string text; + if (extruders_count > 1) + text = "[" + std::to_string(extruder_id) + "] "; + text += _u8L("Color"); + imgui.text(text); + ImGui::PopStyleColor(); + ImGui::SameLine(); - if (times.empty()) + float icon_size = ImGui::GetTextLineHeight(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; +#if USE_ICON_HEXAGON + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); +#else + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ m_tool_colors[i][0], m_tool_colors[i][1], m_tool_colors[i][2], 1.0f })); +#endif // USE_ICON_HEXAGON + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(time.second))); + ImGui::SameLine(offsets[1]); + imgui.text(short_time(get_time_dhms(time.first))); + }; + + if (items.empty()) return; - int color_change_count = 0; - for (auto time : times) { - if (time.first == CustomGCode::ColorChange) - ++color_change_count; - } - - Offsets offsets = calc_offsets(times, headers, color_change_count); + Offsets offsets = calc_offsets(items); ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); @@ -1783,13 +1781,11 @@ void GCodeViewer::render_time_estimate() const ImGui::SameLine(offsets[1]); imgui.text(headers[2]); ImGui::PopStyleColor(); + ImGui::Separator(); - int last_color_id = color_change_count; - - for (int i = static_cast(times.size()) - 1; i >= 0; --i) { - const auto& [type, time] = times[i]; - - switch (type) + unsigned int last_color_id = 0; + for (const Item& item : items) { + switch (item.type) { case CustomGCode::PausePrint: { @@ -1797,15 +1793,13 @@ void GCodeViewer::render_time_estimate() const imgui.text(_u8L("Pause")); ImGui::PopStyleColor(); ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(time.second - time.first))); - - add_color(last_color_id, offsets, time); + imgui.text(short_time(get_time_dhms(item.time.second - item.time.first))); break; } case CustomGCode::ColorChange: { - add_color(color_change_count, offsets, time); - last_color_id = color_change_count--; + append_color(last_color_id, item.extruder_id, item.color, offsets, item.time); + ++last_color_id; break; } default: { break; } @@ -1814,24 +1808,82 @@ void GCodeViewer::render_time_estimate() const }; ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(mode + ":"); + imgui.text(_u8L("Time") + ":"); ImGui::PopStyleColor(); ImGui::SameLine(); - imgui.text(time); - add_partial_times(times, headers); + imgui.text(time_str); + append_partial_times(items); }; + auto generate_items = [this](const TimesList& times) { + std::vector items; + + std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; + int extruders_count = wxGetApp().extruders_edited_cnt(); + std::vector last_color(extruders_count); + for (int i = 0; i < extruders_count; ++i) { + last_color[i] = m_tool_colors[i]; + } + int last_extruder_id = 1; + for (const auto& time_rec : times) { + switch (time_rec.first) + { + case CustomGCode::PausePrint: + { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ CustomGCode::ColorChange, it->extruder, last_color[it->extruder - 1], time_rec.second }); + items.push_back({ time_rec.first, it->extruder, last_color[it->extruder - 1], time_rec.second }); + custom_gcode_per_print_z.erase(it); + } + break; + } + case CustomGCode::ColorChange: + { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ time_rec.first, it->extruder, last_color[it->extruder - 1], time_rec.second }); + last_color[it->extruder - 1] = decode_color(it->color); + last_extruder_id = it->extruder; + custom_gcode_per_print_z.erase(it); + } + else + items.push_back({ time_rec.first, last_extruder_id, last_color[last_extruder_id - 1], time_rec.second }); + + break; + } + default: { break; } + } + } + + return items; + }; + + Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); + ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(-1.0f, 0.5f * static_cast(cnv_size.get_height()))); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::SetNextWindowBgAlpha(0.6f); + imgui.begin(std::string("Time_estimate_2"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); + // title imgui.title(_u8L("Estimated printing time")); - // times - if (ps.estimated_normal_print_time != "N/A") - add_mode(_u8L("Normal mode"), ps.estimated_normal_print_time, ps.estimated_normal_custom_gcode_print_times, Columns_Headers); - - if (ps.estimated_silent_print_time != "N/A") { - ImGui::Separator(); - add_mode(_u8L("Stealth mode"), ps.estimated_silent_print_time, ps.estimated_silent_custom_gcode_print_times, Columns_Headers); + // mode tabs + ImGui::BeginTabBar("mode_tabs"); + if (ps.estimated_normal_print_time != "N/A") { + if (ImGui::BeginTabItem(_u8L("Normal").c_str())) { + append_mode(ps.estimated_normal_print_time, generate_items(ps.estimated_normal_custom_gcode_print_times)); + ImGui::EndTabItem(); + } } + if (ps.estimated_silent_print_time != "N/A") { + if (ImGui::BeginTabItem(_u8L("Stealth").c_str())) { + append_mode(ps.estimated_silent_print_time, generate_items(ps.estimated_silent_custom_gcode_print_times)); + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); imgui.end(); ImGui::PopStyleVar(); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 2c463dc2a5..1253c047ef 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1028,6 +1028,13 @@ void ImGuiWrapper::init_style() // Separator set_color(ImGuiCol_Separator, COL_ORANGE_LIGHT); + + // Tabs + set_color(ImGuiCol_Tab, COL_ORANGE_DARK); + set_color(ImGuiCol_TabHovered, COL_ORANGE_LIGHT); + set_color(ImGuiCol_TabActive, COL_ORANGE_LIGHT); + set_color(ImGuiCol_TabUnfocused, COL_GREY_DARK); + set_color(ImGuiCol_TabUnfocusedActive, COL_GREY_LIGHT); } void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) From 13a8ed0bd03aa508487dc22705f909b4563572b9 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 10 Jul 2020 13:20:03 +0200 Subject: [PATCH 160/826] GCodeViewer -> Reworked layout of color print legend to make it consistent for the single extruder and multi extruders cases --- src/slic3r/GUI/GCodeViewer.cpp | 239 ++++++++++++++++++--------------- 1 file changed, 133 insertions(+), 106 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3ceaf86fa2..f22a691b1f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1351,7 +1351,7 @@ void GCodeViewer::render_legend() const Line }; - auto add_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { + auto append_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { float icon_size = ImGui::GetTextLineHeight(); ImVec2 pos = ImGui::GetCursorScreenPos(); switch (type) @@ -1419,28 +1419,75 @@ void GCodeViewer::render_legend() const imgui.text(label); }; - auto add_range = [this, draw_list, &imgui, add_item](const Extrusions::Range& range, unsigned int decimals) { - auto add_range_item = [this, draw_list, &imgui, add_item](int i, float value, unsigned int decimals) { + auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) { + auto append_range_item = [this, draw_list, &imgui, append_item](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, Range_Colors[i], buf); + append_item(EItemType::Hexagon, Range_Colors[i], buf); #else - add_item(EItemType::Rect, Range_Colors[i], buf); + append_item(EItemType::Rect, Range_Colors[i], buf); #endif // USE_ICON_HEXAGON }; float step_size = range.step_size(); if (step_size == 0.0f) // single item use case - add_range_item(0, range.min, decimals); + append_range_item(0, range.min, decimals); else { for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { - add_range_item(i, range.min + static_cast(i) * step_size, decimals); + append_range_item(i, range.min + static_cast(i) * step_size, decimals); } } }; + auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { + std::vector>> ret; + ret.reserve(custom_gcode_per_print_z.size()); + + for (const auto& item : custom_gcode_per_print_z) { + if (extruder_id + 1 != static_cast(item.extruder)) + continue; + + if (item.type != ColorChange) + continue; + + auto lower_b = std::lower_bound(m_layers_zs.begin(), m_layers_zs.end(), item.print_z - Slic3r::DoubleSlider::epsilon()); + + if (lower_b == m_layers_zs.end()) + continue; + + double current_z = *lower_b; + double previous_z = lower_b == m_layers_zs.begin() ? 0.0 : *(--lower_b); + + // to avoid duplicate values, check adding values + if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) + ret.push_back({ decode_color(item.color), { previous_z, current_z } }); + } + + return ret; + }; + + auto upto_label = [](double z) { + char buf[64]; + ::sprintf(buf, "%.2f", z); + return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm"); + }; + + auto above_label = [](double z) { + char buf[64]; + ::sprintf(buf, "%.2f", z); + return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm"); + }; + + auto fromto_label = [](double z1, double z2) { + char buf1[64]; + ::sprintf(buf1, "%.2f", z1); + char buf2[64]; + ::sprintf(buf2, "%.2f", z2); + return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); + }; + // extrusion paths -> title switch (m_view_type) { @@ -1466,9 +1513,9 @@ void GCodeViewer::render_legend() const ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { + append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { #else - add_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { + append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { #endif // USE_ICON_HEXAGON if (role < erCount) { @@ -1485,24 +1532,19 @@ void GCodeViewer::render_legend() const } break; } - case EViewType::Height: { add_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { add_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { add_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { add_range(m_extrusions.ranges.fan_speed, 0); break; } - case EViewType::VolumetricRate: { add_range(m_extrusions.ranges.volumetric_rate, 3); break; } + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } case EViewType::Tool: { - size_t tools_count = m_tool_colors.size(); - for (size_t i = 0; i < tools_count; ++i) { - // shows only extruders actually used - auto it = std::find(m_extruder_ids.begin(), m_extruder_ids.end(), static_cast(i)); - if (it == m_extruder_ids.end()) - continue; - + // shows only extruders actually used + for (unsigned char i : m_extruder_ids) { #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); + append_item(EItemType::Hexagon, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); #else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); #endif // USE_ICON_HEXAGON } break; @@ -1512,97 +1554,85 @@ void GCodeViewer::render_legend() const const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; const int extruders_count = wxGetApp().extruders_edited_cnt(); if (extruders_count == 1) { // single extruder use case - if (custom_gcode_per_print_z.empty()) - // no data to show + std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors.front(), _u8L("Default print color")); + append_item(EItemType::Hexagon, m_tool_colors.front(), _u8L("Default color")); #else - add_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default print color")); + append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); #endif // USE_ICON_HEXAGON + } else { - std::vector> cp_values; - cp_values.reserve(custom_gcode_per_print_z.size()); - - for (auto custom_code : custom_gcode_per_print_z) { - if (custom_code.type != ColorChange) - continue; - - auto lower_b = std::lower_bound(m_layers_zs.begin(), m_layers_zs.end(), custom_code.print_z - Slic3r::DoubleSlider::epsilon()); - - if (lower_b == m_layers_zs.end()) - continue; - - double current_z = *lower_b; - double previous_z = lower_b == m_layers_zs.begin() ? 0.0 : *(--lower_b); - - // to avoid duplicate values, check adding values - if (cp_values.empty() || !(cp_values.back().first == previous_z && cp_values.back().second == current_z)) - cp_values.emplace_back(std::make_pair(previous_z, current_z)); - } - - const int items_cnt = static_cast(cp_values.size()); - if (items_cnt == 0) { // There is no one color change, but there are some pause print or custom Gcode + for (int i = items_cnt; i >= 0; --i) { + // create label for color change item + if (i == 0) { #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors.front(), _u8L("Default print color")); + append_item(EItemType::Hexagon, m_tool_colors[0], upto_label(cp_values.front().second.first)); #else - add_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default print color")); -#endif // USE_ICON_HEXAGON - } - else { - for (int i = items_cnt; i >= 0; --i) { - // create label for color change item - if (i == 0) { -#if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str()); -#else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("up to %.2f mm")) % cp_values.front().first).str()); -#endif // USE_ICON_HEXAGON - break; - } - else if (i == items_cnt) { -#if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str()); -#else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("above %.2f mm")) % cp_values[i - 1].second).str()); -#endif // USE_ICON_HEXAGON - continue; - } -#if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second % cp_values[i].first).str()); -#else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("%.2f - %.2f mm")) % cp_values[i - 1].second % cp_values[i].first).str()); + append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first); #endif // USE_ICON_HEXAGON + break; } + else if (i == items_cnt) { +#if USE_ICON_HEXAGON + append_item(EItemType::Hexagon, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); +#else + append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second); +#endif // USE_ICON_HEXAGON + continue; + } +#if USE_ICON_HEXAGON + append_item(EItemType::Hexagon, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); +#else + append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); +#endif // USE_ICON_HEXAGON } } } else // multi extruder use case { - // extruders - for (unsigned int i = 0; i < (unsigned int)extruders_count; ++i) { - // shows only extruders actually used - auto it = std::find(m_extruder_ids.begin(), m_extruder_ids.end(), static_cast(i)); - if (it == m_extruder_ids.end()) - continue; - + // shows only extruders actually used + for (unsigned char i : m_extruder_ids) { + std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const int items_cnt = static_cast(cp_values.size()); + if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); + append_item(EItemType::Hexagon, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); #else - add_item(EItemType::Rect, m_tool_colors[i], (boost::format(_u8L("Extruder %d")) % (i + 1)).str()); + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); #endif // USE_ICON_HEXAGON - } - - // color changes - int color_change_idx = 1 + static_cast(m_tool_colors.size()) - extruders_count; - size_t last_color_id = m_tool_colors.size() - 1; - for (int i = static_cast(custom_gcode_per_print_z.size()) - 1; i >= 0; --i) { - if (custom_gcode_per_print_z[i].type == ColorChange) { + } + else { + for (int j = items_cnt; j >= 0; --j) { + // create label for color change item + std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); + if (j == 0) { + label += " " + upto_label(cp_values.front().second.first); #if USE_ICON_HEXAGON - add_item(EItemType::Hexagon, m_tool_colors[last_color_id--], + append_item(EItemType::Hexagon, m_tool_colors[i], label); #else - add_item(EItemType::Rect, m_tool_colors[last_color_id--], + append_item(EItemType::Rect, m_tool_colors[i], label); #endif // USE_ICON_HEXAGON - (boost::format(_u8L("Color change for Extruder %d at %.2f mm")) % custom_gcode_per_print_z[i].extruder % custom_gcode_per_print_z[i].print_z).str()); + break; + } + else if (j == items_cnt) { + label += " " + above_label(cp_values[j - 1].second.second); +#if USE_ICON_HEXAGON + append_item(EItemType::Hexagon, cp_values[j - 1].first, label); +#else + append_item(EItemType::Rect, cp_values[j - 1].first, label); +#endif // USE_ICON_HEXAGON + continue; + } + + label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); +#if USE_ICON_HEXAGON + append_item(EItemType::Hexagon, cp_values[j - 1].first, label); +#else + append_item(EItemType::Rect, cp_values[j - 1].first, label); +#endif // USE_ICON_HEXAGON + } } } } @@ -1629,9 +1659,9 @@ void GCodeViewer::render_legend() const imgui.title(_u8L("Travel")); // items - add_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); - add_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); - add_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); + append_item(EItemType::Line, Travel_Colors[0], _u8L("Movement")); + append_item(EItemType::Line, Travel_Colors[1], _u8L("Extrusion")); + append_item(EItemType::Line, Travel_Colors[2], _u8L("Retraction")); break; } @@ -1652,13 +1682,13 @@ void GCodeViewer::render_legend() const available(GCodeProcessor::EMoveType::Unretract); }; - auto add_option = [this, add_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { + auto add_option = [this, append_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { const TBuffer& buffer = m_buffers[buffer_id(move_type)]; if (buffer.visible && buffer.indices.count > 0) #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - add_item((m_shaders_editor.points.shader_version == 0) ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); + append_item((m_shaders_editor.points.shader_version == 0) ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); #else - add_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); + append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR }; @@ -1681,7 +1711,6 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } - void GCodeViewer::render_time_estimate() const { if (!m_time_estimate_enabled) @@ -1724,8 +1753,7 @@ void GCodeViewer::render_time_estimate() const case CustomGCode::PausePrint: { label = _u8L("Pause"); break; } case CustomGCode::ColorChange: { - int extruders_count = wxGetApp().extruders_edited_cnt(); - label = (extruders_count > 1) ? _u8L("[XX] Color") : _u8L("Color"); + label = (wxGetApp().extruders_edited_cnt() > 1) ? _u8L("[XX] Color") : _u8L("Color"); break; } default: { break; } @@ -1742,9 +1770,8 @@ void GCodeViewer::render_time_estimate() const }; auto append_color = [this, &imgui](int id, int extruder_id, const Color& color, Offsets& offsets, const Time& time) { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - int extruders_count = wxGetApp().extruders_edited_cnt(); std::string text; - if (extruders_count > 1) + if (wxGetApp().extruders_edited_cnt() > 1) text = "[" + std::to_string(extruder_id) + "] "; text += _u8L("Color"); imgui.text(text); From 755fdb5ab478042a2a7edf9318ab46defb637444 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 10 Jul 2020 15:31:56 +0200 Subject: [PATCH 161/826] GCodeViewer -> Refactoring of data shown into estimated printing time dialog --- src/slic3r/GUI/GCodeViewer.cpp | 73 +++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index f22a691b1f..aaabd62220 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1728,9 +1728,16 @@ void GCodeViewer::render_time_estimate() const // helper structure containig the data needed to render the items struct Item { - CustomGCode::Type type; + enum class EType : unsigned char + { + Print, + ColorChange, + Pause + }; + EType type; int extruder_id; - Color color; + Color color1; + Color color2; Time time; }; using Items = std::vector; @@ -1750,13 +1757,9 @@ void GCodeViewer::render_time_estimate() const std::string label; switch (item.type) { - case CustomGCode::PausePrint: { label = _u8L("Pause"); break; } - case CustomGCode::ColorChange: - { - label = (wxGetApp().extruders_edited_cnt() > 1) ? _u8L("[XX] Color") : _u8L("Color"); - break; - } - default: { break; } + case Item::EType::Print: { label = _u8L("Print"); break; } + case Item::EType::Pause: { label = _u8L("Pause"); break; } + case Item::EType::ColorChange: { label = _u8L("Color change"); break; } } ret[0] = std::max(ret[0], ImGui::CalcTextSize(label.c_str()).x); @@ -1764,17 +1767,13 @@ void GCodeViewer::render_time_estimate() const } const ImGuiStyle& style = ImGui::GetStyle(); - ret[0] += ImGui::GetTextLineHeight() + 2.0f * style.ItemSpacing.x; + ret[0] += 2.0f * (ImGui::GetTextLineHeight() + style.ItemSpacing.x); ret[1] += ret[0] + style.ItemSpacing.x; return ret; }; - auto append_color = [this, &imgui](int id, int extruder_id, const Color& color, Offsets& offsets, const Time& time) { + auto append_color = [this, &imgui](const Color& color1, const Color& color2, Offsets& offsets, const Time& time) { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - std::string text; - if (wxGetApp().extruders_edited_cnt() > 1) - text = "[" + std::to_string(extruder_id) + "] "; - text += _u8L("Color"); - imgui.text(text); + imgui.text(_u8L("Color change")); ImGui::PopStyleColor(); ImGui::SameLine(); @@ -1784,15 +1783,18 @@ void GCodeViewer::render_time_estimate() const pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; #if USE_ICON_HEXAGON ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6); + center.x += icon_size; + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6); #else draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ m_tool_colors[i][0], m_tool_colors[i][1], m_tool_colors[i][2], 1.0f })); + ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); + pos.x += icon_size; + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); #endif // USE_ICON_HEXAGON ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(time.second))); - ImGui::SameLine(offsets[1]); - imgui.text(short_time(get_time_dhms(time.first))); + imgui.text(short_time(get_time_dhms(time.second - time.first))); }; if (items.empty()) @@ -1810,11 +1812,21 @@ void GCodeViewer::render_time_estimate() const ImGui::PopStyleColor(); ImGui::Separator(); - unsigned int last_color_id = 0; for (const Item& item : items) { switch (item.type) { - case CustomGCode::PausePrint: + case Item::EType::Print: + { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_u8L("Print")); + ImGui::PopStyleColor(); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(item.time.second))); + ImGui::SameLine(offsets[1]); + imgui.text(short_time(get_time_dhms(item.time.first))); + break; + } + case Item::EType::Pause: { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Pause")); @@ -1823,13 +1835,11 @@ void GCodeViewer::render_time_estimate() const imgui.text(short_time(get_time_dhms(item.time.second - item.time.first))); break; } - case CustomGCode::ColorChange: + case Item::EType::ColorChange: { - append_color(last_color_id, item.extruder_id, item.color, offsets, item.time); - ++last_color_id; + append_color(item.color1, item.color2, offsets, item.time); break; } - default: { break; } } } }; @@ -1859,8 +1869,8 @@ void GCodeViewer::render_time_estimate() const { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ CustomGCode::ColorChange, it->extruder, last_color[it->extruder - 1], time_rec.second }); - items.push_back({ time_rec.first, it->extruder, last_color[it->extruder - 1], time_rec.second }); + items.push_back({ Item::EType::Print, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ Item::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); custom_gcode_per_print_z.erase(it); } break; @@ -1869,13 +1879,14 @@ void GCodeViewer::render_time_estimate() const { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ time_rec.first, it->extruder, last_color[it->extruder - 1], time_rec.second }); + items.push_back({ Item::EType::Print, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ Item::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); last_color[it->extruder - 1] = decode_color(it->color); last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else - items.push_back({ time_rec.first, last_extruder_id, last_color[last_extruder_id - 1], time_rec.second }); + items.push_back({ Item::EType::Print, last_extruder_id, Color(), Color(), time_rec.second }); break; } From c5197f33502effe0bd19a257db6235ca8df41edd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 14 Jul 2020 15:34:08 +0200 Subject: [PATCH 162/826] PhysicalPrinterDialog is completed --- src/libslic3r/Preset.cpp | 82 +++++++++++++-- src/libslic3r/Preset.hpp | 28 +++-- src/slic3r/GUI/PresetComboBoxes.cpp | 156 ++++++++++++++++++++++------ src/slic3r/GUI/PresetComboBoxes.hpp | 13 ++- 4 files changed, 230 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ec3e933384..c9f5bd0af0 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1345,6 +1345,11 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model // *** PhysicalPrinter *** // ------------------------- +std::string PhysicalPrinter::separator() +{ + return " * "; +} + const std::vector& PhysicalPrinter::printer_options() { static std::vector s_opts; @@ -1370,9 +1375,9 @@ const std::string& PhysicalPrinter::get_preset_name() const return config.opt_string("preset_name"); } -const std::string& PhysicalPrinter::get_printer_model() const +const std::set& PhysicalPrinter::get_preset_names() const { - return config.opt_string("printer_model"); + return preset_names; } bool PhysicalPrinter::has_empty_config() const @@ -1384,20 +1389,62 @@ bool PhysicalPrinter::has_empty_config() const config.opt_string("password" ).empty(); } +void PhysicalPrinter::update_preset_names_in_config() +{ + if (!preset_names.empty()) { + std::string name; + for (auto el : preset_names) + name += el + ";"; + name.pop_back(); + config.set_key_value("preset_name", new ConfigOptionString(name)); + } +} + +void PhysicalPrinter::save(const std::string& file_name_from, const std::string& file_name_to) +{ + // rename the file + boost::nowide::rename(file_name_from.data(), file_name_to.data()); + this->file = file_name_to; + // save configuration + this->config.save(this->file); +} + void PhysicalPrinter::update_from_preset(const Preset& preset) { config.apply_only(preset.config, printer_options(), false); - // add preset name to the options list - config.set_key_value("preset_name", new ConfigOptionString(preset.name)); + // add preset names to the options list + auto ret = preset_names.emplace(preset.name); + update_preset_names_in_config(); + update_full_name(); } void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) { config.apply_only(new_config, printer_options(), false); + + std::string str = config.opt_string("preset_name"); + std::set values{}; + if (!str.empty()) { + boost::split(values, str, boost::is_any_of(";")); + for (const std::string& val : values) + preset_names.emplace(val); + } + preset_names = values; + update_full_name(); } +void PhysicalPrinter::reset_presets() +{ + return preset_names.clear(); +} + +bool PhysicalPrinter::add_preset(const std::string& preset_name) +{ + return preset_names.emplace(preset_name).second; +} + PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : name(name) { @@ -1412,16 +1459,23 @@ void PhysicalPrinter::set_name(const std::string& name) void PhysicalPrinter::update_full_name() { - full_name = name + " * " + get_preset_name(); + full_name = name + separator() + get_preset_name(); } std::string PhysicalPrinter::get_short_name(std::string full_name) { - int pos = full_name.find_first_of(" * "); + int pos = full_name.find(separator()); boost::erase_tail(full_name, full_name.length() - pos); return full_name; } +std::string PhysicalPrinter::get_preset_name(std::string full_name) +{ + int pos = full_name.find(separator()); + boost::erase_head(full_name, pos + 2); + return full_name; +} + // ----------------------------------- // *** PhysicalPrinterCollection *** @@ -1497,15 +1551,18 @@ std::string PhysicalPrinterCollection::path_from_name(const std::string& new_nam return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } -void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer) +void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer, const std::string& renamed_from) { + std::string name = renamed_from.empty() ? edited_printer.name : renamed_from; // 1) Find the printer with a new_name or create a new one, // initialize it with the edited config. - auto it = this->find_printer_internal(edited_printer.name); - if (it != m_printers.end() && it->name == edited_printer.name) { + auto it = this->find_printer_internal(name); + if (it != m_printers.end() && it->name == name) { // Printer with the same name found. // Overwriting an existing preset. it->config = std::move(edited_printer.config); + it->name = edited_printer.name; + it->preset_names = edited_printer.preset_names; it->full_name = edited_printer.full_name; } else { @@ -1518,7 +1575,12 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print PhysicalPrinter& printer = *it; if (printer.file.empty()) printer.file = this->path_from_name(printer.name); - printer.save(); + + if (printer.file == this->path_from_name(printer.name)) + printer.save(); + else + // if printer was renamed, we should rename a file and than save the config + printer.save(printer.file, this->path_from_name(printer.name)); // update idx_selected m_idx_selected = it - m_printers.begin(); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index a076a9a217..d583bed20d 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -549,20 +549,33 @@ public: std::string file; // Configuration data, loaded from a file, or set from the defaults. DynamicPrintConfig config; + // set of presets used with this physical printer + std::set preset_names; + + static std::string separator(); // Has this profile been loaded? bool loaded = false; - static const std::vector& printer_options(); - const std::string& get_preset_name() const; - const std::string& get_printer_model() const; + static const std::vector& printer_options(); + const std::string& get_preset_name() const; + + const std::set& get_preset_names() const; + bool has_empty_config() const; + void update_preset_names_in_config(); void save() { this->config.save(this->file); } - void save_to(const std::string& file_name) const { this->config.save(file_name); } + void save(const std::string& file_name_from, const std::string& file_name_to); + void update_from_preset(const Preset& preset); void update_from_config(const DynamicPrintConfig &new_config); + // add preset to the preset_names + // return false, if preset with this name is already exist in the set + bool add_preset(const std::string& preset_name); + void reset_presets(); + // Return a printer technology, return ptFFF if the printer technology is not set. static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) { auto* opt = cfg.option>("printer_technology"); @@ -578,6 +591,9 @@ public: // get printer name from the full name uncluded preset name static std::string get_short_name(std::string full_name); + // get preset name from the full name uncluded printer name + static std::string get_preset_name(std::string full_name); + protected: friend class PhysicalPrinterCollection; }; @@ -615,7 +631,7 @@ public: // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. // New printer is activated. - void save_printer(const PhysicalPrinter& printer); + void save_printer(const PhysicalPrinter& printer, const std::string& renamed_from); // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. @@ -633,8 +649,6 @@ public: // Returns the full name of the selected preset, or an empty string if no preset is selected. std::string get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().full_name; } // Returns the printer model of the selected preset, or an empty string if no preset is selected. - std::string get_selected_printer_model() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_printer_model(); } - // Returns the printer model of the selected preset, or an empty string if no preset is selected. std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_preset_name(); } // Returns the config of the selected preset, or nullptr if no preset is selected. DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index dc5365a139..1473bf6dab 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -41,6 +41,8 @@ static const std::pair THUMBNAIL_SIZE_3MF = { 256, 2 namespace Slic3r { namespace GUI { +#define BORDER_W 10 + // --------------------------------- // *** PresetComboBox *** // --------------------------------- @@ -762,8 +764,8 @@ void TabPresetComboBox::update() // check this value just for printer presets, when physical printer is selected if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { is_enabled = m_enable_all ? true : - preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name() || - preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model(); + preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name()/* || + preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model()*/; } std::string bitmap_key = "tab"; @@ -888,7 +890,7 @@ void TabPresetComboBox::update_dirty() // PresetForPrinter //------------------------------------------ -PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable) : +PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) : m_parent(parent) { m_sizer = new wxBoxSizer(wxVERTICAL); @@ -899,8 +901,7 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_en m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); m_presets_list = new TabPresetComboBox(parent, Preset::TYPE_PRINTER); - - if (is_all_enable) + if (preset_name.empty()) m_presets_list->set_enable_all(); m_presets_list->set_selection_changed_function([this](int selection) { @@ -921,22 +922,37 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_en update_full_printer_name(); }); + m_presets_list->update(); + m_presets_list->SetStringSelection(from_u8(preset_name)); + + m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); + m_full_printer_name->SetFont(wxGetApp().bold_font()); - m_presets_list->update(); + wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL); + preset_sizer->Add(m_presets_list , 1, wxEXPAND); + preset_sizer->Add(m_delete_preset_btn , 0, wxEXPAND | wxLEFT, BORDER_W); + + wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL); + name_sizer->Add(m_info_line, 0, wxEXPAND); + name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_sizer->Add(preset_sizer , 0, wxEXPAND); + m_sizer->Add(name_sizer, 0, wxEXPAND); } PresetForPrinter::~PresetForPrinter() { m_presets_list->Destroy(); m_delete_preset_btn->Destroy(); + m_info_line->Destroy(); m_full_printer_name->Destroy(); } void PresetForPrinter::DeletePreset(wxEvent& event) { - + m_parent->DeletePreset(this); } void PresetForPrinter::update_full_printer_name() @@ -947,6 +963,22 @@ void PresetForPrinter::update_full_printer_name() m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); } +std::string PresetForPrinter::get_preset_name() +{ + return into_u8(m_presets_list->GetString(m_presets_list->GetSelection())); +} + +void PresetForPrinter::DisableDeleteBtn() +{ + m_delete_preset_btn->Enable(false); +} + +void PresetForPrinter::EnableDeleteBtn() +{ + if (!m_delete_preset_btn->IsEnabled()) + m_delete_preset_btn->Enable(); +} + void PresetForPrinter::msw_rescale() { m_presets_list->msw_rescale(); @@ -965,22 +997,17 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - int border = 10; - m_info_string = _("This printer name will be shown in the presets list") + ":\n"; - - TabPresetComboBox* printer_presets = new TabPresetComboBox(this, Preset::TYPE_PRINTER); - if (printer_name.IsEmpty()) { printer_name = _L("My Printer Device"); // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, true)); + m_presets.emplace_back(new PresetForPrinter(this, "")); } else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); } - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _("Descriptive name for the printer device") + ":"); + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); m_add_preset_btn->SetFont(wxGetApp().normal_font()); @@ -990,17 +1017,26 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); - update_full_printer_names(); - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); if (!printer) { const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); printer = new PhysicalPrinter(into_u8(printer_name), preset); } + else + { + const std::set& preset_names = printer->get_preset_names(); + for (const std::string& preset_name : preset_names) + m_presets.emplace_back(new PresetForPrinter(this, preset_name)); + } assert(printer); m_printer = *printer; + if (m_presets.size() == 1) + m_presets.front()->DisableDeleteBtn(); + + update_full_printer_names(); + m_config = &m_printer.config; m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); @@ -1013,21 +1049,19 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); nameSizer->Add(m_printer_name, 1, wxEXPAND); - nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, border); + nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_presets_sizer = new wxBoxSizer(wxVERTICAL); + for (PresetForPrinter* preset : m_presets) + m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - for (PresetForPrinter* preset : m_presets) - topSizer->Add(preset->sizer(), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);; - /* - topSizer->Add(m_printer_presets , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(label_bottom , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_full_printer_name , 0, wxEXPAND | wxLEFT | wxRIGHT, border); - */ - topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(btns , 0, wxEXPAND | wxALL, border); + topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_presets_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -1203,7 +1237,7 @@ void PhysicalPrinterDialog::update() wxString PhysicalPrinterDialog::get_printer_name() { - return m_info_string + m_printer_name->GetValue() + "\t"; + return m_printer_name->GetValue(); } void PhysicalPrinterDialog::update_full_printer_names() @@ -1260,11 +1294,39 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) printers.delete_printer(into_u8(printer_name)); } + std::set repeat_presets; + m_printer.reset_presets(); + for (PresetForPrinter* preset : m_presets) { + if (!m_printer.add_preset(preset->get_preset_name())) + repeat_presets.emplace(preset->get_preset_name()); + } + // update preset_names in printer config + m_printer.update_preset_names_in_config(); + + if (!repeat_presets.empty()) + { + wxString repeatable_presets = "\n"; + for (const std::string& preset_name : repeat_presets) + repeatable_presets += " " + from_u8(preset_name) + "\n"; + repeatable_presets += "\n"; + + wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" + "It(they) will be added just once for the printer \"%2%\".")) % repeatable_presets % printer_name).str()); + wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + + std::string renamed_from; + // temporary save previous printer name if it was edited + if (m_printer.name != _u8L("My Printer Device") && + m_printer.name != into_u8(printer_name)) + renamed_from = m_printer.name; + //update printer name, if it was changed m_printer.set_name(into_u8(printer_name)); // save new physical printer - printers.save_printer(m_printer); + printers.save_printer(m_printer, renamed_from); // update selection on the tab only when it was changed /* @@ -1282,7 +1344,41 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) void PhysicalPrinterDialog::AddPreset(wxEvent& event) { + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, "")); + // enable DELETE button for the first preset, if was disabled + m_presets.front()->EnableDeleteBtn(); + m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); + update_full_printer_names(); + + this->Fit(); +} + +void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) +{ + if (m_presets.size() == 1) { + wxString msg_text = _L("It's not possible to delete last related preset for the printer."); + wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + return; + } + + assert(preset_for_printer); + auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer); + if (it == m_presets.end()) + return; + + const int remove_id = it - m_presets.begin(); + m_presets_sizer->Remove(remove_id); + delete preset_for_printer; + m_presets.erase(it); + + if (m_presets.size() == 1) + m_presets.front()->DisableDeleteBtn(); + + this->Layout(); + this->Fit(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 3d5ae298f0..c818b6b911 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -15,6 +15,7 @@ class wxString; class wxTextCtrl; class wxStaticText; class ScalableButton; +class wxBoxSizer; namespace Slic3r { @@ -169,6 +170,7 @@ public: //------------------------------------------ // PresetForPrinter //------------------------------------------ +static std::string g_info_string = " (modified)"; class PhysicalPrinterDialog; class PresetForPrinter { @@ -176,6 +178,7 @@ class PresetForPrinter TabPresetComboBox* m_presets_list { nullptr }; ScalableButton* m_delete_preset_btn { nullptr }; + wxStaticText* m_info_line { nullptr }; wxStaticText* m_full_printer_name { nullptr }; wxBoxSizer* m_sizer { nullptr }; @@ -183,11 +186,14 @@ class PresetForPrinter void DeletePreset(wxEvent& event); public: - PresetForPrinter(PhysicalPrinterDialog* parent, bool is_all_enable); + PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name); ~PresetForPrinter(); wxBoxSizer* sizer() { return m_sizer; } void update_full_printer_name(); + std::string get_preset_name(); + void DisableDeleteBtn(); + void EnableDeleteBtn(); void msw_rescale(); void on_sys_color_changed() {}; @@ -203,7 +209,6 @@ class PhysicalPrinterDialog : public DPIDialog { PhysicalPrinter m_printer; DynamicPrintConfig* m_config { nullptr }; - wxString m_info_string; wxTextCtrl* m_printer_name { nullptr }; std::vector m_presets; @@ -215,6 +220,8 @@ class PhysicalPrinterDialog : public DPIDialog ScalableButton* m_printhost_test_btn {nullptr}; ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + wxBoxSizer* m_presets_sizer {nullptr}; + void build_printhost_settings(ConfigOptionsGroup* optgroup); void OnOK(wxEvent& event); void AddPreset(wxEvent& event); @@ -228,6 +235,8 @@ public: void update_full_printer_names(); PhysicalPrinter* get_printer() {return &m_printer; } + void DeletePreset(PresetForPrinter* preset_for_printer); + protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {}; From 3a88e698969bdcdf5cd04225bedff96215cf5fa6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 16 Jul 2020 11:09:21 +0200 Subject: [PATCH 163/826] ENABLE_GCODE_VIEWER -> Integration of time estimator into GCodeProcessor --- src/libslic3r/GCode.cpp | 1246 ++++++++++++------------ src/libslic3r/GCode/GCodeProcessor.cpp | 821 +++++++++++++++- src/libslic3r/GCode/GCodeProcessor.hpp | 193 +++- src/libslic3r/GCodeTimeEstimator.cpp | 16 - src/libslic3r/GCodeTimeEstimator.hpp | 4 - src/libslic3r/Print.cpp | 18 +- src/libslic3r/Print.hpp | 12 +- src/slic3r/GUI/GCodeViewer.cpp | 3 + src/slic3r/GUI/Plater.cpp | 22 +- 9 files changed, 1631 insertions(+), 704 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7bfb73aa37..47448954c8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -48,662 +48,684 @@ using namespace std::literals::string_view_literals; namespace Slic3r { -//! macro used to mark string used at localization, -//! return same string + //! macro used to mark string used at localization, + //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) // Only add a newline in case the current G-code does not end with a newline. -static inline void check_add_eol(std::string &gcode) -{ - if (! gcode.empty() && gcode.back() != '\n') - gcode += '\n'; -} - - -// Return true if tch_prefix is found in custom_gcode -static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) -{ - bool ok = false; - size_t from_pos = 0; - size_t pos = 0; - while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) { - if (pos+1 == custom_gcode.size()) - break; - from_pos = pos+1; - // only whitespace is allowed before the command - while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') { - if (! std::isspace(custom_gcode[pos])) - goto NEXT; - } - { - // we should also check that the extruder changes to what was expected - std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos)); - unsigned num = 0; - if (ss >> num) - ok = (num == next_extruder); - } -NEXT: ; + static inline void check_add_eol(std::string& gcode) + { + if (!gcode.empty() && gcode.back() != '\n') + gcode += '\n'; } - return ok; -} -void AvoidCrossingPerimeters::init_external_mp(const Print &print) -{ - m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); -} -// Plan a travel move while minimizing the number of perimeter crossings. -// point is in unscaled coordinates, in the coordinate system of the current active object -// (set by gcodegen.set_origin()). -Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point) -{ - // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). - // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> - shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - if (use_external) - result.translate(- scaled_origin); - return result; -} - -// Collect outer contours of all objects over all layers. -// Discard objects only containing thin walls (offset would fail on an empty polygon). -// Used by avoid crossing perimeters feature. -Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) -{ - Polygons islands; - for (const PrintObject *object : objects) { - // Reducing all the object slices into the Z projection in a logarithimc fashion. - // First reduce to half the number of layers. - std::vector polygons_per_layer((object->layers().size() + 1) / 2); - tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), - [&object, &polygons_per_layer](const tbb::blocked_range &range) { - for (size_t i = range.begin(); i < range.end(); ++ i) { - const Layer* layer1 = object->layers()[i * 2]; - const Layer* layer2 = object->layers()[i * 2 + 1]; - Polygons polys; - polys.reserve(layer1->lslices.size() + layer2->lslices.size()); - for (const ExPolygon &expoly : layer1->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - for (const ExPolygon &expoly : layer2->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer[i] = union_(polys); - } - }); - if (object->layers().size() & 1) { - const Layer *layer = object->layers().back(); - Polygons polys; - polys.reserve(layer->lslices.size()); - for (const ExPolygon &expoly : layer->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer.back() = union_(polys); - } - // Now reduce down to a single layer. - size_t cnt = polygons_per_layer.size(); - while (cnt > 1) { - tbb::parallel_for(tbb::blocked_range(0, cnt / 2), - [&polygons_per_layer](const tbb::blocked_range &range) { - for (size_t i = range.begin(); i < range.end(); ++ i) { - Polygons polys; - polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); - polygons_append(polys, polygons_per_layer[i * 2]); - polygons_append(polys, polygons_per_layer[i * 2 + 1]); - polygons_per_layer[i * 2] = union_(polys); - } - }); - for (size_t i = 0; i < cnt / 2; ++ i) - polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); - if (cnt & 1) - polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); - cnt = (cnt + 1) / 2; - } - // And collect copies of the objects. - for (const PrintInstance &instance : object->instances()) { - // All the layers were reduced to the 1st item of polygons_per_layer. - size_t i = islands.size(); - polygons_append(islands, polygons_per_layer.front()); - for (; i < islands.size(); ++ i) - islands[i].translate(instance.shift); - } - } - return islands; -} - -std::string OozePrevention::pre_toolchange(GCode &gcodegen) -{ - std::string gcode; - - // move to the nearest standby point - if (!this->standby_points.empty()) { - // get current position in print coordinates - Vec3d writer_pos = gcodegen.writer().get_position(); - Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); - - // find standby point - Point standby_point; - pos.nearest_point(this->standby_points, &standby_point); - - /* We don't call gcodegen.travel_to() because we don't need retraction (it was already - triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates - of the destination point must not be transformed by origin nor current extruder offset. */ - gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), - "move to standby position"); - } - - if (gcodegen.config().standby_temperature_delta.value != 0) { - // we assume that heating is always slower than cooling, so no need to block - gcode += gcodegen.writer().set_temperature - (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id()); - } - - return gcode; -} - -std::string OozePrevention::post_toolchange(GCode &gcodegen) -{ - return (gcodegen.config().standby_temperature_delta.value != 0) ? - gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) : - std::string(); -} - -int -OozePrevention::_get_temp(GCode &gcodegen) -{ - return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0) - ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id()) - : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id()); -} - -std::string Wipe::wipe(GCode &gcodegen, bool toolchange) -{ - std::string gcode; - - /* Reduce feedrate a bit; travel speed is often too high to move on existing material. - Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ - double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8; - - // get the retraction length - double length = toolchange - ? gcodegen.writer().extruder()->retract_length_toolchange() - : gcodegen.writer().extruder()->retract_length(); - // Shorten the retraction length by the amount already retracted before wipe. - length *= (1. - gcodegen.writer().extruder()->retract_before_wipe()); - - if (length > 0) { - /* Calculate how long we need to travel in order to consume the required - amount of retraction. In other words, how far do we move in XY at wipe_speed - for the time needed to consume retract_length at retract_speed? */ - double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed); - - /* Take the stored wipe path and replace first point with the current actual position - (they might be different, for example, in case of loop clipping). */ - Polyline wipe_path; - wipe_path.append(gcodegen.last_pos()); - wipe_path.append( - this->path.points.begin() + 1, - this->path.points.end() - ); - - wipe_path.clip_end(wipe_path.length() - wipe_dist); - - // subdivide the retraction in segments - if (! wipe_path.empty()) { - for (const Line &line : wipe_path.lines()) { - double segment_length = line.length(); - /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one - due to rounding (TODO: test and/or better math for this) */ - double dE = length * (segment_length / wipe_dist) * 0.95; - //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. - // Is it here for the cooling markers? Or should it be outside of the cycle? - gcode += gcodegen.writer().set_speed(wipe_speed*60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); - gcode += gcodegen.writer().extrude_to_xy( - gcodegen.point_to_gcode(line.b), - -dE, - "wipe and retract" - ); + // Return true if tch_prefix is found in custom_gcode + static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) + { + bool ok = false; + size_t from_pos = 0; + size_t pos = 0; + while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) { + if (pos + 1 == custom_gcode.size()) + break; + from_pos = pos + 1; + // only whitespace is allowed before the command + while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') { + if (!std::isspace(custom_gcode[pos])) + goto NEXT; } - gcodegen.set_last_pos(wipe_path.points.back()); + { + // we should also check that the extruder changes to what was expected + std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos)); + unsigned num = 0; + if (ss >> num) + ok = (num == next_extruder); + } + NEXT:; } - - // prevent wiping again on same path - this->reset_path(); - } - - return gcode; -} - -static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt) -{ - return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); -} - -std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z) const -{ - if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) - throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); - - std::string gcode; - - // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) - // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position - float alpha = m_wipe_tower_rotation/180.f * float(M_PI); - Vec2f start_pos = tcr.start_pos; - Vec2f end_pos = tcr.end_pos; - if (!tcr.priming) { - start_pos = Eigen::Rotation2Df(alpha) * start_pos; - start_pos += m_wipe_tower_pos; - end_pos = Eigen::Rotation2Df(alpha) * end_pos; - end_pos += m_wipe_tower_pos; + return ok; } - Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; - float wipe_tower_rotation = tcr.priming ? 0.f : alpha; - - std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); - - if (!tcr.priming) { - // Move over the wipe tower. - // Retract for a tool change, using the toolchange retract value and setting the priming extra length. - gcode += gcodegen.retract(true); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, start_pos), - erMixed, - "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); + void AvoidCrossingPerimeters::init_external_mp(const Print& print) + { + m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); } - double current_z = gcodegen.writer().get_position().z(); - if (z == -1.) // in case no specific z was provided, print at current_z pos - z = current_z; - if (! is_approx(z, current_z)) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); - gcode += gcodegen.writer().unretract(); + // Plan a travel move while minimizing the number of perimeter crossings. + // point is in unscaled coordinates, in the coordinate system of the current active object + // (set by gcodegen.set_origin()). + Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) + { + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = this->use_external_mp || this->use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> + shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); + if (use_external) + result.translate(-scaled_origin); + return result; } - - // Process the end filament gcode. - std::string end_filament_gcode_str; - if (gcodegen.writer().extruder() != nullptr) { - // Process the custom end_filament_gcode in case of single_extruder_multi_material. - unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); - const std::string &end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id); - if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) { - end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); - check_add_eol(end_filament_gcode_str); + // Collect outer contours of all objects over all layers. + // Discard objects only containing thin walls (offset would fail on an empty polygon). + // Used by avoid crossing perimeters feature. + Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) + { + Polygons islands; + for (const PrintObject* object : objects) { + // Reducing all the object slices into the Z projection in a logarithimc fashion. + // First reduce to half the number of layers. + std::vector polygons_per_layer((object->layers().size() + 1) / 2); + tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), + [&object, &polygons_per_layer](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + const Layer* layer1 = object->layers()[i * 2]; + const Layer* layer2 = object->layers()[i * 2 + 1]; + Polygons polys; + polys.reserve(layer1->lslices.size() + layer2->lslices.size()); + for (const ExPolygon& expoly : layer1->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + for (const ExPolygon& expoly : layer2->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + polygons_per_layer[i] = union_(polys); + } + }); + if (object->layers().size() & 1) { + const Layer* layer = object->layers().back(); + Polygons polys; + polys.reserve(layer->lslices.size()); + for (const ExPolygon& expoly : layer->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + polygons_per_layer.back() = union_(polys); + } + // Now reduce down to a single layer. + size_t cnt = polygons_per_layer.size(); + while (cnt > 1) { + tbb::parallel_for(tbb::blocked_range(0, cnt / 2), + [&polygons_per_layer](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + Polygons polys; + polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); + polygons_append(polys, polygons_per_layer[i * 2]); + polygons_append(polys, polygons_per_layer[i * 2 + 1]); + polygons_per_layer[i * 2] = union_(polys); + } + }); + for (size_t i = 0; i < cnt / 2; ++i) + polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); + if (cnt & 1) + polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); + cnt = (cnt + 1) / 2; + } + // And collect copies of the objects. + for (const PrintInstance& instance : object->instances()) { + // All the layers were reduced to the 1st item of polygons_per_layer. + size_t i = islands.size(); + polygons_append(islands, polygons_per_layer.front()); + for (; i < islands.size(); ++i) + islands[i].translate(instance.shift); + } } + return islands; } - // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. - // Otherwise, leave control to the user completely. - std::string toolchange_gcode_str; - if (true /*gcodegen.writer().extruder() != nullptr*/) { - const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; - if (!toolchange_gcode.empty()) { + std::string OozePrevention::pre_toolchange(GCode& gcodegen) + { + std::string gcode; + + // move to the nearest standby point + if (!this->standby_points.empty()) { + // get current position in print coordinates + Vec3d writer_pos = gcodegen.writer().get_position(); + Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); + + // find standby point + Point standby_point; + pos.nearest_point(this->standby_points, &standby_point); + + /* We don't call gcodegen.travel_to() because we don't need retraction (it was already + triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates + of the destination point must not be transformed by origin nor current extruder offset. */ + gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), + "move to standby position"); + } + + if (gcodegen.config().standby_temperature_delta.value != 0) { + // we assume that heating is always slower than cooling, so no need to block + gcode += gcodegen.writer().set_temperature + (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id()); + } + + return gcode; + } + + std::string OozePrevention::post_toolchange(GCode& gcodegen) + { + return (gcodegen.config().standby_temperature_delta.value != 0) ? + gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) : + std::string(); + } + + int + OozePrevention::_get_temp(GCode& gcodegen) + { + return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0) + ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id()) + : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id()); + } + + std::string Wipe::wipe(GCode& gcodegen, bool toolchange) + { + std::string gcode; + + /* Reduce feedrate a bit; travel speed is often too high to move on existing material. + Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ + double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8; + + // get the retraction length + double length = toolchange + ? gcodegen.writer().extruder()->retract_length_toolchange() + : gcodegen.writer().extruder()->retract_length(); + // Shorten the retraction length by the amount already retracted before wipe. + length *= (1. - gcodegen.writer().extruder()->retract_before_wipe()); + + if (length > 0) { + /* Calculate how long we need to travel in order to consume the required + amount of retraction. In other words, how far do we move in XY at wipe_speed + for the time needed to consume retract_length at retract_speed? */ + double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed); + + /* Take the stored wipe path and replace first point with the current actual position + (they might be different, for example, in case of loop clipping). */ + Polyline wipe_path; + wipe_path.append(gcodegen.last_pos()); + wipe_path.append( + this->path.points.begin() + 1, + this->path.points.end() + ); + + wipe_path.clip_end(wipe_path.length() - wipe_dist); + + // subdivide the retraction in segments + if (!wipe_path.empty()) { + for (const Line& line : wipe_path.lines()) { + double segment_length = line.length(); + /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one + due to rounding (TODO: test and/or better math for this) */ + double dE = length * (segment_length / wipe_dist) * 0.95; + //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. + // Is it here for the cooling markers? Or should it be outside of the cycle? + gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); + gcode += gcodegen.writer().extrude_to_xy( + gcodegen.point_to_gcode(line.b), + -dE, + "wipe and retract" + ); + } + gcodegen.set_last_pos(wipe_path.points.back()); + } + + // prevent wiping again on same path + this->reset_path(); + } + + return gcode; + } + + static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt) + { + return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); + } + + std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const + { + if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + + std::string gcode; + + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; + if (!tcr.priming) { + start_pos = Eigen::Rotation2Df(alpha) * start_pos; + start_pos += m_wipe_tower_pos; + end_pos = Eigen::Rotation2Df(alpha) * end_pos; + end_pos += m_wipe_tower_pos; + } + + Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + + std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); + + if (!tcr.priming) { + // Move over the wipe tower. + // Retract for a tool change, using the toolchange retract value and setting the priming extra length. + gcode += gcodegen.retract(true); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcode += gcodegen.travel_to( + wipe_tower_point_to_object_point(gcodegen, start_pos), + erMixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + } + + double current_z = gcodegen.writer().get_position().z(); + if (z == -1.) // in case no specific z was provided, print at current_z pos + z = current_z; + if (!is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); + gcode += gcodegen.writer().unretract(); + } + + + // Process the end filament gcode. + std::string end_filament_gcode_str; + if (gcodegen.writer().extruder() != nullptr) { + // Process the custom end_filament_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); + const std::string& end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id); + if (gcodegen.writer().extruder() != nullptr && !end_filament_gcode.empty()) { + end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); + check_add_eol(end_filament_gcode_str); + } + } + + // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. + // Otherwise, leave control to the user completely. + std::string toolchange_gcode_str; + if (true /*gcodegen.writer().extruder() != nullptr*/) { + const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; + if (!toolchange_gcode.empty()) { + DynamicConfig config; + int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + } + + std::string toolchange_command; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) + toolchange_gcode_str += toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. + } + } + + gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); + + // Process the start filament gcode. + std::string start_filament_gcode_str; + const std::string& start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); + if (!start_filament_gcode.empty()) { + // Process the start_filament_gcode for the active filament only. DynamicConfig config; - int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; - config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); - toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); - check_add_eol(toolchange_gcode_str); + config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); + start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); + check_add_eol(start_filament_gcode_str); } - std::string toolchange_command; - if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) - toolchange_command = gcodegen.writer().toolchange(new_extruder_id); - if (! custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) - toolchange_gcode_str += toolchange_command; - else { - // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. - } - } - - gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - - // Process the start filament gcode. - std::string start_filament_gcode_str; - const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); - if (! start_filament_gcode.empty()) { - // Process the start_filament_gcode for the active filament only. + // Insert the end filament, toolchange, and start filament gcode into the generated gcode. DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); - start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); - check_add_eol(start_filament_gcode_str); + config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str)); + config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); + std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + check_add_eol(toolchange_gcode_str); + + + // A phony move to the end position at the wipe tower. + gcodegen.writer().travel_to_xy(end_pos.cast()); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); + if (!is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); + gcode += gcodegen.writer().unretract(); + } + + else { + // Prepare a future wipe. + gcodegen.m_wipe.path.points.clear(); + if (new_extruder_id >= 0) { + // Start the wipe at the current position. + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); + // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, + Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, + end_pos.y()))); + } + } + + // Let the planner know we are traveling between objects. + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + return gcode; } - // Insert the end filament, toolchange, and start filament gcode into the generated gcode. - DynamicConfig config; - config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str)); - config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); - config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); - std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); - unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); - gcode += tcr_gcode; - check_add_eol(toolchange_gcode_str); + // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode + // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) + std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const + { + Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); + std::istringstream gcode_str(tcr.gcode); + std::string gcode_out; + std::string line; + Vec2f pos = tcr.start_pos; + Vec2f transformed_pos = pos; + Vec2f old_pos(-1000.1f, -1000.1f); - // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(end_pos.cast()); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); - if (! is_approx(z, current_z)) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); - gcode += gcodegen.writer().unretract(); + while (gcode_str) { + std::getline(gcode_str, line); // we read the gcode line by line + + // All G1 commands should be translated and rotated. X and Y coords are + // only pushed to the output when they differ from last time. + // WT generator can override this by appending the never_skip_tag + if (line.find("G1 ") == 0) { + bool never_skip = false; + auto it = line.find(WipeTower::never_skip_tag()); + if (it != std::string::npos) { + // remove the tag and remember we saw it + never_skip = true; + line.erase(it, it + WipeTower::never_skip_tag().size()); + } + std::ostringstream line_out; + std::istringstream line_str(line); + line_str >> std::noskipws; // don't skip whitespace + char ch = 0; + while (line_str >> ch) { + if (ch == 'X' || ch == 'Y') + line_str >> (ch == 'X' ? pos.x() : pos.y()); + else + line_out << ch; + } + + transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; + + if (transformed_pos != old_pos || never_skip) { + line = line_out.str(); + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << "G1 "; + if (transformed_pos.x() != old_pos.x() || never_skip) + oss << " X" << transformed_pos.x() - extruder_offset.x(); + if (transformed_pos.y() != old_pos.y() || never_skip) + oss << " Y" << transformed_pos.y() - extruder_offset.y(); + oss << " "; + line.replace(line.find("G1 "), 3, oss.str()); + old_pos = transformed_pos; + } + } + + gcode_out += line + "\n"; + + // If this was a toolchange command, we should change current extruder offset + if (line == "[toolchange_gcode]") { + extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); + + // If the extruder offset changed, add an extra move so everything is continuous + if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) + << "G1 X" << transformed_pos.x() - extruder_offset.x() + << " Y" << transformed_pos.y() - extruder_offset.y() + << "\n"; + gcode_out += oss.str(); + } + } + } + return gcode_out; } - else { + + std::string WipeTowerIntegration::prime(GCode& gcodegen) + { + assert(m_layer_idx == 0); + std::string gcode; + + + // Disable linear advance for the wipe tower operations. + //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + + for (const WipeTower::ToolChangeResult& tcr : m_priming) { + if (!tcr.extrusions.empty()) + gcode += append_tcr(gcodegen, tcr, tcr.new_tool); + + + // Let the tool change be executed by the wipe tower class. + // Inform the G-code writer about the changes done behind its back. + //gcode += tcr.gcode; + // Let the m_writer know the current extruder_id, but ignore the generated G-code. + // unsigned int current_extruder_id = tcr.extrusions.back().tool; + // gcodegen.writer().toolchange(current_extruder_id); + // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); + + } + + // A phony move to the end position at the wipe tower. + /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y)); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); - if (new_extruder_id >= 0) { - // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); - // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, - end_pos.y()))); + // Start the wipe at the current position. + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); + // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, + WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, + m_priming.back().end_pos.y)));*/ + + return gcode; + } + + std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer) + { + std::string gcode; + assert(m_layer_idx >= 0); + if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (m_layer_idx < (int)m_tool_changes.size()) { + if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) + throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer."); + + + // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, + // resulting in a wipe tower with sparse layers. + double wipe_tower_z = -1; + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) { + wipe_tower_z = m_last_wipe_tower_print_z; + ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + if (m_tool_change_idx == 0 && !ignore_sparse) + wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; + } + + if (!ignore_sparse) { + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); + m_last_wipe_tower_print_z = wipe_tower_z; + } + } + m_brim_done = true; } + return gcode; } - // Let the planner know we are traveling between objects. - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - return gcode; -} - -// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode -// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) -std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const -{ - Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); - - std::istringstream gcode_str(tcr.gcode); - std::string gcode_out; - std::string line; - Vec2f pos = tcr.start_pos; - Vec2f transformed_pos = pos; - Vec2f old_pos(-1000.1f, -1000.1f); - - while (gcode_str) { - std::getline(gcode_str, line); // we read the gcode line by line - - // All G1 commands should be translated and rotated. X and Y coords are - // only pushed to the output when they differ from last time. - // WT generator can override this by appending the never_skip_tag - if (line.find("G1 ") == 0) { - bool never_skip = false; - auto it = line.find(WipeTower::never_skip_tag()); - if (it != std::string::npos) { - // remove the tag and remember we saw it - never_skip = true; - line.erase(it, it+WipeTower::never_skip_tag().size()); - } - std::ostringstream line_out; - std::istringstream line_str(line); - line_str >> std::noskipws; // don't skip whitespace - char ch = 0; - while (line_str >> ch) { - if (ch == 'X' || ch =='Y') - line_str >> (ch == 'X' ? pos.x() : pos.y()); - else - line_out << ch; - } - - transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; - - if (transformed_pos != old_pos || never_skip) { - line = line_out.str(); - std::ostringstream oss; - oss << std::fixed << std::setprecision(3) << "G1 "; - if (transformed_pos.x() != old_pos.x() || never_skip) - oss << " X" << transformed_pos.x() - extruder_offset.x(); - if (transformed_pos.y() != old_pos.y() || never_skip) - oss << " Y" << transformed_pos.y() - extruder_offset.y(); - oss << " "; - line.replace(line.find("G1 "), 3, oss.str()); - old_pos = transformed_pos; - } - } - - gcode_out += line + "\n"; - - // If this was a toolchange command, we should change current extruder offset - if (line == "[toolchange_gcode]") { - extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); - - // If the extruder offset changed, add an extra move so everything is continuous - if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { - std::ostringstream oss; - oss << std::fixed << std::setprecision(3) - << "G1 X" << transformed_pos.x() - extruder_offset.x() - << " Y" << transformed_pos.y() - extruder_offset.y() - << "\n"; - gcode_out += oss.str(); - } - } + // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. + std::string WipeTowerIntegration::finalize(GCode& gcodegen) + { + std::string gcode; + if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON) + gcode += gcodegen.change_layer(m_final_purge.print_z); + gcode += append_tcr(gcodegen, m_final_purge, -1); + return gcode; } - return gcode_out; -} - - -std::string WipeTowerIntegration::prime(GCode &gcodegen) -{ - assert(m_layer_idx == 0); - std::string gcode; - - - // Disable linear advance for the wipe tower operations. - //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - - for (const WipeTower::ToolChangeResult& tcr : m_priming) { - if (!tcr.extrusions.empty()) - gcode += append_tcr(gcodegen, tcr, tcr.new_tool); - - - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - //gcode += tcr.gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - // unsigned int current_extruder_id = tcr.extrusions.back().tool; - // gcodegen.writer().toolchange(current_extruder_id); - // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); - - } - - // A phony move to the end position at the wipe tower. - /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); - // Prepare a future wipe. - gcodegen.m_wipe.path.points.clear(); - // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); - // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, - m_priming.back().end_pos.y)));*/ - - return gcode; -} - -std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) -{ - std::string gcode; - assert(m_layer_idx >= 0); - if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { - if (m_layer_idx < (int)m_tool_changes.size()) { - if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) - throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer."); - - - // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, - // resulting in a wipe tower with sparse layers. - double wipe_tower_z = -1; - bool ignore_sparse = false; - if (gcodegen.config().wipe_tower_no_sparse_layers.value) { - wipe_tower_z = m_last_wipe_tower_print_z; - ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); - if (m_tool_change_idx == 0 && ! ignore_sparse) - wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; - } - - if (! ignore_sparse) { - gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); - m_last_wipe_tower_print_z = wipe_tower_z; - } - } - m_brim_done = true; - } - return gcode; -} - -// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. -std::string WipeTowerIntegration::finalize(GCode &gcodegen) -{ - std::string gcode; - if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON) - gcode += gcodegen.change_layer(m_final_purge.print_z); - gcode += append_tcr(gcodegen, m_final_purge, -1); - return gcode; -} #if ENABLE_GCODE_VIEWER -const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; + const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; #endif // ENABLE_GCODE_VIEWER #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) -// Collect pairs of object_layer + support_layer sorted by print_z. -// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. -std::vector GCode::collect_layers_to_print(const PrintObject &object) -{ - std::vector layers_to_print; - layers_to_print.reserve(object.layers().size() + object.support_layers().size()); + // Collect pairs of object_layer + support_layer sorted by print_z. + // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. + std::vector GCode::collect_layers_to_print(const PrintObject& object) + { + std::vector layers_to_print; + layers_to_print.reserve(object.layers().size() + object.support_layers().size()); - // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. - // This is the same logic as in support generator. - //FIXME should we use the printing extruders instead? - double gap_over_supports = object.config().support_material_contact_distance; - // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. - assert(! object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers); - if (gap_over_supports != 0.) { - gap_over_supports = std::max(0., gap_over_supports); - // Not a soluble support, - double support_layer_height_min = 1000000.; - for (auto lh : object.print()->config().min_layer_height.values) - support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh)); - gap_over_supports += support_layer_height_min; - } + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. + // This is the same logic as in support generator. + //FIXME should we use the printing extruders instead? + double gap_over_supports = object.config().support_material_contact_distance; + // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. + assert(!object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers); + if (gap_over_supports != 0.) { + gap_over_supports = std::max(0., gap_over_supports); + // Not a soluble support, + double support_layer_height_min = 1000000.; + for (auto lh : object.print()->config().min_layer_height.values) + support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh)); + gap_over_supports += support_layer_height_min; + } - // Pair the object layers with the support layers by z. - size_t idx_object_layer = 0; - size_t idx_support_layer = 0; - const LayerToPrint* last_extrusion_layer = nullptr; - while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { - LayerToPrint layer_to_print; - layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr; - layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr; - if (layer_to_print.object_layer && layer_to_print.support_layer) { - if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) { - layer_to_print.support_layer = nullptr; - -- idx_support_layer; - } else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) { - layer_to_print.object_layer = nullptr; - -- idx_object_layer; + // Pair the object layers with the support layers by z. + size_t idx_object_layer = 0; + size_t idx_support_layer = 0; + const LayerToPrint* last_extrusion_layer = nullptr; + while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { + LayerToPrint layer_to_print; + layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr; + layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr; + if (layer_to_print.object_layer && layer_to_print.support_layer) { + if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) { + layer_to_print.support_layer = nullptr; + --idx_support_layer; + } + else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) { + layer_to_print.object_layer = nullptr; + --idx_object_layer; + } + } + + layers_to_print.emplace_back(layer_to_print); + + // In case there are extrusions on this layer, check there is a layer to lay it on. + if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. + || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { + double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer) + ? gap_over_supports + : 0.; + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + + layer_to_print.layer()->height + + support_contact_z; + // Negative support_contact_z is not taken into account, it can result in false positives in cases + // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) + + // Only check this layer in case it has some extrusions. + bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); + + if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) + throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + + std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " + "usually caused by negligibly small extrusions or by a faulty model. Try to repair " + "the model or change its orientation on the bed."))); + // Remember last layer with extrusions. + last_extrusion_layer = &layers_to_print.back(); } } - layers_to_print.emplace_back(layer_to_print); - - // In case there are extrusions on this layer, check there is a layer to lay it on. - if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. - || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { - double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer) - ? gap_over_supports - : 0.; - double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) - + layer_to_print.layer()->height - + support_contact_z; - // Negative support_contact_z is not taken into account, it can result in false positives in cases - // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) - - // Only check this layer in case it has some extrusions. - bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - - if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) - throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + - _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + - std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " - "usually caused by negligibly small extrusions or by a faulty model. Try to repair " - "the model or change its orientation on the bed."))); - // Remember last layer with extrusions. - last_extrusion_layer = &layers_to_print.back(); - } + return layers_to_print; } - return layers_to_print; -} + // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z + // will be printed for all objects at once. + // Return a list of items. + std::vector>> GCode::collect_layers_to_print(const Print& print) + { + struct OrderingItem { + coordf_t print_z; + size_t object_idx; + size_t layer_idx; + }; -// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z -// will be printed for all objects at once. -// Return a list of items. -std::vector>> GCode::collect_layers_to_print(const Print &print) -{ - struct OrderingItem { - coordf_t print_z; - size_t object_idx; - size_t layer_idx; - }; - - std::vector> per_object(print.objects().size(), std::vector()); - std::vector ordering; - for (size_t i = 0; i < print.objects().size(); ++i) { - per_object[i] = collect_layers_to_print(*print.objects()[i]); - OrderingItem ordering_item; - ordering_item.object_idx = i; - ordering.reserve(ordering.size() + per_object[i].size()); - const LayerToPrint &front = per_object[i].front(); - for (const LayerToPrint <p : per_object[i]) { - ordering_item.print_z = ltp.print_z(); - ordering_item.layer_idx = <p - &front; - ordering.emplace_back(ordering_item); + std::vector> per_object(print.objects().size(), std::vector()); + std::vector ordering; + for (size_t i = 0; i < print.objects().size(); ++i) { + per_object[i] = collect_layers_to_print(*print.objects()[i]); + OrderingItem ordering_item; + ordering_item.object_idx = i; + ordering.reserve(ordering.size() + per_object[i].size()); + const LayerToPrint& front = per_object[i].front(); + for (const LayerToPrint& ltp : per_object[i]) { + ordering_item.print_z = ltp.print_z(); + ordering_item.layer_idx = <p - &front; + ordering.emplace_back(ordering_item); + } } - } - std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; }); + std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); - std::vector>> layers_to_print; - // Merge numerically very close Z values. - for (size_t i = 0; i < ordering.size();) { - // Find the last layer with roughly the same print_z. - size_t j = i + 1; - coordf_t zmax = ordering[i].print_z + EPSILON; - for (; j < ordering.size() && ordering[j].print_z <= zmax; ++ j) ; - // Merge into layers_to_print. - std::pair> merged; - // Assign an average print_z to the set of layers with nearly equal print_z. - merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z); - merged.second.assign(print.objects().size(), LayerToPrint()); - for (; i < j; ++i) { - const OrderingItem &oi = ordering[i]; - assert(merged.second[oi.object_idx].layer() == nullptr); - merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); + std::vector>> layers_to_print; + // Merge numerically very close Z values. + for (size_t i = 0; i < ordering.size();) { + // Find the last layer with roughly the same print_z. + size_t j = i + 1; + coordf_t zmax = ordering[i].print_z + EPSILON; + for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); + // Merge into layers_to_print. + std::pair> merged; + // Assign an average print_z to the set of layers with nearly equal print_z. + merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); + merged.second.assign(print.objects().size(), LayerToPrint()); + for (; i < j; ++i) { + const OrderingItem& oi = ordering[i]; + assert(merged.second[oi.object_idx].layer() == nullptr); + merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); + } + layers_to_print.emplace_back(std::move(merged)); } - layers_to_print.emplace_back(std::move(merged)); - } - return layers_to_print; -} + return layers_to_print; + } #if ENABLE_GCODE_VIEWER +// free functions called by GCode::do_export() +namespace DoExport { + static void update_print_stats_estimated_times( + const GCodeProcessor& processor, + const bool silent_time_estimator_enabled, + PrintStatistics& print_statistics) + { + print_statistics.estimated_normal_print_time = processor.get_time_dhm(GCodeProcessor::ETimeMode::Normal); + print_statistics.estimated_normal_custom_gcode_print_times = processor.get_custom_gcode_times(GCodeProcessor::ETimeMode::Normal, true); + if (silent_time_estimator_enabled) { + print_statistics.estimated_silent_print_time = processor.get_time_dhm(GCodeProcessor::ETimeMode::Stealth); + print_statistics.estimated_silent_custom_gcode_print_times = processor.get_custom_gcode_times(GCodeProcessor::ETimeMode::Stealth, true); + } + else { + print_statistics.estimated_silent_print_time = "N/A"; + print_statistics.estimated_silent_custom_gcode_print_times.clear(); + } + } + +} // namespace DoExport + void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) #else void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) @@ -768,6 +790,8 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ m_processor.process_file(path_tmp); if (result != nullptr) *result = std::move(m_processor.extract_result()); + + DoExport::update_print_stats_estimated_times(m_processor, m_silent_time_estimator_enabled, print->m_print_statistics); #endif // ENABLE_GCODE_VIEWER GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); @@ -883,10 +907,11 @@ namespace DoExport { } #if ENABLE_GCODE_VIEWER - static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor) + static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool silent_time_estimator_enabled) { processor.reset(); processor.apply_config(config); + processor.enable_stealth_time_estimator(silent_time_estimator_enabled); } #else static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer) @@ -1036,7 +1061,7 @@ namespace DoExport { } // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. - static std::string update_print_stats_and_format_filament_stats( + static std::string update_print_stats_and_format_filament_stats( const GCodeTimeEstimator &normal_time_estimator, const GCodeTimeEstimator &silent_time_estimator, const bool silent_time_estimator_enabled, @@ -1044,20 +1069,19 @@ namespace DoExport { const WipeTowerData &wipe_tower_data, const std::vector &extruders, PrintStatistics &print_statistics) - { + { std::string filament_stats_string_out; print_statistics.clear(); - print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/(); - print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; #if ENABLE_GCODE_VIEWER + print_statistics.estimated_normal_print_time_str = normal_time_estimator.get_time_dhm/*s*/(); + print_statistics.estimated_silent_print_time_str = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; print_statistics.estimated_normal_custom_gcode_print_times_str = normal_time_estimator.get_custom_gcode_times_dhm(true); - print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times(true); - if (silent_time_estimator_enabled) { + if (silent_time_estimator_enabled) print_statistics.estimated_silent_custom_gcode_print_times_str = silent_time_estimator.get_custom_gcode_times_dhm(true); - print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times(true); - } #else + print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/(); + print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true); if (silent_time_estimator_enabled) print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true); @@ -1156,7 +1180,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // modifies the following: m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled); #if ENABLE_GCODE_VIEWER - DoExport::init_gcode_processor(print.config(), m_processor); + DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled); #else DoExport::init_gcode_analyzer(print.config(), m_analyzer); #endif // ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 14f29b56b6..7174e5e36d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,9 +1,11 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" #include "GCodeProcessor.hpp" #include #include +#include #if ENABLE_GCODE_VIEWER @@ -14,20 +16,65 @@ static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; -static bool is_valid_extrusion_role(int value) -{ - return ((int)Slic3r::erNone <= value) && (value <= (int)Slic3r::erMixed); -} +static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 namespace Slic3r { -const std::string GCodeProcessor::Extrusion_Role_Tag = "_PROCESSOR_EXTRUSION_ROLE:"; -const std::string GCodeProcessor::Width_Tag = "_PROCESSOR_WIDTH:"; -const std::string GCodeProcessor::Height_Tag = "_PROCESSOR_HEIGHT:"; -const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "_PROCESSOR_MM3_PER_MM:"; -const std::string GCodeProcessor::Color_Change_Tag = "_PROCESSOR_COLOR_CHANGE"; -const std::string GCodeProcessor::Pause_Print_Tag = "_PROCESSOR_PAUSE_PRINT"; -const std::string GCodeProcessor::Custom_Code_Tag = "_PROCESSOR_CUSTOM_CODE"; +const std::string GCodeProcessor::Extrusion_Role_Tag = "PrusaSlicer__EXTRUSION_ROLE:"; +const std::string GCodeProcessor::Width_Tag = "PrusaSlicer__WIDTH:"; +const std::string GCodeProcessor::Height_Tag = "PrusaSlicer__HEIGHT:"; +const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "PrusaSlicer__MM3_PER_MM:"; +const std::string GCodeProcessor::Color_Change_Tag = "PrusaSlicer__COLOR_CHANGE"; +const std::string GCodeProcessor::Pause_Print_Tag = "PrusaSlicer__PAUSE_PRINT"; +const std::string GCodeProcessor::Custom_Code_Tag = "PrusaSlicer__CUSTOM_CODE"; + +static bool is_valid_extrusion_role(int value) +{ + return (static_cast(erNone) <= value) && (value <= static_cast(erMixed)); +} + +static void set_option_value(ConfigOptionFloats& option, size_t id, float value) +{ + if (id < option.values.size()) + option.values[id] = static_cast(value); +}; + +static float get_option_value(const ConfigOptionFloats& option, size_t id) +{ + return option.values.empty() ? 0.0f : + ((id < option.values.size()) ? static_cast(option.values[id]) : static_cast(option.values.back())); +} + +static float estimated_acceleration_distance(float initial_rate, float target_rate, float acceleration) +{ + return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration); +} + +static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance) +{ + return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration); +} + +static float speed_from_distance(float initial_feedrate, float distance, float acceleration) +{ + // to avoid invalid negative numbers due to numerical errors + float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); + return ::sqrt(value); +} + +// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the +// acceleration within the allotted distance. +static float max_allowable_speed(float acceleration, float target_velocity, float distance) +{ + // to avoid invalid negative numbers due to numerical errors + float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); + return std::sqrt(value); +} + +static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration) +{ + return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f; +} void GCodeProcessor::CachedPosition::reset() { @@ -41,6 +88,208 @@ void GCodeProcessor::CpColor::reset() current = 0; } +float GCodeProcessor::Trapezoid::acceleration_time(float entry_feedrate, float acceleration) const +{ + return acceleration_time_from_distance(entry_feedrate, accelerate_until, acceleration); +} + +float GCodeProcessor::Trapezoid::cruise_time() const +{ + return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f; +} + +float GCodeProcessor::Trapezoid::deceleration_time(float distance, float acceleration) const +{ + return acceleration_time_from_distance(cruise_feedrate, (distance - decelerate_after), -acceleration); +} + +float GCodeProcessor::Trapezoid::cruise_distance() const +{ + return decelerate_after - accelerate_until; +} + +void GCodeProcessor::TimeBlock::calculate_trapezoid() +{ + trapezoid.cruise_feedrate = feedrate_profile.cruise; + + float accelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.entry, feedrate_profile.cruise, acceleration)); + float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.exit, -acceleration)); + float cruise_distance = distance - accelerate_distance - decelerate_distance; + + // Not enough space to reach the nominal feedrate. + // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration + // and start braking in order to reach the exit_feedrate exactly at the end of this block. + if (cruise_distance < 0.0f) { + accelerate_distance = std::clamp(intersection_distance(feedrate_profile.entry, feedrate_profile.exit, acceleration, distance), 0.0f, distance); + cruise_distance = 0.0f; + trapezoid.cruise_feedrate = speed_from_distance(feedrate_profile.entry, accelerate_distance, acceleration); + } + + trapezoid.accelerate_until = accelerate_distance; + trapezoid.decelerate_after = accelerate_distance + cruise_distance; +} + +float GCodeProcessor::TimeBlock::time() const +{ + return trapezoid.acceleration_time(feedrate_profile.entry, acceleration) + + trapezoid.cruise_time() + + trapezoid.deceleration_time(distance, acceleration); +} + +void GCodeProcessor::TimeMachine::State::reset() +{ + feedrate = 0.0f; + safe_feedrate = 0.0f; + axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; + abs_axis_feedrate = { 0.0f, 0.0f, 0.0f, 0.0f }; +} + +void GCodeProcessor::TimeMachine::CustomGCodeTime::reset() +{ + needed = false; + cache = 0.0f; + times = std::vector>(); +} + +void GCodeProcessor::TimeMachine::reset() +{ + enabled = false; + acceleration = 0.0f; + extrude_factor_override_percentage = 1.0f; + time = 0.0f; + curr.reset(); + prev.reset(); + gcode_time.reset(); + blocks = std::vector(); +} + +void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) +{ + if (!enabled) + return; + + time += additional_time; + gcode_time.cache += additional_time; + calculate_time(); +} + +static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr) +{ + // If the previous block is an acceleration block, but it is not long enough to complete the + // full speed change within the block, we need to adjust the entry speed accordingly. Entry + // speeds have already been reset, maximized, and reverse planned by reverse planner. + // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck. + if (!prev.flags.nominal_length) { + if (prev.feedrate_profile.entry < curr.feedrate_profile.entry) { + float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance)); + + // Check for junction speed change + if (curr.feedrate_profile.entry != entry_speed) { + curr.feedrate_profile.entry = entry_speed; + curr.flags.recalculate = true; + } + } + } +} + +void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, GCodeProcessor::TimeBlock& next) +{ + // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising. + // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and + // check for maximum allowable speed reductions to ensure maximum possible planned speed. + if (curr.feedrate_profile.entry != curr.max_entry_speed) { + // If nominal length true, max junction speed is guaranteed to be reached. Only compute + // for max allowable speed if block is decelerating and nominal length is false. + if (!curr.flags.nominal_length && curr.max_entry_speed > next.feedrate_profile.entry) + curr.feedrate_profile.entry = std::min(curr.max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance)); + else + curr.feedrate_profile.entry = curr.max_entry_speed; + + curr.flags.recalculate = true; + } +} + +static void recalculate_trapezoids(std::vector& blocks) +{ + GCodeProcessor::TimeBlock* curr = nullptr; + GCodeProcessor::TimeBlock* next = nullptr; + + for (size_t i = 0; i < blocks.size(); ++i) { + GCodeProcessor::TimeBlock& b = blocks[i]; + + curr = next; + next = &b; + + if (curr != nullptr) { + // Recalculate if current block entry or exit junction speed has changed. + if (curr->flags.recalculate || next->flags.recalculate) { + // NOTE: Entry and exit factors always > 0 by all previous logic operations. + GCodeProcessor::TimeBlock block = *curr; + block.feedrate_profile.exit = next->feedrate_profile.entry; + block.calculate_trapezoid(); + curr->trapezoid = block.trapezoid; + curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed + } + } + } + + // Last/newest block in buffer. Always recalculated. + if (next != nullptr) { + GCodeProcessor::TimeBlock block = *next; + block.feedrate_profile.exit = next->safe_feedrate; + block.calculate_trapezoid(); + next->trapezoid = block.trapezoid; + next->flags.recalculate = false; + } +} + +void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) +{ + if (!enabled || blocks.size() < 2) + return; + + assert(keep_last_n_blocks <= blocks.size()); + + // forward_pass + for (size_t i = 0; i + 1 < blocks.size(); ++i) { + planner_forward_pass_kernel(blocks[i], blocks[i + 1]); + } + + // reverse_pass + for (int i = static_cast(blocks.size()) - 1; i > 0; --i) + planner_reverse_pass_kernel(blocks[i - 1], blocks[i]); + + recalculate_trapezoids(blocks); + + size_t n_blocks_process = blocks.size() - keep_last_n_blocks; +// m_g1_times.reserve(m_g1_times.size() + n_blocks_process); + for (size_t i = 0; i < n_blocks_process; ++i) { + float block_time = blocks[i].time(); + time += block_time; + gcode_time.cache += block_time; + +// if (block.g1_line_id >= 0) +// m_g1_times.emplace_back(block.g1_line_id, time); + } + + if (keep_last_n_blocks) + blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process); + else + blocks.clear(); +} + +void GCodeProcessor::TimeProcessor::reset() +{ + extruder_unloaded = true; + machine_limits = MachineEnvelopeConfig(); + filament_load_times = std::vector(); + filament_unload_times = std::vector(); + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + machines[i].reset(); + } + machines[static_cast(ETimeMode::Normal)].enabled = true; +} + unsigned int GCodeProcessor::s_result_id = 0; void GCodeProcessor::apply_config(const PrintConfig& config) @@ -61,6 +310,28 @@ void GCodeProcessor::apply_config(const PrintConfig& config) for (size_t id = 0; id < extruders_count; ++id) { m_extruders_color[id] = static_cast(id); } + + m_time_processor.machine_limits = reinterpret_cast(config); + // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + m_time_processor.filament_load_times.clear(); + for (double d : config.filament_load_time.values) { + m_time_processor.filament_load_times.push_back(static_cast(d)); + } + m_time_processor.filament_unload_times.clear(); + for (double d : config.filament_unload_time.values) { + m_time_processor.filament_unload_times.push_back(static_cast(d)); + } + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); + m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + } +} + +void GCodeProcessor::enable_stealth_time_estimator(bool enabled) +{ + m_time_processor.machines[static_cast(ETimeMode::Stealth)].enabled = enabled; } void GCodeProcessor::reset() @@ -71,9 +342,9 @@ void GCodeProcessor::reset() m_extruder_offsets = std::vector(1, Vec3f::Zero()); m_flavor = gcfRepRap; - std::fill(m_start_position.begin(), m_start_position.end(), 0.0f); - std::fill(m_end_position.begin(), m_end_position.end(), 0.0f); - std::fill(m_origin.begin(), m_origin.end(), 0.0f); + m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_end_position = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_origin = { 0.0f, 0.0f, 0.0f, 0.0f }; m_cached_position.reset(); m_feedrate = 0.0f; @@ -87,6 +358,8 @@ void GCodeProcessor::reset() m_extruders_color = ExtrudersColor(); m_cp_color.reset(); + m_time_processor.reset(); + m_result.reset(); m_result.id = ++s_result_id; } @@ -101,11 +374,43 @@ void GCodeProcessor::process_file(const std::string& filename) m_result.moves.emplace_back(MoveVertex()); m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); + // process the remaining time blocks + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; + machine.calculate_time(); + if (gcode_time.needed && gcode_time.cache != 0.0f) + gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); + } + #if ENABLE_GCODE_VIEWER_STATISTICS m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } +std::string GCodeProcessor::get_time_dhm(ETimeMode mode) const +{ + std::string ret = "N/A"; + if (mode < ETimeMode::Count) + ret = short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)); + return ret; +} + +std::vector>> GCodeProcessor::get_custom_gcode_times(ETimeMode mode, bool include_remaining) const +{ + std::vector>> ret; + if (mode < ETimeMode::Count) { + const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; + float total_time = 0.0f; + for (const auto& [type, time] : machine.gcode_time.times) { + float remaining = include_remaining ? machine.time - total_time : 0.0f; + ret.push_back({ type, { time, remaining } }); + total_time += time; + } + } + return ret; +} + void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { /* std::cout << line.raw() << std::endl; */ @@ -126,6 +431,8 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) case 1: { process_G1(line); break; } // Move case 10: { process_G10(line); break; } // Retract case 11: { process_G11(line); break; } // Unretract + case 20: { process_G20(line); break; } // Set Units to Inches + case 21: { process_G21(line); break; } // Set Units to Millimeters case 22: { process_G22(line); break; } // Firmware controlled retract case 23: { process_G23(line); break; } // Firmware controlled unretract case 90: { process_G90(line); break; } // Set to Absolute Positioning @@ -139,6 +446,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { switch (::atoi(&cmd[1])) { + case 1: { process_M1(line); break; } // Sleep or Conditional stop case 82: { process_M82(line); break; } // Set extruder to absolute mode case 83: { process_M83(line); break; } // Set extruder to relative mode case 106: { process_M106(line); break; } // Set fan speed @@ -146,8 +454,15 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) case 108: { process_M108(line); break; } // Set tool (Sailfish) case 132: { process_M132(line); break; } // Recall stored home offsets case 135: { process_M135(line); break; } // Set tool (MakerWare) + case 201: { process_M201(line); break; } // Set max printing acceleration + case 203: { process_M203(line); break; } // Set maximum feedrate + case 204: { process_M204(line); break; } // Set default acceleration + case 205: { process_M205(line); break; } // Advanced settings + case 221: { process_M221(line); break; } // Set extrude factor override percentage case 401: { process_M401(line); break; } // Repetier: Store x, y and z position case 402: { process_M402(line); break; } // Repetier: Go to stored position + case 566: { process_M566(line); break; } // Set allowable instantaneous speed change + case 702: { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. default: { break; } } break; @@ -160,8 +475,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) default: { break; } } } - else - { + else { std::string comment = line.comment(); if (comment.length() > 1) // process tags embedded into comments @@ -179,8 +493,7 @@ void GCodeProcessor::process_tags(const std::string& comment) int role = std::stoi(comment.substr(pos + Extrusion_Role_Tag.length())); if (is_valid_extrusion_role(role)) m_extrusion_role = static_cast(role); - else - { + else { // todo: show some error ? } } @@ -247,11 +560,12 @@ void GCodeProcessor::process_tags(const std::string& comment) if (m_cp_color.counter == UCHAR_MAX) m_cp_color.counter = 0; - if (m_extruder_id == extruder_id) - { + if (m_extruder_id == extruder_id) { m_cp_color.current = m_extruders_color[extruder_id]; store_move_vertex(EMoveType::Color_change); } + + process_custom_gcode_time(CustomGCode::ColorChange); } catch (...) { @@ -265,6 +579,7 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Pause_Print_Tag); if (pos != comment.npos) { store_move_vertex(EMoveType::Pause_Print); + process_custom_gcode_time(CustomGCode::PausePrint); return; } @@ -306,12 +621,14 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) type = EMoveType::Travel; else type = EMoveType::Retract; - } else if (delta_pos[E] > 0.0f) { + } + else if (delta_pos[E] > 0.0f) { if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f) type = EMoveType::Unretract; else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f)) type = EMoveType::Extrude; - } else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) + } + else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) type = EMoveType::Travel; #if ENABLE_GCODE_VIEWER_AS_STATE @@ -351,7 +668,165 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (max_abs_delta == 0.0f) return; - // store g1 move + // time estimate section + auto move_length = [](const AxisCoords& delta_pos) { + float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); + return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]); + }; + + auto is_extruder_only_move = [](const AxisCoords& delta_pos) { + return (delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f) && (delta_pos[E] != 0.0f); + }; + + float distance = move_length(delta_pos); + assert(distance != 0.0f); + float inv_distance = 1.0f / distance; + + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + + TimeMachine::State& curr = machine.curr; + TimeMachine::State& prev = machine.prev; + std::vector& blocks = machine.blocks; + + curr.feedrate = (delta_pos[E] == 0.0f) ? + minimum_travel_feedrate(static_cast(i), m_feedrate) : + minimum_feedrate(static_cast(i), m_feedrate); + + TimeBlock block; + block.distance = distance; + + // calculates block cruise feedrate + float min_feedrate_factor = 1.0f; + for (unsigned char a = X; a <= E; ++a) { + curr.axis_feedrate[a] = curr.feedrate * delta_pos[a] * inv_distance; + if (a == E) + curr.axis_feedrate[a] *= machine.extrude_factor_override_percentage; + + curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); + if (curr.abs_axis_feedrate[a] != 0.0f) { + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + if (axis_max_feedrate != 0.0f) + min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); + } + } + + block.feedrate_profile.cruise = min_feedrate_factor * curr.feedrate; + + if (min_feedrate_factor < 1.0f) { + for (unsigned char a = X; a <= E; ++a) { + curr.axis_feedrate[a] *= min_feedrate_factor; + curr.abs_axis_feedrate[a] *= min_feedrate_factor; + } + } + + // calculates block acceleration + float acceleration = is_extruder_only_move(delta_pos) ? + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i)); + + for (unsigned char a = X; a <= E; ++a) { + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) + acceleration = axis_max_acceleration; + } + + block.acceleration = acceleration; + + // calculates block exit feedrate + curr.safe_feedrate = block.feedrate_profile.cruise; + + for (unsigned char a = X; a <= E; ++a) { + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + if (curr.abs_axis_feedrate[a] > axis_max_jerk) + curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); + } + + block.feedrate_profile.exit = curr.safe_feedrate; + + static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; + + // calculates block entry feedrate + float vmax_junction = curr.safe_feedrate; + if (!blocks.empty() && prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD) { + bool prev_speed_larger = prev.feedrate > block.feedrate_profile.cruise; + float smaller_speed_factor = prev_speed_larger ? (block.feedrate_profile.cruise / prev.feedrate) : (prev.feedrate / block.feedrate_profile.cruise); + // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. + vmax_junction = prev_speed_larger ? block.feedrate_profile.cruise : prev.feedrate; + + float v_factor = 1.0f; + bool limited = false; + + for (unsigned char a = X; a <= E; ++a) { + // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. + float v_exit = prev.axis_feedrate[a]; + float v_entry = curr.axis_feedrate[a]; + + if (prev_speed_larger) + v_exit *= smaller_speed_factor; + + if (limited) { + v_exit *= v_factor; + v_entry *= v_factor; + } + + // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. + float jerk = + (v_exit > v_entry) ? + (((v_entry > 0.0f) || (v_exit < 0.0f)) ? + // coasting + (v_exit - v_entry) : + // axis reversal + std::max(v_exit, -v_entry)) : + // v_exit <= v_entry + (((v_entry < 0.0f) || (v_exit > 0.0f)) ? + // coasting + (v_entry - v_exit) : + // axis reversal + std::max(-v_exit, v_entry)); + + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + if (jerk > axis_max_jerk) { + v_factor *= axis_max_jerk / jerk; + limited = true; + } + } + + if (limited) + vmax_junction *= v_factor; + + // Now the transition velocity is known, which maximizes the shared exit / entry velocity while + // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints. + float vmax_junction_threshold = vmax_junction * 0.99f; + + // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. + if ((prev.safe_feedrate > vmax_junction_threshold) && (curr.safe_feedrate > vmax_junction_threshold)) + vmax_junction = curr.safe_feedrate; + } + + float v_allowable = max_allowable_speed(-acceleration, curr.safe_feedrate, block.distance); + block.feedrate_profile.entry = std::min(vmax_junction, v_allowable); + + block.max_entry_speed = vmax_junction; + block.flags.nominal_length = (block.feedrate_profile.cruise <= v_allowable); + block.flags.recalculate = true; + block.safe_feedrate = curr.safe_feedrate; + + // calculates block trapezoid + block.calculate_trapezoid(); + + // updates previous + prev = curr; + + blocks.push_back(block); + + if (blocks.size() > TimeProcessor::Planner::refresh_threshold) + machine.calculate_time(TimeProcessor::Planner::queue_size); + } + + // store move store_move_vertex(move_type(delta_pos)); } @@ -367,6 +842,16 @@ void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line) store_move_vertex(EMoveType::Unretract); } +void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line) +{ + m_units = EUnits::Inches; +} + +void GCodeProcessor::process_G21(const GCodeReader::GCodeLine& line) +{ + m_units = EUnits::Millimeters; +} + void GCodeProcessor::process_G22(const GCodeReader::GCodeLine& line) { // stores retract move @@ -391,32 +876,34 @@ void GCodeProcessor::process_G91(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) { - float lengthsScaleFactor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - bool anyFound = false; + float lengths_scale_factor = (m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + bool any_found = false; if (line.has_x()) { - m_origin[X] = m_end_position[X] - line.x() * lengthsScaleFactor; - anyFound = true; + m_origin[X] = m_end_position[X] - line.x() * lengths_scale_factor; + any_found = true; } if (line.has_y()) { - m_origin[Y] = m_end_position[Y] - line.y() * lengthsScaleFactor; - anyFound = true; + m_origin[Y] = m_end_position[Y] - line.y() * lengths_scale_factor; + any_found = true; } if (line.has_z()) { - m_origin[Z] = m_end_position[Z] - line.z() * lengthsScaleFactor; - anyFound = true; + m_origin[Z] = m_end_position[Z] - line.z() * lengths_scale_factor; + any_found = true; } if (line.has_e()) { // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, // we set the value taken from the G92 line as the new current position for it - m_end_position[E] = line.e() * lengthsScaleFactor; - anyFound = true; + m_end_position[E] = line.e() * lengths_scale_factor; + any_found = true; } + else + simulate_st_synchronize(); - if (!anyFound && !line.has_unknown_axis()) { + if (!any_found && !line.has_unknown_axis()) { // The G92 may be called for axes that PrusaSlicer does not recognize, for example see GH issue #3510, // where G92 A0 B0 is called although the extruder axis is till E. for (unsigned char a = X; a <= E; ++a) { @@ -425,6 +912,11 @@ void GCodeProcessor::process_G92(const GCodeReader::GCodeLine& line) } } +void GCodeProcessor::process_M1(const GCodeReader::GCodeLine& line) +{ + simulate_st_synchronize(); +} + void GCodeProcessor::process_M82(const GCodeReader::GCodeLine& line) { m_e_local_positioning_type = EPositioningType::Absolute; @@ -501,6 +993,117 @@ void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line) process_T(cmd.substr(pos)); } +void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) +{ + // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration + float factor = (m_flavor != gcfRepRap && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; + + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + if (line.has_x()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); + + if (line.has_y() && i < m_time_processor.machine_limits.machine_max_acceleration_y.values.size()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor); + + if (line.has_z() && i < m_time_processor.machine_limits.machine_max_acceleration_z.values.size()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor); + + if (line.has_e() && i < m_time_processor.machine_limits.machine_max_acceleration_e.values.size()) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor); + } +} + +void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) +{ + // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate + if (m_flavor == gcfRepetier) + return; + + // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate + // http://smoothieware.org/supported-g-codes + float factor = (m_flavor == gcfMarlin || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; + + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + if (line.has_x()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); + + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, i, line.y() * factor); + + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, i, line.z() * factor); + + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, i, line.e() * factor); + } +} + +void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) +{ + float value; + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + if (line.has_value('S', value)) { + // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, + // and it is also generated by Slic3r to control acceleration per extrusion type + // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). + set_acceleration(static_cast(i), value); + if (line.has_value('T', value)) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); + } + else { + // New acceleration format, compatible with the upstream Marlin. + if (line.has_value('P', value)) + set_acceleration(static_cast(i), value); + if (line.has_value('R', value)) + set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); + if (line.has_value('T', value)) { + // Interpret the T value as the travel acceleration in the new Marlin format. + //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value. + // set_travel_acceleration(value); + } + } + } +} + +void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) +{ + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + if (line.has_x()) { + float max_jerk = line.x(); + set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, max_jerk); + set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, max_jerk); + } + + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y()); + + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z()); + + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e()); + + float value; + if (line.has_value('S', value)) + set_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, i, value); + + if (line.has_value('T', value)) + set_option_value(m_time_processor.machine_limits.machine_min_travel_rate, i, value); + } +} + +void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) +{ + float value_s; + float value_t; + if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { + value_s *= 0.01f; + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + m_time_processor.machines[i].extrude_factor_override_percentage = value_s; + } + } +} + void GCodeProcessor::process_M401(const GCodeReader::GCodeLine& line) { if (m_flavor != gcfRepetier) @@ -544,6 +1147,34 @@ void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) m_feedrate = p; } +void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) +{ + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + if (line.has_x()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); + + if (line.has_y()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, line.y() * MMMIN_TO_MMSEC); + + if (line.has_z()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_z, i, line.z() * MMMIN_TO_MMSEC); + + if (line.has_e()) + set_option_value(m_time_processor.machine_limits.machine_max_jerk_e, i, line.e() * MMMIN_TO_MMSEC); + } +} + +void GCodeProcessor::process_M702(const GCodeReader::GCodeLine& line) +{ + if (line.has('C')) { + // MK3 MMU2 specific M code: + // M702 C is expected to be sent by the custom end G-code when finalizing a print. + // The MK3 unit shall unload and park the active filament into the MMU2 unit. + m_time_processor.extruder_unloaded = true; + simulate_st_synchronize(get_filament_unload_time(m_extruder_id)); + } +} + void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) { process_T(line.cmd()); @@ -560,8 +1191,16 @@ void GCodeProcessor::process_T(const std::string& command) if (id >= extruders_count) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; else { + unsigned char old_extruder_id = m_extruder_id; m_extruder_id = id; m_cp_color.current = m_extruders_color[id]; + // Specific to the MK3 MMU2: + // The initial value of extruder_unloaded is set to true indicating + // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. + float extra_time = get_filament_unload_time(static_cast(old_extruder_id)); + m_time_processor.extruder_unloaded = false; + extra_time += get_filament_load_time(static_cast(m_extruder_id)); + simulate_st_synchronize(extra_time); } // store tool change move @@ -593,6 +1232,120 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_result.moves.emplace_back(vertex); } +float GCodeProcessor::minimum_feedrate(ETimeMode mode, float feedrate) const +{ + if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) + return feedrate; + + return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); +} + +float GCodeProcessor::minimum_travel_feedrate(ETimeMode mode, float feedrate) const +{ + if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) + return feedrate; + + return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); +} + +float GCodeProcessor::get_axis_max_feedrate(ETimeMode mode, Axis axis) const +{ + switch (axis) + { + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_y, static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_z, static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_feedrate_e, static_cast(mode)); } + default: { return 0.0f; } + } +} + +float GCodeProcessor::get_axis_max_acceleration(ETimeMode mode, Axis axis) const +{ + switch (axis) + { + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, static_cast(mode)); } + default: { return 0.0f; } + } +} + +float GCodeProcessor::get_axis_max_jerk(ETimeMode mode, Axis axis) const +{ + switch (axis) + { + case X: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_x, static_cast(mode)); } + case Y: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_y, static_cast(mode)); } + case Z: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_z, static_cast(mode)); } + case E: { return get_option_value(m_time_processor.machine_limits.machine_max_jerk_e, static_cast(mode)); } + default: { return 0.0f; } + } +} + +float GCodeProcessor::get_retract_acceleration(ETimeMode mode) const +{ + return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast(mode)); +} + +float GCodeProcessor::get_acceleration(ETimeMode mode) const +{ + size_t id = static_cast(mode); + return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; +} + +void GCodeProcessor::set_acceleration(ETimeMode mode, float value) +{ + size_t id = static_cast(mode); + if (id < m_time_processor.machines.size()) { + float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, id); + m_time_processor.machines[id].acceleration = (max_acceleration == 0.0f) ? value : std::min(value, max_acceleration); + } +} + +float GCodeProcessor::get_filament_load_time(size_t extruder_id) +{ + return (m_time_processor.filament_load_times.empty() || m_time_processor.extruder_unloaded) ? + 0.0f : + ((extruder_id < m_time_processor.filament_load_times.size()) ? + m_time_processor.filament_load_times[extruder_id] : m_time_processor.filament_load_times.front()); +} + +float GCodeProcessor::get_filament_unload_time(size_t extruder_id) +{ + return (m_time_processor.filament_unload_times.empty() || m_time_processor.extruder_unloaded) ? + 0.0f : + ((extruder_id < m_time_processor.filament_unload_times.size()) ? + m_time_processor.filament_unload_times[extruder_id] : m_time_processor.filament_unload_times.front()); +} + +void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) +{ + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + if (!machine.enabled) + continue; + + TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; + gcode_time.needed = true; + //FIXME this simulates st_synchronize! is it correct? + // The estimated time may be longer than the real print time. + machine.simulate_st_synchronize(); + if (gcode_time.cache != 0.0f) { + gcode_time.times.push_back({ code, gcode_time.cache }); + gcode_time.cache = 0.0f; + } + } +} + +void GCodeProcessor::simulate_st_synchronize(float additional_time) +{ + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + m_time_processor.machines[i].simulate_st_synchronize(additional_time); + } +} + } /* namespace Slic3r */ #endif // ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 3f596c9c2b..9878eea9dd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -5,6 +5,8 @@ #include "libslic3r/GCodeReader.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/CustomGCode.hpp" #include #include @@ -41,7 +43,7 @@ namespace Slic3r { struct CachedPosition { AxisCoords position; // mm - float feedrate; // mm/s + float feedrate; // mm/s void reset(); }; @@ -54,6 +56,118 @@ namespace Slic3r { void reset(); }; + public: + struct FeedrateProfile + { + float entry{ 0.0f }; // mm/s + float cruise{ 0.0f }; // mm/s + float exit{ 0.0f }; // mm/s + }; + + struct Trapezoid + { + float accelerate_until{ 0.0f }; // mm + float decelerate_after{ 0.0f }; // mm + float cruise_feedrate{ 0.0f }; // mm/sec + + float acceleration_time(float entry_feedrate, float acceleration) const; + float cruise_time() const; + float deceleration_time(float distance, float acceleration) const; + float cruise_distance() const; + }; + + struct TimeBlock + { + struct Flags + { + bool recalculate{ false }; + bool nominal_length{ false }; + }; + + float distance{ 0.0f }; // mm + float acceleration{ 0.0f }; // mm/s^2 + float max_entry_speed{ 0.0f }; // mm/s + float safe_feedrate{ 0.0f }; // mm/s + Flags flags; + FeedrateProfile feedrate_profile; + Trapezoid trapezoid; + + // Calculates this block's trapezoid + void calculate_trapezoid(); + + float time() const; + }; + + enum class ETimeMode : unsigned char + { + Normal, + Stealth, + Count + }; + + private: + struct TimeMachine + { + struct State + { + float feedrate; // mm/s + float safe_feedrate; // mm/s + AxisCoords axis_feedrate; // mm/s + AxisCoords abs_axis_feedrate; // mm/s + + void reset(); + }; + + struct CustomGCodeTime + { + bool needed; + float cache; + std::vector> times; + + void reset(); + }; + + bool enabled; + float acceleration; // mm/s^2 + float extrude_factor_override_percentage; + float time; // s + State curr; + State prev; + CustomGCodeTime gcode_time; + std::vector blocks; + + void reset(); + + // Simulates firmware st_synchronize() call + void simulate_st_synchronize(float additional_time = 0.0f); + void calculate_time(size_t keep_last_n_blocks = 0); + }; + + struct TimeProcessor + { + struct Planner + { + // Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks. + // Let's be conservative and plan for newer boards with more memory. + static constexpr size_t queue_size = 64; + // The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added. + // We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate. + static constexpr size_t refresh_threshold = queue_size * 4; + }; + + // extruder_id is currently used to correctly calculate filament load / unload times into the total print time. + // This is currently only really used by the MK3 MMU2: + // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. + bool extruder_unloaded; + MachineEnvelopeConfig machine_limits; + // Additional load / unload times for a filament exchange sequence. + std::vector filament_load_times; + std::vector filament_unload_times; + std::array(ETimeMode::Count)> machines; + + void reset(); + }; + public: enum class EMoveType : unsigned char { @@ -85,21 +199,6 @@ namespace Slic3r { float time{ 0.0f }; // s float volumetric_rate() const { return feedrate * mm3_per_mm; } - - std::string to_string() const - { - std::string str = std::to_string((int)type); - str += ", " + std::to_string((int)extrusion_role); - str += ", " + Slic3r::to_string((Vec3d)position.cast()); - str += ", " + std::to_string(extruder_id); - str += ", " + std::to_string(cp_color_id); - str += ", " + std::to_string(feedrate); - str += ", " + std::to_string(width); - str += ", " + std::to_string(height); - str += ", " + std::to_string(mm3_per_mm); - str += ", " + std::to_string(fan_speed); - return str; - } }; struct Result @@ -124,13 +223,13 @@ namespace Slic3r { GCodeFlavor m_flavor; AxisCoords m_start_position; // mm - AxisCoords m_end_position; // mm - AxisCoords m_origin; // mm + AxisCoords m_end_position; // mm + AxisCoords m_origin; // mm CachedPosition m_cached_position; - float m_feedrate; // mm/s - float m_width; // mm - float m_height; // mm + float m_feedrate; // mm/s + float m_width; // mm + float m_height; // mm float m_mm3_per_mm; float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; @@ -138,6 +237,8 @@ namespace Slic3r { ExtrudersColor m_extruders_color; CpColor m_cp_color; + TimeProcessor m_time_processor; + Result m_result; static unsigned int s_result_id; @@ -145,6 +246,7 @@ namespace Slic3r { GCodeProcessor() { reset(); } void apply_config(const PrintConfig& config); + void enable_stealth_time_estimator(bool enabled); void reset(); const Result& get_result() const { return m_result; } @@ -153,6 +255,9 @@ namespace Slic3r { // Process the gcode contained in the file with the given filename void process_file(const std::string& filename); + std::string get_time_dhm(ETimeMode mode) const; + std::vector>> get_custom_gcode_times(ETimeMode mode, bool include_remaining) const; + private: void process_gcode_line(const GCodeReader::GCodeLine& line); @@ -169,6 +274,12 @@ namespace Slic3r { // Unretract void process_G11(const GCodeReader::GCodeLine& line); + // Set Units to Inches + void process_G20(const GCodeReader::GCodeLine& line); + + // Set Units to Millimeters + void process_G21(const GCodeReader::GCodeLine& line); + // Firmware controlled Retract void process_G22(const GCodeReader::GCodeLine& line); @@ -184,6 +295,9 @@ namespace Slic3r { // Set Position void process_G92(const GCodeReader::GCodeLine& line); + // Sleep or Conditional stop + void process_M1(const GCodeReader::GCodeLine& line); + // Set extruder to absolute mode void process_M82(const GCodeReader::GCodeLine& line); @@ -205,17 +319,54 @@ namespace Slic3r { // Set tool (MakerWare) void process_M135(const GCodeReader::GCodeLine& line); + // Set max printing acceleration + void process_M201(const GCodeReader::GCodeLine& line); + + // Set maximum feedrate + void process_M203(const GCodeReader::GCodeLine& line); + + // Set default acceleration + void process_M204(const GCodeReader::GCodeLine& line); + + // Advanced settings + void process_M205(const GCodeReader::GCodeLine& line); + + // Set extrude factor override percentage + void process_M221(const GCodeReader::GCodeLine& line); + // Repetier: Store x, y and z position void process_M401(const GCodeReader::GCodeLine& line); // Repetier: Go to stored position void process_M402(const GCodeReader::GCodeLine& line); + // Set allowable instantaneous speed change + void process_M566(const GCodeReader::GCodeLine& line); + + // Unload the current filament into the MK3 MMU2 unit at the end of print. + void process_M702(const GCodeReader::GCodeLine& line); + // Processes T line (Select Tool) void process_T(const GCodeReader::GCodeLine& line); void process_T(const std::string& command); void store_move_vertex(EMoveType type); + + float minimum_feedrate(ETimeMode mode, float feedrate) const; + float minimum_travel_feedrate(ETimeMode mode, float feedrate) const; + float get_axis_max_feedrate(ETimeMode mode, Axis axis) const; + float get_axis_max_acceleration(ETimeMode mode, Axis axis) const; + float get_axis_max_jerk(ETimeMode mode, Axis axis) const; + float get_retract_acceleration(ETimeMode mode) const; + float get_acceleration(ETimeMode mode) const; + void set_acceleration(ETimeMode mode, float value); + float get_filament_load_time(size_t extruder_id); + float get_filament_unload_time(size_t extruder_id); + + void process_custom_gcode_time(CustomGCode::Type code); + + // Simulates firmware st_synchronize() call + void simulate_st_synchronize(float additional_time = 0.0f); }; } /* namespace Slic3r */ diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index d67db84819..bc3adefc08 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -678,21 +678,6 @@ namespace Slic3r { return _get_time_minutes(get_time()); } -#if ENABLE_GCODE_VIEWER - std::vector>> GCodeTimeEstimator::get_custom_gcode_times(bool include_remaining) const - { - std::vector>> ret; - - float total_time = 0.0f; - for (const auto& [type, time] : m_custom_gcode_times) { - float remaining = include_remaining ? m_time - total_time : 0.0f; - ret.push_back({ type, { time, remaining } }); - total_time += time; - } - - return ret; - } -#else std::vector> GCodeTimeEstimator::get_custom_gcode_times() const { return m_custom_gcode_times; @@ -736,7 +721,6 @@ namespace Slic3r { } return ret; } -#endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER std::vector>> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index cfa12b40be..ce6b2f4af0 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -358,9 +358,6 @@ namespace Slic3r { std::string get_time_minutes() const; // Returns the estimated time, in seconds, for each custom gcode -#if ENABLE_GCODE_VIEWER - std::vector>> get_custom_gcode_times(bool include_remaining) const; -#else std::vector> get_custom_gcode_times() const; // Returns the estimated time, in format DDd HHh MMm SSs, for each color @@ -370,7 +367,6 @@ namespace Slic3r { // Returns the estimated time, in minutes (integer), for each color // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" std::vector get_color_times_minutes(bool include_remaining) const; -#endif // ENABLE_GCODE_VIEWER // Returns the estimated time, in format DDd HHh MMm, for each custom_gcode // If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)" diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 5a1a9868d7..34fab6f307 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2190,18 +2190,24 @@ std::string Print::output_filename(const std::string &filename_base) const DynamicConfig PrintStatistics::config() const { DynamicConfig config; +#if ENABLE_GCODE_VIEWER + config.set_key_value("print_time", new ConfigOptionString(this->estimated_normal_print_time_str)); + config.set_key_value("normal_print_time", new ConfigOptionString(this->estimated_normal_print_time_str)); + config.set_key_value("silent_print_time", new ConfigOptionString(this->estimated_silent_print_time_str)); +#else std::string normal_print_time = short_time(this->estimated_normal_print_time); std::string silent_print_time = short_time(this->estimated_silent_print_time); config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); - config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament / 1000.)); - config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume)); - config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost)); +#endif // ENABLE_GCODE_VIEWER + config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.)); + config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume)); + config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost)); config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges)); - config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight)); - config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost)); - config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament)); + config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight)); + config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost)); + config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament)); return config; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index eb9a4fb4b7..b46ec42174 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -303,14 +303,18 @@ private: struct PrintStatistics { PrintStatistics() { clear(); } +#if ENABLE_GCODE_VIEWER std::string estimated_normal_print_time; std::string estimated_silent_print_time; -#if ENABLE_GCODE_VIEWER + std::string estimated_normal_print_time_str; + std::string estimated_silent_print_time_str; std::vector>> estimated_normal_custom_gcode_print_times; std::vector>> estimated_silent_custom_gcode_print_times; std::vector>> estimated_normal_custom_gcode_print_times_str; std::vector>> estimated_silent_custom_gcode_print_times_str; #else + std::string estimated_normal_print_time; + std::string estimated_silent_print_time; std::vector> estimated_normal_custom_gcode_print_times; std::vector> estimated_silent_custom_gcode_print_times; #endif // ENABLE_GCODE_VIEWER @@ -331,14 +335,12 @@ struct PrintStatistics std::string finalize_output_path(const std::string &path_in) const; void clear() { - estimated_normal_print_time.clear(); - estimated_silent_print_time.clear(); #if ENABLE_GCODE_VIEWER estimated_normal_custom_gcode_print_times_str.clear(); estimated_silent_custom_gcode_print_times_str.clear(); - estimated_normal_custom_gcode_print_times.clear(); - estimated_silent_custom_gcode_print_times.clear(); #else + estimated_normal_print_time.clear(); + estimated_silent_print_time.clear(); estimated_normal_custom_gcode_print_times.clear(); estimated_silent_custom_gcode_print_times.clear(); #endif //ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index aaabd62220..27b0db83f5 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1720,6 +1720,9 @@ void GCodeViewer::render_time_estimate() const if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") return; + if (ps.estimated_normal_print_time.empty() && ps.estimated_silent_print_time.empty()) + return; + ImGuiWrapper& imgui = *wxGetApp().imgui(); using Time = std::pair; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0981740ebf..9c22a6d60a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1322,7 +1322,11 @@ void Sidebar::update_sliced_info_sizer() wxString::Format("%.2f", ps.total_cost); p->sliced_info->SetTextAndShow(siCost, info_text, new_label); +#if ENABLE_GCODE_VIEWER + if (ps.estimated_normal_print_time_str == "N/A" && ps.estimated_silent_print_time_str == "N/A") +#else if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") +#endif // ENABLE_GCODE_VIEWER p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); else { new_label = _L("Estimated printing time") +":"; @@ -1360,21 +1364,25 @@ void Sidebar::update_sliced_info_sizer() } }; +#if ENABLE_GCODE_VIEWER + if (ps.estimated_normal_print_time_str != "N/A") { + new_label += format_wxstr("\n - %1%", _L("normal mode")); + info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time_str); + fill_labels(ps.estimated_normal_custom_gcode_print_times_str, new_label, info_text); + } + if (ps.estimated_silent_print_time_str != "N/A") { + new_label += format_wxstr("\n - %1%", _L("stealth mode")); + info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time_str); + fill_labels(ps.estimated_silent_custom_gcode_print_times_str, new_label, info_text); +#else if (ps.estimated_normal_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); -#if ENABLE_GCODE_VIEWER - fill_labels(ps.estimated_normal_custom_gcode_print_times_str, new_label, info_text); -#else fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); -#endif // ENABLE_GCODE_VIEWER } if (ps.estimated_silent_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("stealth mode")); info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time); -#if ENABLE_GCODE_VIEWER - fill_labels(ps.estimated_silent_custom_gcode_print_times_str, new_label, info_text); -#else fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text); #endif // ENABLE_GCODE_VIEWER } From f7164db68e760c9df9a937b5b3674c9f8bcd3be8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Jul 2020 08:27:23 +0200 Subject: [PATCH 164/826] GCodeViewer -> Added estimated printing times for move types --- src/libslic3r/GCode.cpp | 28 +---- src/libslic3r/GCode/GCodeProcessor.cpp | 51 ++++++++- src/libslic3r/GCode/GCodeProcessor.hpp | 36 +++--- src/libslic3r/Print.hpp | 19 +++- src/slic3r/GUI/GCodeViewer.cpp | 148 ++++++++++++++++++------- 5 files changed, 195 insertions(+), 87 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 47448954c8..9e365f9558 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -705,27 +705,6 @@ namespace Slic3r { } #if ENABLE_GCODE_VIEWER -// free functions called by GCode::do_export() -namespace DoExport { - static void update_print_stats_estimated_times( - const GCodeProcessor& processor, - const bool silent_time_estimator_enabled, - PrintStatistics& print_statistics) - { - print_statistics.estimated_normal_print_time = processor.get_time_dhm(GCodeProcessor::ETimeMode::Normal); - print_statistics.estimated_normal_custom_gcode_print_times = processor.get_custom_gcode_times(GCodeProcessor::ETimeMode::Normal, true); - if (silent_time_estimator_enabled) { - print_statistics.estimated_silent_print_time = processor.get_time_dhm(GCodeProcessor::ETimeMode::Stealth); - print_statistics.estimated_silent_custom_gcode_print_times = processor.get_custom_gcode_times(GCodeProcessor::ETimeMode::Stealth, true); - } - else { - print_statistics.estimated_silent_print_time = "N/A"; - print_statistics.estimated_silent_custom_gcode_print_times.clear(); - } - } - -} // namespace DoExport - void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) #else void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) @@ -787,11 +766,12 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ } #if ENABLE_GCODE_VIEWER + print->m_print_statistics.clear_time_estimates(); m_processor.process_file(path_tmp); - if (result != nullptr) + if (result != nullptr) { *result = std::move(m_processor.extract_result()); - - DoExport::update_print_stats_estimated_times(m_processor, m_silent_time_estimator_enabled, print->m_print_statistics); + m_processor.update_print_stats_estimated_times(print->m_print_statistics); + } #endif // ENABLE_GCODE_VIEWER GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7174e5e36d..7f8efc53fc 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,5 +1,6 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" +#include "libslic3r/Print.hpp" #include "GCodeProcessor.hpp" #include @@ -161,6 +162,7 @@ void GCodeProcessor::TimeMachine::reset() prev.reset(); gcode_time.reset(); blocks = std::vector(); + std::fill(moves_time.begin(), moves_time.end(), 0.0f); } void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) @@ -264,9 +266,11 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) size_t n_blocks_process = blocks.size() - keep_last_n_blocks; // m_g1_times.reserve(m_g1_times.size() + n_blocks_process); for (size_t i = 0; i < n_blocks_process; ++i) { - float block_time = blocks[i].time(); + const TimeBlock& block = blocks[i]; + float block_time = block.time(); time += block_time; gcode_time.cache += block_time; + moves_time[static_cast(block.move_type)] += block_time; // if (block.g1_line_id >= 0) // m_g1_times.emplace_back(block.g1_line_id, time); @@ -388,12 +392,31 @@ void GCodeProcessor::process_file(const std::string& filename) #endif // ENABLE_GCODE_VIEWER_STATISTICS } +void GCodeProcessor::update_print_stats_estimated_times(PrintStatistics& print_statistics) +{ + print_statistics.estimated_normal_print_time = get_time(GCodeProcessor::ETimeMode::Normal); + print_statistics.estimated_normal_custom_gcode_print_times = get_custom_gcode_times(GCodeProcessor::ETimeMode::Normal, true); + print_statistics.estimated_normal_moves_times = get_moves_time(GCodeProcessor::ETimeMode::Normal); + if (m_time_processor.machines[static_cast(GCodeProcessor::ETimeMode::Stealth)].enabled) { + print_statistics.estimated_silent_print_time = get_time(GCodeProcessor::ETimeMode::Stealth); + print_statistics.estimated_silent_custom_gcode_print_times = get_custom_gcode_times(GCodeProcessor::ETimeMode::Stealth, true); + print_statistics.estimated_silent_moves_times = get_moves_time(GCodeProcessor::ETimeMode::Stealth); + } + else { + print_statistics.estimated_silent_print_time = 0.0f; + print_statistics.estimated_silent_custom_gcode_print_times.clear(); + print_statistics.estimated_silent_moves_times.clear(); + } +} + +float GCodeProcessor::get_time(ETimeMode mode) const +{ + return (mode < ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; +} + std::string GCodeProcessor::get_time_dhm(ETimeMode mode) const { - std::string ret = "N/A"; - if (mode < ETimeMode::Count) - ret = short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)); - return ret; + return (mode < ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } std::vector>> GCodeProcessor::get_custom_gcode_times(ETimeMode mode, bool include_remaining) const @@ -411,6 +434,19 @@ std::vector>> GCodeProcesso return ret; } +std::vector> GCodeProcessor::get_moves_time(ETimeMode mode) const +{ + std::vector> ret; + if (mode < ETimeMode::Count) { + for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { + float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; + if (time > 0.0f) + ret.push_back({ static_cast(i), time }); + } + } + return ret; +} + void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { /* std::cout << line.raw() << std::endl; */ @@ -668,6 +704,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (max_abs_delta == 0.0f) return; + EMoveType type = move_type(delta_pos); + // time estimate section auto move_length = [](const AxisCoords& delta_pos) { float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); @@ -696,6 +734,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) minimum_feedrate(static_cast(i), m_feedrate); TimeBlock block; + block.move_type = type; block.distance = distance; // calculates block cruise feedrate @@ -827,7 +866,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } // store move - store_move_vertex(move_type(delta_pos)); + store_move_vertex(type); } void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 9878eea9dd..214ed3d911 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -13,6 +13,8 @@ namespace Slic3r { + struct PrintStatistics; + class GCodeProcessor { public: @@ -57,6 +59,20 @@ namespace Slic3r { }; public: + enum class EMoveType : unsigned char + { + Noop, + Retract, + Unretract, + Tool_change, + Color_change, + Pause_Print, + Custom_GCode, + Travel, + Extrude, + Count + }; + struct FeedrateProfile { float entry{ 0.0f }; // mm/s @@ -84,6 +100,7 @@ namespace Slic3r { bool nominal_length{ false }; }; + EMoveType move_type{ EMoveType::Noop }; float distance{ 0.0f }; // mm float acceleration{ 0.0f }; // mm/s^2 float max_entry_speed{ 0.0f }; // mm/s @@ -135,6 +152,7 @@ namespace Slic3r { State prev; CustomGCodeTime gcode_time; std::vector blocks; + std::array(EMoveType::Count)> moves_time; void reset(); @@ -169,20 +187,6 @@ namespace Slic3r { }; public: - enum class EMoveType : unsigned char - { - Noop, - Retract, - Unretract, - Tool_change, - Color_change, - Pause_Print, - Custom_GCode, - Travel, - Extrude, - Count - }; - struct MoveVertex { EMoveType type{ EMoveType::Noop }; @@ -255,9 +259,13 @@ namespace Slic3r { // Process the gcode contained in the file with the given filename void process_file(const std::string& filename); + void update_print_stats_estimated_times(PrintStatistics& print_statistics); + float get_time(ETimeMode mode) const; std::string get_time_dhm(ETimeMode mode) const; std::vector>> get_custom_gcode_times(ETimeMode mode, bool include_remaining) const; + std::vector> get_moves_time(ETimeMode mode) const; + private: void process_gcode_line(const GCodeReader::GCodeLine& line); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 4a0657061a..748411cd77 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -304,14 +304,16 @@ struct PrintStatistics { PrintStatistics() { clear(); } #if ENABLE_GCODE_VIEWER - std::string estimated_normal_print_time; - std::string estimated_silent_print_time; + float estimated_normal_print_time; + float estimated_silent_print_time; std::string estimated_normal_print_time_str; std::string estimated_silent_print_time_str; std::vector>> estimated_normal_custom_gcode_print_times; std::vector>> estimated_silent_custom_gcode_print_times; std::vector>> estimated_normal_custom_gcode_print_times_str; std::vector>> estimated_silent_custom_gcode_print_times_str; + std::vector> estimated_normal_moves_times; + std::vector> estimated_silent_moves_times; #else std::string estimated_normal_print_time; std::string estimated_silent_print_time; @@ -336,6 +338,8 @@ struct PrintStatistics void clear() { #if ENABLE_GCODE_VIEWER + estimated_normal_print_time_str.clear(); + estimated_silent_print_time_str.clear(); estimated_normal_custom_gcode_print_times_str.clear(); estimated_silent_custom_gcode_print_times_str.clear(); #else @@ -353,6 +357,17 @@ struct PrintStatistics total_wipe_tower_filament = 0.; filament_stats.clear(); } + +#if ENABLE_GCODE_VIEWER + void clear_time_estimates() { + estimated_normal_print_time = 0.0f; + estimated_silent_print_time = 0.0f; + estimated_normal_custom_gcode_print_times.clear(); + estimated_silent_custom_gcode_print_times.clear(); + estimated_normal_moves_times.clear(); + estimated_silent_moves_times.clear(); + } +#endif //ENABLE_GCODE_VIEWER }; typedef std::vector PrintObjectPtrs; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 27b0db83f5..db95c2b224 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1717,19 +1717,16 @@ void GCodeViewer::render_time_estimate() const return; const PrintStatistics& ps = wxGetApp().plater()->fff_print().print_statistics(); - if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") - return; - - if (ps.estimated_normal_print_time.empty() && ps.estimated_silent_print_time.empty()) + if (ps.estimated_normal_print_time <= 0.0f && ps.estimated_silent_print_time <= 0.0f) return; ImGuiWrapper& imgui = *wxGetApp().imgui(); - using Time = std::pair; - using TimesList = std::vector>; + using Times = std::pair; + using TimesList = std::vector>; - // helper structure containig the data needed to render the items - struct Item + // helper structure containig the data needed to render the time items + struct PartialTime { enum class EType : unsigned char { @@ -1741,12 +1738,13 @@ void GCodeViewer::render_time_estimate() const int extruder_id; Color color1; Color color2; - Time time; + Times times; }; - using Items = std::vector; + using PartialTimes = std::vector; - auto append_mode = [this, &imgui](const std::string& time_str, const Items& items) { - auto append_partial_times = [this, &imgui](const Items& items) { + auto append_mode = [this, &imgui](float total_time, const PartialTimes& items, + const std::vector>& moves_time) { + auto append_partial_times = [this, &imgui](const PartialTimes& items) { using Headers = std::vector; const Headers headers = { _u8L("Event"), @@ -1754,19 +1752,19 @@ void GCodeViewer::render_time_estimate() const _u8L("Duration") }; using Offsets = std::array; - auto calc_offsets = [this, &headers](const Items& items) { + auto calc_offsets = [this, &headers](const PartialTimes& items) { Offsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; - for (const Item& item : items) { + for (const PartialTime& item : items) { std::string label; switch (item.type) { - case Item::EType::Print: { label = _u8L("Print"); break; } - case Item::EType::Pause: { label = _u8L("Pause"); break; } - case Item::EType::ColorChange: { label = _u8L("Color change"); break; } + case PartialTime::EType::Print: { label = _u8L("Print"); break; } + case PartialTime::EType::Pause: { label = _u8L("Pause"); break; } + case PartialTime::EType::ColorChange: { label = _u8L("Color change"); break; } } ret[0] = std::max(ret[0], ImGui::CalcTextSize(label.c_str()).x); - ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(item.time.second)).c_str()).x); + ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(item.times.second)).c_str()).x); } const ImGuiStyle& style = ImGui::GetStyle(); @@ -1774,7 +1772,7 @@ void GCodeViewer::render_time_estimate() const ret[1] += ret[0] + style.ItemSpacing.x; return ret; }; - auto append_color = [this, &imgui](const Color& color1, const Color& color2, Offsets& offsets, const Time& time) { + auto append_color = [this, &imgui](const Color& color1, const Color& color2, Offsets& offsets, const Times& times) { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Color change")); ImGui::PopStyleColor(); @@ -1797,7 +1795,7 @@ void GCodeViewer::render_time_estimate() const ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); #endif // USE_ICON_HEXAGON ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(time.second - time.first))); + imgui.text(short_time(get_time_dhms(times.second - times.first))); }; if (items.empty()) @@ -1815,48 +1813,116 @@ void GCodeViewer::render_time_estimate() const ImGui::PopStyleColor(); ImGui::Separator(); - for (const Item& item : items) { + for (const PartialTime& item : items) { switch (item.type) { - case Item::EType::Print: + case PartialTime::EType::Print: { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Print")); ImGui::PopStyleColor(); ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.time.second))); + imgui.text(short_time(get_time_dhms(item.times.second))); ImGui::SameLine(offsets[1]); - imgui.text(short_time(get_time_dhms(item.time.first))); + imgui.text(short_time(get_time_dhms(item.times.first))); break; } - case Item::EType::Pause: + case PartialTime::EType::Pause: { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Pause")); ImGui::PopStyleColor(); ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.time.second - item.time.first))); + imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); break; } - case Item::EType::ColorChange: + case PartialTime::EType::ColorChange: { - append_color(item.color1, item.color2, offsets, item.time); + append_color(item.color1, item.color2, offsets, item.times); break; } } } }; + auto append_move_times = [this, &imgui](float total_time, const std::vector>& moves_time) { + using Headers = std::vector; + const Headers headers = { + _u8L("Type"), + _u8L("Time"), + _u8L("Percentage") + }; + auto move_type_label = [](GCodeProcessor::EMoveType type) { + switch (type) + { + case GCodeProcessor::EMoveType::Noop: { return _u8L("Noop"); } + case GCodeProcessor::EMoveType::Retract: { return _u8L("Retraction"); } + case GCodeProcessor::EMoveType::Unretract: { return _u8L("Unretraction"); } + case GCodeProcessor::EMoveType::Tool_change: { return _u8L("Tool change"); } + case GCodeProcessor::EMoveType::Color_change: { return _u8L("Color change"); } + case GCodeProcessor::EMoveType::Pause_Print: { return _u8L("Pause print"); } + case GCodeProcessor::EMoveType::Custom_GCode: { return _u8L("Custom GCode"); } + case GCodeProcessor::EMoveType::Travel: { return _u8L("Travel"); } + case GCodeProcessor::EMoveType::Extrude: { return _u8L("Extrusion"); } + default: { return _u8L("Unknown"); } + } + }; + using Offsets = std::array; + auto calc_offsets = [this, &headers, move_type_label](const std::vector>& moves_time) { + Offsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; + for (const auto& [type, time] : moves_time) { + ret[0] = std::max(ret[0], ImGui::CalcTextSize(move_type_label(type).c_str()).x); + ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time)).c_str()).x); + } + + const ImGuiStyle& style = ImGui::GetStyle(); + ret[0] += 2.0f * style.ItemSpacing.x; + ret[1] += ret[0] + style.ItemSpacing.x; + return ret; + }; + + + if (moves_time.empty()) + return; + + if (!ImGui::CollapsingHeader(_u8L("Moves Time").c_str())) + return; + + Offsets offsets = calc_offsets(moves_time); + + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(headers[0]); + ImGui::SameLine(offsets[0]); + imgui.text(headers[1]); + ImGui::SameLine(offsets[1]); + imgui.text(headers[2]); + ImGui::PopStyleColor(); + ImGui::Separator(); + + for (const auto& [type, time] : moves_time) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(move_type_label(type)); + ImGui::PopStyleColor(); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(time))); + ImGui::SameLine(offsets[1]); + char buf[64]; + ::sprintf(buf, "%.2f%%", 100.0f * time / total_time); + ImGui::TextUnformatted(buf); + } + }; + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Time") + ":"); ImGui::PopStyleColor(); ImGui::SameLine(); - imgui.text(time_str); + imgui.text(short_time(get_time_dhms(total_time))); append_partial_times(items); + append_move_times(total_time, moves_time); }; - auto generate_items = [this](const TimesList& times) { - std::vector items; + auto generate_partial_times = [this](const TimesList& times) { + PartialTimes items; std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; int extruders_count = wxGetApp().extruders_edited_cnt(); @@ -1872,8 +1938,8 @@ void GCodeViewer::render_time_estimate() const { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ Item::EType::Print, it->extruder, Color(), Color(), time_rec.second }); - items.push_back({ Item::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); custom_gcode_per_print_z.erase(it); } break; @@ -1882,14 +1948,14 @@ void GCodeViewer::render_time_estimate() const { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ Item::EType::Print, it->extruder, Color(), Color(), time_rec.second }); - items.push_back({ Item::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); + items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); last_color[it->extruder - 1] = decode_color(it->color); last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else - items.push_back({ Item::EType::Print, last_extruder_id, Color(), Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, last_extruder_id, Color(), Color(), time_rec.second }); break; } @@ -1905,22 +1971,22 @@ void GCodeViewer::render_time_estimate() const ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(-1.0f, 0.5f * static_cast(cnv_size.get_height()))); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); - imgui.begin(std::string("Time_estimate_2"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); + imgui.begin(std::string("Time_estimate"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); // title imgui.title(_u8L("Estimated printing time")); // mode tabs ImGui::BeginTabBar("mode_tabs"); - if (ps.estimated_normal_print_time != "N/A") { + if (ps.estimated_normal_print_time > 0.0f) { if (ImGui::BeginTabItem(_u8L("Normal").c_str())) { - append_mode(ps.estimated_normal_print_time, generate_items(ps.estimated_normal_custom_gcode_print_times)); + append_mode(ps.estimated_normal_print_time, generate_partial_times(ps.estimated_normal_custom_gcode_print_times), ps.estimated_normal_moves_times); ImGui::EndTabItem(); } } - if (ps.estimated_silent_print_time != "N/A") { + if (ps.estimated_silent_print_time > 0.0f) { if (ImGui::BeginTabItem(_u8L("Stealth").c_str())) { - append_mode(ps.estimated_silent_print_time, generate_items(ps.estimated_silent_custom_gcode_print_times)); + append_mode(ps.estimated_silent_print_time, generate_partial_times(ps.estimated_silent_custom_gcode_print_times), ps.estimated_silent_moves_times); ImGui::EndTabItem(); } } From b03ae392c5020828e8e96f44f635f77f045ca073 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Jul 2020 10:50:16 +0200 Subject: [PATCH 165/826] GCodeViewer -> Added estimated printing times for extrusion roles --- src/libslic3r/GCode/GCodeProcessor.cpp | 19 +++ src/libslic3r/GCode/GCodeProcessor.hpp | 3 + src/libslic3r/Print.hpp | 4 + src/slic3r/GUI/GCodeViewer.cpp | 172 +++++++++++++++++-------- 4 files changed, 144 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7f8efc53fc..67ed7c699b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -163,6 +163,7 @@ void GCodeProcessor::TimeMachine::reset() gcode_time.reset(); blocks = std::vector(); std::fill(moves_time.begin(), moves_time.end(), 0.0f); + std::fill(roles_time.begin(), roles_time.end(), 0.0f); } void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) @@ -271,6 +272,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) time += block_time; gcode_time.cache += block_time; moves_time[static_cast(block.move_type)] += block_time; + roles_time[static_cast(block.role)] += block_time; // if (block.g1_line_id >= 0) // m_g1_times.emplace_back(block.g1_line_id, time); @@ -397,15 +399,18 @@ void GCodeProcessor::update_print_stats_estimated_times(PrintStatistics& print_s print_statistics.estimated_normal_print_time = get_time(GCodeProcessor::ETimeMode::Normal); print_statistics.estimated_normal_custom_gcode_print_times = get_custom_gcode_times(GCodeProcessor::ETimeMode::Normal, true); print_statistics.estimated_normal_moves_times = get_moves_time(GCodeProcessor::ETimeMode::Normal); + print_statistics.estimated_normal_roles_times = get_roles_time(GCodeProcessor::ETimeMode::Normal); if (m_time_processor.machines[static_cast(GCodeProcessor::ETimeMode::Stealth)].enabled) { print_statistics.estimated_silent_print_time = get_time(GCodeProcessor::ETimeMode::Stealth); print_statistics.estimated_silent_custom_gcode_print_times = get_custom_gcode_times(GCodeProcessor::ETimeMode::Stealth, true); print_statistics.estimated_silent_moves_times = get_moves_time(GCodeProcessor::ETimeMode::Stealth); + print_statistics.estimated_silent_roles_times = get_roles_time(GCodeProcessor::ETimeMode::Stealth); } else { print_statistics.estimated_silent_print_time = 0.0f; print_statistics.estimated_silent_custom_gcode_print_times.clear(); print_statistics.estimated_silent_moves_times.clear(); + print_statistics.estimated_silent_roles_times.clear(); } } @@ -447,6 +452,19 @@ std::vector> GCodeProcessor::get_mov return ret; } +std::vector> GCodeProcessor::get_roles_time(ETimeMode mode) const +{ + std::vector> ret; + if (mode < ETimeMode::Count) { + for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { + float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; + if (time > 0.0f) + ret.push_back({ static_cast(i), time }); + } + } + return ret; +} + void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { /* std::cout << line.raw() << std::endl; */ @@ -735,6 +753,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) TimeBlock block; block.move_type = type; + block.role = m_extrusion_role; block.distance = distance; // calculates block cruise feedrate diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 214ed3d911..d19d363f95 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -101,6 +101,7 @@ namespace Slic3r { }; EMoveType move_type{ EMoveType::Noop }; + ExtrusionRole role{ erNone }; float distance{ 0.0f }; // mm float acceleration{ 0.0f }; // mm/s^2 float max_entry_speed{ 0.0f }; // mm/s @@ -153,6 +154,7 @@ namespace Slic3r { CustomGCodeTime gcode_time; std::vector blocks; std::array(EMoveType::Count)> moves_time; + std::array(ExtrusionRole::erCount)> roles_time; void reset(); @@ -265,6 +267,7 @@ namespace Slic3r { std::vector>> get_custom_gcode_times(ETimeMode mode, bool include_remaining) const; std::vector> get_moves_time(ETimeMode mode) const; + std::vector> get_roles_time(ETimeMode mode) const; private: void process_gcode_line(const GCodeReader::GCodeLine& line); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 748411cd77..e2b7ab5467 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -314,6 +314,8 @@ struct PrintStatistics std::vector>> estimated_silent_custom_gcode_print_times_str; std::vector> estimated_normal_moves_times; std::vector> estimated_silent_moves_times; + std::vector> estimated_normal_roles_times; + std::vector> estimated_silent_roles_times; #else std::string estimated_normal_print_time; std::string estimated_silent_print_time; @@ -366,6 +368,8 @@ struct PrintStatistics estimated_silent_custom_gcode_print_times.clear(); estimated_normal_moves_times.clear(); estimated_silent_moves_times.clear(); + estimated_normal_roles_times.clear(); + estimated_silent_roles_times.clear(); } #endif //ENABLE_GCODE_VIEWER }; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index db95c2b224..df29af67b8 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1724,6 +1724,8 @@ void GCodeViewer::render_time_estimate() const using Times = std::pair; using TimesList = std::vector>; + using Headers = std::vector; + using ColumnOffsets = std::array; // helper structure containig the data needed to render the time items struct PartialTime @@ -1743,17 +1745,14 @@ void GCodeViewer::render_time_estimate() const using PartialTimes = std::vector; auto append_mode = [this, &imgui](float total_time, const PartialTimes& items, - const std::vector>& moves_time) { - auto append_partial_times = [this, &imgui](const PartialTimes& items) { - using Headers = std::vector; - const Headers headers = { - _u8L("Event"), - _u8L("Remaining"), - _u8L("Duration") - }; - using Offsets = std::array; + const Headers& partial_times_headers, + const std::vector>& moves_time, + const Headers& moves_headers, + const std::vector>& roles_time, + const Headers& roles_headers) { + auto append_partial_times = [this, &imgui](const PartialTimes& items, const Headers& headers) { auto calc_offsets = [this, &headers](const PartialTimes& items) { - Offsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; + ColumnOffsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; for (const PartialTime& item : items) { std::string label; switch (item.type) @@ -1772,7 +1771,7 @@ void GCodeViewer::render_time_estimate() const ret[1] += ret[0] + style.ItemSpacing.x; return ret; }; - auto append_color = [this, &imgui](const Color& color1, const Color& color2, Offsets& offsets, const Times& times) { + auto append_color = [this, &imgui](const Color& color1, const Color& color2, ColumnOffsets& offsets, const Times& times) { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Color change")); ImGui::PopStyleColor(); @@ -1801,7 +1800,7 @@ void GCodeViewer::render_time_estimate() const if (items.empty()) return; - Offsets offsets = calc_offsets(items); + ColumnOffsets offsets = calc_offsets(items); ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); @@ -1845,42 +1844,25 @@ void GCodeViewer::render_time_estimate() const } }; - auto append_move_times = [this, &imgui](float total_time, const std::vector>& moves_time) { - using Headers = std::vector; - const Headers headers = { - _u8L("Type"), - _u8L("Time"), - _u8L("Percentage") - }; - auto move_type_label = [](GCodeProcessor::EMoveType type) { - switch (type) - { - case GCodeProcessor::EMoveType::Noop: { return _u8L("Noop"); } - case GCodeProcessor::EMoveType::Retract: { return _u8L("Retraction"); } - case GCodeProcessor::EMoveType::Unretract: { return _u8L("Unretraction"); } - case GCodeProcessor::EMoveType::Tool_change: { return _u8L("Tool change"); } - case GCodeProcessor::EMoveType::Color_change: { return _u8L("Color change"); } - case GCodeProcessor::EMoveType::Pause_Print: { return _u8L("Pause print"); } - case GCodeProcessor::EMoveType::Custom_GCode: { return _u8L("Custom GCode"); } - case GCodeProcessor::EMoveType::Travel: { return _u8L("Travel"); } - case GCodeProcessor::EMoveType::Extrude: { return _u8L("Extrusion"); } - default: { return _u8L("Unknown"); } - } - }; - using Offsets = std::array; - auto calc_offsets = [this, &headers, move_type_label](const std::vector>& moves_time) { - Offsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; - for (const auto& [type, time] : moves_time) { - ret[0] = std::max(ret[0], ImGui::CalcTextSize(move_type_label(type).c_str()).x); - ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time)).c_str()).x); - } - - const ImGuiStyle& style = ImGui::GetStyle(); - ret[0] += 2.0f * style.ItemSpacing.x; - ret[1] += ret[0] + style.ItemSpacing.x; - return ret; - }; + auto move_type_label = [](GCodeProcessor::EMoveType type) { + switch (type) + { + case GCodeProcessor::EMoveType::Noop: { return _u8L("Noop"); } + case GCodeProcessor::EMoveType::Retract: { return _u8L("Retraction"); } + case GCodeProcessor::EMoveType::Unretract: { return _u8L("Unretraction"); } + case GCodeProcessor::EMoveType::Tool_change: { return _u8L("Tool change"); } + case GCodeProcessor::EMoveType::Color_change: { return _u8L("Color change"); } + case GCodeProcessor::EMoveType::Pause_Print: { return _u8L("Pause print"); } + case GCodeProcessor::EMoveType::Custom_GCode: { return _u8L("Custom GCode"); } + case GCodeProcessor::EMoveType::Travel: { return _u8L("Travel"); } + case GCodeProcessor::EMoveType::Extrude: { return _u8L("Extrusion"); } + default: { return _u8L("Unknown"); } + } + }; + auto append_move_times = [this, &imgui, move_type_label](float total_time, + const std::vector>& moves_time, + const Headers& headers, const ColumnOffsets& offsets) { if (moves_time.empty()) return; @@ -1888,8 +1870,6 @@ void GCodeViewer::render_time_estimate() const if (!ImGui::CollapsingHeader(_u8L("Moves Time").c_str())) return; - Offsets offsets = calc_offsets(moves_time); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(headers[0]); ImGui::SameLine(offsets[0]); @@ -1899,7 +1879,10 @@ void GCodeViewer::render_time_estimate() const ImGui::PopStyleColor(); ImGui::Separator(); - for (const auto& [type, time] : moves_time) { + std::vector> sorted_moves_time(moves_time); + std::sort(sorted_moves_time.begin(), sorted_moves_time.end(), [](const auto& p1, const auto& p2) { return p2.second < p1.second; }); + + for (const auto& [type, time] : sorted_moves_time) { ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(move_type_label(type)); ImGui::PopStyleColor(); @@ -1912,13 +1895,72 @@ void GCodeViewer::render_time_estimate() const } }; + auto append_role_times = [this, &imgui](float total_time, + const std::vector>& roles_time, + const Headers& headers, const ColumnOffsets& offsets) { + + if (roles_time.empty()) + return; + + if (!ImGui::CollapsingHeader(_u8L("Features Time").c_str())) + return; + + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(headers[0]); + ImGui::SameLine(offsets[0]); + imgui.text(headers[1]); + ImGui::SameLine(offsets[1]); + imgui.text(headers[2]); + ImGui::PopStyleColor(); + ImGui::Separator(); + + std::vector> sorted_roles_time(roles_time); + std::sort(sorted_roles_time.begin(), sorted_roles_time.end(), [](const auto& p1, const auto& p2) { return p2.second < p1.second; }); + + for (const auto& [role, time] : sorted_roles_time) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(_u8L(ExtrusionEntity::role_to_string(role))); + ImGui::PopStyleColor(); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(time))); + ImGui::SameLine(offsets[1]); + char buf[64]; + ::sprintf(buf, "%.2f%%", 100.0f * time / total_time); + ImGui::TextUnformatted(buf); + } + }; + + auto calc_common_offsets = [move_type_label]( + const std::vector>& moves_time, const Headers& moves_headers, + const std::vector>& roles_time, const Headers& roles_headers) { + ColumnOffsets ret = { std::max(ImGui::CalcTextSize(moves_headers[0].c_str()).x, ImGui::CalcTextSize(roles_headers[0].c_str()).x), + std::max(ImGui::CalcTextSize(moves_headers[1].c_str()).x, ImGui::CalcTextSize(roles_headers[1].c_str()).x) }; + + for (const auto& [type, time] : moves_time) { + ret[0] = std::max(ret[0], ImGui::CalcTextSize(move_type_label(type).c_str()).x); + ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time)).c_str()).x); + } + + for (const auto& [role, time] : roles_time) { + ret[0] = std::max(ret[0], ImGui::CalcTextSize(_u8L(ExtrusionEntity::role_to_string(role)).c_str()).x); + ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time)).c_str()).x); + } + + const ImGuiStyle& style = ImGui::GetStyle(); + ret[0] += 2.0f * style.ItemSpacing.x; + ret[1] += ret[0] + style.ItemSpacing.x; + return ret; + }; + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); imgui.text(_u8L("Time") + ":"); ImGui::PopStyleColor(); ImGui::SameLine(); imgui.text(short_time(get_time_dhms(total_time))); - append_partial_times(items); - append_move_times(total_time, moves_time); + append_partial_times(items, partial_times_headers); + ColumnOffsets common_offsets = calc_common_offsets(moves_time, moves_headers, roles_time, roles_headers); + append_move_times(total_time, moves_time, moves_headers, common_offsets); + append_role_times(total_time, roles_time, roles_headers, common_offsets); }; auto generate_partial_times = [this](const TimesList& times) { @@ -1966,6 +2008,22 @@ void GCodeViewer::render_time_estimate() const return items; }; + const Headers partial_times_headers = { + _u8L("Event"), + _u8L("Remaining"), + _u8L("Duration") + }; + const Headers moves_headers = { + _u8L("Type"), + _u8L("Time"), + _u8L("Percentage") + }; + const Headers roles_headers = { + _u8L("Feature"), + _u8L("Time"), + _u8L("Percentage") + }; + Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(-1.0f, 0.5f * static_cast(cnv_size.get_height()))); @@ -1980,13 +2038,19 @@ void GCodeViewer::render_time_estimate() const ImGui::BeginTabBar("mode_tabs"); if (ps.estimated_normal_print_time > 0.0f) { if (ImGui::BeginTabItem(_u8L("Normal").c_str())) { - append_mode(ps.estimated_normal_print_time, generate_partial_times(ps.estimated_normal_custom_gcode_print_times), ps.estimated_normal_moves_times); + append_mode(ps.estimated_normal_print_time, + generate_partial_times(ps.estimated_normal_custom_gcode_print_times), partial_times_headers, + ps.estimated_normal_moves_times, moves_headers, + ps.estimated_normal_roles_times, roles_headers); ImGui::EndTabItem(); } } if (ps.estimated_silent_print_time > 0.0f) { if (ImGui::BeginTabItem(_u8L("Stealth").c_str())) { - append_mode(ps.estimated_silent_print_time, generate_partial_times(ps.estimated_silent_custom_gcode_print_times), ps.estimated_silent_moves_times); + append_mode(ps.estimated_silent_print_time, + generate_partial_times(ps.estimated_silent_custom_gcode_print_times), partial_times_headers, + ps.estimated_silent_moves_times, moves_headers, + ps.estimated_silent_roles_times, roles_headers); ImGui::EndTabItem(); } } From 0df1d117804264f7b9f30f9f7e5a6367314f1cd5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Jul 2020 11:08:34 +0200 Subject: [PATCH 166/826] GCodeViewer -> Attempt to fix rendering of toolpaths on Mac --- src/slic3r/GUI/GCodeViewer.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index df29af67b8..27bd8b6727 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1221,7 +1221,13 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); for (const RenderPath& path : buffer.render_paths) { +#ifdef __APPLE__ + for (size_t i = 0; i < path.sizes.size(); ++i) { + glsafe(::glDrawElements(GL_POINTS, (GLsizei)path.sizes[i], GL_UNSIGNED_INT, (const void*)path.offsets[i])); + } +#else glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#endif // __APPLE__ #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1235,7 +1241,13 @@ void GCodeViewer::render_toolpaths() const for (const RenderPath& path : buffer.render_paths) { shader.set_uniform("uniform_color", path.color); +#ifdef __APPLE__ + for (size_t i = 0; i < path.sizes.size(); ++i) { + glsafe(::glDrawElements(GL_LINES, (GLsizei)path.sizes[i], GL_UNSIGNED_INT, (const void*)path.offsets[i])); + } +#else glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#endif // __APPLE__ #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS From a35f72442e3d44156f88efa58d16222a6a0b7ee0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Jul 2020 12:10:55 +0200 Subject: [PATCH 167/826] GCodeViewer -> 2nd attempt to fix rendering of toolpaths on Mac --- src/slic3r/GUI/GCodeViewer.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 27bd8b6727..0ab43a98c7 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1221,13 +1221,7 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); for (const RenderPath& path : buffer.render_paths) { -#ifdef __APPLE__ - for (size_t i = 0; i < path.sizes.size(); ++i) { - glsafe(::glDrawElements(GL_POINTS, (GLsizei)path.sizes[i], GL_UNSIGNED_INT, (const void*)path.offsets[i])); - } -#else glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#endif // __APPLE__ #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1241,13 +1235,7 @@ void GCodeViewer::render_toolpaths() const for (const RenderPath& path : buffer.render_paths) { shader.set_uniform("uniform_color", path.color); -#ifdef __APPLE__ - for (size_t i = 0; i < path.sizes.size(); ++i) { - glsafe(::glDrawElements(GL_LINES, (GLsizei)path.sizes[i], GL_UNSIGNED_INT, (const void*)path.offsets[i])); - } -#else glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#endif // __APPLE__ #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_multi_line_strip_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1255,7 +1243,11 @@ void GCodeViewer::render_toolpaths() const }; auto line_width = [zoom]() { - return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); +#ifdef WIN32 + return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); +#else + return 3.0f; +#endif // WIN32 }; glsafe(::glCullFace(GL_BACK)); From 5eac36a31066df16fc581206951fbfe675e177ab Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 17 Jul 2020 14:32:38 +0200 Subject: [PATCH 168/826] Update for PresetComboBoxes All "Printer-PresetName" pairs are like a separated items now + some code refactoring for PresetComboBoxes::update() --- src/libslic3r/Preset.cpp | 60 ++-- src/libslic3r/Preset.hpp | 41 ++- src/libslic3r/PresetBundle.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 21 +- src/slic3r/GUI/PresetComboBoxes.cpp | 514 ++++++++++++++++++---------- src/slic3r/GUI/PresetComboBoxes.hpp | 49 ++- src/slic3r/GUI/Tab.cpp | 67 +++- src/slic3r/GUI/Tab.hpp | 5 +- 8 files changed, 489 insertions(+), 270 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index c9f5bd0af0..ad7615f6d1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1370,11 +1370,6 @@ const std::vector& PhysicalPrinter::printer_options() return s_opts; } -const std::string& PhysicalPrinter::get_preset_name() const -{ - return config.opt_string("preset_name"); -} - const std::set& PhysicalPrinter::get_preset_names() const { return preset_names; @@ -1415,8 +1410,6 @@ void PhysicalPrinter::update_from_preset(const Preset& preset) // add preset names to the options list auto ret = preset_names.emplace(preset.name); update_preset_names_in_config(); - - update_full_name(); } void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) @@ -1431,8 +1424,6 @@ void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config) preset_names.emplace(val); } preset_names = values; - - update_full_name(); } void PhysicalPrinter::reset_presets() @@ -1454,26 +1445,26 @@ PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) void PhysicalPrinter::set_name(const std::string& name) { this->name = name; - update_full_name(); } -void PhysicalPrinter::update_full_name() +std::string PhysicalPrinter::get_full_name(std::string preset_name) const { - full_name = name + separator() + get_preset_name(); + return name + separator() + preset_name; } std::string PhysicalPrinter::get_short_name(std::string full_name) { int pos = full_name.find(separator()); - boost::erase_tail(full_name, full_name.length() - pos); + if (pos > 0) + boost::erase_tail(full_name, full_name.length() - pos); return full_name; } -std::string PhysicalPrinter::get_preset_name(std::string full_name) +std::string PhysicalPrinter::get_preset_name(std::string name) { - int pos = full_name.find(separator()); - boost::erase_head(full_name, pos + 2); - return full_name; + int pos = name.find(separator()); + boost::erase_head(name, pos + 3); + return Preset::remove_suffix_modified(name); } @@ -1563,7 +1554,6 @@ void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_print it->config = std::move(edited_printer.config); it->name = edited_printer.name; it->preset_names = edited_printer.preset_names; - it->full_name = edited_printer.full_name; } else { // Creating a new printer. @@ -1615,17 +1605,43 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } -PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(std::string name) +std::string PhysicalPrinterCollection::get_selected_full_printer_name() const { - name = PhysicalPrinter::get_short_name(name); - auto it = this->find_printer_internal(name); + return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); +} + +PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& full_name) +{ + std::string printer_name = PhysicalPrinter::get_short_name(full_name); + auto it = this->find_printer_internal(printer_name); assert(it != m_printers.end()); // update idx_selected - m_idx_selected = it - m_printers.begin(); + m_idx_selected = it - m_printers.begin(); + // update name of the currently selected preset + m_selected_preset = it->get_preset_name(full_name); + if (m_selected_preset.empty()) + m_selected_preset = *it->preset_names.begin(); return *it; } +bool PhysicalPrinterCollection::has_selection() const +{ + return m_idx_selected != size_t(-1); +} + +void PhysicalPrinterCollection::unselect_printer() +{ + m_idx_selected = size_t(-1); + m_selected_preset.clear(); +} + +bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string& preset_name) const +{ + return m_idx_selected == it - m_printers.begin() && + m_selected_preset == preset_name; +} + namespace PresetUtils { const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index d583bed20d..d30ea70590 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -539,12 +539,9 @@ public: PhysicalPrinter(const std::string& name) : name(name){} PhysicalPrinter(const std::string& name, const Preset& preset); void set_name(const std::string &name); - void update_full_name(); // Name of the Physical Printer, usually derived form the file name. std::string name; - // Full name of the Physical Printer, included related preset name - std::string full_name; // File name of the Physical Printer. std::string file; // Configuration data, loaded from a file, or set from the defaults. @@ -558,8 +555,6 @@ public: bool loaded = false; static const std::vector& printer_options(); - const std::string& get_preset_name() const; - const std::set& get_preset_names() const; bool has_empty_config() const; @@ -588,6 +583,9 @@ public: // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; } + // get full printer name included a name of the preset + std::string get_full_name(std::string preset_name) const; + // get printer name from the full name uncluded preset name static std::string get_short_name(std::string full_name); @@ -641,22 +639,29 @@ public: bool delete_selected_printer(); // Return the selected preset, without the user modifications applied. - PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } - const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; } - size_t get_selected_idx() const { return m_idx_selected; } + PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } + const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; } + + size_t get_selected_idx() const { return m_idx_selected; } // Returns the name of the selected preset, or an empty string if no preset is selected. - std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } - // Returns the full name of the selected preset, or an empty string if no preset is selected. - std::string get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().full_name; } + std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; } + // Returns the config of the selected printer, or nullptr if no printer is selected. + DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } + // Returns the config of the selected printer, or nullptr if no printer is selected. + PrinterTechnology get_selected_printer_technology() { return (m_idx_selected == size_t(-1)) ? PrinterTechnology::ptAny : this->get_selected_printer().printer_technology(); } + + // Each physical printer can have a several related preset, + // so, use the next functions to get an exact names of selections in the list: + // Returns the full name of the selected printer, or an empty string if no preset is selected. + std::string get_selected_full_printer_name() const; // Returns the printer model of the selected preset, or an empty string if no preset is selected. - std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_preset_name(); } - // Returns the config of the selected preset, or nullptr if no preset is selected. - DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); } + std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : m_selected_preset; } // select printer with name and return reference on it - PhysicalPrinter& select_printer_by_name(std::string name); - bool has_selection() const { return m_idx_selected != size_t(-1); } - void unselect_printer() { m_idx_selected = size_t(-1); } + PhysicalPrinter& select_printer_by_name(const std::string& full_name); + bool has_selection() const; + void unselect_printer() ; + bool is_selected(ConstIterator it, const std::string &preset_name) const; // Return a printer by an index. If the printer is active, a temporary copy is returned. PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; } @@ -698,6 +703,8 @@ private: // Selected printer. size_t m_idx_selected = size_t(-1); + // The name of the preset which is currently select for this printer + std::string m_selected_preset; // Path to the directory to store the config files into. std::string m_dir_path; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7c4fa1cb25..7969966e5e 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -458,7 +458,7 @@ void PresetBundle::export_selections(AppConfig &config) config.set("presets", "sla_material", sla_materials.get_selected_preset_name()); config.set("presets", "printer", printers.get_selected_preset_name()); - config.set("extras", "physical_printer", physical_printers.get_selected_printer_name()); + config.set("extras", "physical_printer", physical_printers.get_selected_full_printer_name()); } DynamicPrintConfig PresetBundle::full_config() const diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 048e17926e..5673eece71 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3208,24 +3208,21 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); } - - if (preset_type == Preset::TYPE_PRINTER) { - if(combo->is_selected_physical_printer()) { - // Select related printer preset on the Printer Settings Tab - const std::string printer_name = combo->GetString(selection).ToUTF8().data(); - PhysicalPrinter& printer = wxGetApp().preset_bundle->physical_printers.select_printer_by_name(printer_name); - preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, printer.get_preset_name()); - } - else - wxGetApp().preset_bundle->physical_printers.unselect_printer(); - } + bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { // Only update the plater UI for the 2nd and other filaments. combo->update(); } - else { + else if (select_preset) { + if (preset_type == Preset::TYPE_PRINTER) { + PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; + if(combo->is_selected_physical_printer()) + preset_name = physical_printers.get_selected_printer_preset_name(); + else + physical_printers.unselect_printer(); + } wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); wxGetApp().get_tab(preset_type)->select_preset(preset_name); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 1473bf6dab..e08cf101d6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -63,8 +63,7 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const m_type(preset_type), m_last_selected(wxNOT_FOUND), m_em_unit(em_unit(this)), - m_preset_bundle(wxGetApp().preset_bundle), - m_bitmap_cache(new BitmapCache) + m_preset_bundle(wxGetApp().preset_bundle) { SetFont(wxGetApp().normal_font()); #ifdef _WIN32 @@ -105,17 +104,31 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const m_bitmapCompatible = ScalableBitmap(this, "flag_green"); m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); - m_bitmapLock = ScalableBitmap(this, "lock_closed"); - m_bitmapLockDisabled = ScalableBitmap(this, "lock_closed", 16, true); // parameters for an icon's drawing fill_width_height(); + + Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + // see https://github.com/prusa3d/PrusaSlicer/issues/3889 + // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") + // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. + // So, use GetSelection() from event parameter + auto selected_item = evt.GetSelection(); + + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + if (marker >= LABEL_ITEM_DISABLED && marker < LABEL_ITEM_MAX) + this->SetSelection(this->m_last_selected); + else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty())) { + m_last_selected = selected_item; + on_selection_changed(selected_item); + evt.StopPropagation(); + } + evt.Skip(); + }); } PresetComboBox::~PresetComboBox() { - delete m_bitmap_cache; - m_bitmap_cache = nullptr; } void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) @@ -123,12 +136,96 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) this->SetClientData(item, (void*)label_item_type); } +void PresetComboBox::update(const std::string& select_preset_name) +{ + Freeze(); + Clear(); + size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + + const std::deque& presets = m_collection->get_presets(); + + std::map> nonsys_presets; + wxString selected = ""; + if (!presets.front().is_visible) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) + { + const Preset& preset = presets[i]; + if (!preset.is_visible || !preset.is_compatible) + continue; + + // marker used for disable incompatible printer models for the selected physical printer + bool is_enabled = true; + + std::string bitmap_key = "tab"; + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + + if (m_type == Preset::TYPE_PRINTER) { + bitmap_key += "_printer"; + if (preset.printer_technology() == ptSLA) + bitmap_key += "_sla"; + if (!is_enabled) + bitmap_key += "_disabled"; + } + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + + wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + assert(bmp); + + if (preset.is_default || preset.is_system) { + int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + if (preset.name == select_preset_name ||//i == idx_selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + else + { + std::pair pair(bmp, is_enabled); + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); + if (preset.name == select_preset_name) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + } + if (i + 1 == m_collection->num_default_presets()) + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + } + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + if (it->first == selected || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } + + /* But, if selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + if (selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + + SetSelection(selected_preset_item); + SetToolTip(GetString(selected_preset_item)); + Thaw(); + + m_last_selected = selected_preset_item; +} + void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); - m_bitmapLock.msw_rescale(); - m_bitmapLockDisabled.msw_rescale(); m_bitmapIncompatible.msw_rescale(); m_bitmapCompatible.msw_rescale(); @@ -142,9 +239,9 @@ void PresetComboBox::msw_rescale() void PresetComboBox::fill_width_height() { // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so - // set a bitmap's height to m_bitmapLock->GetHeight() and norm_icon_width to m_bitmapLock->GetWidth() - icon_height = m_bitmapLock.GetBmpHeight(); - norm_icon_width = m_bitmapLock.GetBmpWidth(); + // set a bitmap's height to m_bitmapCompatible->GetHeight() and norm_icon_width to m_bitmapCompatible->GetWidth() + icon_height = m_bitmapCompatible.GetBmpHeight(); + norm_icon_width = m_bitmapCompatible.GetBmpWidth(); /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. * So set sizes for solid_colored icons used for filament preset @@ -165,6 +262,110 @@ wxString PresetComboBox::separator(const std::string& label) return wxString::FromUTF8(separator_head()) + _(label) + wxString::FromUTF8(separator_tail()); } +wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/, + std::string filament_rgb/* = ""*/, std::string extruder_rgb/* = ""*/) +{ + bitmap_key += ",h" + std::to_string(icon_height); + + wxBitmap* bmp = bitmap_cache().find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); + + if (m_type == Preset::TYPE_FILAMENT) + { + unsigned char rgb[3]; + // Paint the color bars. + bitmap_cache().parse_color(filament_rgb, rgb); + bmps.emplace_back(bitmap_cache().mksolid(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb)); + if (!is_single_bar) { + bitmap_cache().parse_color(extruder_rgb, rgb); + bmps.emplace_back(bitmap_cache().mksolid(thin_icon_width, icon_height, rgb)); + } + // Paint a lock at the system presets. + bmps.emplace_back(bitmap_cache().mkclear(space_icon_width, icon_height)); + } + else + { + // Paint the color bars. + bmps.emplace_back(bitmap_cache().mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(create_scaled_bitmap(main_icon_name)); + // Paint a lock at the system presets. + bmps.emplace_back(bitmap_cache().mkclear(wide_space_icon_width, icon_height)); + } + bmps.emplace_back(is_system ? create_scaled_bitmap("lock_closed") : bitmap_cache().mkclear(norm_icon_width, icon_height)); + bmp = bitmap_cache().insert(bitmap_key, bmps); + } + + return bmp; +} + +wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, + bool is_enabled/* = true*/, bool is_compatible/* = true*/, bool is_system/* = false*/) +{ + bitmap_key += ",h" + std::to_string(icon_height); + + wxBitmap* bmp = bitmap_cache().find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? create_scaled_bitmap(main_icon_name, this, 16, !is_enabled) : + is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); + // Paint a lock at the system presets. + bmps.emplace_back(is_system ? create_scaled_bitmap(next_icon_name, this, 16, !is_enabled) : bitmap_cache().mkclear(norm_icon_width, icon_height)); + bmp = bitmap_cache().insert(bitmap_key, bmps); + } + + return bmp; +} + +bool PresetComboBox::is_selected_physical_printer() +{ + auto selected_item = this->GetSelection(); + auto marker = reinterpret_cast(this->GetClientData(selected_item)); + return marker == LABEL_ITEM_PHYSICAL_PRINTER; +} + +bool PresetComboBox::selection_is_changed_according_to_physical_printers() +{ + if (m_type != Preset::TYPE_PRINTER || !is_selected_physical_printer()) + return false; + + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + + std::string selected_string = this->GetString(this->GetSelection()).ToUTF8().data(); + + std::string old_printer_full_name, old_printer_preset; + if (physical_printers.has_selection()) { + old_printer_full_name = physical_printers.get_selected_full_printer_name(); + old_printer_preset = physical_printers.get_selected_printer_preset_name(); + } + else + old_printer_preset = m_collection->get_edited_preset().name; + // Select related printer preset on the Printer Settings Tab + physical_printers.select_printer_by_name(selected_string); + std::string preset_name = physical_printers.get_selected_printer_preset_name(); + + // if new preset wasn't selected, there is no need to call update preset selection + if (old_printer_preset == preset_name) { + // we need just to update according Plater<->Tab PresetComboBox + if (dynamic_cast(this)!=nullptr) + wxGetApp().get_tab(m_type)->update_preset_choice(); + else if (dynamic_cast(this)!=nullptr) + wxGetApp().sidebar().update_presets(m_type); + return true; + } + + Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); + if (tab) + tab->select_preset(preset_name, false, old_printer_full_name); + return true; +} + #ifdef __APPLE__ bool PresetComboBox::OnAddBitmap(const wxBitmap& bitmap) { @@ -245,25 +446,6 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) { this->SetSelection(this->m_last_selected); evt.StopPropagation(); - /* - if (marker == LABEL_ITEM_PHYSICAL_PRINTERS) - { - PhysicalPrinterDialog dlg(wxEmptyString); - if (dlg.ShowModal() == wxID_OK) - this->update(); - return; - } - if (marker >= LABEL_ITEM_WIZARD_PRINTERS) { - ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME; - switch (marker) { - case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; - case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; - case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; - default: break; - } - wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); }); - } - */ if (marker == LABEL_ITEM_WIZARD_PRINTERS) show_add_menu(); else @@ -372,13 +554,6 @@ PlaterPresetComboBox::~PlaterPresetComboBox() edit_btn->Destroy(); } -bool PlaterPresetComboBox::is_selected_physical_printer() -{ - auto selected_item = this->GetSelection(); - auto marker = reinterpret_cast(this->GetClientData(selected_item)); - return marker == LABEL_ITEM_PHYSICAL_PRINTER; -} - bool PlaterPresetComboBox::switch_to_tab() { Tab* tab = wxGetApp().get_tab(m_type); @@ -465,7 +640,7 @@ void PlaterPresetComboBox::update() { unsigned char rgb[3]; extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx); - if (!m_bitmap_cache->parse_color(extruder_color, rgb)) + if (!bitmap_cache().parse_color(extruder_color, rgb)) // Extruder color is not defined. extruder_color.clear(); selected_filament_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); @@ -499,64 +674,33 @@ void PlaterPresetComboBox::update() continue; std::string bitmap_key, filament_rgb, extruder_rgb; + std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + bool single_bar = false; - if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) - bitmap_key = "sla_printer"; - else if (m_type == Preset::TYPE_FILAMENT) + if (m_type == Preset::TYPE_FILAMENT) { // Assign an extruder color to the selected item if the extruder color is defined. filament_rgb = preset.config.opt_string("filament_colour", 0); extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; single_bar = filament_rgb == extruder_rgb; - bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; + bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb; } - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name); // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left // to the filament color image. if (wide_icons) bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); - - if (m_type == Preset::TYPE_FILAMENT) - { - unsigned char rgb[3]; - // Paint the color bars. - m_bitmap_cache->parse_color(filament_rgb, rgb); - bmps.emplace_back(m_bitmap_cache->mksolid(single_bar ? wide_icon_width : norm_icon_width, icon_height, rgb)); - if (!single_bar) { - m_bitmap_cache->parse_color(extruder_rgb, rgb); - bmps.emplace_back(m_bitmap_cache->mksolid(thin_icon_width, icon_height, rgb)); - } - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(space_icon_width, icon_height)); - } - else - { - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(main_bmp); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - } - bmps.emplace_back((preset.is_system || preset.is_default) ? m_bitmapLock.bmp() : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, + preset.is_compatible, preset.is_system || preset.is_default, + single_bar, filament_rgb, extruder_rgb); + assert(bmp); const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { - Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), - !bmp ? main_bmp : *bmp); + Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (is_selected || // just in case: mark selected_preset_item as a first added element selected_preset_item == INT_MAX) { @@ -595,59 +739,26 @@ void PlaterPresetComboBox::update() const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { - std::string bitmap_key = it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "-h" + std::to_string(icon_height); - - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; + for (const std::string preset_name : it->get_preset_names()) { + Preset* preset = m_collection->find_preset(preset_name); + if (!preset) + continue; + std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap(it->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name)); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width+norm_icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + bitmap_key += ",cmpt"; + bitmap_key += ",nsyst"; - set_label_marker(Append(wxString::FromUTF8((it->full_name).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - if (ph_printers.has_selection() && it->name == ph_printers.get_selected_printer_name() || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, main_icon_name); + assert(bmp); + + set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + if (ph_printers.is_selected(it, preset_name) || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } } } - -/* - // add LABEL_ITEM_PHYSICAL_PRINTERS - std::string bitmap_key; - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("printer")); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - set_label_marker(Append(separator(L("Add physical printer")), *bmp), LABEL_ITEM_PHYSICAL_PRINTERS); -*/ } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { @@ -657,23 +768,10 @@ void PlaterPresetComboBox::update() if (wide_icons) bitmap_key += "wide,"; bitmap_key += "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars.update_plater_ui - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name)); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(create_scaled_bitmap("edit_uni")); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, "edit_uni"); + assert(bmp); + if (m_type == Preset::TYPE_SLA_MATERIAL) set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); else @@ -731,8 +829,10 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) update(); }); } - else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty()) ) + else if (on_selection_changed && (m_last_selected != selected_item || m_collection->current_is_dirty()) ) { + m_last_selected = selected_item; on_selection_changed(selected_item); + } evt.StopPropagation(); }); @@ -754,7 +854,12 @@ void TabPresetComboBox::update() if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); int idx_selected = m_collection->get_selected_idx(); - for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { + + std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); + PrinterTechnology proper_pt = (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) ? + m_collection->find_preset(sel_preset_name)->printer_technology() : ptAny; + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) + { const Preset& preset = presets[i]; if (!preset.is_visible || (!show_incompatible && !preset.is_compatible && i != idx_selected)) continue; @@ -762,14 +867,12 @@ void TabPresetComboBox::update() // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = true; // check this value just for printer presets, when physical printer is selected - if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { - is_enabled = m_enable_all ? true : - preset.name == m_preset_bundle->physical_printers.get_selected_printer_preset_name()/* || - preset.config.opt_string("printer_model") == m_preset_bundle->physical_printers.get_selected_printer_model()*/; - } + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) + is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt;//m_preset_bundle->physical_printers.get_selected_printer_technology(); std::string bitmap_key = "tab"; - wxBitmap main_bmp = create_scaled_bitmap(m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name, this, 16, !is_enabled); + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) @@ -779,20 +882,12 @@ void TabPresetComboBox::update() } bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? main_bmp : preset.is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); - // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? (is_enabled ? m_bitmapLock.bmp() : m_bitmapLockDisabled.bmp()) : m_bitmap_cache->mkclear(norm_icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + assert(bmp); if (preset.is_default || preset.is_system) { - int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), !bmp ? main_bmp : *bmp); + int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); if (i == idx_selected || @@ -824,18 +919,38 @@ void TabPresetComboBox::update() selected_preset_item = GetCount() - 1; } } - if (m_type == Preset::TYPE_PRINTER) { - std::string bitmap_key = "edit_preset_list"; - bitmap_key += "-h" + std::to_string(icon_height); - wxBitmap* bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(create_scaled_bitmap(m_main_bitmap_name, this)); - bmps.emplace_back(create_scaled_bitmap("edit_uni", this)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); + if (m_type == Preset::TYPE_PRINTER) + { + // add Physical printers, if any exists + if (!m_preset_bundle->physical_printers.empty()) { + set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; + + for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { + for (const std::string preset_name : it->get_preset_names()) { + Preset* preset = m_collection->find_preset(preset_name); + if (!preset) + continue; + std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; + std::string bitmap_key = main_icon_name + ",nsyst"; + + wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "", true, true, false); + assert(bmp); + + set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); + if (ph_printers.is_selected(it, preset_name) || + // just in case: mark selected_preset_item as a first added element + selected_preset_item == INT_MAX) + selected_preset_item = GetCount() - 1; + } + } } + + // add "Add/Remove printers" item + wxBitmap* bmp = get_bmp("edit_preset_list", m_main_bitmap_name, "edit_uni", true, true, true); + assert(bmp); + set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } @@ -869,11 +984,26 @@ void TabPresetComboBox::update_dirty() // 2) Update the labels. wxWindowUpdateLocker noUpdates(this); for (unsigned int ui_id = 0; ui_id < GetCount(); ++ui_id) { + auto marker = reinterpret_cast(this->GetClientData(ui_id)); + if (marker >= LABEL_ITEM_MARKER) + continue; + std::string old_label = GetString(ui_id).utf8_str().data(); std::string preset_name = Preset::remove_suffix_modified(old_label); + std::string ph_printer_name; + + if (marker == LABEL_ITEM_PHYSICAL_PRINTER) { + ph_printer_name = PhysicalPrinter::get_short_name(preset_name); + preset_name = PhysicalPrinter::get_preset_name(preset_name); + } + const Preset* preset = m_collection->find_preset(preset_name, false); if (preset) { std::string new_label = preset->is_dirty ? preset->name + Preset::suffix_modified() : preset->name; + + if (marker == LABEL_ITEM_PHYSICAL_PRINTER) + new_label = ph_printer_name + PhysicalPrinter::separator() + new_label; + if (old_label != new_label) SetString(ui_id, wxString::FromUTF8(new_label.c_str())); } @@ -885,6 +1015,12 @@ void TabPresetComboBox::update_dirty() #endif /* __APPLE __ */ } +void TabPresetComboBox::update_physical_printers( const std::string& preset_name) +{ + if (m_type == Preset::TYPE_PRINTER && update_ph_printers) + update_ph_printers(preset_name); +} + //------------------------------------------ // PresetForPrinter @@ -900,9 +1036,7 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); - m_presets_list = new TabPresetComboBox(parent, Preset::TYPE_PRINTER); - if (preset_name.empty()) - m_presets_list->set_enable_all(); + m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER); m_presets_list->set_selection_changed_function([this](int selection) { std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); @@ -922,8 +1056,7 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str update_full_printer_name(); }); - m_presets_list->update(); - m_presets_list->SetStringSelection(from_u8(preset_name)); + m_presets_list->update(preset_name); m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); @@ -997,11 +1130,10 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - if (printer_name.IsEmpty()) { - printer_name = _L("My Printer Device"); - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, "")); - } + m_default_name = _L("My Printer Device"); + + if (printer_name.IsEmpty()) + printer_name = m_default_name; else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); @@ -1022,6 +1154,8 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) if (!printer) { const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); printer = new PhysicalPrinter(into_u8(printer_name), preset); + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, preset.name)); } else { @@ -1275,7 +1409,11 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) { wxString printer_name = m_printer_name->GetValue(); if (printer_name.IsEmpty()) { - show_error(this, _L("The supplied name is empty. It can't be saved.")); + warning_catcher(this, _L("The supplied name is empty. It can't be saved.")); + return; + } + if (printer_name == m_default_name) { + warning_catcher(this, _L("You should to change a name of your printer device. It can't be saved.")); return; } @@ -1311,9 +1449,10 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) repeatable_presets += "\n"; wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" - "It(they) will be added just once for the printer \"%2%\".")) % repeatable_presets % printer_name).str()); - wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); + "Should I add it(they) just once for the printer \"%2%\" and close the Editing Dialog?")) % repeatable_presets % printer_name).str()); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dialog.ShowModal() == wxID_NO) + return; } std::string renamed_from; @@ -1344,8 +1483,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) void PhysicalPrinterDialog::AddPreset(wxEvent& event) { - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, "")); + m_presets.emplace_back(new PresetForPrinter(this)); // enable DELETE button for the first preset, if was disabled m_presets.front()->EnableDeleteBtn(); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index c818b6b911..1f5ef026da 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -10,6 +10,7 @@ #include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" +#include "BitmapCache.hpp" class wxString; class wxTextCtrl; @@ -21,8 +22,6 @@ namespace Slic3r { namespace GUI { -class BitmapCache; - // --------------------------------- // *** PresetComboBox *** @@ -49,11 +48,22 @@ public: void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); - virtual void update() {}; + void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } + + bool is_selected_physical_printer(); + + // Return true, if physical printer was selected + // and next internal selection was accomplished + bool selection_is_changed_according_to_physical_printers(); + + void update(const std::string& select_preset); + + virtual void update(){}; virtual void msw_rescale(); protected: typedef std::size_t Marker; + std::function on_selection_changed { nullptr }; Preset::Type m_type; std::string m_main_bitmap_name; @@ -61,16 +71,16 @@ protected: PresetBundle* m_preset_bundle {nullptr}; PresetCollection* m_collection {nullptr}; - // Caching color bitmaps for the filament combo box. - BitmapCache* m_bitmap_cache {nullptr}; + // Caching bitmaps for the all bitmaps, used in preset comboboxes + static BitmapCache& bitmap_cache() { + static BitmapCache bmps; + return bmps; + } + // Indicator, that the preset is compatible with the selected printer. ScalableBitmap m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. ScalableBitmap m_bitmapIncompatible; - // Indicator, that the preset is system and not modified. - ScalableBitmap m_bitmapLock; - // Disabled analogue of the m_bitmapLock . - ScalableBitmap m_bitmapLockDisabled; int m_last_selected; int m_em_unit; @@ -93,6 +103,13 @@ protected: #endif // __linux__ static wxString separator(const std::string& label); + wxBitmap* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + bool is_compatible = true, bool is_system = false, bool is_single_bar = false, + std::string filament_rgb = "", std::string extruder_rgb = ""); + + wxBitmap* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, + bool is_enabled = true, bool is_compatible = true, bool is_system = false); + #ifdef __APPLE__ /* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean @@ -128,7 +145,6 @@ public: void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; } int get_extruder_idx() const { return m_extruder_idx; } - bool is_selected_physical_printer(); bool switch_to_tab(); void show_add_menu(); void show_edit_menu(); @@ -149,7 +165,8 @@ class TabPresetComboBox : public PresetComboBox { bool show_incompatible {false}; bool m_enable_all {false}; - std::function on_selection_changed { nullptr }; + + std::function update_ph_printers { nullptr }; public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); @@ -157,12 +174,15 @@ public: void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; } + void set_update_physical_printers_function(std::function update_fn) { + update_ph_printers = update_fn; + } void update() override; void update_dirty(); + void update_physical_printers(const std::string& preset_name); void msw_rescale() override; - void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } void set_enable_all(bool enable=true) { m_enable_all = enable; } }; @@ -176,7 +196,7 @@ class PresetForPrinter { PhysicalPrinterDialog* m_parent { nullptr }; - TabPresetComboBox* m_presets_list { nullptr }; + PresetComboBox* m_presets_list { nullptr }; ScalableButton* m_delete_preset_btn { nullptr }; wxStaticText* m_info_line { nullptr }; wxStaticText* m_full_printer_name { nullptr }; @@ -186,7 +206,7 @@ class PresetForPrinter void DeletePreset(wxEvent& event); public: - PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name); + PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = ""); ~PresetForPrinter(); wxBoxSizer* sizer() { return m_sizer; } @@ -208,6 +228,7 @@ class ConfigOptionsGroup; class PhysicalPrinterDialog : public DPIDialog { PhysicalPrinter m_printer; + wxString m_default_name; DynamicPrintConfig* m_config { nullptr }; wxTextCtrl* m_printer_name { nullptr }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 595283e983..dbea9b3f5c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -162,10 +162,20 @@ void Tab::create_preset_tab() // preset chooser m_presets_choice = new TabPresetComboBox(panel, m_type); m_presets_choice->set_selection_changed_function([this](int selection) { - std::string selected_string = m_presets_choice->GetString(selection).ToUTF8().data(); - update_physical_printers(selected_string); - // select preset - select_preset(selected_string); + if (!m_presets_choice->selection_is_changed_according_to_physical_printers()) + { + // for the printer presets set callback for the updating of the physical printers + if (m_type == Preset::TYPE_PRINTER) + m_presets_choice->set_update_physical_printers_function([this](std::string preset_name) { update_physical_printers(preset_name);}); + + // select preset + select_preset(m_presets_choice->GetString(selection).ToUTF8().data()); + + // Disable callback for the updating of the physical printers to avoid a case, + // when select_preset is called from the others than this place + if (m_type == Preset::TYPE_PRINTER) + m_presets_choice->set_update_physical_printers_function(nullptr); + } }); auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -764,9 +774,11 @@ void Tab::update_tab_ui() void Tab::update_physical_printers(std::string preset_name) { - if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) + PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; + if (physical_printers.has_selection() && + Preset::remove_suffix_modified(preset_name) != physical_printers.get_selected_printer_preset_name()) { - std::string printer_name = m_preset_bundle->physical_printers.get_selected_full_printer_name(); + std::string printer_name = physical_printers.get_selected_full_printer_name(); wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\".")) % printer_name).str()); msg_text += "\n\n" + _L("Would you like to change related preset for this printer?") + "\n\n" + _L("Select YES if you want to change related preset for this printer \n" @@ -780,12 +792,14 @@ void Tab::update_physical_printers(std::string preset_name) Preset& edited_preset = m_presets->get_edited_preset(); if (preset->name == edited_preset.name) preset = &edited_preset; - m_preset_bundle->physical_printers.get_selected_printer().update_from_preset(*preset); + physical_printers.get_selected_printer().update_from_preset(*preset); + physical_printers.select_printer_by_name(physical_printers.get_selected_printer().get_full_name(preset_name)); + return; } - else - // unselect physical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); } + + // unselect physical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); } // Load a provied DynamicConfig into the tab, modifying the active preset. @@ -2148,11 +2162,10 @@ void TabPrinter::build_fff() line.append_widget(serial_test); optgroup->append_line(line); } -#endif optgroup = page->new_optgroup(L("Print Host upload")); build_printhost(optgroup.get()); - +#endif optgroup = page->new_optgroup(L("Firmware")); optgroup->append_single_option_line("gcode_flavor"); optgroup->append_single_option_line("silent_mode"); @@ -2310,8 +2323,10 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("min_initial_exposure_time"); optgroup->append_single_option_line("max_initial_exposure_time"); + /* optgroup = page->new_optgroup(L("Print Host upload")); build_printhost(optgroup.get()); + */ const int notes_field_height = 25; // 250 @@ -2699,11 +2714,13 @@ void TabPrinter::update_fff() m_serial_test_btn->Disable(); } + /* { std::unique_ptr host(PrintHost::get_print_host(m_config)); m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); m_printhost_browse_btn->Enable(host->has_auto_discovery()); } + */ bool have_multiple_extruders = m_extruders_count > 1; get_field("toolchange_gcode")->toggle(have_multiple_extruders); @@ -2942,10 +2959,15 @@ void Tab::update_page_tree_visibility() } +void Tab::update_preset_choice() +{ + m_presets_choice->update(); +} + // Called by the UI combo box when the user switches profiles, and also to delete the current profile. // Select a preset by a name.If !defined(name), then the default preset is selected. // If the current profile is modified, user is asked to save the changes. -void Tab::select_preset(std::string preset_name, bool delete_current) +void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const std::string& last_selected_ph_printer_name/* =""*/) { if (preset_name.empty()) { if (delete_current) { @@ -3054,9 +3076,22 @@ void Tab::select_preset(std::string preset_name, bool delete_current) if (canceled) { update_tab_ui(); + /* // unselect physical printer selection to the correct synchronization of the printer presets between Tab and Plater if (m_type == Preset::TYPE_PRINTER) m_preset_bundle->physical_printers.unselect_printer(); + */ + + + // Check if preset really was changed. + // If preset selection was canceled and previously was selected physical printer, we should select it back + if (m_type == Preset::TYPE_PRINTER && !last_selected_ph_printer_name.empty()) { + if (m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { + m_preset_bundle->physical_printers.select_printer_by_name(last_selected_ph_printer_name); + m_presets_choice->update(); + } + } + // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. on_presets_changed(); @@ -3098,6 +3133,10 @@ void Tab::select_preset(std::string preset_name, bool delete_current) else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } + + //update physical printer's related printer preset if it's needed + m_presets_choice->update_physical_printers(preset_name); + load_current_preset(); } } @@ -3295,7 +3334,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); //update physical printer's related printer preset if it's needed - update_physical_printers(name); + m_presets_choice->update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); // Update the selection boxes at the plater. diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 69720ff65e..82df31b590 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -275,8 +275,9 @@ public: void load_current_preset(); void rebuild_page_tree(); void update_page_tree_visibility(); - // Select a new preset, possibly delete the current one. - void select_preset(std::string preset_name = "", bool delete_current = false); + void update_preset_choice(); + // Select a new preset, possibly delete the current one. + void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); bool may_switch_to_SLA_preset(); From 087c83c95877bc0c09ef55ba24919436617b5d7a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Jul 2020 14:58:58 +0200 Subject: [PATCH 169/826] GCodeViewer -> 3rd attempt to fix rendering of toolpaths on Mac --- src/slic3r/GUI/GCodeViewer.cpp | 43 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 0ab43a98c7..a697306cfd 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1232,8 +1232,7 @@ void GCodeViewer::render_toolpaths() const }; auto render_as_lines = [this](const TBuffer& buffer, GLShaderProgram& shader) { - for (const RenderPath& path : buffer.render_paths) - { + for (const RenderPath& path : buffer.render_paths) { shader.set_uniform("uniform_color", path.color); glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -1242,16 +1241,11 @@ void GCodeViewer::render_toolpaths() const } }; - auto line_width = [zoom]() { -#ifdef WIN32 + auto line_width = [](double zoom) { return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); -#else - return 3.0f; -#endif // WIN32 }; - glsafe(::glCullFace(GL_BACK)); - glsafe(::glLineWidth(static_cast(line_width()))); + glsafe(::glLineWidth(static_cast(line_width(zoom)))); unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); @@ -1269,8 +1263,12 @@ void GCodeViewer::render_toolpaths() const shader->start_using(); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glVertexAttribPointer(0, buffer.vertices.vertex_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)0)); - glsafe(::glEnableVertexAttribArray(0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + glsafe(::glVertexPointer(buffer.vertices.vertex_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)0)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); +// glsafe(::glVertexAttribPointer(0, buffer.vertices.vertex_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)0)); +// glsafe(::glEnableVertexAttribArray(0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); @@ -1286,26 +1284,27 @@ void GCodeViewer::render_toolpaths() const case GCodeProcessor::EMoveType::Extrude: case GCodeProcessor::EMoveType::Travel: { - std::array light_intensity; #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - light_intensity[0] = m_shaders_editor.lines.lights.ambient; - light_intensity[1] = m_shaders_editor.lines.lights.top_diffuse; - light_intensity[2] = m_shaders_editor.lines.lights.front_diffuse; - light_intensity[3] = m_shaders_editor.lines.lights.global; + std::array light_intensity = { + m_shaders_editor.lines.lights.ambient, + m_shaders_editor.lines.lights.top_diffuse, + m_shaders_editor.lines.lights.front_diffuse, + m_shaders_editor.lines.lights.global }; #else - light_intensity[0] = 0.25f; - light_intensity[1] = 0.7f; - light_intensity[2] = 0.75f; - light_intensity[3] = 0.75f; + std::array light_intensity = { 0.25f, 0.7f, 0.75f, 0.75f }; #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader->set_uniform("light_intensity", light_intensity); - render_as_lines(buffer, *shader); break; + render_as_lines(buffer, *shader); + break; } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - glsafe(::glDisableVertexAttribArray(0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); +// glsafe(::glDisableVertexAttribArray(0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); shader->stop_using(); From afd9429e6d9b56a26db85270f498cf287e45864d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 17 Jul 2020 15:18:29 +0200 Subject: [PATCH 170/826] Code cleanup --- src/slic3r/GUI/GCodeViewer.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a697306cfd..3edf0d907c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1263,12 +1263,8 @@ void GCodeViewer::render_toolpaths() const shader->start_using(); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glVertexPointer(buffer.vertices.vertex_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)0)); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); -// glsafe(::glVertexAttribPointer(0, buffer.vertices.vertex_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)0)); -// glsafe(::glEnableVertexAttribArray(0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); @@ -1301,10 +1297,7 @@ void GCodeViewer::render_toolpaths() const glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); -// glsafe(::glDisableVertexAttribArray(0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); shader->stop_using(); From 51f0fd8912ef2f368c20e99d41131f2e7f87e16b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Jul 2020 09:45:49 +0200 Subject: [PATCH 171/826] GCodeViewer -> Added visualization of percentage in estimated printing time dialog --- src/libslic3r/Print.hpp | 2 + src/slic3r/GUI/GCodeViewer.cpp | 88 +++++++++++++++------------------ src/slic3r/GUI/ImGuiWrapper.cpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 4 ++ 4 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index e2b7ab5467..7a0ecdf170 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -340,6 +340,7 @@ struct PrintStatistics void clear() { #if ENABLE_GCODE_VIEWER + clear_time_estimates(); estimated_normal_print_time_str.clear(); estimated_silent_print_time_str.clear(); estimated_normal_custom_gcode_print_times_str.clear(); @@ -461,6 +462,7 @@ public: const Polygon& first_layer_convex_hull() const { return m_first_layer_convex_hull; } const PrintStatistics& print_statistics() const { return m_print_statistics; } + PrintStatistics& print_statistics() { return m_print_statistics; } // Wipe tower support. bool has_wipe_tower() const; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3edf0d907c..07cfb396e8 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1740,14 +1740,25 @@ void GCodeViewer::render_time_estimate() const }; using PartialTimes = std::vector; - auto append_mode = [this, &imgui](float total_time, const PartialTimes& items, + auto append_headers = [&imgui](const Headers& headers, const ColumnOffsets& offsets) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(headers[0]); + ImGui::SameLine(offsets[0]); + imgui.text(headers[1]); + ImGui::SameLine(offsets[1]); + imgui.text(headers[2]); + ImGui::PopStyleColor(); + ImGui::Separator(); + }; + + auto append_mode = [this, &imgui, append_headers](float total_time, const PartialTimes& items, const Headers& partial_times_headers, const std::vector>& moves_time, const Headers& moves_headers, const std::vector>& roles_time, const Headers& roles_headers) { - auto append_partial_times = [this, &imgui](const PartialTimes& items, const Headers& headers) { - auto calc_offsets = [this, &headers](const PartialTimes& items) { + auto append_partial_times = [this, &imgui, append_headers](const PartialTimes& items, const Headers& headers) { + auto calc_offsets = [this, &headers](const PartialTimes& items) { ColumnOffsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; for (const PartialTime& item : items) { std::string label; @@ -1799,14 +1810,7 @@ void GCodeViewer::render_time_estimate() const ColumnOffsets offsets = calc_offsets(items); ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(headers[0]); - ImGui::SameLine(offsets[0]); - imgui.text(headers[1]); - ImGui::SameLine(offsets[1]); - imgui.text(headers[2]); - ImGui::PopStyleColor(); - ImGui::Separator(); + append_headers(headers, offsets); for (const PartialTime& item : items) { switch (item.type) @@ -1856,7 +1860,26 @@ void GCodeViewer::render_time_estimate() const } }; - auto append_move_times = [this, &imgui, move_type_label](float total_time, + auto append_time_item = [&imgui] (const std::string& label, float time, float percentage, const ImVec4& color, const ColumnOffsets& offsets) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); + imgui.text(label); + ImGui::PopStyleColor(); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(time))); + ImGui::SameLine(offsets[1]); + char buf[64]; + ::sprintf(buf, "%.2f%%", 100.0f * percentage); + ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImRect frame_bb; + frame_bb.Min = { ImGui::GetCursorScreenPos().x, window->DC.CursorPos.y }; + frame_bb.Max = { frame_bb.Min.x + percentage * (window->WorkRect.Max.x - frame_bb.Min.x), window->DC.CursorPos.y + ImGui::CalcTextSize(buf, nullptr, false).y }; + frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); + frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); + window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32({ color.x, color.y, color.z, 1.0f }), 0.0f, 0); + ImGui::TextUnformatted(buf); + }; + + auto append_move_times = [this, &imgui, move_type_label, append_headers, append_time_item](float total_time, const std::vector>& moves_time, const Headers& headers, const ColumnOffsets& offsets) { @@ -1866,32 +1889,17 @@ void GCodeViewer::render_time_estimate() const if (!ImGui::CollapsingHeader(_u8L("Moves Time").c_str())) return; - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(headers[0]); - ImGui::SameLine(offsets[0]); - imgui.text(headers[1]); - ImGui::SameLine(offsets[1]); - imgui.text(headers[2]); - ImGui::PopStyleColor(); - ImGui::Separator(); + append_headers(headers, offsets); std::vector> sorted_moves_time(moves_time); std::sort(sorted_moves_time.begin(), sorted_moves_time.end(), [](const auto& p1, const auto& p2) { return p2.second < p1.second; }); for (const auto& [type, time] : sorted_moves_time) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(move_type_label(type)); - ImGui::PopStyleColor(); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(time))); - ImGui::SameLine(offsets[1]); - char buf[64]; - ::sprintf(buf, "%.2f%%", 100.0f * time / total_time); - ImGui::TextUnformatted(buf); + append_time_item(move_type_label(type), time, time / total_time, ImGuiWrapper::COL_ORANGE_LIGHT, offsets); } }; - auto append_role_times = [this, &imgui](float total_time, + auto append_role_times = [this, &imgui, append_headers, append_time_item](float total_time, const std::vector>& roles_time, const Headers& headers, const ColumnOffsets& offsets) { @@ -1901,28 +1909,14 @@ void GCodeViewer::render_time_estimate() const if (!ImGui::CollapsingHeader(_u8L("Features Time").c_str())) return; - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(headers[0]); - ImGui::SameLine(offsets[0]); - imgui.text(headers[1]); - ImGui::SameLine(offsets[1]); - imgui.text(headers[2]); - ImGui::PopStyleColor(); - ImGui::Separator(); + append_headers(headers, offsets); std::vector> sorted_roles_time(roles_time); std::sort(sorted_roles_time.begin(), sorted_roles_time.end(), [](const auto& p1, const auto& p2) { return p2.second < p1.second; }); for (const auto& [role, time] : sorted_roles_time) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(_u8L(ExtrusionEntity::role_to_string(role))); - ImGui::PopStyleColor(); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(time))); - ImGui::SameLine(offsets[1]); - char buf[64]; - ::sprintf(buf, "%.2f%%", 100.0f * time / total_time); - ImGui::TextUnformatted(buf); + Color color = Extrusion_Role_Colors[static_cast(role)]; + append_time_item(_u8L(ExtrusionEntity::role_to_string(role)), time, time / total_time, { 0.666f * color[0], 0.666f * color[1], 0.666f * color[2], 1.0f}, offsets); } }; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 1253c047ef..b4e8c6d0f9 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -760,12 +760,10 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co void ImGuiWrapper::title(const std::string& str) { ImGuiWindow* window = ImGui::GetCurrentWindow(); - const float frame_height = ImGui::CalcTextSize(str.c_str(), nullptr, false).y; ImRect frame_bb; frame_bb.Min = { window->WorkRect.Min.x, window->DC.CursorPos.y }; - frame_bb.Max = { window->WorkRect.Max.x, window->DC.CursorPos.y + frame_height }; - + frame_bb.Max = { window->WorkRect.Max.x, window->DC.CursorPos.y + ImGui::CalcTextSize(str.c_str(), nullptr, false).y }; frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index eda980a93b..d917e82b4d 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1428,6 +1428,8 @@ void MainFrame::set_mode(EMode mode) select_tab(0); #endif // ENABLE_LAYOUT_NO_RESTART + m_plater->fff_print().print_statistics().clear_time_estimates(); + m_plater->reset(); m_plater->reset_gcode_toolpaths(); @@ -1471,6 +1473,8 @@ void MainFrame::set_mode(EMode mode) update_layout(); #endif // ENABLE_LAYOUT_NO_RESTART + m_plater->fff_print().print_statistics().clear_time_estimates(); + m_plater->reset(); m_plater->reset_last_loaded_gcode(); m_plater->reset_gcode_toolpaths(); From 4700579589377f8036ec442913e3b4b6a273987b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Jul 2020 12:25:00 +0200 Subject: [PATCH 172/826] GCodeViewer -> Estimated printing time dialog hidden by defaul --- src/slic3r/GUI/GCodeViewer.cpp | 8 ++++++ src/slic3r/GUI/GCodeViewer.hpp | 4 +-- src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/Plater.cpp | 45 +++++++++++++++++++--------------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 07cfb396e8..2d2bb512d9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -495,6 +495,14 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range wxGetApp().plater()->update_preview_moves_slider(); } +void GCodeViewer::enable_time_estimate(bool enable) +{ + m_time_estimate_enabled = enable; + wxGetApp().update_ui_from_settings(); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); +} + void GCodeViewer::export_toolpaths_to_obj(const char* filename) const { if (filename == nullptr) diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 90155c7281..564b625699 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -341,7 +341,7 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; - bool m_time_estimate_enabled{ true }; + bool m_time_estimate_enabled{ false }; #if ENABLE_GCODE_VIEWER_STATISTICS mutable Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -398,7 +398,7 @@ public: void enable_legend(bool enable) { m_legend_enabled = enable; } bool is_time_estimate_enabled() const { return m_time_estimate_enabled; } - void enable_time_estimate(bool enable) { m_time_estimate_enabled = enable; } + void enable_time_estimate(bool enable); void export_toolpaths_to_obj(const char* filename) const; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 782a9425d7..0b5a357fdb 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -558,6 +558,7 @@ public: void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); } + bool is_time_estimate_enabled() const { return m_gcode_viewer.is_time_estimate_enabled(); } #endif // ENABLE_GCODE_VIEWER void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index db19ff39b6..39003e9aaa 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1323,13 +1323,14 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siCost, info_text, new_label); #if ENABLE_GCODE_VIEWER - if (ps.estimated_normal_print_time_str == "N/A" && ps.estimated_silent_print_time_str == "N/A") + if (p->plater->get_current_canvas3D()->is_time_estimate_enabled() || (ps.estimated_normal_print_time_str == "N/A" && ps.estimated_silent_print_time_str == "N/A")) #else + if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") #endif // ENABLE_GCODE_VIEWER p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); else { - new_label = _L("Estimated printing time") +":"; + new_label = _L("Estimated printing time") + ":"; info_text = ""; wxString str_color = _L("Color"); wxString str_pause = _L("Pause"); @@ -1340,29 +1341,29 @@ void Sidebar::update_sliced_info_sizer() auto fill_labels = [str_color, str_pause](const std::vector>& times, #endif // ENABLE_GCODE_VIEWER wxString& new_label, wxString& info_text) - { - int color_change_count = 0; - for (auto time : times) - if (time.first == CustomGCode::ColorChange) - color_change_count++; - - for (int i = (int)times.size() - 1; i >= 0; --i) { - if (i == 0 || times[i - 1].first == CustomGCode::PausePrint) - new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count); - else if (times[i - 1].first == CustomGCode::ColorChange) - new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--); + int color_change_count = 0; + for (auto time : times) + if (time.first == CustomGCode::ColorChange) + color_change_count++; - if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint) - new_label += format_wxstr(" -> %1%", str_pause); + for (int i = (int)times.size() - 1; i >= 0; --i) + { + if (i == 0 || times[i - 1].first == CustomGCode::PausePrint) + new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count); + else if (times[i - 1].first == CustomGCode::ColorChange) + new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--); + + if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint) + new_label += format_wxstr(" -> %1%", str_pause); #if ENABLE_GCODE_VIEWER - info_text += format_wxstr("\n%1% (%2%)", times[i].second.first, times[i].second.second); + info_text += format_wxstr("\n%1% (%2%)", times[i].second.first, times[i].second.second); #else - info_text += format_wxstr("\n%1%", times[i].second); + info_text += format_wxstr("\n%1%", times[i].second); #endif // ENABLE_GCODE_VIEWER - } - }; + } + }; #if ENABLE_GCODE_VIEWER if (ps.estimated_normal_print_time_str != "N/A") { @@ -1386,7 +1387,7 @@ void Sidebar::update_sliced_info_sizer() fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text); #endif // ENABLE_GCODE_VIEWER } - p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); } // if there is a wipe tower, insert number of toolchanges info into the array: @@ -2180,6 +2181,10 @@ void Plater::priv::select_view_3D(const std::string& name) set_current_panel(view3D); else if (name == "Preview") set_current_panel(preview); + +#if ENABLE_GCODE_VIEWER + wxGetApp().update_ui_from_settings(); +#endif // ENABLE_GCODE_VIEWER } void Plater::priv::select_next_view_3D() From e27519751896025c8d629556755b31d0ea8abf69 Mon Sep 17 00:00:00 2001 From: Paul Arden Date: Mon, 20 Jul 2020 20:57:37 +1000 Subject: [PATCH 173/826] Add G10 temperature G-code support for the RepRapFirmware flavour. --- src/libslic3r/GCode.cpp | 42 +++++++++++++++++++++++++++++++---- src/libslic3r/GCodeWriter.cpp | 21 ++++++++++++------ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7fc531b92f..975e15c6c7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1574,9 +1574,9 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std } } -// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code. +// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code. // Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. -static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out) +static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out) { temp_out = -1; if (gcode.empty()) @@ -1619,6 +1619,40 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc } } } + } else if (*ptr == 'G' && include_g10) { // Only check for G10 if requested + // Line starts with 'G'. + ++ ptr; + // Parse the G code value. + char *endptr = nullptr; + int gcode = int(strtol(ptr, &endptr, 10)); + if (endptr != nullptr && endptr != ptr && gcode == 10 /* G10 */) { + // G10 code found + ptr = endptr; + // Now try to parse the temperature value. + // While not at the end of the line: + while (strchr(";\r\n\0", *ptr) == nullptr) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'S') { + // Skip whitespaces. + for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); + // Parse an int. + endptr = nullptr; + long temp_parsed = strtol(ptr, &endptr, 10); + if (endptr > ptr) { + ptr = endptr; + temp_out = temp_parsed; + // Let the caller know that the custom G-code sets the temperature + // Only do this after successfully parsing temperature since G10 + // can be used for other reasons + temp_set_by_gcode = true; + } + } else { + // Skip this word. + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + } + } + } } // Skip the rest of the line. for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); @@ -1668,7 +1702,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id); // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; - bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode); + bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode); if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if @@ -1686,7 +1720,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c { // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; - if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) { + if (custom_gcode_sets_temperature(gcode, 104, 109, true, temp_by_gcode)) { // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp_by_gcode >= 0 && temp_by_gcode < 1000) diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 38a1c3ebee..98a6ed4a49 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -72,11 +72,15 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in return ""; std::string code, comment; - if (wait && FLAVOR_IS_NOT(gcfTeacup)) { + if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRap)) { code = "M109"; comment = "set temperature and wait for it to be reached"; } else { - code = "M104"; + if (FLAVOR_IS(gcfRepRap)) { // M104 is deprecated on RepRapFirmware + code = "G10"; + } else { + code = "M104"; + } comment = "set temperature"; } @@ -88,14 +92,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in gcode << "S"; } gcode << temperature; - if (tool != -1 && - ( (this->multiple_extruders && ! m_single_extruder_multi_material) || - FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) { - gcode << " T" << tool; + bool multiple_tools = this->multiple_extruders && ! m_single_extruder_multi_material; + if (tool != -1 && (multiple_tools || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) { + if (FLAVOR_IS(gcfRepRap)) { + gcode << " P" << tool; + } else { + gcode << " T" << tool; + } } gcode << " ; " << comment << "\n"; - if (FLAVOR_IS(gcfTeacup) && wait) + if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRap)) && wait) gcode << "M116 ; wait for temperature to be reached\n"; return gcode.str(); From 72ec414f1e4492d67465efe368a4a224a21ed7eb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 20 Jul 2020 14:56:09 +0200 Subject: [PATCH 174/826] PhysicalPrinters improvements: - Added possibility to correct delete presets considering with the physical printers - Smart switching to the printer preset if physical printer was selected --- src/libslic3r/Preset.cpp | 55 +++++++++- src/libslic3r/Preset.hpp | 9 +- src/slic3r/GUI/PresetComboBoxes.cpp | 154 ++++++++++++++++++++++++---- src/slic3r/GUI/PresetComboBoxes.hpp | 32 +++++- src/slic3r/GUI/Tab.cpp | 141 ++++++++++++++----------- src/slic3r/GUI/Tab.hpp | 2 +- 6 files changed, 302 insertions(+), 91 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ad7615f6d1..a55dc24cac 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1436,6 +1436,11 @@ bool PhysicalPrinter::add_preset(const std::string& preset_name) return preset_names.emplace(preset_name).second; } +bool PhysicalPrinter::delete_preset(const std::string& preset_name) +{ + return preset_names.erase(preset_name) > 0; +} + PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : name(name) { @@ -1521,7 +1526,6 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const } m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); std::sort(m_printers.begin(), m_printers.end()); -//! this->select_preset(first_visible_idx()); if (!errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); } @@ -1542,8 +1546,11 @@ std::string PhysicalPrinterCollection::path_from_name(const std::string& new_nam return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } -void PhysicalPrinterCollection::save_printer(const PhysicalPrinter& edited_printer, const std::string& renamed_from) +void PhysicalPrinterCollection::save_printer(PhysicalPrinter& edited_printer, const std::string& renamed_from/* = ""*/) { + // controll and update preset_names in edited_printer config + edited_printer.update_preset_names_in_config(); + std::string name = renamed_from.empty() ? edited_printer.name : renamed_from; // 1) Find the printer with a new_name or create a new one, // initialize it with the edited config. @@ -1605,6 +1612,33 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } +bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name, bool first_check /*=true*/) +{ + if (first_check) { + for (auto printer: m_printers) + if (printer.preset_names.size()==1 && *printer.preset_names.begin() == preset_name) + return false; + } + + std::vector printers_for_delete; + for (PhysicalPrinter& printer : m_printers) + if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) + printers_for_delete.emplace_back(printer.name); + else if (printer.delete_preset(preset_name)) { + if (printer.name == get_selected_printer_name() && + preset_name == get_selected_printer_preset_name()) + select_printer(printer); + save_printer(printer); + } + + if (!printers_for_delete.empty()) { + for (const std::string& printer_name : printers_for_delete) + delete_printer(printer_name); + unselect_printer(); + } + return true; +} + std::string PhysicalPrinterCollection::get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); @@ -1625,6 +1659,23 @@ PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::st return *it; } +PhysicalPrinter& PhysicalPrinterCollection::select_printer(const std::string& printer_name) +{ + auto it = this->find_printer_internal(printer_name); + assert(it != m_printers.end()); + + // update idx_selected + m_idx_selected = it - m_printers.begin(); + // update name of the currently selected preset + m_selected_preset = *it->preset_names.begin(); + return *it; +} + +PhysicalPrinter& PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer) +{ + return select_printer(printer.name); +} + bool PhysicalPrinterCollection::has_selection() const { return m_idx_selected != size_t(-1); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index d30ea70590..e8afb0f6fd 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -569,6 +569,7 @@ public: // add preset to the preset_names // return false, if preset with this name is already exist in the set bool add_preset(const std::string& preset_name); + bool delete_preset(const std::string& preset_name); void reset_presets(); // Return a printer technology, return ptFFF if the printer technology is not set. @@ -629,7 +630,7 @@ public: // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. // New printer is activated. - void save_printer(const PhysicalPrinter& printer, const std::string& renamed_from); + void save_printer(PhysicalPrinter& printer, const std::string& renamed_from = ""); // Delete the current preset, activate the first visible preset. // returns true if the preset was deleted successfully. @@ -637,6 +638,10 @@ public: // Delete the selected preset // returns true if the preset was deleted successfully. bool delete_selected_printer(); + // Delete preset_name preset from all printers: + // If there is last preset for the printer and first_check== false, then delete this printer + // returns true if all presets were deleted successfully. + bool delete_preset_from_printers(const std::string& preset_name, bool first_check = true); // Return the selected preset, without the user modifications applied. PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } @@ -659,6 +664,8 @@ public: // select printer with name and return reference on it PhysicalPrinter& select_printer_by_name(const std::string& full_name); + PhysicalPrinter& select_printer(const std::string &printer_name); + PhysicalPrinter& select_printer(const PhysicalPrinter& printer); bool has_selection() const; void unselect_printer() ; bool is_selected(ConstIterator it, const std::string &preset_name) const; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index e08cf101d6..bc1f48dd64 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -357,6 +357,8 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers() wxGetApp().get_tab(m_type)->update_preset_choice(); else if (dynamic_cast(this)!=nullptr) wxGetApp().sidebar().update_presets(m_type); + + this->update(); return true; } @@ -614,6 +616,8 @@ void PlaterPresetComboBox::show_edit_menu() return; m_preset_bundle->physical_printers.delete_selected_printer(); + + wxGetApp().get_tab(m_type)->update_preset_choice(); update(); }, "cross", menu, []() { return true; }, wxGetApp().plater()); @@ -855,9 +859,16 @@ void TabPresetComboBox::update() set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); int idx_selected = m_collection->get_selected_idx(); - std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); - PrinterTechnology proper_pt = (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) ? - m_collection->find_preset(sel_preset_name)->printer_technology() : ptAny; + PrinterTechnology proper_pt = ptAny; + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { + std::string sel_preset_name = m_preset_bundle->physical_printers.get_selected_printer_preset_name(); + Preset* preset = m_collection->find_preset(sel_preset_name); + if (preset) + proper_pt = preset->printer_technology(); + else + m_preset_bundle->physical_printers.unselect_printer(); + } + for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; @@ -868,7 +879,7 @@ void TabPresetComboBox::update() bool is_enabled = true; // check this value just for printer presets, when physical printer is selected if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) - is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt;//m_preset_bundle->physical_printers.get_selected_printer_technology(); + is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt; std::string bitmap_key = "tab"; std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; @@ -1017,8 +1028,49 @@ void TabPresetComboBox::update_dirty() void TabPresetComboBox::update_physical_printers( const std::string& preset_name) { - if (m_type == Preset::TYPE_PRINTER && update_ph_printers) - update_ph_printers(preset_name); + if (m_type != Preset::TYPE_PRINTER || !m_allow_to_update_physical_printers) + return; + + m_allow_to_update_physical_printers = false; + + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + if (!physical_printers.has_selection()) + return; + + std::string printer_preset_name = physical_printers.get_selected_printer_preset_name(); + + if (Preset::remove_suffix_modified(preset_name) == printer_preset_name) { + if (!this->is_selected_physical_printer()) + physical_printers.unselect_printer(); + } + else + { + ChangePresetForPhysicalPrinterDialog dlg(Preset::remove_suffix_modified(preset_name)); + if(dlg.ShowModal() == wxID_OK) + { + if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::Switch) + // unselect physical printer, if it was selected + m_preset_bundle->physical_printers.unselect_printer(); + else + { + PhysicalPrinter printer = physical_printers.get_selected_printer(); + + if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::ChangePreset) + printer.delete_preset(printer_preset_name); + + if (printer.add_preset(preset_name)) + physical_printers.save_printer(printer); + else { + wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + + physical_printers.select_printer_by_name(printer.get_full_name(preset_name)); + } + } + else + wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printer_preset_name); + } } @@ -1123,7 +1175,6 @@ void PresetForPrinter::msw_rescale() // PhysicalPrinterDialog //------------------------------------------ - PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { @@ -1427,9 +1478,6 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) if (dialog.ShowModal() == wxID_NO) return; - - // Remove the printer from the list. - printers.delete_printer(into_u8(printer_name)); } std::set repeat_presets; @@ -1438,8 +1486,6 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) if (!m_printer.add_preset(preset->get_preset_name())) repeat_presets.emplace(preset->get_preset_name()); } - // update preset_names in printer config - m_printer.update_preset_names_in_config(); if (!repeat_presets.empty()) { @@ -1467,16 +1513,10 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) // save new physical printer printers.save_printer(m_printer, renamed_from); - // update selection on the tab only when it was changed - /* - if (m_printer.get_preset_name() != wxGetApp().preset_bundle->printers.get_selected_preset_name()) { - Tab* tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); - if (tab) { - wxString preset_name = m_printer_presets->GetString(m_printer_presets->GetSelection()); - tab->select_preset(into_u8(preset_name)); - } - } - */ + printers.select_printer(m_printer); + + // refresh preset list on Printer Settings Tab + wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice(); event.Skip(); } @@ -1520,4 +1560,74 @@ void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) } +//----------------------------------------------- +// ChangePresetForPhysicalPrinterDialog +//----------------------------------------------- + +ChangePresetForPhysicalPrinterDialog::ChangePresetForPhysicalPrinterDialog(const std::string& preset_name) + : DPIDialog(nullptr, wxID_ANY, _L("Warning"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING/* | wxRESIZE_BORDER*/) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + std::string printer_name = printers.get_selected_printer_name(); + std::string old_preset_name = printers.get_selected_printer_preset_name(); + + wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\"\n" + "with related printer preset \"%2%\"")) % + printer_name % old_preset_name).str()); + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, msg_text); + label_top->SetFont(wxGetApp().bold_font()); + + wxString choices[] = { from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()), + from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), + from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()) }; + + wxRadioBox* selection_type_box = new wxRadioBox(this, wxID_ANY, _L("What would you like to do?"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, + 3, wxRA_SPECIFY_ROWS); + selection_type_box->SetFont(wxGetApp().normal_font()); + selection_type_box->SetSelection(0); + + selection_type_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { + int selection = e.GetSelection(); + m_selection = (SelectionType)selection; + }); + + auto radio_sizer = new wxBoxSizer(wxHORIZONTAL); + radio_sizer->Add(selection_type_box, 1, wxALIGN_CENTER_VERTICAL); + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, &ChangePresetForPhysicalPrinterDialog::OnOK, this); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(label_top, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void ChangePresetForPhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + + const wxSize& size = wxSize(45 * em, 35 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + +void ChangePresetForPhysicalPrinterDialog::OnOK(wxEvent& event) +{ + event.Skip(); +} + + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 1f5ef026da..1cea97e41e 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -166,7 +166,7 @@ class TabPresetComboBox : public PresetComboBox bool show_incompatible {false}; bool m_enable_all {false}; - std::function update_ph_printers { nullptr }; + bool m_allow_to_update_physical_printers {false}; public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); @@ -174,8 +174,8 @@ public: void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; } - void set_update_physical_printers_function(std::function update_fn) { - update_ph_printers = update_fn; + void allow_to_update_physical_printers() { + m_allow_to_update_physical_printers = m_type == Preset::TYPE_PRINTER; } void update() override; @@ -263,6 +263,32 @@ protected: void on_sys_color_changed() override {}; }; + +//------------------------------------------------ +// ChangePresetForPhysicalPrinterDialog +//------------------------------------------------ + +class ChangePresetForPhysicalPrinterDialog : public DPIDialog +{ + void OnOK(wxEvent& event); + +public: + + enum SelectionType + { + Switch, + ChangePreset, + AddPreset + } m_selection {Switch}; + + ChangePresetForPhysicalPrinterDialog(const std::string& preset_name); + ~ChangePresetForPhysicalPrinterDialog() {} + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override {}; +}; + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index dbea9b3f5c..e498f56b77 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -164,17 +164,14 @@ void Tab::create_preset_tab() m_presets_choice->set_selection_changed_function([this](int selection) { if (!m_presets_choice->selection_is_changed_according_to_physical_printers()) { - // for the printer presets set callback for the updating of the physical printers + // For the printer presets allow to update a physical printer if it is needed. + // After call of the update_physical_printers() this possibility will be disabled again to avoid a case, + // when select_preset is called from the others than this place if (m_type == Preset::TYPE_PRINTER) - m_presets_choice->set_update_physical_printers_function([this](std::string preset_name) { update_physical_printers(preset_name);}); + m_presets_choice->allow_to_update_physical_printers(); // select preset select_preset(m_presets_choice->GetString(selection).ToUTF8().data()); - - // Disable callback for the updating of the physical printers to avoid a case, - // when select_preset is called from the others than this place - if (m_type == Preset::TYPE_PRINTER) - m_presets_choice->set_update_physical_printers_function(nullptr); } }); @@ -772,36 +769,6 @@ void Tab::update_tab_ui() m_presets_choice->update(); } -void Tab::update_physical_printers(std::string preset_name) -{ - PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; - if (physical_printers.has_selection() && - Preset::remove_suffix_modified(preset_name) != physical_printers.get_selected_printer_preset_name()) - { - std::string printer_name = physical_printers.get_selected_full_printer_name(); - wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\".")) % printer_name).str()); - msg_text += "\n\n" + _L("Would you like to change related preset for this printer?") + "\n\n" + - _L("Select YES if you want to change related preset for this printer \n" - "or NO to switch to the another preset (logical printer)."); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_YES) { - preset_name = Preset::remove_suffix_modified(preset_name); - Preset* preset = m_presets->find_preset(preset_name); - assert(preset); - Preset& edited_preset = m_presets->get_edited_preset(); - if (preset->name == edited_preset.name) - preset = &edited_preset; - physical_printers.get_selected_printer().update_from_preset(*preset); - physical_printers.select_printer_by_name(physical_printers.get_selected_printer().get_full_name(preset_name)); - return; - } - } - - // unselect physical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); -} - // Load a provied DynamicConfig into the tab, modifying the active preset. // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) @@ -2822,7 +2789,8 @@ void Tab::load_current_preset() { const Preset& preset = m_presets->get_edited_preset(); - (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); +// (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); + update_delete_preset_btn(); update(); if (m_type == Slic3r::Preset::TYPE_PRINTER) { @@ -2959,9 +2927,23 @@ void Tab::update_page_tree_visibility() } +void Tab::update_delete_preset_btn() +{ + if (m_type == Preset::TYPE_PRINTER && m_presets_choice->is_selected_physical_printer() && + m_preset_bundle->physical_printers.has_selection()) { + // we can't delete last preset from the physical printer + m_btn_delete_preset->Enable(m_preset_bundle->physical_printers.get_selected_printer().preset_names.size() > 1); + } + else { + const Preset& preset = m_presets->get_edited_preset(); + m_btn_delete_preset->Enable(!preset.is_default && !preset.is_system); + } +} + void Tab::update_preset_choice() { m_presets_choice->update(); + update_delete_preset_btn(); } // Called by the UI combo box when the user switches profiles, and also to delete the current profile. @@ -2971,16 +2953,55 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, { if (preset_name.empty()) { if (delete_current) { - // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_current = this->m_presets->get_idx_selected(); - // Find the next visible preset. - size_t idx_new = idx_current + 1; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - if (idx_new == presets.size()) - for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); - preset_name = presets[idx_new].name; + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + if (m_presets_choice->is_selected_physical_printer()) { + PhysicalPrinter& printer = physical_printers.get_selected_printer(); + + if (printer.preset_names.size()==1) { + wxMessageDialog dialog(nullptr, _L("It's a last for this physical printer. We can't delete it"), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + else { + // just delete this preset from the current physical printer + printer.delete_preset(m_presets->get_edited_preset().name); + // select first from the possible presets for this printer + physical_printers.select_printer(printer); + + preset_name = physical_printers.get_selected_printer_preset_name(); + // revert delete_current value to avoid deleting of the new selected preset + delete_current = false; + } + } + else { + // Check preset for delete in physical printers + // Ask a customer about next action , if there is a printer with just one preset and this preset is equal to delete + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty() ) + { + // try to delete selected preset from the all printers it has + if (!physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name)) + { + wxMessageDialog dialog(nullptr, _L("There is/are a physical printer(s), which has/have one and only this printer preset.\n" + "This/Those printer(s) will be deletede after deleting of the selected preset.\n" + "Are you sure you want to delete the selected preset?"), _L("Warning"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + if (dialog.ShowModal() == wxID_NO) + return; + + // delete selected preset from printers and printer, if it's needed + physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name, false); + } + } + + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_current = this->m_presets->get_idx_selected(); + // Find the next visible preset. + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); + preset_name = presets[idx_new].name; + } } else { // If no name is provided, select the "-- default --" preset. preset_name = m_presets->default_preset().name; @@ -3075,23 +3096,16 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, } if (canceled) { - update_tab_ui(); - /* - // unselect physical printer selection to the correct synchronization of the printer presets between Tab and Plater - if (m_type == Preset::TYPE_PRINTER) - m_preset_bundle->physical_printers.unselect_printer(); - */ - - - // Check if preset really was changed. - // If preset selection was canceled and previously was selected physical printer, we should select it back - if (m_type == Preset::TYPE_PRINTER && !last_selected_ph_printer_name.empty()) { - if (m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { + if (m_type == Preset::TYPE_PRINTER) { + if (!last_selected_ph_printer_name.empty() && + m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { + // If preset selection was canceled and previously was selected physical printer, we should select it back m_preset_bundle->physical_printers.select_printer_by_name(last_selected_ph_printer_name); - m_presets_choice->update(); } } + update_tab_ui(); + // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, // if this action was initiated from the plater. on_presets_changed(); @@ -3334,6 +3348,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); //update physical printer's related printer preset if it's needed + m_presets_choice->allow_to_update_physical_printers(); m_presets_choice->update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); @@ -3389,7 +3404,9 @@ void Tab::delete_preset() // Don't let the user delete the ' - default - ' configuration. std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); // TRN remove/delete - const wxString msg = from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); + const wxString msg = m_presets_choice->is_selected_physical_printer() ? + from_u8((boost::format(_utf8(L("Are you sure you want to delete \"%1%\" preset from the physical printer?"))) % current_preset.name).str()) : + from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); // TRN Remove/Delete wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); //action + _(L(" Preset")); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 82df31b590..57129955b0 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -275,6 +275,7 @@ public: void load_current_preset(); void rebuild_page_tree(); void update_page_tree_visibility(); + void update_delete_preset_btn(); void update_preset_choice(); // Select a new preset, possibly delete the current one. void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); @@ -307,7 +308,6 @@ public: void load_initial_data(); void update_dirty(); void update_tab_ui(); - void update_physical_printers(std::string preset_name); void load_config(const DynamicPrintConfig& config); virtual void reload_config(); void update_mode(); From 3d990a918904fb1ab10afa4f95f2885350821994 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 20 Jul 2020 16:27:39 +0200 Subject: [PATCH 175/826] First try to convert a user printer profiles to the physical printers --- src/libslic3r/Preset.cpp | 33 +++++++++++++++++++++++++++-- src/libslic3r/Preset.hpp | 1 + src/libslic3r/PresetBundle.cpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 6 ++++++ src/slic3r/GUI/PresetComboBoxes.hpp | 9 +++----- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index a55dc24cac..401de0fc3f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1481,7 +1481,7 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vectorfind_printer(name, false)) { // This happens when there's is a preset (most likely legacy one) with the same name as a system preset // that's already been loaded from a bundle. - BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; + BOOST_LOG_TRIVIAL(warning) << "Printer already present, not loading: " << name; continue; } try { @@ -1530,6 +1530,35 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const throw std::runtime_error(errors_cummulative); } +// if there is saved user presets, contains information about "Print Host upload", +// Create default printers with this presets +// Throws an exception on error. +void PhysicalPrinterCollection::load_printers(const PrinterPresetCollection& printer_presets, std::string def_printer_name/* = ""*/) +{ + if (def_printer_name.empty()) + def_printer_name = "Printer"; + + int cnt=0; + std::string errors_cummulative; + // Store the loaded printers into a new vector + std::deque printers_loaded; + for (const Preset& preset: printer_presets) { + const DynamicPrintConfig& config = preset.config; + if (!config.opt_string("print_host").empty() || + !config.opt_string("printhost_apikey").empty() || + !config.opt_string("printhost_cafile").empty() ) { + PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); + printer.loaded = true; + printers_loaded.emplace_back(printer); + + save_printer(printer); + } + } + + if (!errors_cummulative.empty()) + throw std::runtime_error(errors_cummulative); +} + PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found) { PhysicalPrinter key(name); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index e8afb0f6fd..02f831136f 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -626,6 +626,7 @@ public: // Load ini files of the particular type from the provided directory path. void load_printers(const std::string& dir_path, const std::string& subdir); + void load_printers(const PrinterPresetCollection &printer_presets, std::string def_printer_name = ""); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 7969966e5e..0e215a2ae7 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -201,6 +201,7 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ } try { this->physical_printers.load_printers(dir_user_presets, "physical_printer"); + this->physical_printers.load_printers(this->printers); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index bc1f48dd64..2d74344bd3 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -131,6 +131,12 @@ PresetComboBox::~PresetComboBox() { } +BitmapCache& PresetComboBox::bitmap_cache() +{ + static BitmapCache bmps; + return bmps; +} + void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) { this->SetClientData(item, (void*)label_item_type); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 1cea97e41e..89d043f7bb 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -10,7 +10,7 @@ #include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" -#include "BitmapCache.hpp" +//#include "BitmapCache.hpp" class wxString; class wxTextCtrl; @@ -22,7 +22,7 @@ namespace Slic3r { namespace GUI { - +class BitmapCache; // --------------------------------- // *** PresetComboBox *** // --------------------------------- @@ -72,10 +72,7 @@ protected: PresetCollection* m_collection {nullptr}; // Caching bitmaps for the all bitmaps, used in preset comboboxes - static BitmapCache& bitmap_cache() { - static BitmapCache bmps; - return bmps; - } + static BitmapCache& bitmap_cache(); // Indicator, that the preset is compatible with the selected printer. ScalableBitmap m_bitmapCompatible; From dc59e86d2c10fac0e58694a37ff77f72790da0eb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 21 Jul 2020 09:34:54 +0200 Subject: [PATCH 176/826] ENABLE_GCODE_VIEWER -> Partial refactoring in preparation for removal of old time estimator --- src/libslic3r/GCode.cpp | 2 ++ src/libslic3r/GCodeTimeEstimator.cpp | 2 ++ src/libslic3r/GCodeTimeEstimator.hpp | 2 ++ src/libslic3r/Print.cpp | 6 ++++++ src/libslic3r/Print.hpp | 6 ++++++ src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/Plater.cpp | 27 +++++++++++++++++++++++++++ 7 files changed, 46 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 56cf357b7a..a4f25fa14c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1065,11 +1065,13 @@ namespace DoExport { print_statistics.clear(); #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR print_statistics.estimated_normal_print_time_str = normal_time_estimator.get_time_dhm/*s*/(); print_statistics.estimated_silent_print_time_str = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; print_statistics.estimated_normal_custom_gcode_print_times_str = normal_time_estimator.get_custom_gcode_times_dhm(true); if (silent_time_estimator_enabled) print_statistics.estimated_silent_custom_gcode_print_times_str = silent_time_estimator.get_custom_gcode_times_dhm(true); +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/(); print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index bc3adefc08..4bfe322ce2 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -723,6 +723,7 @@ namespace Slic3r { } #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::vector>> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const { std::vector>> ret; @@ -737,6 +738,7 @@ namespace Slic3r { return ret; } +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else std::vector> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const { diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index ce6b2f4af0..3b92f02692 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -371,7 +371,9 @@ namespace Slic3r { // Returns the estimated time, in format DDd HHh MMm, for each custom_gcode // If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)" #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::vector>> get_custom_gcode_times_dhm(bool include_remaining) const; +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else std::vector> get_custom_gcode_times_dhm(bool include_remaining) const; #endif // ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 34fab6f307..a48d6f602f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2191,9 +2191,15 @@ DynamicConfig PrintStatistics::config() const { DynamicConfig config; #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR config.set_key_value("print_time", new ConfigOptionString(this->estimated_normal_print_time_str)); config.set_key_value("normal_print_time", new ConfigOptionString(this->estimated_normal_print_time_str)); config.set_key_value("silent_print_time", new ConfigOptionString(this->estimated_silent_print_time_str)); +#else + config.set_key_value("print_time", new ConfigOptionString(short_time(get_time_dhms(this->estimated_normal_print_time)))); + config.set_key_value("normal_print_time", new ConfigOptionString(short_time(get_time_dhms(this->estimated_normal_print_time)))); + config.set_key_value("silent_print_time", new ConfigOptionString(short_time(get_time_dhms(this->estimated_silent_print_time)))); +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else std::string normal_print_time = short_time(this->estimated_normal_print_time); std::string silent_print_time = short_time(this->estimated_silent_print_time); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7a0ecdf170..064145a2e7 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -306,12 +306,16 @@ struct PrintStatistics #if ENABLE_GCODE_VIEWER float estimated_normal_print_time; float estimated_silent_print_time; +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::string estimated_normal_print_time_str; std::string estimated_silent_print_time_str; +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::vector>> estimated_normal_custom_gcode_print_times; std::vector>> estimated_silent_custom_gcode_print_times; +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::vector>> estimated_normal_custom_gcode_print_times_str; std::vector>> estimated_silent_custom_gcode_print_times_str; +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::vector> estimated_normal_moves_times; std::vector> estimated_silent_moves_times; std::vector> estimated_normal_roles_times; @@ -341,10 +345,12 @@ struct PrintStatistics void clear() { #if ENABLE_GCODE_VIEWER clear_time_estimates(); +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR estimated_normal_print_time_str.clear(); estimated_silent_print_time_str.clear(); estimated_normal_custom_gcode_print_times_str.clear(); estimated_silent_custom_gcode_print_times_str.clear(); +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index b04e78c4ed..06d217812b 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -62,6 +62,7 @@ #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR (1 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 39003e9aaa..f4c017adde 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1323,7 +1323,11 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siCost, info_text, new_label); #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR if (p->plater->get_current_canvas3D()->is_time_estimate_enabled() || (ps.estimated_normal_print_time_str == "N/A" && ps.estimated_silent_print_time_str == "N/A")) +#else + if (p->plater->get_current_canvas3D()->is_time_estimate_enabled() || (ps.estimated_normal_print_time <= 0.0f && ps.estimated_silent_print_time <= 0.0f)) +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") @@ -1336,7 +1340,11 @@ void Sidebar::update_sliced_info_sizer() wxString str_pause = _L("Pause"); #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR auto fill_labels = [str_color, str_pause](const std::vector>>& times, +#else + auto fill_labels = [str_color, str_pause](const std::vector>>& times, +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else auto fill_labels = [str_color, str_pause](const std::vector>& times, #endif // ENABLE_GCODE_VIEWER @@ -1358,7 +1366,11 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr(" -> %1%", str_pause); #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR info_text += format_wxstr("\n%1% (%2%)", times[i].second.first, times[i].second.second); +#else + info_text += format_wxstr("\n%1% (%2%)", short_time(get_time_dhms(times[i].second.first)), short_time(get_time_dhms(times[i].second.second))); +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else info_text += format_wxstr("\n%1%", times[i].second); #endif // ENABLE_GCODE_VIEWER @@ -1366,6 +1378,7 @@ void Sidebar::update_sliced_info_sizer() }; #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR if (ps.estimated_normal_print_time_str != "N/A") { new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time_str); @@ -1375,6 +1388,17 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr("\n - %1%", _L("stealth mode")); info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time_str); fill_labels(ps.estimated_silent_custom_gcode_print_times_str, new_label, info_text); +#else + if (ps.estimated_normal_print_time > 0.0f) { + new_label += format_wxstr("\n - %1%", _L("normal mode")); + info_text += format_wxstr("\n%1%", short_time(get_time_dhms(ps.estimated_normal_print_time))); + fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); + } + if (ps.estimated_silent_print_time > 0.0f) { + new_label += format_wxstr("\n - %1%", _L("stealth mode")); + info_text += format_wxstr("\n%1%", short_time(get_time_dhms(ps.estimated_silent_print_time))); + fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text); +#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR #else if (ps.estimated_normal_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("normal mode")); @@ -1397,6 +1421,9 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); } } +#if ENABLE_GCODE_VIEWER + Layout(); +#endif // ENABLE_GCODE_VIEWER } void Sidebar::show_sliced_info_sizer(const bool show) From f7ceffb46e5abd687625d4ef730b8003b6cadcc7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 21 Jul 2020 15:33:28 +0200 Subject: [PATCH 177/826] Fixed back-end warning infrastructure: The Print / PrintObject should have been derived from ObjectBase, not from ObjectID. --- src/libslic3r/ObjectID.hpp | 2 ++ src/libslic3r/Print.hpp | 2 +- src/libslic3r/PrintBase.cpp | 4 ++-- src/libslic3r/PrintBase.hpp | 12 ++++++------ src/libslic3r/SLAPrint.hpp | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 484d1173ba..920f512de3 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -42,6 +42,8 @@ private: // Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID // to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). +// Also base for Print, PrintObject, SLAPrint, SLAPrintObject to provide a unique ID for matching Model / ModelObject +// with their corresponding Print / PrintObject objects by the notification center at the UI when processing back-end warnings. // Achtung! The s_last_id counter is not thread safe, so it is expected, that the ObjectBase derived instances // are only instantiated from the main thread. class ObjectBase diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index bf541e1222..05929dd2ef 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -399,7 +399,7 @@ public: // in the notification center. const PrintObject* get_object(ObjectID object_id) const { auto it = std::find_if(m_objects.begin(), m_objects.end(), - [object_id](const PrintObject *obj) { return *static_cast(obj) == object_id; }); + [object_id](const PrintObject *obj) { return obj->id() == object_id; }); return (it == m_objects.end()) ? nullptr : *it; } const PrintRegionPtrs& regions() const { return m_regions; } diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 14339f3c62..ab6ca3d350 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -93,7 +93,7 @@ void PrintBase::status_update_warnings(ObjectID object_id, int step, PrintStateB if (this->m_status_callback) m_status_callback(SlicingStatus(*this, step)); else if (! message.empty()) - printf("%s warning: %s\n", (object_id == ObjectID(*this)) ? "print" : "print object", message.c_str()); + printf("%s warning: %s\n", (object_id == this->id()) ? "print" : "print object", message.c_str()); } tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print) @@ -108,7 +108,7 @@ std::function PrintObjectBase::cancel_callback(PrintBase *print) void PrintObjectBase::status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message) { - print->status_update_warnings(*this, step, warning_level, message); + print->status_update_warnings(this->id(), step, warning_level, message); } } // namespace Slic3r diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index d7f3483e87..5e94e011a7 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -304,7 +304,7 @@ private: class PrintBase; -class PrintObjectBase : public ObjectID +class PrintObjectBase : public ObjectBase { public: const ModelObject* model_object() const { return m_model_object; } @@ -335,7 +335,7 @@ protected: * The PrintBase class will abstract this flow for different technologies. * */ -class PrintBase : public ObjectID +class PrintBase : public ObjectBase { public: PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); } @@ -386,9 +386,9 @@ public: struct SlicingStatus { SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} SlicingStatus(const PrintBase &print, int warning_step) : - flags(UPDATE_PRINT_STEP_WARNINGS), warning_object_id(print), warning_step(warning_step) {} + flags(UPDATE_PRINT_STEP_WARNINGS), warning_object_id(print.id()), warning_step(warning_step) {} SlicingStatus(const PrintObjectBase &print_object, int warning_step) : - flags(UPDATE_PRINT_OBJECT_STEP_WARNINGS), warning_object_id(print_object), warning_step(warning_step) {} + flags(UPDATE_PRINT_OBJECT_STEP_WARNINGS), warning_object_id(print_object.id()), warning_step(warning_step) {} int percent { -1 }; std::string text; // Bitmap of flags. @@ -508,7 +508,7 @@ protected: PrintStateBase::TimeStamp set_done(PrintStepEnum step) { std::pair status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); if (status.second) - this->status_update_warnings(*this, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + this->status_update_warnings(this->id(), static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); return status.first; } bool invalidate_step(PrintStepEnum step) @@ -530,7 +530,7 @@ protected: std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex()); if (active_step.second) // Update UI. - this->status_update_warnings(*this, static_cast(active_step.first), warning_level, message); + this->status_update_warnings(this->id(), static_cast(active_step.first), warning_level, message); } private: diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 573fc2b0c8..9d41586ee6 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -433,7 +433,7 @@ public: // in the notification center. const SLAPrintObject* get_object(ObjectID object_id) const { auto it = std::find_if(m_objects.begin(), m_objects.end(), - [object_id](const SLAPrintObject *obj) { return *static_cast(obj) == object_id; }); + [object_id](const SLAPrintObject *obj) { return obj->id() == object_id; }); return (it == m_objects.end()) ? nullptr : *it; } From 42677107a57a0ad7319d856dcfcdc520148d8924 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 21 Jul 2020 16:00:03 +0200 Subject: [PATCH 178/826] ENABLE_GCODE_VIEWER -> Fixed scene update when opening a gcode file --- src/slic3r/GUI/Plater.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f4c017adde..ef113b7b96 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4650,6 +4650,7 @@ void Plater::load_gcode(const wxString& filename) // cleanup view before to start loading/processing p->gcode_result.reset(); + reset_gcode_toolpaths(); p->preview->reload_print(false); p->get_current_canvas3D()->render(); From 435355adfe425243e2a5780f1869cff17eeaf05a Mon Sep 17 00:00:00 2001 From: rongith Date: Wed, 13 May 2020 16:12:58 +0200 Subject: [PATCH 179/826] Temporary ironing icon to avoid crashes on GTK Ironing type and spacing can be set per-object --- src/libslic3r/PrintConfig.cpp | 2 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 0c1c828f7e..a25292298c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1099,6 +1099,7 @@ void PrintConfigDef::init_fff_params() def = this->add("ironing_type", coEnum); def->label = L("Ironing Type"); + def->category = L("Ironing"); def->tooltip = L("Ironing Type"); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("top"); @@ -1122,6 +1123,7 @@ void PrintConfigDef::init_fff_params() def = this->add("ironing_spacing", coFloat); def->label = L("Spacing between ironing passes"); + def->category = L("Ironing"); def->tooltip = L("Distance between ironing lines"); def->sidetext = L("mm"); def->min = 0; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d44db72b10..a47c8f6711 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -94,6 +94,7 @@ ObjectList::ObjectList(wxWindow* parent) : // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); @@ -644,6 +645,7 @@ void ObjectList::msw_rescale_icons() // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); From 6d28d68e4a87e3e53baf4d5c84d19a8bf4e56a61 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 21 Jul 2020 16:21:18 +0200 Subject: [PATCH 180/826] PhysicalPrinter : Implemented synchronizations from user printer profiles with "Print Host upload" information to the new physical printers --- src/libslic3r/Preset.cpp | 102 ++++++++++++++++++++++------ src/libslic3r/Preset.hpp | 11 ++- src/libslic3r/PresetBundle.cpp | 4 +- src/slic3r/GUI/GUI_App.cpp | 44 ++++++++++++ src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/PresetComboBoxes.cpp | 5 +- 6 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 401de0fc3f..ae74bffd75 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1370,6 +1370,37 @@ const std::vector& PhysicalPrinter::printer_options() return s_opts; } +const std::vector& PhysicalPrinter::print_host_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "print_host", + "printhost_apikey", + "printhost_cafile" + }; + } + return s_opts; +} + +bool PhysicalPrinter::has_print_host_information(const PrinterPresetCollection& printer_presets) +{ + for (const Preset& preset : printer_presets) + if (has_print_host_information(preset.config)) + return true; + + return false; +} + +bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config) +{ + for (const std::string& opt : print_host_options()) + if (!config.opt_string(opt).empty()) + return true; + + return false; +} + const std::set& PhysicalPrinter::get_preset_names() const { return preset_names; @@ -1532,42 +1563,69 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const // if there is saved user presets, contains information about "Print Host upload", // Create default printers with this presets -// Throws an exception on error. -void PhysicalPrinterCollection::load_printers(const PrinterPresetCollection& printer_presets, std::string def_printer_name/* = ""*/) +// Note! "Print Host upload" options will be cleared after physical printer creations +void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets, std::string def_printer_name) { - if (def_printer_name.empty()) - def_printer_name = "Printer"; - int cnt=0; - std::string errors_cummulative; - // Store the loaded printers into a new vector - std::deque printers_loaded; - for (const Preset& preset: printer_presets) { - const DynamicPrintConfig& config = preset.config; - if (!config.opt_string("print_host").empty() || - !config.opt_string("printhost_apikey").empty() || - !config.opt_string("printhost_cafile").empty() ) { - PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); - printer.loaded = true; - printers_loaded.emplace_back(printer); + for (Preset& preset: printer_presets) { + DynamicPrintConfig& config = preset.config; + const std::vector& options = PhysicalPrinter::print_host_options(); - save_printer(printer); + for(const std::string& option : options) { + if (!config.opt_string(option).empty()) { + // check if printer with those "Print Host upload" options already exist + PhysicalPrinter* existed_printer = find_printer_with_same_config(config); + if (existed_printer) + // just add preset for this printer + existed_printer->add_preset(preset.name); + else { + // create new printer from this preset + PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); + printer.loaded = true; + save_printer(printer); + } + + // erase "Print Host upload" information from the preset + for (const std::string& opt : options) + config.opt_string(opt).clear(); + // save changes for preset + preset.save(); + + // update those changes for edited preset if it's equal to the preset + Preset& edited = printer_presets.get_edited_preset(); + if (preset.name == edited.name) { + for (const std::string& opt : options) + edited.config.opt_string(opt).clear(); + } + + break; + } } } - - if (!errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); } PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found) { - PhysicalPrinter key(name); auto it = this->find_printer_internal(name); // Ensure that a temporary copy is returned if the preset found is currently selected. - return (it != m_printers.end() && it->name == key.name) ? &this->printer(it - m_printers.begin()) : + return (it != m_printers.end() && it->name == name) ? &this->printer(it - m_printers.begin()) : first_visible_if_not_found ? &this->printer(0) : nullptr; } +PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const DynamicPrintConfig& config) +{ + for (const PhysicalPrinter& printer :*this) { + bool is_equal = true; + for (const std::string& opt : PhysicalPrinter::print_host_options()) + if (is_equal && printer.config.opt_string(opt) != config.opt_string(opt)) + is_equal = false; + + if (is_equal) + return find_printer(printer.name); + } + return nullptr; +} + // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string PhysicalPrinterCollection::path_from_name(const std::string& new_name) const { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 02f831136f..98b805b4e4 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -549,12 +549,15 @@ public: // set of presets used with this physical printer std::set preset_names; - static std::string separator(); - // Has this profile been loaded? bool loaded = false; + static std::string separator(); static const std::vector& printer_options(); + static const std::vector& print_host_options(); + static bool has_print_host_information(const PrinterPresetCollection& printer_presets); + static bool has_print_host_information(const DynamicPrintConfig& config); + const std::set& get_preset_names() const; bool has_empty_config() const; @@ -626,7 +629,7 @@ public: // Load ini files of the particular type from the provided directory path. void load_printers(const std::string& dir_path, const std::string& subdir); - void load_printers(const PrinterPresetCollection &printer_presets, std::string def_printer_name = ""); + void load_printers_from_presets(PrinterPresetCollection &printer_presets, std::string def_printer_name); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. @@ -704,6 +707,8 @@ private: return const_cast(this)->find_printer_internal(name); } + PhysicalPrinter* find_printer_with_same_config( const DynamicPrintConfig &config); + // List of printers // Use deque to force the container to allocate an object per each entry, // so that the addresses of the presets don't change during resizing of the container. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 0e215a2ae7..d074c77d7d 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -201,7 +201,6 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ } try { this->physical_printers.load_printers(dir_user_presets, "physical_printer"); - this->physical_printers.load_printers(this->printers); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } @@ -436,8 +435,7 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer")); // Activate physical printer from the config - const PhysicalPrinter* initial_physical_printer = physical_printers.find_printer(initial_physical_printer_name); - if (initial_physical_printer) + if (!initial_physical_printer_name.empty()) physical_printers.select_printer_by_name(initial_physical_printer_name); } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 089a38c6f7..f7689c6e95 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -28,6 +28,9 @@ #include #include +#include +#include + #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/I18N.hpp" @@ -629,6 +632,43 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const app_config->set("auto_toolbar_size", val); } +// check user printer_presets for the containing information about "Print Host upload" +void GUI_App::check_printer_presets() +{ + if (!PhysicalPrinter::has_print_host_information(preset_bundle->printers)) + return; + + wxString msg_text = _L("You have presets with saved options for \"Print Host upload\".\n" + "But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" + "Now, this information will be exposed in physical printers settings.") + "\n\n" + + _L("Enter the name for the Printer device used by defaul during its creation.\n" + "Note: This name can be changed later from the physical printers settings") + ":"; + wxString msg_header = _L("Name for printer device"); + + // get custom gcode + wxTextEntryDialog dlg(nullptr, msg_text, msg_header, _L("Printer"), wxTextEntryDialogStyle); + + // detect TextCtrl and OK button + wxTextCtrl* textctrl{ nullptr }; + wxWindowList& dlg_items = dlg.GetChildren(); + for (auto item : dlg_items) { + textctrl = dynamic_cast(item); + if (textctrl) + break; + } + + if (textctrl) { + textctrl->SetSelection(0, textctrl->GetLastPosition()); + + wxButton* btn_OK = static_cast(dlg.FindWindowById(wxID_OK)); + btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { + evt.Enable(!textctrl->IsEmpty()); + }, btn_OK->GetId()); + } + if (dlg.ShowModal() == wxID_OK) + preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers, into_u8(dlg.GetValue())); +} + void GUI_App::recreate_GUI(const wxString& msg_name) { mainframe->shutdown(); @@ -1171,6 +1211,10 @@ bool GUI_App::checked_tab(Tab* tab) // Update UI / Tabs to reflect changes in the currently loaded presets void GUI_App::load_current_presets() { + // check printer_presets for the containing information about "Print Host upload" + // and create physical printer from it, if any exists + check_printer_presets(); + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); this->plater()->set_printer_technology(printer_technology); for (Tab *tab : tabs_list) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 23567695cd..db551610b7 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -150,6 +150,7 @@ public: wxSize get_min_size() const; float toolbar_icon_scale(const bool is_limited = false) const; void set_auto_toolbar_icon_scale(float scale) const; + void check_printer_presets(); void recreate_GUI(const wxString& message); void system_info(); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 2d74344bd3..e527ef9c90 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -359,8 +359,11 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers() // if new preset wasn't selected, there is no need to call update preset selection if (old_printer_preset == preset_name) { // we need just to update according Plater<->Tab PresetComboBox - if (dynamic_cast(this)!=nullptr) + if (dynamic_cast(this)!=nullptr) { wxGetApp().get_tab(m_type)->update_preset_choice(); + // Synchronize config.ini with the current selections. + m_preset_bundle->export_selections(*wxGetApp().app_config); + } else if (dynamic_cast(this)!=nullptr) wxGetApp().sidebar().update_presets(m_type); From 8f90fe16095225062c6a738bebc994714778023d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 22 Jul 2020 10:37:25 +0200 Subject: [PATCH 181/826] Code cleanup and small refactoring --- src/libslic3r/GCode.cpp | 146 ++----------------------- src/libslic3r/GCode/GCodeProcessor.cpp | 15 +-- 2 files changed, 16 insertions(+), 145 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d492acf2e8..8a835e07d5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -579,7 +579,6 @@ namespace Slic3r { #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Collect pairs of object_layer + support_layer sorted by print_z. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. std::vector GCode::collect_layers_to_print(const PrintObject& object) @@ -663,127 +662,6 @@ std::vector GCode::collect_layers_to_print(const PrintObjec return layers_to_print; } - -// // Collect pairs of object_layer + support_layer sorted by print_z. -// // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. -// std::vector GCode::collect_layers_to_print(const PrintObject& object) -// { -// std::vector layers_to_print; -// layers_to_print.reserve(object.layers().size() + object.support_layers().size()); -// -// // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. -// // This is the same logic as in support generator. -// //FIXME should we use the printing extruders instead? -// double gap_over_supports = object.config().support_material_contact_distance; -// // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. -// assert(!object.config().support_material || gap_over_supports != 0. || object.config().support_material_synchronize_layers); -// if (gap_over_supports != 0.) { -// gap_over_supports = std::max(0., gap_over_supports); -// // Not a soluble support, -// double support_layer_height_min = 1000000.; -// for (auto lh : object.print()->config().min_layer_height.values) -// support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh)); -// gap_over_supports += support_layer_height_min; -// } -// -// // Pair the object layers with the support layers by z. -// size_t idx_object_layer = 0; -// size_t idx_support_layer = 0; -// const LayerToPrint* last_extrusion_layer = nullptr; -// while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { -// LayerToPrint layer_to_print; -// layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr; -// layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr; -// if (layer_to_print.object_layer && layer_to_print.support_layer) { -// if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) { -// layer_to_print.support_layer = nullptr; -// --idx_support_layer; -// } -// else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) { -// layer_to_print.object_layer = nullptr; -// --idx_object_layer; -// } -// } -// -//<<<<<<< HEAD -// layers_to_print.emplace_back(layer_to_print); -// -// // Check that there are extrusions on the very first layer. -// if (layers_to_print.size() == 1u) { -// if ((layer_to_print.object_layer && !layer_to_print.object_layer->has_extrusions()) -// || (layer_to_print.support_layer && !layer_to_print.support_layer->has_extrusions())) -// throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); -//======= -// layers_to_print.emplace_back(layer_to_print); -// -// bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) -// || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); -// -// // Check that there are extrusions on the very first layer. -// if (layers_to_print.size() == 1u) { -// if (! has_extrusions) -// throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); -// } -// -// // In case there are extrusions on this layer, check there is a layer to lay it on. -// if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) -// // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. -// || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { -// double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer) -// ? gap_over_supports -// : 0.; -// double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) -// + layer_to_print.layer()->height -// + support_contact_z; -// // Negative support_contact_z is not taken into account, it can result in false positives in cases -// // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) -// -// if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { -// const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, -// _(L("Empty layers detected, the output would not be printable.")) + "\n\n" + -// _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + -// std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " -// "usually caused by negligibly small extrusions or by a faulty model. Try to repair " -// "the model or change its orientation on the bed."))); -//>>>>>>> b587289c141022323753fa1810552964de0b1356 -// } -// -// // In case there are extrusions on this layer, check there is a layer to lay it on. -// if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) -// // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. -// || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { -// double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer) -// ? gap_over_supports -// : 0.; -// double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) -// + layer_to_print.layer()->height -// + support_contact_z; -// // Negative support_contact_z is not taken into account, it can result in false positives in cases -// // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) -// -// // Only check this layer in case it has some extrusions. -// bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) -// || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); -// -// if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { -// const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, -// _(L("Empty layers detected, the output would not be printable.")) + "\n\n" + -// _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + -// std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " -// "usually caused by negligibly small extrusions or by a faulty model. Try to repair " -// "the model or change its orientation on the bed."))); -// } -// -// // Remember last layer with extrusions. -// if (has_extrusions) -// last_extrusion_layer = &layers_to_print.back(); -// } -// } -// -// return layers_to_print; -// } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z // will be printed for all objects at once. // Return a list of items. @@ -3332,36 +3210,34 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // adds analyzer tags and updates analyzer's tracking data #if !ENABLE_GCODE_VIEWER - if (m_enable_analyzer) - { + if (m_enable_analyzer) { #endif // !ENABLE_GCODE_VIEWER - // PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width - // so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines #if ENABLE_GCODE_VIEWER + // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines without updating m_last_height and m_last_width + // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); #else + // PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width + // so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines bool last_was_wipe_tower = (m_last_analyzer_extrusion_role == erWipeTower); #endif // ENABLE_GCODE_VIEWER char buf[64]; #if ENABLE_GCODE_VIEWER - if (path.role() != m_last_processor_extrusion_role) - { + if (path.role() != m_last_processor_extrusion_role) { m_last_processor_extrusion_role = path.role(); sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), int(m_last_processor_extrusion_role)); gcode += buf; } #else - if (path.role() != m_last_analyzer_extrusion_role) - { + if (path.role() != m_last_analyzer_extrusion_role) { m_last_analyzer_extrusion_role = path.role(); sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role)); gcode += buf; } #endif // ENABLE_GCODE_VIEWER - if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) - { + if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); @@ -3372,8 +3248,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, #endif // ENABLE_GCODE_VIEWER } - if (last_was_wipe_tower || (m_last_width != path.width)) - { + if (last_was_wipe_tower || (m_last_width != path.width)) { m_last_width = path.width; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); @@ -3384,8 +3259,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, #endif // ENABLE_GCODE_VIEWER } - if (last_was_wipe_tower || (m_last_height != path.height)) - { + if (last_was_wipe_tower || (m_last_height != path.height)) { m_last_height = path.height; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_last_height); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 67ed7c699b..83cbb876ef 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -671,15 +671,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) EMoveType type = EMoveType::Noop; if (delta_pos[E] < 0.0f) { - if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) - type = EMoveType::Travel; - else - type = EMoveType::Retract; - } + type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; + } else if (delta_pos[E] > 0.0f) { if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f) type = EMoveType::Unretract; - else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f)) + else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f) type = EMoveType::Extrude; } else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) @@ -730,8 +727,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) return (sq_xyz_length > 0.0f) ? std::sqrt(sq_xyz_length) : std::abs(delta_pos[E]); }; - auto is_extruder_only_move = [](const AxisCoords& delta_pos) { - return (delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f) && (delta_pos[E] != 0.0f); + auto is_extrusion_only_move = [](const AxisCoords& delta_pos) { + return delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f && delta_pos[E] != 0.0f; }; float distance = move_length(delta_pos); @@ -781,7 +778,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } // calculates block acceleration - float acceleration = is_extruder_only_move(delta_pos) ? + float acceleration = is_extrusion_only_move(delta_pos) ? get_retract_acceleration(static_cast(i)) : get_acceleration(static_cast(i)); From f7119c42f46b76aea67d914f48be663e4ab0a742 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jul 2020 13:23:44 +0200 Subject: [PATCH 182/826] PresetComboBox class:: code refactoring --- src/slic3r/GUI/PresetComboBoxes.cpp | 165 ++++++++++------------------ src/slic3r/GUI/PresetComboBoxes.hpp | 4 + 2 files changed, 65 insertions(+), 104 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index e527ef9c90..4cf9ac40b6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -142,11 +142,36 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) this->SetClientData(item, (void*)label_item_type); } +void PresetComboBox::invalidate_selection() +{ + m_last_selected = INT_MAX; // this value means that no one item is selected +} + +void PresetComboBox::validate_selection(bool predicate/*=false*/) +{ + if (predicate || + // just in case: mark m_last_selected as a first added element + m_last_selected == INT_MAX) + m_last_selected = GetCount() - 1; +} + +void PresetComboBox::update_selection() +{ + /* If selected_preset_item is still equal to INT_MAX, it means that + * there is no presets added to the list. + * So, select last combobox item ("Add/Remove preset") + */ + validate_selection(); + + SetSelection(m_last_selected); + SetToolTip(GetString(m_last_selected)); +} + void PresetComboBox::update(const std::string& select_preset_name) { Freeze(); Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + invalidate_selection(); const std::deque& presets = m_collection->get_presets(); @@ -164,18 +189,13 @@ void PresetComboBox::update(const std::string& select_preset_name) // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = true; - std::string bitmap_key = "tab"; - std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - + std::string bitmap_key = "cb"; if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) bitmap_key += "_sla"; - if (!is_enabled) - bitmap_key += "_disabled"; } - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); @@ -184,10 +204,7 @@ void PresetComboBox::update(const std::string& select_preset_name) int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (preset.name == select_preset_name ||//i == idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(preset.name == select_preset_name); } else { @@ -207,25 +224,12 @@ void PresetComboBox::update(const std::string& select_preset_name) bool is_enabled = it->second.second; if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(it->first == selected); } } - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - - SetSelection(selected_preset_item); - SetToolTip(GetString(selected_preset_item)); + update_selection(); Thaw(); - - m_last_selected = selected_preset_item; } void PresetComboBox::msw_rescale() @@ -272,6 +276,12 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/, std::string filament_rgb/* = ""*/, std::string extruder_rgb/* = ""*/) { + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += is_compatible ? ",cmpt" : ",ncmpt"; + + bitmap_key += is_system ? ",syst" : ",nsyst"; bitmap_key += ",h" + std::to_string(icon_height); wxBitmap* bmp = bitmap_cache().find(bitmap_key); @@ -313,6 +323,9 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, bool is_enabled/* = true*/, bool is_compatible/* = true*/, bool is_system/* = false*/) { + bitmap_key += !is_enabled ? "_disabled" : ""; + bitmap_key += is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += is_system ? ",syst" : ",nsyst"; bitmap_key += ",h" + std::to_string(icon_height); wxBitmap* bmp = bitmap_cache().find(bitmap_key); @@ -645,7 +658,7 @@ void PlaterPresetComboBox::update() // Otherwise fill in the list from scratch. this->Freeze(); this->Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + invalidate_selection(); const Preset* selected_filament_preset; std::string extruder_color; @@ -700,12 +713,6 @@ void PlaterPresetComboBox::update() bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb; } - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, preset.is_compatible, preset.is_system || preset.is_default, single_bar, filament_rgb, extruder_rgb); @@ -714,12 +721,9 @@ void PlaterPresetComboBox::update() const std::string name = preset.alias.empty() ? preset.name : preset.alias; if (preset.is_default || preset.is_system) { Append(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); - if (is_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) { - selected_preset_item = GetCount() - 1; + validate_selection(is_selected); + if (is_selected) tooltip = wxString::FromUTF8(preset.name.c_str()); - } } else { @@ -737,10 +741,7 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { Append(it->first, *it->second); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(it->first == selected); } } @@ -757,32 +758,18 @@ void PlaterPresetComboBox::update() if (!preset) continue; std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - if (wide_icons) - bitmap_key += ",cmpt"; - bitmap_key += ",nsyst"; - - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, main_icon_name); + wxBitmap* bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); assert(bmp); set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - if (ph_printers.is_selected(it, preset_name) || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(ph_printers.is_selected(it, preset_name)); } } } } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "edit_preset_list"; - - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, "edit_uni"); + wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); if (m_type == Preset::TYPE_SLA_MATERIAL) @@ -791,18 +778,12 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - - SetSelection(selected_preset_item); - SetToolTip(tooltip.IsEmpty() ? GetString(selected_preset_item) : tooltip); - m_last_selected = selected_preset_item; + update_selection(); Thaw(); + if (!tooltip.IsEmpty()) + SetToolTip(tooltip); + // Update control min size after rescale (changed Display DPI under MSW) if (GetMinWidth() != 20 * m_em_unit) SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight())); @@ -858,7 +839,7 @@ void TabPresetComboBox::update() { Freeze(); Clear(); - size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected + invalidate_selection(); const std::deque& presets = m_collection->get_presets(); @@ -890,18 +871,13 @@ void TabPresetComboBox::update() if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt; - std::string bitmap_key = "tab"; - std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - + std::string bitmap_key = "tab"; if (m_type == Preset::TYPE_PRINTER) { bitmap_key += "_printer"; if (preset.printer_technology() == ptSLA) bitmap_key += "_sla"; - if (!is_enabled) - bitmap_key += "_disabled"; } - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); @@ -910,10 +886,7 @@ void TabPresetComboBox::update() int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (i == idx_selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(i == idx_selected); } else { @@ -933,10 +906,7 @@ void TabPresetComboBox::update() bool is_enabled = it->second.second; if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - if (it->first == selected || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(it->first == selected); } } @@ -953,39 +923,26 @@ void TabPresetComboBox::update() if (!preset) continue; std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - std::string bitmap_key = main_icon_name + ",nsyst"; - wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "", true, true, false); + wxBitmap* bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); assert(bmp); set_label_marker(Append(wxString::FromUTF8((it->get_full_name(preset_name) + (preset->is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); - if (ph_printers.is_selected(it, preset_name) || - // just in case: mark selected_preset_item as a first added element - selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; + validate_selection(ph_printers.is_selected(it, preset_name)); } } } // add "Add/Remove printers" item - wxBitmap* bmp = get_bmp("edit_preset_list", m_main_bitmap_name, "edit_uni", true, true, true); + std::string icon_name = "edit_uni"; + wxBitmap* bmp = get_bmp("edit_preset_list, tab,", icon_name, ""); assert(bmp); set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); } - /* But, if selected_preset_item is still equal to INT_MAX, it means that - * there is no presets added to the list. - * So, select last combobox item ("Add/Remove preset") - */ - if (selected_preset_item == INT_MAX) - selected_preset_item = GetCount() - 1; - - SetSelection(selected_preset_item); - SetToolTip(GetString(selected_preset_item)); + update_selection(); Thaw(); - - m_last_selected = selected_preset_item; } void TabPresetComboBox::msw_rescale() diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 89d043f7bb..9a70818e15 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -91,6 +91,10 @@ protected: int thin_space_icon_width; int wide_space_icon_width; + void invalidate_selection(); + void validate_selection(bool predicate = false); + void update_selection(); + #ifdef __linux__ static const char* separator_head() { return "------- "; } static const char* separator_tail() { return " -------"; } From 2f43c1f3fa7473f5fe6c5a6b8baa7766fbf0756c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jul 2020 15:52:01 +0200 Subject: [PATCH 183/826] Fixed update of the TreeCtrls and "revert to system values" buttons on preset tabs, if application was run in New or Dlg mode --- src/slic3r/GUI/MainFrame.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 10d9d800f9..64a1319d44 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1527,6 +1527,7 @@ void MainFrame::load_config(const DynamicPrintConfig& config) void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { + bool tabpanel_was_hidden = false; #if ENABLE_LAYOUT_NO_RESTART if (m_layout == ESettingsLayout::Dlg) { #else @@ -1557,6 +1558,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) if (m_settings_dialog.IsShown()) m_settings_dialog.SetFocus(); else { + tabpanel_was_hidden = true; m_tabpanel->Show(); m_settings_dialog.Show(); } @@ -1576,6 +1578,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) #if ENABLE_LAYOUT_NO_RESTART else if (m_layout == ESettingsLayout::New) { m_main_sizer->Show(m_plater, tab == 0); + tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel); m_main_sizer->Show(m_tabpanel, tab != 0); #else else if (m_layout == slNew) { @@ -1589,6 +1592,14 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) Layout(); } + // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning + // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, + // which are used for update TreeCtrl and "revert_buttons". + // So, force the call of this function for Tabs, if tab panel was hidden + if (tabpanel_was_hidden) + for (auto tab : wxGetApp().tabs_list) + tab->update_changed_tree_ui(); + // when tab == -1, it means we should show the last selected tab #if ENABLE_LAYOUT_NO_RESTART m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab); From 299b783601c0584992aafb00689718c1ab51627e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jul 2020 16:28:34 +0200 Subject: [PATCH 184/826] PhysicalPrinterDialog: Select first related preset for the printer, if printer was just created or previously selected preset doesn't exist now --- src/slic3r/GUI/PresetComboBoxes.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 4cf9ac40b6..2c2b1d7a3d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1479,10 +1479,12 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) // save new physical printer printers.save_printer(m_printer, renamed_from); - printers.select_printer(m_printer); - - // refresh preset list on Printer Settings Tab - wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice(); + if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) { + // select first preset for this printer + printers.select_printer(m_printer); + // refresh preset list on Printer Settings Tab + wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); + } event.Skip(); } From a4c12b90f19786c00efb909819718f4801c47170 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 23 Jul 2020 12:17:18 +0200 Subject: [PATCH 185/826] PhysicalPrinterCollection: Use select_preset() instead of select_preset_by_name() + changed signature for select_preset() --- src/libslic3r/Preset.cpp | 31 +++++++++++------------------ src/libslic3r/Preset.hpp | 14 ++++++------- src/libslic3r/PresetBundle.cpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 4 ++-- src/slic3r/GUI/Tab.cpp | 2 +- 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ae74bffd75..7e30831fe5 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1731,34 +1731,27 @@ std::string PhysicalPrinterCollection::get_selected_full_printer_name() const return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); } -PhysicalPrinter& PhysicalPrinterCollection::select_printer_by_name(const std::string& full_name) +void PhysicalPrinterCollection::select_printer(const std::string& full_name) { std::string printer_name = PhysicalPrinter::get_short_name(full_name); auto it = this->find_printer_internal(printer_name); - assert(it != m_printers.end()); + if (it == m_printers.end()) { + unselect_printer(); + return; + } // update idx_selected - m_idx_selected = it - m_printers.begin(); + m_idx_selected = it - m_printers.begin(); + // update name of the currently selected preset - m_selected_preset = it->get_preset_name(full_name); - if (m_selected_preset.empty()) + if (printer_name == full_name) + // use first preset in the list m_selected_preset = *it->preset_names.begin(); - return *it; + else + m_selected_preset = it->get_preset_name(full_name); } -PhysicalPrinter& PhysicalPrinterCollection::select_printer(const std::string& printer_name) -{ - auto it = this->find_printer_internal(printer_name); - assert(it != m_printers.end()); - - // update idx_selected - m_idx_selected = it - m_printers.begin(); - // update name of the currently selected preset - m_selected_preset = *it->preset_names.begin(); - return *it; -} - -PhysicalPrinter& PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer) +void PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer) { return select_printer(printer.name); } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 98b805b4e4..6b5a2a5112 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -666,13 +666,13 @@ public: // Returns the printer model of the selected preset, or an empty string if no preset is selected. std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : m_selected_preset; } - // select printer with name and return reference on it - PhysicalPrinter& select_printer_by_name(const std::string& full_name); - PhysicalPrinter& select_printer(const std::string &printer_name); - PhysicalPrinter& select_printer(const PhysicalPrinter& printer); - bool has_selection() const; - void unselect_printer() ; - bool is_selected(ConstIterator it, const std::string &preset_name) const; + // Select printer by the full printer name, which contains name of printer, separator and name of selected preset + // If full_name doesn't contain name of selected preset, then select first preset in the list for this printer + void select_printer(const std::string& full_name); + void select_printer(const PhysicalPrinter& printer); + bool has_selection() const; + void unselect_printer() ; + bool is_selected(ConstIterator it, const std::string &preset_name) const; // Return a printer by an index. If the printer is active, a temporary copy is returned. PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d074c77d7d..108985704c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -436,7 +436,7 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr // Activate physical printer from the config if (!initial_physical_printer_name.empty()) - physical_printers.select_printer_by_name(initial_physical_printer_name); + physical_printers.select_printer(initial_physical_printer_name); } // Export selections (current print, current filaments, current printer) into config.ini diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 2c2b1d7a3d..f6a2a036bc 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -366,7 +366,7 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers() else old_printer_preset = m_collection->get_edited_preset().name; // Select related printer preset on the Printer Settings Tab - physical_printers.select_printer_by_name(selected_string); + physical_printers.select_printer(selected_string); std::string preset_name = physical_printers.get_selected_printer_preset_name(); // if new preset wasn't selected, there is no need to call update preset selection @@ -1031,7 +1031,7 @@ void TabPresetComboBox::update_physical_printers( const std::string& preset_name dialog.ShowModal(); } - physical_printers.select_printer_by_name(printer.get_full_name(preset_name)); + physical_printers.select_printer(printer.get_full_name(preset_name)); } } else diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e498f56b77..dd63dc1412 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3100,7 +3100,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (!last_selected_ph_printer_name.empty() && m_presets->get_edited_preset().name == PhysicalPrinter::get_preset_name(last_selected_ph_printer_name)) { // If preset selection was canceled and previously was selected physical printer, we should select it back - m_preset_bundle->physical_printers.select_printer_by_name(last_selected_ph_printer_name); + m_preset_bundle->physical_printers.select_printer(last_selected_ph_printer_name); } } From 257e77ed407a13cd43eb9f233ee40d53d29c701e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 23 Jul 2020 12:44:08 +0200 Subject: [PATCH 186/826] Fixed a typo --- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f7689c6e95..1b7278bd7f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -641,7 +641,7 @@ void GUI_App::check_printer_presets() wxString msg_text = _L("You have presets with saved options for \"Print Host upload\".\n" "But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" "Now, this information will be exposed in physical printers settings.") + "\n\n" + - _L("Enter the name for the Printer device used by defaul during its creation.\n" + _L("Enter the name for the Printer device used by default during its creation.\n" "Note: This name can be changed later from the physical printers settings") + ":"; wxString msg_header = _L("Name for printer device"); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index dd63dc1412..3f566eacb3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2981,7 +2981,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, if (!physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name)) { wxMessageDialog dialog(nullptr, _L("There is/are a physical printer(s), which has/have one and only this printer preset.\n" - "This/Those printer(s) will be deletede after deleting of the selected preset.\n" + "This/Those printer(s) will be deleted after deleting of the selected preset.\n" "Are you sure you want to delete the selected preset?"), _L("Warning"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); if (dialog.ShowModal() == wxID_NO) return; From fd50c3d262942338eac2abaab0c3e107d512a7ae Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Jul 2020 11:21:49 +0200 Subject: [PATCH 187/826] Fixed a bug in selection from the 3D scene. Steps to the reproduce a crash: 1. In SLA mode add some object with several instances 2. Slice 3. Back to 3Dview scene, select all using Ctrl+A 4. Press "Delete" --- src/slic3r/GUI/GUI_ObjectList.cpp | 5 +++-- src/slic3r/GUI/Selection.cpp | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a47c8f6711..f3ff264ced 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2364,8 +2364,9 @@ void ObjectList::del_layers_from_object(const int obj_idx) bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { - if (obj_idx == 1000) - // Cannot delete a wipe tower. + assert(idx >= 0); + if (obj_idx == 1000 || idx<0) + // Cannot delete a wipe tower or volume with negative id return false; ModelObject* object = (*m_objects)[obj_idx]; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 69550748d8..d76c2c7121 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1605,10 +1605,11 @@ void Selection::update_type() if ((*m_volumes)[i]->volume_idx() < 0) ++sla_volumes_count; } - unsigned int volumes_count = model_volumes_count + sla_volumes_count; + // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is + unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (volumes_count * instances_count == (unsigned int)m_list.size()) + if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullObject; // ensures the correct mode is selected @@ -1616,7 +1617,7 @@ void Selection::update_type() } else if (selected_instances_count == 1) { - if (volumes_count == (unsigned int)m_list.size()) + if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullInstance; // ensures the correct mode is selected @@ -1639,7 +1640,7 @@ void Selection::update_type() requires_disable = true; } } - else if ((selected_instances_count > 1) && (selected_instances_count * volumes_count == (unsigned int)m_list.size())) + else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) { m_type = MultipleFullInstance; // ensures the correct mode is selected From 0280a2a15bcb10d3ba53692ab60a33713390aba8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Jul 2020 13:02:46 +0200 Subject: [PATCH 188/826] Hot fix for the last commit --- src/slic3r/GUI/Selection.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index d76c2c7121..e9250fe9e4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1595,17 +1595,17 @@ void Selection::update_type() } else { + unsigned int sla_volumes_count = 0; + // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is + for (unsigned int i : m_list) { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + if (m_cache.content.size() == 1) // single object { const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int sla_volumes_count = 0; - for (unsigned int i : m_list) - { - if ((*m_volumes)[i]->volume_idx() < 0) - ++sla_volumes_count; - } - // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); @@ -1657,7 +1657,7 @@ void Selection::update_type() unsigned int instances_count = (unsigned int)model_object->instances.size(); sels_cntr += volumes_count * instances_count; } - if (sels_cntr == (unsigned int)m_list.size()) + if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) { m_type = MultipleFullObject; // ensures the correct mode is selected From b155c3c4f829f3e77321939ebe356969ee35ce5f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 24 Jul 2020 16:34:25 +0200 Subject: [PATCH 189/826] PhysicalPrinterDialog :: next improvement --- src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 556 +++++++++++++++++++++++ src/slic3r/GUI/PhysicalPrinterDialog.hpp | 105 +++++ src/slic3r/GUI/PresetComboBoxes.cpp | 539 ++-------------------- src/slic3r/GUI/PresetComboBoxes.hpp | 89 +--- 5 files changed, 702 insertions(+), 591 deletions(-) create mode 100644 src/slic3r/GUI/PhysicalPrinterDialog.cpp create mode 100644 src/slic3r/GUI/PhysicalPrinterDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 49e0692858..20ea4e33a9 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -76,9 +76,11 @@ set(SLIC3R_GUI_SOURCES GUI/MainFrame.cpp GUI/MainFrame.hpp GUI/Plater.cpp + GUI/Plater.hpp GUI/PresetComboBoxes.hpp GUI/PresetComboBoxes.cpp - GUI/Plater.hpp + GUI/PhysicalPrinterDialog.hpp + GUI/PhysicalPrinterDialog.cpp GUI/GUI_ObjectList.cpp GUI/GUI_ObjectList.hpp GUI/GUI_ObjectManipulation.cpp diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp new file mode 100644 index 0000000000..7d3c92c138 --- /dev/null +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -0,0 +1,556 @@ +#include "PhysicalPrinterDialog.hpp" +#include "PresetComboBoxes.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "format.hpp" +#include "Tab.hpp" +#include "wxExtensions.hpp" +#include "PrintHostDialogs.hpp" +#include "../Utils/ASCIIFolding.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/FixModelByWin10.hpp" +#include "../Utils/UndoRedo.hpp" +#include "RemovableDriveManager.hpp" +#include "BitmapCache.hpp" +#include "BonjourDialog.hpp" + +using Slic3r::GUI::format_wxstr; + +//static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; + +namespace Slic3r { +namespace GUI { + +#define BORDER_W 10 + +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ + +PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) : + m_parent(parent) +{ + m_sizer = new wxBoxSizer(wxVERTICAL); + + m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross", "", wxDefaultSize, wxDefaultPosition, /*wxBU_LEFT | */wxBU_EXACTFIT); + m_delete_preset_btn->SetFont(wxGetApp().normal_font()); + m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); + m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); + + m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER); + m_presets_list->set_printer_technology(parent->get_printer_technology()); + + m_presets_list->set_selection_changed_function([this](int selection) { + std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); + Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); + assert(preset); + Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + if (preset->name == edited_preset.name) + preset = &edited_preset; + + // if created physical printer doesn't have any settings, use the settings from the selected preset + if (m_parent->get_printer()->has_empty_config()) { + // update Print Host upload from the selected preset + m_parent->get_printer()->update_from_preset(*preset); + // update values in parent (PhysicalPrinterDialog) + m_parent->update(); + } + + // update PrinterTechnology if it was changed + if (m_presets_list->set_printer_technology(preset->printer_technology())) + m_parent->set_printer_technology(preset->printer_technology()); + + update_full_printer_name(); + }); + m_presets_list->update(preset_name); + + m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); + + m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); + m_full_printer_name->SetFont(wxGetApp().bold_font()); + + wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL); + preset_sizer->Add(m_presets_list , 1, wxEXPAND); + preset_sizer->Add(m_delete_preset_btn , 0, wxEXPAND | wxLEFT, BORDER_W); + + wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL); + name_sizer->Add(m_info_line, 0, wxEXPAND); + name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_sizer->Add(preset_sizer , 0, wxEXPAND); + m_sizer->Add(name_sizer, 0, wxEXPAND); +} + +PresetForPrinter::~PresetForPrinter() +{ + m_presets_list->Destroy(); + m_delete_preset_btn->Destroy(); + m_info_line->Destroy(); + m_full_printer_name->Destroy(); +} + +void PresetForPrinter::DeletePreset(wxEvent& event) +{ + m_parent->DeletePreset(this); +} + +void PresetForPrinter::update_full_printer_name() +{ + wxString printer_name = m_parent->get_printer_name(); + wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection()); + + m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); +} + +std::string PresetForPrinter::get_preset_name() +{ + return into_u8(m_presets_list->GetString(m_presets_list->GetSelection())); +} + +void PresetForPrinter::SuppressDelete() +{ + m_delete_preset_btn->Enable(false); + + // this case means that now we have only one related preset for the printer + // So, allow any selection + m_presets_list->set_printer_technology(ptAny); + m_presets_list->update(); +} + +void PresetForPrinter::AllowDelete() +{ + if (!m_delete_preset_btn->IsEnabled()) + m_delete_preset_btn->Enable(); + + m_presets_list->set_printer_technology(m_parent->get_printer_technology()); + m_presets_list->update(); +} + +void PresetForPrinter::msw_rescale() +{ + m_presets_list->msw_rescale(); + m_delete_preset_btn->msw_rescale(); +} + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + +PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) + : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + m_default_name = _L("My Printer Device"); + + if (printer_name.IsEmpty()) + printer_name = m_default_name; + else { + std::string full_name = into_u8(printer_name); + printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); + } + + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); + + m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, /*wxBU_LEFT | */wxBU_EXACTFIT); + m_add_preset_btn->SetFont(wxGetApp().normal_font()); + m_add_preset_btn->SetToolTip(_L("Add preset for this printer device")); + m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this); + + m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); + m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); + if (!printer) { + const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); + printer = new PhysicalPrinter(into_u8(printer_name), preset); + // if printer_name is empty it means that new printer is created, so enable all items in the preset list + m_presets.emplace_back(new PresetForPrinter(this, preset.name)); + } + else + { + const std::set& preset_names = printer->get_preset_names(); + for (const std::string& preset_name : preset_names) + m_presets.emplace_back(new PresetForPrinter(this, preset_name)); + } + assert(printer); + m_printer = *printer; + + if (m_presets.size() == 1) + m_presets.front()->SuppressDelete(); + + update_full_printer_names(); + + m_config = &m_printer.config; + + m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); + build_printhost_settings(m_optgroup); + //m_optgroup->reload_config(); + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); + + wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); + nameSizer->Add(m_printer_name, 1, wxEXPAND); + nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W); + + m_presets_sizer = new wxBoxSizer(wxVERTICAL); + for (PresetForPrinter* preset : m_presets) + m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_presets_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); + topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +PhysicalPrinterDialog::~PhysicalPrinterDialog() +{ + for (PresetForPrinter* preset : m_presets) { + delete preset; + preset = nullptr; + } +} + +void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) +{ + m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (opt_key == "authorization_type") + this->update(); + }; + + m_optgroup->append_single_option_line("host_type"); + + auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); + (*btn)->SetFont(wxGetApp().normal_font()); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(*btn); + return sizer; + }; + + auto printhost_browse = [=](wxWindow* parent) + { + auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { + BonjourDialog dialog(this, Preset::printer_technology(m_printer.config)); + if (dialog.show_and_lookup()) { + m_optgroup->set_value("print_host", std::move(dialog.get_selected()), true); + m_optgroup->get_field("print_host")->field_changed(); + } + }); + + return sizer; + }; + + auto print_host_test = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); + + m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (!host) { + const wxString text = _L("Could not get a valid Printer Host reference"); + show_error(this, text); + return; + } + wxString msg; + if (host->test(msg)) { + show_info(this, host->get_test_ok_msg(), _L("Success!")); + } + else { + show_error(this, host->get_test_failed_msg(msg)); + } + }); + + return sizer; + }; + + // Set a wider width for a better alignment + Option option = m_optgroup->get_option("print_host"); + option.opt.width = Field::def_width_wider(); + Line host_line = m_optgroup->create_single_option_line(option); + host_line.append_widget(printhost_browse); + host_line.append_widget(print_host_test); + m_optgroup->append_line(host_line); + + m_optgroup->append_single_option_line("authorization_type"); + + option = m_optgroup->get_option("printhost_apikey"); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + + const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."); + + if (Http::ca_file_supported()) { + option = m_optgroup->get_option("printhost_cafile"); + option.opt.width = Field::def_width_wider(); + Line cafile_line = m_optgroup->create_single_option_line(option); + + auto printhost_cafile_browse = [=](wxWindow* parent) { + auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); + m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) { + static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); + wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) { + m_optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); + m_optgroup->get_field("printhost_cafile")->field_changed(); + } + }); + + return sizer; + }; + + cafile_line.append_widget(printhost_cafile_browse); + m_optgroup->append_line(cafile_line); + + Line cafile_hint{ "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + m_optgroup->append_line(cafile_hint); + } + else { + Line line{ "", "" }; + line.full_width = 1; + + line.widget = [ca_file_hint](wxWindow* parent) { + std::string info = _u8L("HTTPS CA File") + ":\n\t" + + (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + + "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); + + //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); + auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); + txt->SetFont(wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt, 1, wxEXPAND); + return sizer; + }; + + m_optgroup->append_line(line); + } + + for (const std::string& opt_key : std::vector{ "login", "password" }) { + option = m_optgroup->get_option(opt_key); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + } + + update(); +} + +void PhysicalPrinterDialog::update() +{ + m_optgroup->reload_config(); + + const PrinterTechnology tech = Preset::printer_technology(*m_config); + // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) + if (tech == ptFFF) { + m_optgroup->show_field("host_type"); + m_optgroup->hide_field("authorization_type"); + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->hide_field(opt_key); + } + else { + m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); + m_optgroup->hide_field("host_type"); + + m_optgroup->show_field("authorization_type"); + + AuthorizationType auth_type = m_config->option>("authorization_type")->value; + m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); + + for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); + } + + this->Layout(); +} + + +wxString PhysicalPrinterDialog::get_printer_name() +{ + return m_printer_name->GetValue(); +} + +void PhysicalPrinterDialog::update_full_printer_names() +{ + for (PresetForPrinter* preset : m_presets) + preset->update_full_printer_name(); + + this->Layout(); +} + +void PhysicalPrinterDialog::set_printer_technology(PrinterTechnology pt) +{ + m_config->set_key_value("printer_technology", new ConfigOptionEnum(pt)); + update(); +} + +PrinterTechnology PhysicalPrinterDialog::get_printer_technology() +{ + return m_printer.printer_technology(); +} + +void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + m_printhost_browse_btn->msw_rescale(); + m_printhost_test_btn->msw_rescale(); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->msw_rescale(); + + m_optgroup->msw_rescale(); + + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + + for (PresetForPrinter* preset : m_presets) + preset->msw_rescale(); + + const wxSize& size = wxSize(45 * em, 35 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + +void PhysicalPrinterDialog::OnOK(wxEvent& event) +{ + wxString printer_name = m_printer_name->GetValue(); + if (printer_name.IsEmpty()) { + warning_catcher(this, _L("The supplied name is empty. It can't be saved.")); + return; + } + if (printer_name == m_default_name) { + warning_catcher(this, _L("You should to change a name of your printer device. It can't be saved.")); + return; + } + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name)); + if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) + { + wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str()); + msg_text += "\n" + _L("Replace?"); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_NO) + return; + } + + std::set repeat_presets; + m_printer.reset_presets(); + for (PresetForPrinter* preset : m_presets) { + if (!m_printer.add_preset(preset->get_preset_name())) + repeat_presets.emplace(preset->get_preset_name()); + } + + if (!repeat_presets.empty()) + { + wxString repeatable_presets = "\n"; + for (const std::string& preset_name : repeat_presets) + repeatable_presets += " " + from_u8(preset_name) + "\n"; + repeatable_presets += "\n"; + + wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" + "Should I add it(they) just once for the printer \"%2%\" and close the Editing Dialog?")) % repeatable_presets % printer_name).str()); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dialog.ShowModal() == wxID_NO) + return; + } + + std::string renamed_from; + // temporary save previous printer name if it was edited + if (m_printer.name != _u8L("My Printer Device") && + m_printer.name != into_u8(printer_name)) + renamed_from = m_printer.name; + + //update printer name, if it was changed + m_printer.set_name(into_u8(printer_name)); + + // save new physical printer + printers.save_printer(m_printer, renamed_from); + + if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) { + // select first preset for this printer + printers.select_printer(m_printer); + // refresh preset list on Printer Settings Tab + wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); + } + + event.Skip(); +} + +void PhysicalPrinterDialog::AddPreset(wxEvent& event) +{ + m_presets.emplace_back(new PresetForPrinter(this)); + // enable DELETE button for the first preset, if was disabled + m_presets.front()->AllowDelete(); + + m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); + update_full_printer_names(); + + this->Fit(); +} + +void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) +{ + if (m_presets.size() == 1) { + wxString msg_text = _L("It's not possible to delete last related preset for the printer."); + wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + return; + } + + assert(preset_for_printer); + auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer); + if (it == m_presets.end()) + return; + + const int remove_id = it - m_presets.begin(); + m_presets_sizer->Remove(remove_id); + delete preset_for_printer; + m_presets.erase(it); + + if (m_presets.size() == 1) + m_presets.front()->SuppressDelete(); + + this->Layout(); + this->Fit(); +} + + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp new file mode 100644 index 0000000000..3d0cf2d9f2 --- /dev/null +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -0,0 +1,105 @@ +#ifndef slic3r_PhysicalPrinterDialog_hpp_ +#define slic3r_PhysicalPrinterDialog_hpp_ + +#include + +#include + +#include "libslic3r/Preset.hpp" +#include "GUI_Utils.hpp" + +class wxString; +class wxTextCtrl; +class wxStaticText; +class ScalableButton; +class wxBoxSizer; + +namespace Slic3r { + +namespace GUI { + +class PresetComboBox; + +//------------------------------------------ +// PresetForPrinter +//------------------------------------------ +//static std::string g_info_string = " (modified)"; +class PhysicalPrinterDialog; +class PresetForPrinter +{ + PhysicalPrinterDialog* m_parent { nullptr }; + + PresetComboBox* m_presets_list { nullptr }; + ScalableButton* m_delete_preset_btn { nullptr }; + wxStaticText* m_info_line { nullptr }; + wxStaticText* m_full_printer_name { nullptr }; + + wxBoxSizer* m_sizer { nullptr }; + + void DeletePreset(wxEvent& event); + +public: + PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = ""); + ~PresetForPrinter(); + + wxBoxSizer* sizer() { return m_sizer; } + void update_full_printer_name(); + std::string get_preset_name(); + void SuppressDelete(); + void AllowDelete(); + + void msw_rescale(); + void on_sys_color_changed() {}; +}; + + +//------------------------------------------ +// PhysicalPrinterDialog +//------------------------------------------ + +class ConfigOptionsGroup; +class PhysicalPrinterDialog : public DPIDialog +{ + PhysicalPrinter m_printer; + wxString m_default_name; + DynamicPrintConfig* m_config { nullptr }; + + wxTextCtrl* m_printer_name { nullptr }; + std::vector m_presets; + + ConfigOptionsGroup* m_optgroup { nullptr }; + + ScalableButton* m_add_preset_btn {nullptr}; + ScalableButton* m_printhost_browse_btn {nullptr}; + ScalableButton* m_printhost_test_btn {nullptr}; + ScalableButton* m_printhost_cafile_browse_btn {nullptr}; + + wxBoxSizer* m_presets_sizer {nullptr}; + + void build_printhost_settings(ConfigOptionsGroup* optgroup); + void OnOK(wxEvent& event); + void AddPreset(wxEvent& event); + +public: + PhysicalPrinterDialog(wxString printer_name); + ~PhysicalPrinterDialog(); + + void update(); + wxString get_printer_name(); + void update_full_printer_names(); + PhysicalPrinter* get_printer() {return &m_printer; } + void set_printer_technology(PrinterTechnology pt); + PrinterTechnology get_printer_technology(); + + void DeletePreset(PresetForPrinter* preset_for_printer); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override {}; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index f6a2a036bc..3edc3947a1 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -24,20 +24,15 @@ #include "MainFrame.hpp" #include "format.hpp" #include "Tab.hpp" -#include "PrintHostDialogs.hpp" #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" -#include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" -#include "RemovableDriveManager.hpp" #include "BitmapCache.hpp" -#include "BonjourDialog.hpp" +#include "PhysicalPrinterDialog.hpp" using Slic3r::GUI::format_wxstr; -static const std::pair THUMBNAIL_SIZE_3MF = { 256, 256 }; - namespace Slic3r { namespace GUI { @@ -142,6 +137,15 @@ void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) this->SetClientData(item, (void*)label_item_type); } +bool PresetComboBox::set_printer_technology(PrinterTechnology pt) +{ + if (printer_technology != pt) { + printer_technology = pt; + return true; + } + return false; +} + void PresetComboBox::invalidate_selection() { m_last_selected = INT_MAX; // this value means that no one item is selected @@ -187,7 +191,7 @@ void PresetComboBox::update(const std::string& select_preset_name) continue; // marker used for disable incompatible printer models for the selected physical printer - bool is_enabled = true; + bool is_enabled = m_type == Preset::TYPE_PRINTER && printer_technology != ptAny ? preset.printer_technology() == printer_technology : true; std::string bitmap_key = "cb"; if (m_type == Preset::TYPE_PRINTER) { @@ -204,13 +208,13 @@ void PresetComboBox::update(const std::string& select_preset_name) int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(preset.name == select_preset_name); + validate_selection(preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)); } else { std::pair pair(bmp, is_enabled); nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); - if (preset.name == select_preset_name) + if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } if (i + 1 == m_collection->num_default_presets()) @@ -232,6 +236,11 @@ void PresetComboBox::update(const std::string& select_preset_name) Thaw(); } +void PresetComboBox::update() +{ + this->update(into_u8(this->GetString(this->GetSelection()))); +} + void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); @@ -547,7 +556,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) { // In a case of a physical printer, for its editing open PhysicalPrinterDialog - if (m_type == Preset::TYPE_PRINTER && this->is_selected_physical_printer()) { + if (m_type == Preset::TYPE_PRINTER/* && this->is_selected_physical_printer()*/) { this->show_edit_menu(); return; } @@ -598,7 +607,7 @@ void PlaterPresetComboBox::show_add_menu() { wxMenu* menu = new wxMenu(); - append_menu_item(menu, wxID_ANY, _L("Add/Remove logical printers"), "", + append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "", [this](wxCommandEvent&) { wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); @@ -617,9 +626,10 @@ void PlaterPresetComboBox::show_edit_menu() { wxMenu* menu = new wxMenu(); - append_menu_item(menu, wxID_ANY, _L("Edit related printer profile"), "", + append_menu_item(menu, wxID_ANY, _L("Edit preset"), "", [this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater()); + if (this->is_selected_physical_printer()) { append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "", [this](wxCommandEvent&) { PhysicalPrinterDialog dlg(this->GetString(this->GetSelection())); @@ -642,6 +652,12 @@ void PlaterPresetComboBox::show_edit_menu() wxGetApp().get_tab(m_type)->update_preset_choice(); update(); }, "cross", menu, []() { return true; }, wxGetApp().plater()); + } + else + append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "", + [this](wxCommandEvent&) { + wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); wxGetApp().plater()->PopupMenu(menu); } @@ -768,7 +784,7 @@ void PlaterPresetComboBox::update() } } - if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { + if (/*m_type == Preset::TYPE_PRINTER || */m_type == Preset::TYPE_SLA_MATERIAL) { wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); @@ -867,9 +883,6 @@ void TabPresetComboBox::update() // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = true; - // check this value just for printer presets, when physical printer is selected - if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) - is_enabled = m_enable_all ? true : preset.printer_technology() == proper_pt; std::string bitmap_key = "tab"; if (m_type == Preset::TYPE_PRINTER) { @@ -1040,494 +1053,6 @@ void TabPresetComboBox::update_physical_printers( const std::string& preset_name } -//------------------------------------------ -// PresetForPrinter -//------------------------------------------ - -PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) : - m_parent(parent) -{ - m_sizer = new wxBoxSizer(wxVERTICAL); - - m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); - m_delete_preset_btn->SetFont(wxGetApp().normal_font()); - m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device")); - m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this); - - m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER); - - m_presets_list->set_selection_changed_function([this](int selection) { - std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data()); - Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string); - assert(preset); - Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - if (preset->name == edited_preset.name) - preset = &edited_preset; - - // if created physical printer doesn't have any settings, use the settings from the selected preset - if (m_parent->get_printer()->has_empty_config()) { - // update Print Host upload from the selected preset - m_parent->get_printer()->update_from_preset(*preset); - // update values in parent (PhysicalPrinterDialog) - m_parent->update(); - } - - update_full_printer_name(); - }); - m_presets_list->update(preset_name); - - m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":"); - - m_full_printer_name = new wxStaticText(parent, wxID_ANY, ""); - m_full_printer_name->SetFont(wxGetApp().bold_font()); - - wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL); - preset_sizer->Add(m_presets_list , 1, wxEXPAND); - preset_sizer->Add(m_delete_preset_btn , 0, wxEXPAND | wxLEFT, BORDER_W); - - wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL); - name_sizer->Add(m_info_line, 0, wxEXPAND); - name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W); - - m_sizer->Add(preset_sizer , 0, wxEXPAND); - m_sizer->Add(name_sizer, 0, wxEXPAND); -} - -PresetForPrinter::~PresetForPrinter() -{ - m_presets_list->Destroy(); - m_delete_preset_btn->Destroy(); - m_info_line->Destroy(); - m_full_printer_name->Destroy(); -} - -void PresetForPrinter::DeletePreset(wxEvent& event) -{ - m_parent->DeletePreset(this); -} - -void PresetForPrinter::update_full_printer_name() -{ - wxString printer_name = m_parent->get_printer_name(); - wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection()); - - m_full_printer_name->SetLabelText(printer_name + " * " + preset_name); -} - -std::string PresetForPrinter::get_preset_name() -{ - return into_u8(m_presets_list->GetString(m_presets_list->GetSelection())); -} - -void PresetForPrinter::DisableDeleteBtn() -{ - m_delete_preset_btn->Enable(false); -} - -void PresetForPrinter::EnableDeleteBtn() -{ - if (!m_delete_preset_btn->IsEnabled()) - m_delete_preset_btn->Enable(); -} - -void PresetForPrinter::msw_rescale() -{ - m_presets_list->msw_rescale(); - m_delete_preset_btn->msw_rescale(); -} - - -//------------------------------------------ -// PhysicalPrinterDialog -//------------------------------------------ - -PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) - : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - SetFont(wxGetApp().normal_font()); - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - - m_default_name = _L("My Printer Device"); - - if (printer_name.IsEmpty()) - printer_name = m_default_name; - else { - std::string full_name = into_u8(printer_name); - printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); - } - - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); - - m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); - m_add_preset_btn->SetFont(wxGetApp().normal_font()); - m_add_preset_btn->SetToolTip(_L("Add preset for this printer device")); - m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this); - - m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize); - m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); }); - - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; - PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); - if (!printer) { - const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - printer = new PhysicalPrinter(into_u8(printer_name), preset); - // if printer_name is empty it means that new printer is created, so enable all items in the preset list - m_presets.emplace_back(new PresetForPrinter(this, preset.name)); - } - else - { - const std::set& preset_names = printer->get_preset_names(); - for (const std::string& preset_name : preset_names) - m_presets.emplace_back(new PresetForPrinter(this, preset_name)); - } - assert(printer); - m_printer = *printer; - - if (m_presets.size() == 1) - m_presets.front()->DisableDeleteBtn(); - - update_full_printer_names(); - - m_config = &m_printer.config; - - m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); - build_printhost_settings(m_optgroup); - //m_optgroup->reload_config(); - - wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); - wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); - btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this); - - wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL); - nameSizer->Add(m_printer_name, 1, wxEXPAND); - nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W); - - m_presets_sizer = new wxBoxSizer(wxVERTICAL); - for (PresetForPrinter* preset : m_presets) - m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); - - wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - - topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); - topSizer->Add(m_presets_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W); - topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); - - SetSizer(topSizer); - topSizer->SetSizeHints(this); -} - -PhysicalPrinterDialog::~PhysicalPrinterDialog() -{ - for (PresetForPrinter* preset : m_presets) { - delete preset; - preset = nullptr; - } -} - -void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) -{ - m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "authorization_type") - this->update(); - }; - - m_optgroup->append_single_option_line("host_type"); - - auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); - (*btn)->SetFont(wxGetApp().normal_font()); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(*btn); - return sizer; - }; - - auto printhost_browse = [=](wxWindow* parent) - { - auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots); - m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { - BonjourDialog dialog(this, Preset::printer_technology(m_printer.config)); - if (dialog.show_and_lookup()) { - m_optgroup->set_value("print_host", std::move(dialog.get_selected()), true); - m_optgroup->get_field("print_host")->field_changed(); - } - }); - - return sizer; - }; - - auto print_host_test = [=](wxWindow* parent) { - auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test")); - - m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - if (!host) { - const wxString text = _L("Could not get a valid Printer Host reference"); - show_error(this, text); - return; - } - wxString msg; - if (host->test(msg)) { - show_info(this, host->get_test_ok_msg(), _L("Success!")); - } - else { - show_error(this, host->get_test_failed_msg(msg)); - } - }); - - return sizer; - }; - - // Set a wider width for a better alignment - Option option = m_optgroup->get_option("print_host"); - option.opt.width = Field::def_width_wider(); - Line host_line = m_optgroup->create_single_option_line(option); - host_line.append_widget(printhost_browse); - host_line.append_widget(print_host_test); - m_optgroup->append_line(host_line); - - m_optgroup->append_single_option_line("authorization_type"); - - option = m_optgroup->get_option("printhost_apikey"); - option.opt.width = Field::def_width_wider(); - m_optgroup->append_single_option_line(option); - - const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate."); - - if (Http::ca_file_supported()) { - option = m_optgroup->get_option("printhost_cafile"); - option.opt.width = Field::def_width_wider(); - Line cafile_line = m_optgroup->create_single_option_line(option); - - auto printhost_cafile_browse = [=](wxWindow* parent) { - auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots); - m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) { - static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*"); - wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (openFileDialog.ShowModal() != wxID_CANCEL) { - m_optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); - m_optgroup->get_field("printhost_cafile")->field_changed(); - } - }); - - return sizer; - }; - - cafile_line.append_widget(printhost_cafile_browse); - m_optgroup->append_line(cafile_line); - - Line cafile_hint{ "", "" }; - cafile_hint.full_width = 1; - cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt); - return sizer; - }; - m_optgroup->append_line(cafile_hint); - } - else { - Line line{ "", "" }; - line.full_width = 1; - - line.widget = [ca_file_hint](wxWindow* parent) { - std::string info = _u8L("HTTPS CA File") + ":\n\t" + - (boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() + - "\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain."); - - //auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str())); - auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str())); - txt->SetFont(wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt, 1, wxEXPAND); - return sizer; - }; - - m_optgroup->append_line(line); - } - - for (const std::string& opt_key : std::vector{ "login", "password" }) { - option = m_optgroup->get_option(opt_key); - option.opt.width = Field::def_width_wider(); - m_optgroup->append_single_option_line(option); - } - - update(); -} - -void PhysicalPrinterDialog::update() -{ - m_optgroup->reload_config(); - - const PrinterTechnology tech = Preset::printer_technology(m_printer.config); - // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - if (tech == ptFFF) { - m_optgroup->show_field("host_type"); - m_optgroup->hide_field("authorization_type"); - for (const std::string& opt_key : std::vector{ "login", "password" }) - m_optgroup->hide_field(opt_key); - } - else { - m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); - m_optgroup->hide_field("host_type"); - - m_optgroup->show_field("authorization_type"); - - AuthorizationType auth_type = m_config->option>("authorization_type")->value; - m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); - - for (const std::string& opt_key : std::vector{ "login", "password" }) - m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); - } - - this->Layout(); -} - - -wxString PhysicalPrinterDialog::get_printer_name() -{ - return m_printer_name->GetValue(); -} - -void PhysicalPrinterDialog::update_full_printer_names() -{ - for (PresetForPrinter* preset : m_presets) - preset->update_full_printer_name(); - - this->Layout(); -} - -void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) -{ - const int& em = em_unit(); - - m_printhost_browse_btn->msw_rescale(); - m_printhost_test_btn->msw_rescale(); - if (m_printhost_cafile_browse_btn) - m_printhost_cafile_browse_btn->msw_rescale(); - - m_optgroup->msw_rescale(); - - msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - - for (PresetForPrinter* preset : m_presets) - preset->msw_rescale(); - - const wxSize& size = wxSize(45 * em, 35 * em); - SetMinSize(size); - - Fit(); - Refresh(); -} - -void PhysicalPrinterDialog::OnOK(wxEvent& event) -{ - wxString printer_name = m_printer_name->GetValue(); - if (printer_name.IsEmpty()) { - warning_catcher(this, _L("The supplied name is empty. It can't be saved.")); - return; - } - if (printer_name == m_default_name) { - warning_catcher(this, _L("You should to change a name of your printer device. It can't be saved.")); - return; - } - - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; - const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name)); - if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) - { - wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str()); - msg_text += "\n" + _L("Replace?"); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_NO) - return; - } - - std::set repeat_presets; - m_printer.reset_presets(); - for (PresetForPrinter* preset : m_presets) { - if (!m_printer.add_preset(preset->get_preset_name())) - repeat_presets.emplace(preset->get_preset_name()); - } - - if (!repeat_presets.empty()) - { - wxString repeatable_presets = "\n"; - for (const std::string& preset_name : repeat_presets) - repeatable_presets += " " + from_u8(preset_name) + "\n"; - repeatable_presets += "\n"; - - wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%" - "Should I add it(they) just once for the printer \"%2%\" and close the Editing Dialog?")) % repeatable_presets % printer_name).str()); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - if (dialog.ShowModal() == wxID_NO) - return; - } - - std::string renamed_from; - // temporary save previous printer name if it was edited - if (m_printer.name != _u8L("My Printer Device") && - m_printer.name != into_u8(printer_name)) - renamed_from = m_printer.name; - - //update printer name, if it was changed - m_printer.set_name(into_u8(printer_name)); - - // save new physical printer - printers.save_printer(m_printer, renamed_from); - - if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) { - // select first preset for this printer - printers.select_printer(m_printer); - // refresh preset list on Printer Settings Tab - wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); - } - - event.Skip(); -} - -void PhysicalPrinterDialog::AddPreset(wxEvent& event) -{ - m_presets.emplace_back(new PresetForPrinter(this)); - // enable DELETE button for the first preset, if was disabled - m_presets.front()->EnableDeleteBtn(); - - m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); - update_full_printer_names(); - - this->Fit(); -} - -void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) -{ - if (m_presets.size() == 1) { - wxString msg_text = _L("It's not possible to delete last related preset for the printer."); - wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - return; - } - - assert(preset_for_printer); - auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer); - if (it == m_presets.end()) - return; - - const int remove_id = it - m_presets.begin(); - m_presets_sizer->Remove(remove_id); - delete preset_for_printer; - m_presets.erase(it); - - if (m_presets.size() == 1) - m_presets.front()->DisableDeleteBtn(); - - this->Layout(); - this->Fit(); -} - - //----------------------------------------------- // ChangePresetForPhysicalPrinterDialog //----------------------------------------------- @@ -1548,9 +1073,9 @@ ChangePresetForPhysicalPrinterDialog::ChangePresetForPhysicalPrinterDialog(const wxStaticText* label_top = new wxStaticText(this, wxID_ANY, msg_text); label_top->SetFont(wxGetApp().bold_font()); - wxString choices[] = { from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()), - from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), - from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()) }; + wxString choices[] = { from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), + from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()), + from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()) }; wxRadioBox* selection_type_box = new wxRadioBox(this, wxID_ANY, _L("What would you like to do?"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 9a70818e15..6b8017f311 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -1,16 +1,12 @@ #ifndef slic3r_PresetComboBoxes_hpp_ #define slic3r_PresetComboBoxes_hpp_ -#include - -#include #include #include #include "libslic3r/Preset.hpp" #include "wxExtensions.hpp" #include "GUI_Utils.hpp" -//#include "BitmapCache.hpp" class wxString; class wxTextCtrl; @@ -23,6 +19,7 @@ namespace Slic3r { namespace GUI { class BitmapCache; + // --------------------------------- // *** PresetComboBox *** // --------------------------------- @@ -47,8 +44,9 @@ public: }; void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); + bool set_printer_technology(PrinterTechnology pt); - void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } + void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } bool is_selected_physical_printer(); @@ -58,7 +56,7 @@ public: void update(const std::string& select_preset); - virtual void update(){}; + virtual void update(); virtual void msw_rescale(); protected: @@ -91,6 +89,8 @@ protected: int thin_space_icon_width; int wide_space_icon_width; + PrinterTechnology printer_technology {ptAny}; + void invalidate_selection(); void validate_selection(bool predicate = false); void update_selection(); @@ -188,83 +188,6 @@ public: }; -//------------------------------------------ -// PresetForPrinter -//------------------------------------------ -static std::string g_info_string = " (modified)"; -class PhysicalPrinterDialog; -class PresetForPrinter -{ - PhysicalPrinterDialog* m_parent { nullptr }; - - PresetComboBox* m_presets_list { nullptr }; - ScalableButton* m_delete_preset_btn { nullptr }; - wxStaticText* m_info_line { nullptr }; - wxStaticText* m_full_printer_name { nullptr }; - - wxBoxSizer* m_sizer { nullptr }; - - void DeletePreset(wxEvent& event); - -public: - PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = ""); - ~PresetForPrinter(); - - wxBoxSizer* sizer() { return m_sizer; } - void update_full_printer_name(); - std::string get_preset_name(); - void DisableDeleteBtn(); - void EnableDeleteBtn(); - - void msw_rescale(); - void on_sys_color_changed() {}; -}; - - -//------------------------------------------ -// PhysicalPrinterDialog -//------------------------------------------ - -class ConfigOptionsGroup; -class PhysicalPrinterDialog : public DPIDialog -{ - PhysicalPrinter m_printer; - wxString m_default_name; - DynamicPrintConfig* m_config { nullptr }; - - wxTextCtrl* m_printer_name { nullptr }; - std::vector m_presets; - - ConfigOptionsGroup* m_optgroup { nullptr }; - - ScalableButton* m_add_preset_btn {nullptr}; - ScalableButton* m_printhost_browse_btn {nullptr}; - ScalableButton* m_printhost_test_btn {nullptr}; - ScalableButton* m_printhost_cafile_browse_btn {nullptr}; - - wxBoxSizer* m_presets_sizer {nullptr}; - - void build_printhost_settings(ConfigOptionsGroup* optgroup); - void OnOK(wxEvent& event); - void AddPreset(wxEvent& event); - -public: - PhysicalPrinterDialog(wxString printer_name); - ~PhysicalPrinterDialog(); - - void update(); - wxString get_printer_name(); - void update_full_printer_names(); - PhysicalPrinter* get_printer() {return &m_printer; } - - void DeletePreset(PresetForPrinter* preset_for_printer); - -protected: - void on_dpi_changed(const wxRect& suggested_rect) override; - void on_sys_color_changed() override {}; -}; - - //------------------------------------------------ // ChangePresetForPhysicalPrinterDialog //------------------------------------------------ From 953d1417a0a751ffcb9fc5c93d9e42a203a0cda4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 11 Jun 2020 13:09:34 +0200 Subject: [PATCH 190/826] TriangleSelector: draft of interface --- src/libslic3r/Model.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 66 ++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 71 ++++++++++++++++++++ 3 files changed, 138 insertions(+) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index e5930fb8ac..be298ae4bb 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -394,6 +394,7 @@ enum class ModelVolumeType : int { }; enum class FacetSupportType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits! NONE = 0, ENFORCER = 1, BLOCKER = 2 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cd42857247..00e236b64b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -805,6 +805,10 @@ void GLGizmoFdmSupports::on_set_state() activate_internal_undo_redo_stack(true); }); } + + TriangleSelector ts{TriangleMesh()}; + ts.test(); + } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down @@ -854,5 +858,67 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const +void TriangleSelector::test() +{ + DivisionNode node; + while (true) { + std::cout << "Zadej pocet stran a spec stranu: "; + int num; + int spec; + std::cin >> num >> spec; + node.set_division(num, spec); + std::cout << node.number_of_split_sides() << " " + << (node.number_of_split_sides()==1 ? node.side_to_split() : node.side_to_keep()) + << std::endl << std::endl; + } +} + + +void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) +{ + assert(sides_to_split >=0 && sides_to_split <= 3); + assert(special_side_idx >=-1 && special_side_idx < 3); + + // If splitting one or two sides, second argument must be provided. + assert(sides_to_split != 1 || special_side_idx != -1); + assert(sides_to_split != 2 || special_side_idx != -1); + + division_type = sides_to_split | (special_side_idx != -1 ? (special_side_idx << 2) : 0 ); +} + + + +void TriangleSelector::DivisionNode::set_type(FacetSupportType type) +{ + // If this is not a leaf-node, this makes no sense and + // the bits are used for storing index of an edge. + assert(number_of_split_sides() == 0); + division_type = type | (type << 2); +} + + + +int TriangleSelector::DivisionNode::side_to_keep() const +{ + assert(number_of_split_sides() == 2); + return (division_type & 0b1100) >> 2; +} + + + +int TriangleSelector::DivisionNode::side_to_split() const +{ + assert(number_of_split_sides() == 1); + return (division_type & 0b1100) >> 2; +} + + + +FacetSupportType TriangleSelector::DivisionNode::get_type() const +{ + assert(number_of_split_sides() == 0); // this must be leaf + return (division_type & 0b1100) >> 2; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index c4f5b153ec..f815a80633 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -19,6 +19,77 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class ClippingPlane; + +// Following class holds information about selected triangles. It also has power +// to recursively subdivide the triangles and make the selection finer. +class TriangleSelector { +public: + void test(); + explicit TriangleSelector(const TriangleMesh& mesh) {} + + // Select all triangles inside the circle, subdivide where needed. + void select_patch(const Vec3f& hit, // point where to start + int facet_idx, // facet that point belongs to + const Vec3f& dir, // direction of the ray + float radius_sqr, // squared radius of the cursor + bool enforcer); // enforcer or blocker? + + void unselect_all(); + + // Remove all unnecessary data (such as vertices that are not needed + // because the selection has been made larger. + void garbage_collect(); + +private: + // A struct to hold information about how a triangle was divided. + struct DivisionNode { + // Index of triangle this describes. + int triangle_idx; + + // Bitmask encoding which sides are split. + unsigned char division_type; + // bits 0 and 1 : 00 - no division + // 01 - one-edge split + // 10 - two-edge split + // 11 - three-edge split + // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that + // splits in one-edge split or one that stays in two-edge split). + + // Pointers to children nodes (not all are always used). + std::array children; + + // Set the division type. + void set_division(int sides_to_split, int special_side_idx = -1); + + // Helpers that decode the division_type bitmask. + int number_of_split_sides() const { return division_type & 0b11; } + int side_to_keep() const; + int side_to_split() const; + }; + + // Triangle and pointer to how it's divided (nullptr = not divided). + // The ptr is nullptr for all new triangles, it is only valid for + // the original (undivided) triangles. + struct Triangle { + stl_triangle_vertex_indices verts_idxs; + DivisionNode* div_info; + }; + + // Lists of vertices and triangles, both original and new + std::vector m_vertices; + std::vector m_triangles; + + // Number of original vertices and triangles. + int m_orig_size_vertices; + int m_orig_size_indices; + + // Limits for stopping the recursion. + float m_max_edge_length; + int m_max_recursion_depth; +}; + + + class GLGizmoFdmSupports : public GLGizmoBase { private: From d2b2446b077fb90b7211cc4a2a3e20e1f02e7fdc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 16 Jun 2020 16:10:26 +0200 Subject: [PATCH 191/826] TriangleSelector: first partially working implementation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 500 ++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 65 ++- 2 files changed, 422 insertions(+), 143 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 00e236b64b..1b045f38e2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -16,7 +16,6 @@ namespace Slic3r { namespace GUI { -static constexpr size_t MaxVertexBuffers = 50; GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -90,12 +89,13 @@ void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const S void GLGizmoFdmSupports::on_render() const { - const Selection& selection = m_parent.get_selection(); + //const Selection& selection = m_parent.get_selection(); glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - render_triangles(selection); + //render_triangles(selection); + m_triangle_selector->render(); m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -146,13 +146,13 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const glsafe(::glMultMatrixd(trafo_matrix.data())); // Now render both enforcers and blockers. - for (int i=0; i<2; ++i) { - glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { - if (iva.has_VBOs()) - iva.render(); - } - } + //for (int i=0; i<2; ++i) { + // glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); + // for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { + if (m_iva.has_VBOs()) + m_iva.render(); + // } + //} glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); @@ -209,7 +209,8 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { - ModelObject* mo = m_c->selection_info()->model_object(); + return; + /*ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; @@ -217,7 +218,7 @@ void GLGizmoFdmSupports::update_model_object() const continue; for (int i=0; im_supported_facets.set_facet(i, m_selected_facets[idx][i]); - } + }*/ } @@ -226,13 +227,15 @@ void GLGizmoFdmSupports::update_from_model_object() wxBusyCursor wait; const ModelObject* mo = m_c->selection_info()->model_object(); - size_t num_of_volumes = 0; + /*size_t num_of_volumes = 0; for (const ModelVolume* mv : mo->volumes) if (mv->is_model_part()) ++num_of_volumes; - m_selected_facets.resize(num_of_volumes); + m_selected_facets.resize(num_of_volumes);*/ - m_ivas.clear(); + m_triangle_selector = std::make_unique(mo->volumes.front()->mesh()); + + /*m_ivas.clear(); m_ivas.resize(num_of_volumes); for (size_t i=0; ivolumes) { if (! mv->is_model_part()) continue; ++mesh_id; if (mesh_id == closest_hit_mesh_id) { - mesh = &mv->mesh(); + //mesh = &mv->mesh(); break; } } - bool update_both = false; + // FIXME: just for now, only process first mesh + if (mesh_id != 0) + return false; const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; @@ -426,80 +435,10 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; const float limit = pow(m_cursor_radius/avg_scaling , 2.f); - const std::pair& hit_and_facet = { closest_hit, closest_facet }; - // Calculate direction from camera to the hit (in mesh coords): - Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - hit_and_facet.first).normalized(); + Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - closest_hit).normalized(); - // A lambda to calculate distance from the centerline: - auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f& point) -> float { - Vec3f diff = hit_and_facet.first - point; - return (diff - diff.dot(dir) * dir).squaredNorm(); - }; - - // A lambda to determine whether this facet is potentionally visible (still can be obscured) - auto faces_camera = [&dir, &mesh](const size_t& facet) -> bool { - return (mesh->stl.facet_start[facet].normal.dot(dir) > 0.); - }; - // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_select{hit_and_facet.second}; - std::vector visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed - size_t facet_idx = 0; // index into facets_to_select - while (facet_idx < facets_to_select.size()) { - size_t facet = facets_to_select[facet_idx]; - if (! visited[facet]) { - // check all three vertices and in case they're close enough, - // add neighboring facets to be proccessed later - for (size_t i=0; i<3; ++i) { - float dist = squared_distance_from_line( - mesh->its.vertices[mesh->its.indices[facet](i)]); - if (dist < limit) { - for (int n=0; n<3; ++n) { - if (faces_camera(mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_select.push_back(mesh->stl.neighbors_start[facet].neighbor[n]); - } - } - } - visited[facet] = true; - } - ++facet_idx; - } - - std::vector new_facets; - new_facets.reserve(facets_to_select.size()); - - // Now just select all facets that passed and remember which - // ones have really changed state. - for (size_t next_facet : facets_to_select) { - FacetSupportType& facet = m_selected_facets[mesh_id][next_facet]; - - if (facet != new_state) { - if (facet != FacetSupportType::NONE) { - // this triangle is currently in the other VBA. - // Both VBAs need to be refreshed. - update_both = true; - } - facet = new_state; - new_facets.push_back(next_facet); - } - } - - if (! new_facets.empty()) { - if (new_state != FacetSupportType::NONE) { - // append triangles into the respective VBA - update_vertex_buffers(mesh, mesh_id, new_state, &new_facets); - if (update_both) { - auto other = new_state == FacetSupportType::ENFORCER - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER; - update_vertex_buffers(mesh, mesh_id, other); // regenerate the other VBA - } - } - else { - update_vertex_buffers(mesh, mesh_id, FacetSupportType::ENFORCER); - update_vertex_buffers(mesh, mesh_id, FacetSupportType::BLOCKER); - } - } + m_triangle_selector->select_patch(closest_hit, closest_facet, dir, limit, new_state); return true; } @@ -529,7 +468,7 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, FacetSupportType type, const std::vector* new_facets) { - std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; + //std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; // lambda to push facet into vertex buffer auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) { @@ -543,24 +482,26 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, }; - if (ivas.size() == MaxVertexBuffers || ! new_facets) { + //if (ivas.size() == MaxVertexBuffers || ! new_facets) { // If there are too many or they should be regenerated, make one large // GLVertexBufferArray. - ivas.clear(); // destructors release geometry - ivas.push_back(GLIndexedVertexArray()); + //ivas.clear(); // destructors release geometry + //ivas.push_back(GLIndexedVertexArray()); + + m_iva.release_geometry(); + m_iva.clear(); bool pushed = false; for (size_t facet_idx=0; facet_idxselection_info()->model_object(); @@ -615,6 +558,7 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr update_model_object(); m_parent.set_as_dirty(); m_setting_angle = false; +*/ } @@ -670,7 +614,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->button(m_desc.at("remove_all"))) { - ModelObject* mo = m_c->selection_info()->model_object(); + /*ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; @@ -681,7 +625,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER); m_parent.set_as_dirty(); } - } + }*/ } const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -805,10 +749,6 @@ void GLGizmoFdmSupports::on_set_state() activate_internal_undo_redo_stack(true); }); } - - TriangleSelector ts{TriangleMesh()}; - ts.test(); - } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down @@ -818,7 +758,7 @@ void GLGizmoFdmSupports::on_set_state() } activate_internal_undo_redo_stack(false); m_old_mo_id = -1; - m_ivas.clear(); + m_iva.release_geometry(); m_selected_facets.clear(); } m_old_state = m_state; @@ -858,22 +798,6 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const -void TriangleSelector::test() -{ - DivisionNode node; - while (true) { - std::cout << "Zadej pocet stran a spec stranu: "; - int num; - int spec; - std::cin >> num >> spec; - node.set_division(num, spec); - std::cout << node.number_of_split_sides() << " " - << (node.number_of_split_sides()==1 ? node.side_to_split() : node.side_to_keep()) - << std::endl << std::endl; - } -} - - void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) { assert(sides_to_split >=0 && sides_to_split <= 3); @@ -883,17 +807,17 @@ void TriangleSelector::DivisionNode::set_division(int sides_to_split, int specia assert(sides_to_split != 1 || special_side_idx != -1); assert(sides_to_split != 2 || special_side_idx != -1); - division_type = sides_to_split | (special_side_idx != -1 ? (special_side_idx << 2) : 0 ); + division_type = sides_to_split | ((special_side_idx != -1 ? special_side_idx : 0 ) <<2); } -void TriangleSelector::DivisionNode::set_type(FacetSupportType type) +void TriangleSelector::DivisionNode::set_state(FacetSupportType type) { // If this is not a leaf-node, this makes no sense and // the bits are used for storing index of an edge. assert(number_of_split_sides() == 0); - division_type = type | (type << 2); + division_type = (int8_t(type) << 2); } @@ -914,10 +838,326 @@ int TriangleSelector::DivisionNode::side_to_split() const -FacetSupportType TriangleSelector::DivisionNode::get_type() const +FacetSupportType TriangleSelector::DivisionNode::get_state() const { assert(number_of_split_sides() == 0); // this must be leaf - return (division_type & 0b1100) >> 2; + return FacetSupportType((division_type & 0b1100) >> 2); +} + + + +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& dir, + float radius_sqr, FacetSupportType new_state) +{ + assert(facet_start < m_orig_size_indices); + + // Save current cursor center, squared radius and camera direction, + // so we don't have to pass it around. + m_cursor = {hit, dir, radius_sqr}; + + // Now start with the facet the pointer points to and check all adjacent facets. + std::vector facets_to_check{facet_start}; + std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed + int facet_idx = 0; // index into facets_to_check + while (facet_idx < int(facets_to_check.size())) { + int facet = facets_to_check[facet_idx]; + if (! visited[facet]) { + int num_of_inside_vertices = vertices_inside(facet); + // select the facet... + select_triangle(facet, new_state, num_of_inside_vertices, facet == facet_start); + + // ...and add neighboring facets to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } + } + visited[facet] = true; + ++facet_idx; + } +} + + + +// Selects either the whole triangle (discarding any children it has), or divides +// the triangle recursively, selecting just subtriangles truly inside the circle. +// This is done by an actual recursive call. +void TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, + int num_of_inside_vertices, bool cursor_inside) +{ + assert(facet_idx < m_triangles.size()); +//cursor_inside=false; + if (num_of_inside_vertices == -1) + num_of_inside_vertices = vertices_inside(facet_idx); + + if (num_of_inside_vertices == 0 && ! cursor_inside) + return; // FIXME: just an edge can be inside + + if (num_of_inside_vertices == 3) { + // dump any subdivision and select whole triangle + undivide_triangle(facet_idx); + m_triangles[facet_idx].div_info->set_state(type); + } else { + // the triangle is partially inside, let's recursively divide it + // (if not already) and try selecting its children. + split_triangle(facet_idx); + assert(facet_idx < m_triangles.size()); + int num_of_children = m_triangles[facet_idx].div_info->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i=0; ichildren[i], type, -1, cursor_inside); + } + } + } + //if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) + // remove_needless(m_triangles[facet_idx].div_info->children[0]); +} + + +bool TriangleSelector::split_triangle(int facet_idx) +{ + if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) { + // The triangle was divided already. + return 0; + } + + FacetSupportType old_type = m_triangles[facet_idx].div_info->get_state(); + + const double limit_squared = 4; + + stl_triangle_vertex_indices& facet = m_triangles[facet_idx].verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; + + std::vector sides_to_split; + int side_to_keep = -1; + for (int pt_idx = 0; pt_idx<3; ++pt_idx) { + if (sides[pt_idx] > limit_squared) + sides_to_split.push_back(pt_idx); + else + side_to_keep = pt_idx; + } + if (sides_to_split.empty()) { + m_triangles[facet_idx].div_info->set_division(0); + return 0; + } + + // indices of triangle vertices + std::vector verts_idxs; + int idx = sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]; + for (int j=0; j<3; ++j) { + verts_idxs.push_back(facet[idx++]); + if (idx == 3) + idx = 0; + } + + + if (sides_to_split.size() == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]] + m_vertices[verts_idxs[2]])/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split.size() == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[3]])/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split.size() == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]] + m_vertices[verts_idxs[3]])/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]] + m_vertices[verts_idxs[0]])/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + m_triangles.emplace_back(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + // Save how the triangle was split. Second argument makes sense only for one + // or two split sides, otherwise the value is ignored. + m_triangles[facet_idx].div_info->set_division(sides_to_split.size(), + sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); + + // And save the children. All children should start in the same state as the triangle we just split. + assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); + for (int i=0; i<=int(sides_to_split.size()); ++i) { + m_triangles[facet_idx].div_info->children[i] = m_triangles.size()-1-i; + m_triangles[m_triangles.size()-1-i].div_info->parent = facet_idx; + m_triangles[m_triangles[facet_idx].div_info->children[i]].div_info->set_state(old_type); + } + + +#ifndef NDEBUG + int split_sides = m_triangles[facet_idx].div_info->number_of_split_sides(); + if (split_sides != 0) { + // check that children are range + for (int i=0; i<=split_sides; ++i) + assert(m_triangles[facet_idx].div_info->children[i] >= 0 && m_triangles[facet_idx].div_info->children[i] < int(m_triangles.size())); + + } +#endif + + return 1; +} + + +// Calculate distance of a point from a line. +bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const +{ + Vec3f diff = m_cursor.center - point; + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; +} + + + +// Determine whether this facet is potentially visible (still can be obscured). +bool TriangleSelector::faces_camera(int facet) const +{ + assert(facet < m_orig_size_indices); + // The normal is cached in mesh->stl, use it. + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) > 0.); +} + + + +// How many vertices of a triangle are inside the circle? +int TriangleSelector::vertices_inside(int facet_idx) const +{ + int inside = 0; + for (size_t i=0; i<3; ++i) { + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]])) + ++inside; + } + return inside; +} + + +// Is mouse pointer inside a triangle? +/*bool TriangleSelector::is_pointer_inside_triangle(int facet_idx) const +{ + +}*/ + + + +// Recursively remove all subtriangles. +void TriangleSelector::undivide_triangle(int facet_idx) +{ + assert(facet_idx < m_triangles.size()); + auto& dn_ptr = m_triangles[facet_idx].div_info; + assert(dn_ptr); + + if (dn_ptr->number_of_split_sides() != 0) { + for (int i=0; i<=dn_ptr->number_of_split_sides(); ++i) { + undivide_triangle(dn_ptr->children[i]); + m_triangles[dn_ptr->children[i]].div_info->valid = false; + } + } + + dn_ptr->set_division(0); // not split +} + + +void TriangleSelector::remove_needless(int child_facet) +{ + assert(m_triangles[child_facet].div_info->number_of_split_sides() == 0); + int parent = m_triangles[child_facet].div_info->parent; + if (parent == -1) + return; // root + // Check type of all valid children. + FacetSupportType type = m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state(); + for (int i=0; i<=m_triangles[parent].div_info->number_of_split_sides(); ++i) + if (m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state() != type) + return; // not all children are the same + + // All children are the same, let's kill them. + undivide_triangle(parent); + m_triangles[parent].div_info->set_state(type); + + // And not try the same for grandparent. + remove_needless(parent); +} + + +TriangleSelector::TriangleSelector(const TriangleMesh& mesh) +{ + for (const stl_vertex& vert : mesh.its.vertices) + m_vertices.push_back(vert); + for (const stl_triangle_vertex_indices& ind : mesh.its.indices) + m_triangles.emplace_back(Triangle(ind[0], ind[1], ind[2])); + m_orig_size_vertices = m_vertices.size(); + m_orig_size_indices = m_triangles.size(); + m_mesh = &mesh; +} + + +void TriangleSelector::render() const +{ + ::glColor3f(0.f, 0.f, 1.f); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); + ::glTranslatef(offset.x(), offset.y(), offset.z()); + ::glScalef(1.01f, 1.01f, 1.01f); + + ::glBegin( GL_TRIANGLES); + + for (int tr_id=0; tr_idvalid) { + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); + } + } + ::glEnd(); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + ::glBegin( GL_TRIANGLES); + for (int tr_id=0; tr_idvalid) + continue; + + if (tr.div_info->number_of_split_sides() == 0) { + if (tr.div_info->get_state() == FacetSupportType::ENFORCER) + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + else if (tr.div_info->get_state() == FacetSupportType::BLOCKER) + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + else + continue; + } + else + continue; + + + if (tr.div_info->valid) { + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); + } + } + ::glEnd(); +} + + +TriangleSelector::DivisionNode::DivisionNode() +{ + set_division(0); + set_state(FacetSupportType::NONE); } } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index f815a80633..07c2e86d52 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -24,15 +24,17 @@ class ClippingPlane; // to recursively subdivide the triangles and make the selection finer. class TriangleSelector { public: - void test(); - explicit TriangleSelector(const TriangleMesh& mesh) {} + void render() const; + // Create new object on a TriangleMesh. The referenced mesh must + // stay valid, a ptr to it is saved and used. + explicit TriangleSelector(const TriangleMesh& mesh); // Select all triangles inside the circle, subdivide where needed. void select_patch(const Vec3f& hit, // point where to start - int facet_idx, // facet that point belongs to + int facet_start, // facet that point belongs to const Vec3f& dir, // direction of the ray float radius_sqr, // squared radius of the cursor - bool enforcer); // enforcer or blocker? + FacetSupportType new_state); // enforcer or blocker? void unselect_all(); @@ -43,11 +45,15 @@ public: private: // A struct to hold information about how a triangle was divided. struct DivisionNode { + DivisionNode(); // Index of triangle this describes. - int triangle_idx; + bool valid{true}; + + // Index of parent triangle (-1: original) + int parent{-1}; // Bitmask encoding which sides are split. - unsigned char division_type; + int8_t division_type; // bits 0 and 1 : 00 - no division // 01 - one-edge split // 10 - two-edge split @@ -55,29 +61,37 @@ private: // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that // splits in one-edge split or one that stays in two-edge split). - // Pointers to children nodes (not all are always used). - std::array children; + // Children triangles (0 = no child) + std::array children; // Set the division type. void set_division(int sides_to_split, int special_side_idx = -1); + void set_state(FacetSupportType state); // Helpers that decode the division_type bitmask. int number_of_split_sides() const { return division_type & 0b11; } int side_to_keep() const; int side_to_split() const; + + FacetSupportType get_state() const; }; // Triangle and pointer to how it's divided (nullptr = not divided). // The ptr is nullptr for all new triangles, it is only valid for // the original (undivided) triangles. struct Triangle { + Triangle(int a, int b, int c) + : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + div_info{std::make_unique()} + {} stl_triangle_vertex_indices verts_idxs; - DivisionNode* div_info; + std::unique_ptr div_info; }; // Lists of vertices and triangles, both original and new std::vector m_vertices; std::vector m_triangles; + const TriangleMesh* m_mesh; // Number of original vertices and triangles. int m_orig_size_vertices; @@ -86,6 +100,32 @@ private: // Limits for stopping the recursion. float m_max_edge_length; int m_max_recursion_depth; + + // Caches for cursor position, radius and direction. + struct Cursor { + Vec3f center; + Vec3f dir; + float radius_sqr; + }; + + Cursor m_cursor; + + // Private functions: + void select_triangle(int facet_idx, FacetSupportType type, + int num_of_inside_vertices = -1, + bool cursor_inside = false); + + bool is_point_inside_cursor(const Vec3f& point) const; + + int vertices_inside(int facet_idx) const; + + bool faces_camera(int facet) const; + + void undivide_triangle(int facet_idx); + + bool split_triangle(int facet_idx); + + void remove_needless(int child_facet); }; @@ -107,10 +147,7 @@ private: // individual facets (one of the enum values above). std::vector> m_selected_facets; - // Vertex buffer arrays for each model-part volume. There is a vector of - // arrays so that adding triangles can be done without regenerating all - // other triangles. Enforcers and blockers are of course separate. - std::vector, 2>> m_ivas; + GLIndexedVertexArray m_iva; void update_vertex_buffers(const TriangleMesh* mesh, int mesh_id, @@ -148,6 +185,8 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; + + std::unique_ptr m_triangle_selector; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. From c3db84e3821d7c94fb4f615cbc7bedf10695a59b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 19 Jun 2020 09:20:58 +0200 Subject: [PATCH 192/826] TriangleSelector: Improvements --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 254 ++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 70 ++--- 2 files changed, 176 insertions(+), 148 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1b045f38e2..1c77f437c0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -436,9 +436,11 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const float limit = pow(m_cursor_radius/avg_scaling , 2.f); // Calculate direction from camera to the hit (in mesh coords): - Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - closest_hit).normalized(); + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + Vec3f dir = (closest_hit - camera_pos).normalized(); - m_triangle_selector->select_patch(closest_hit, closest_facet, dir, limit, new_state); + m_triangle_selector->select_patch(closest_hit, closest_facet, camera_pos, + dir, limit, new_state); return true; } @@ -567,6 +569,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; + m_imgui->begin(std::string("TriangleSelector DEBUG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + m_imgui->slider_float("Edge limit (mm): ", &edge_limit, 0.1f, 8.f); + m_triangle_selector->set_edge_limit(edge_limit); + m_imgui->end(); + const float approx_height = m_imgui->scaled(18.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -798,7 +806,7 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const -void TriangleSelector::DivisionNode::set_division(int sides_to_split, int special_side_idx) +void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) { assert(sides_to_split >=0 && sides_to_split <= 3); assert(special_side_idx >=-1 && special_side_idx < 3); @@ -812,48 +820,49 @@ void TriangleSelector::DivisionNode::set_division(int sides_to_split, int specia -void TriangleSelector::DivisionNode::set_state(FacetSupportType type) +void TriangleSelector::Triangle::set_state(FacetSupportType type) { // If this is not a leaf-node, this makes no sense and // the bits are used for storing index of an edge. - assert(number_of_split_sides() == 0); + assert(! is_split()); division_type = (int8_t(type) << 2); } -int TriangleSelector::DivisionNode::side_to_keep() const +int TriangleSelector::Triangle::side_to_keep() const { assert(number_of_split_sides() == 2); - return (division_type & 0b1100) >> 2; + return division_type >> 2; } -int TriangleSelector::DivisionNode::side_to_split() const +int TriangleSelector::Triangle::side_to_split() const { assert(number_of_split_sides() == 1); - return (division_type & 0b1100) >> 2; + return division_type >> 2; } -FacetSupportType TriangleSelector::DivisionNode::get_state() const +FacetSupportType TriangleSelector::Triangle::get_state() const { - assert(number_of_split_sides() == 0); // this must be leaf - return FacetSupportType((division_type & 0b1100) >> 2); + assert(! is_split()); // this must be leaf + return FacetSupportType(division_type >> 2); } -void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& dir, +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, + const Vec3f& source, const Vec3f& dir, float radius_sqr, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. - m_cursor = {hit, dir, radius_sqr}; + m_cursor = {hit, source, dir, radius_sqr}; // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check{facet_start}; @@ -862,14 +871,12 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec while (facet_idx < int(facets_to_check.size())) { int facet = facets_to_check[facet_idx]; if (! visited[facet]) { - int num_of_inside_vertices = vertices_inside(facet); - // select the facet... - select_triangle(facet, new_state, num_of_inside_vertices, facet == facet_start); - - // ...and add neighboring facets to be proccessed later - for (int n=0; n<3; ++n) { - if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + if (select_triangle(facet, new_state, facet == facet_start)) { + // add neighboring facets to list to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } } } visited[facet] = true; @@ -879,53 +886,71 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec -// Selects either the whole triangle (discarding any children it has), or divides +// Selects either the whole triangle (discarding any children it had), or divides // the triangle recursively, selecting just subtriangles truly inside the circle. -// This is done by an actual recursive call. -void TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, - int num_of_inside_vertices, bool cursor_inside) +// This is done by an actual recursive call. Returns false if the triangle is +// outside the cursor. +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) { - assert(facet_idx < m_triangles.size()); -//cursor_inside=false; - if (num_of_inside_vertices == -1) - num_of_inside_vertices = vertices_inside(facet_idx); + bool out = false; + assert(facet_idx < int(m_triangles.size())); + + Triangle& tr = m_triangles[facet_idx]; + if (! tr.valid) + return false; + + cursor_inside = is_pointer_in_triangle(facet_idx); + + int num_of_inside_vertices = vertices_inside(facet_idx); if (num_of_inside_vertices == 0 && ! cursor_inside) - return; // FIXME: just an edge can be inside + return out; // FIXME: just an edge can be inside if (num_of_inside_vertices == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); - m_triangles[facet_idx].div_info->set_state(type); + tr.set_state(type); } else { // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. + + + if (! tr.is_split() && tr.get_state() == type) { + // This is leaf triangle that is already of correct type as a whole. + // No need to split, all children would end up selected anyway. + return true; + } + split_triangle(facet_idx); - assert(facet_idx < m_triangles.size()); - int num_of_children = m_triangles[facet_idx].div_info->number_of_split_sides() + 1; + assert(facet_idx < int(m_triangles.size())); + int num_of_children = tr.number_of_split_sides() + 1; if (num_of_children != 1) { - for (int i=0; ichildren[i], type, -1, cursor_inside); - } + for (int i=0; inumber_of_split_sides() != 0) - // remove_needless(m_triangles[facet_idx].div_info->children[0]); + + // In case that all siblings are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. + //remove_if_needless(facet_idx); + return true; } bool TriangleSelector::split_triangle(int facet_idx) { - if (m_triangles[facet_idx].div_info->number_of_split_sides() != 0) { + if (m_triangles[facet_idx].is_split()) { // The triangle was divided already. - return 0; + return false; } - FacetSupportType old_type = m_triangles[facet_idx].div_info->get_state(); + Triangle& tr = m_triangles[facet_idx]; - const double limit_squared = 4; + FacetSupportType old_type = tr.get_state(); - stl_triangle_vertex_indices& facet = m_triangles[facet_idx].verts_idxs; + const double limit_squared = m_edge_limit_sqr; + + stl_triangle_vertex_indices& facet = tr.verts_idxs; const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; @@ -938,8 +963,8 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { - m_triangles[facet_idx].div_info->set_division(0); - return 0; + tr.set_division(0); + return false; } // indices of triangle vertices @@ -988,29 +1013,18 @@ bool TriangleSelector::split_triangle(int facet_idx) // Save how the triangle was split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. - m_triangles[facet_idx].div_info->set_division(sides_to_split.size(), + tr.set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); // And save the children. All children should start in the same state as the triangle we just split. assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { - m_triangles[facet_idx].div_info->children[i] = m_triangles.size()-1-i; - m_triangles[m_triangles.size()-1-i].div_info->parent = facet_idx; - m_triangles[m_triangles[facet_idx].div_info->children[i]].div_info->set_state(old_type); + tr.children[i] = m_triangles.size()-1-i; + m_triangles[tr.children[i]].parent = facet_idx; + m_triangles[tr.children[i]].set_state(old_type); } - -#ifndef NDEBUG - int split_sides = m_triangles[facet_idx].div_info->number_of_split_sides(); - if (split_sides != 0) { - // check that children are range - for (int i=0; i<=split_sides; ++i) - assert(m_triangles[facet_idx].div_info->children[i] >= 0 && m_triangles[facet_idx].div_info->children[i] < int(m_triangles.size())); - - } -#endif - - return 1; + return true; } @@ -1022,6 +1036,29 @@ bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const } +// Is pointer in a triangle? +bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const +{ + auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, + const Vec3f& c, const Vec3f& d) -> bool { + return ((b-a).cross(c-a)).dot(d-a) > 0.; + }; + + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]]; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]]; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]]; + const Vec3f& q1 = m_cursor.center + m_cursor.dir; + const Vec3f q2 = m_cursor.center - m_cursor.dir; + + if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { + bool pos = signed_volume_sign(q1,q2,p1,p2); + if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) + return true; + } + return false; +} + + // Determine whether this facet is potentially visible (still can be obscured). bool TriangleSelector::faces_camera(int facet) const @@ -1056,39 +1093,41 @@ int TriangleSelector::vertices_inside(int facet_idx) const // Recursively remove all subtriangles. void TriangleSelector::undivide_triangle(int facet_idx) { - assert(facet_idx < m_triangles.size()); - auto& dn_ptr = m_triangles[facet_idx].div_info; - assert(dn_ptr); + assert(facet_idx < int(m_triangles.size())); + Triangle& tr = m_triangles[facet_idx]; - if (dn_ptr->number_of_split_sides() != 0) { - for (int i=0; i<=dn_ptr->number_of_split_sides(); ++i) { - undivide_triangle(dn_ptr->children[i]); - m_triangles[dn_ptr->children[i]].div_info->valid = false; + if (tr.is_split()) { + for (int i=0; i<=tr.number_of_split_sides(); ++i) { + undivide_triangle(tr.children[i]); + m_triangles[tr.children[i]].valid = false; } } - dn_ptr->set_division(0); // not split + tr.set_division(0); // not split } -void TriangleSelector::remove_needless(int child_facet) +void TriangleSelector::remove_if_needless(int child_facet) { - assert(m_triangles[child_facet].div_info->number_of_split_sides() == 0); - int parent = m_triangles[child_facet].div_info->parent; + if (m_triangles[child_facet].is_split() || ! m_triangles[child_facet].valid) + return; + int parent = m_triangles[child_facet].parent; if (parent == -1) return; // root - // Check type of all valid children. - FacetSupportType type = m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state(); - for (int i=0; i<=m_triangles[parent].div_info->number_of_split_sides(); ++i) - if (m_triangles[m_triangles[parent].div_info->children[0]].div_info->get_state() != type) + FacetSupportType child_type = m_triangles[child_facet].get_state(); + + // Check type of all valid children, if they're same, they are needless. + for (int i=0; i<=m_triangles[parent].number_of_split_sides(); ++i) + if (m_triangles[m_triangles[parent].children[i]].is_split() + || m_triangles[m_triangles[parent].children[i]].get_state() != child_type) return; // not all children are the same - // All children are the same, let's kill them. + // All children are the same, kill them. undivide_triangle(parent); - m_triangles[parent].div_info->set_state(type); + m_triangles[parent].set_state(child_type); - // And not try the same for grandparent. - remove_needless(parent); + // And now try the same for parent (which has just become leaf). + remove_if_needless(parent); } @@ -1114,51 +1153,40 @@ void TriangleSelector::render() const ::glScalef(1.01f, 1.01f, 1.01f); ::glBegin( GL_TRIANGLES); - - for (int tr_id=0; tr_idvalid) { - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); - } + if (! tr.valid) + continue; + + if (tr_id == m_orig_size_indices-1) + ::glColor3f(1.f, 0.f, 0.f); + + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], + m_vertices[tr.verts_idxs[i]][1], + m_vertices[tr.verts_idxs[i]][2]); } ::glEnd(); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_idvalid) + for (const Triangle& tr : m_triangles) { + if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; - if (tr.div_info->number_of_split_sides() == 0) { - if (tr.div_info->get_state() == FacetSupportType::ENFORCER) - ::glColor4f(0.f, 0.f, 1.f, 0.2f); - else if (tr.div_info->get_state() == FacetSupportType::BLOCKER) - ::glColor4f(1.f, 0.f, 0.f, 0.2f); - else - continue; - } + if (tr.get_state() == FacetSupportType::ENFORCER) + ::glColor4f(0.f, 0.f, 1.f, 0.2f); else - continue; + ::glColor4f(1.f, 0.f, 0.f, 0.2f); - - if (tr.div_info->valid) { - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], m_vertices[tr.verts_idxs[i]][1], m_vertices[tr.verts_idxs[i]][2]); - } + for (int i=0; i<3; ++i) + ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], + m_vertices[tr.verts_idxs[i]][1], + m_vertices[tr.verts_idxs[i]][2]); } ::glEnd(); } - -TriangleSelector::DivisionNode::DivisionNode() -{ - set_division(0); - set_state(FacetSupportType::NONE); -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 07c2e86d52..3e1b076c53 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -24,16 +24,18 @@ class ClippingPlane; // to recursively subdivide the triangles and make the selection finer. class TriangleSelector { public: + void set_edge_limit(float edge_limit) { m_edge_limit_sqr = std::pow(edge_limit, 2.f); } void render() const; // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); // Select all triangles inside the circle, subdivide where needed. - void select_patch(const Vec3f& hit, // point where to start - int facet_start, // facet that point belongs to - const Vec3f& dir, // direction of the ray - float radius_sqr, // squared radius of the cursor + void select_patch(const Vec3f& hit, // point where to start + int facet_start, // facet that point belongs to + const Vec3f& source, // camera position (mesh coords) + const Vec3f& dir, // direction of the ray (mesh coords) + float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? void unselect_all(); @@ -43,49 +45,44 @@ public: void garbage_collect(); private: - // A struct to hold information about how a triangle was divided. - struct DivisionNode { - DivisionNode(); - // Index of triangle this describes. + // Triangle and info about how it's split. + struct Triangle { + public: + Triangle(int a, int b, int c) + : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + division_type{0} + {} + stl_triangle_vertex_indices verts_idxs; + + // Is this triangle valid or marked to remove? bool valid{true}; // Index of parent triangle (-1: original) int parent{-1}; - // Bitmask encoding which sides are split. - int8_t division_type; - // bits 0 and 1 : 00 - no division - // 01 - one-edge split - // 10 - two-edge split - // 11 - three-edge split - // bits 2 and 3 : decimal 0, 1 or 2 identifying the special edge (one that - // splits in one-edge split or one that stays in two-edge split). - // Children triangles (0 = no child) std::array children; // Set the division type. void set_division(int sides_to_split, int special_side_idx = -1); - void set_state(FacetSupportType state); - // Helpers that decode the division_type bitmask. + // Get/set current state. + void set_state(FacetSupportType state); + FacetSupportType get_state() const; + + // Get info on how it's split. + bool is_split() const { return number_of_split_sides() != 0; } int number_of_split_sides() const { return division_type & 0b11; } int side_to_keep() const; int side_to_split() const; - FacetSupportType get_state() const; - }; - - // Triangle and pointer to how it's divided (nullptr = not divided). - // The ptr is nullptr for all new triangles, it is only valid for - // the original (undivided) triangles. - struct Triangle { - Triangle(int a, int b, int c) - : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, - div_info{std::make_unique()} - {} - stl_triangle_vertex_indices verts_idxs; - std::unique_ptr div_info; + private: + // Bitmask encoding which sides are split. + int8_t division_type; + // bits 0, 1 : decimal 0, 1, 2 or 3 (how many sides are split) + // bits 2, 3 (non-leaf): decimal 0, 1 or 2 identifying the special edge + // (one that splits in one-edge split or one that stays in two-edge split). + // bits 2, 3 (leaf): FacetSupportType value }; // Lists of vertices and triangles, both original and new @@ -93,6 +90,8 @@ private: std::vector m_triangles; const TriangleMesh* m_mesh; + float m_edge_limit_sqr = 1.f; + // Number of original vertices and triangles. int m_orig_size_vertices; int m_orig_size_indices; @@ -104,6 +103,7 @@ private: // Caches for cursor position, radius and direction. struct Cursor { Vec3f center; + Vec3f source; Vec3f dir; float radius_sqr; }; @@ -111,8 +111,7 @@ private: Cursor m_cursor; // Private functions: - void select_triangle(int facet_idx, FacetSupportType type, - int num_of_inside_vertices = -1, + bool select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside = false); bool is_point_inside_cursor(const Vec3f& point) const; @@ -125,7 +124,8 @@ private: bool split_triangle(int facet_idx); - void remove_needless(int child_facet); + void remove_if_needless(int child_facet); + bool is_pointer_in_triangle(int facet_idx) const; }; From bed28bb2fff974cd2097b47d404b3cbeb69d570b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Jun 2020 11:45:51 +0200 Subject: [PATCH 193/826] TriangleSelector: even more progress --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 36 ++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 7 +--- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1c77f437c0..95f46b3fe6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -859,6 +859,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, float radius_sqr, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); + assert(is_approx(dir.norm(), 1.f)); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. @@ -892,7 +893,6 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // outside the cursor. bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) { - bool out = false; assert(facet_idx < int(m_triangles.size())); Triangle& tr = m_triangles[facet_idx]; @@ -903,10 +903,12 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo int num_of_inside_vertices = vertices_inside(facet_idx); - if (num_of_inside_vertices == 0 && ! cursor_inside) - return out; // FIXME: just an edge can be inside + if (num_of_inside_vertices == 0 + && ! cursor_inside + && ! is_edge_inside_cursor(facet_idx)) + return false; - if (num_of_inside_vertices == 3) { + if (vertices_inside(facet_idx) == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); tr.set_state(type); @@ -914,7 +916,6 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. - if (! tr.is_split() && tr.get_state() == type) { // This is leaf triangle that is already of correct type as a whole. // No need to split, all children would end up selected anyway. @@ -1065,11 +1066,10 @@ bool TriangleSelector::faces_camera(int facet) const { assert(facet < m_orig_size_indices); // The normal is cached in mesh->stl, use it. - return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) > 0.); + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); } - // How many vertices of a triangle are inside the circle? int TriangleSelector::vertices_inside(int facet_idx) const { @@ -1082,11 +1082,27 @@ int TriangleSelector::vertices_inside(int facet_idx) const } -// Is mouse pointer inside a triangle? -/*bool TriangleSelector::is_pointer_inside_triangle(int facet_idx) const +// Is edge inside cursor? +bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const { + Vec3f pts[3]; + for (int i=0; i<3; ++i) + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]]; -}*/ + const Vec3f& p = m_cursor.center; + + for (int side = 0; side < 3; ++side) { + const Vec3f& a = pts[side]; + const Vec3f& b = pts[side<2 ? side+1 : 0]; + Vec3f s = (b-a).normalized(); + float t = (p-a).dot(s); + Vec3f vector = a+t*s - p; + float dist_sqr = vector.squaredNorm(); + if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + return true; + } + return false; +} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 3e1b076c53..a50328d3b8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -113,19 +113,14 @@ private: // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside = false); - bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; - bool faces_camera(int facet) const; - void undivide_triangle(int facet_idx); - bool split_triangle(int facet_idx); - void remove_if_needless(int child_facet); bool is_pointer_in_triangle(int facet_idx) const; + bool is_edge_inside_cursor(int facet_idx) const; }; From fb73bb1c6637fc6deb350e987b3308bb32a70e36 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 23 Jun 2020 16:07:33 +0200 Subject: [PATCH 194/826] TriangleSelector: remerging triangles, bugfixes --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 202 ++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 18 +- 2 files changed, 140 insertions(+), 80 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 95f46b3fe6..8972e92467 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -95,7 +95,10 @@ void GLGizmoFdmSupports::on_render() const glsafe(::glEnable(GL_DEPTH_TEST)); //render_triangles(selection); - m_triangle_selector->render(); + + if (m_triangle_selector && ! m_setting_angle) + m_triangle_selector->render(m_imgui); + m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -569,12 +572,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - m_imgui->begin(std::string("TriangleSelector DEBUG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - static float edge_limit = 1.f; - m_imgui->slider_float("Edge limit (mm): ", &edge_limit, 0.1f, 8.f); - m_triangle_selector->set_edge_limit(edge_limit); - m_imgui->end(); - const float approx_height = m_imgui->scaled(18.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); @@ -740,9 +737,7 @@ CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); + | int(CommonGizmosDataID::ObjectClipper)); } @@ -872,7 +867,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, while (facet_idx < int(facets_to_check.size())) { int facet = facets_to_check[facet_idx]; if (! visited[facet]) { - if (select_triangle(facet, new_state, facet == facet_start)) { + if (select_triangle(facet, new_state)) { // add neighboring facets to list to be proccessed later for (int n=0; n<3; ++n) { if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) @@ -891,49 +886,54 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // the triangle recursively, selecting just subtriangles truly inside the circle. // This is done by an actual recursive call. Returns false if the triangle is // outside the cursor. -bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool cursor_inside) +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) { assert(facet_idx < int(m_triangles.size())); - Triangle& tr = m_triangles[facet_idx]; - if (! tr.valid) + Triangle* tr = &m_triangles[facet_idx]; + if (! tr->valid) return false; - cursor_inside = is_pointer_in_triangle(facet_idx); - int num_of_inside_vertices = vertices_inside(facet_idx); if (num_of_inside_vertices == 0 - && ! cursor_inside + && ! is_pointer_in_triangle(facet_idx) && ! is_edge_inside_cursor(facet_idx)) return false; - if (vertices_inside(facet_idx) == 3) { + if (num_of_inside_vertices == 3) { // dump any subdivision and select whole triangle undivide_triangle(facet_idx); - tr.set_state(type); + tr->set_state(type); } else { // the triangle is partially inside, let's recursively divide it // (if not already) and try selecting its children. - if (! tr.is_split() && tr.get_state() == type) { + if (! tr->is_split() && tr->get_state() == type) { // This is leaf triangle that is already of correct type as a whole. // No need to split, all children would end up selected anyway. return true; } split_triangle(facet_idx); - assert(facet_idx < int(m_triangles.size())); - int num_of_children = tr.number_of_split_sides() + 1; + tr = &m_triangles[facet_idx]; // might have been invalidated + + + int num_of_children = tr->number_of_split_sides() + 1; if (num_of_children != 1) { - for (int i=0; ichildren.size())); + assert(tr->children[i] < int(m_triangles.size())); + + select_triangle(tr->children[i], type, true); + tr = &m_triangles[facet_idx]; // might have been invalidated + } } } - - // In case that all siblings are leafs and have the same state now, + // In case that all children are leafs and have the same state now, // they may be removed and substituted by the parent triangle. - //remove_if_needless(facet_idx); + if (! recursive_call) + remove_useless_children(facet_idx); return true; } @@ -945,13 +945,13 @@ bool TriangleSelector::split_triangle(int facet_idx) return false; } - Triangle& tr = m_triangles[facet_idx]; + Triangle* tr = &m_triangles[facet_idx]; - FacetSupportType old_type = tr.get_state(); + FacetSupportType old_type = tr->get_state(); const double limit_squared = m_edge_limit_sqr; - stl_triangle_vertex_indices& facet = tr.verts_idxs; + stl_triangle_vertex_indices& facet = tr->verts_idxs; const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; @@ -964,7 +964,7 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { - tr.set_division(0); + tr->set_division(0); return false; } @@ -1012,17 +1012,19 @@ bool TriangleSelector::split_triangle(int facet_idx) m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); } + tr = &m_triangles[facet_idx]; // may have been invalidated + // Save how the triangle was split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. - tr.set_division(sides_to_split.size(), + tr->set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); // And save the children. All children should start in the same state as the triangle we just split. assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { - tr.children[i] = m_triangles.size()-1-i; - m_triangles[tr.children[i]].parent = facet_idx; - m_triangles[tr.children[i]].set_state(old_type); + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].parent = facet_idx; + m_triangles[tr->children[i]].set_state(old_type); } return true; @@ -1097,7 +1099,10 @@ bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const Vec3f s = (b-a).normalized(); float t = (p-a).dot(s); Vec3f vector = a+t*s - p; - float dist_sqr = vector.squaredNorm(); + + // vector is 3D vector from center to the intersection. What we want to + // measure is length of its projection onto plane perpendicular to dir. + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) return true; } @@ -1117,33 +1122,47 @@ void TriangleSelector::undivide_triangle(int facet_idx) undivide_triangle(tr.children[i]); m_triangles[tr.children[i]].valid = false; } + tr.set_division(0); // not split } - - tr.set_division(0); // not split } -void TriangleSelector::remove_if_needless(int child_facet) +void TriangleSelector::remove_useless_children(int facet_idx) { - if (m_triangles[child_facet].is_split() || ! m_triangles[child_facet].valid) + // Check that all children are leafs of the same type. If not, try to + // make them (recursive call). Remove them if sucessful. + + assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); + Triangle& tr = m_triangles[facet_idx]; + + if (! tr.is_split()) { + // This is a leaf, there nothing to do. This can happen during the + // first (non-recursive call). Shouldn't otherwise. return; - int parent = m_triangles[child_facet].parent; - if (parent == -1) - return; // root - FacetSupportType child_type = m_triangles[child_facet].get_state(); + } - // Check type of all valid children, if they're same, they are needless. - for (int i=0; i<=m_triangles[parent].number_of_split_sides(); ++i) - if (m_triangles[m_triangles[parent].children[i]].is_split() - || m_triangles[m_triangles[parent].children[i]].get_state() != child_type) - return; // not all children are the same + // Call this for all non-leaf children. + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); + if (m_triangles[tr.children[child_idx]].is_split()) + remove_useless_children(tr.children[child_idx]); + } - // All children are the same, kill them. - undivide_triangle(parent); - m_triangles[parent].set_state(child_type); - // And now try the same for parent (which has just become leaf). - remove_if_needless(parent); + // Return if a child is not leaf or two children differ in type. + FacetSupportType first_child_type; + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + if (m_triangles[tr.children[child_idx]].is_split()) + return; + if (child_idx == 0) + first_child_type = m_triangles[tr.children[0]].get_state(); + else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) + return; + } + + // If we got here, the children can be removed. + undivide_triangle(facet_idx); + tr.set_state(first_child_type); } @@ -1158,33 +1177,12 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh) m_mesh = &mesh; } - -void TriangleSelector::render() const +void TriangleSelector::render(ImGuiWrapper* imgui) { - ::glColor3f(0.f, 0.f, 1.f); - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); ::glTranslatef(offset.x(), offset.y(), offset.z()); - ::glScalef(1.01f, 1.01f, 1.01f); + ::glScalef(1.005f, 1.005f, 1.005f); - ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_idbegin(std::string("TriangleSelector dialog (DEV ONLY)"), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + imgui->text("Edge limit (mm): "); + imgui->slider_float("", &edge_limit, 0.1f, 8.f); + set_edge_limit(edge_limit); + imgui->checkbox("Show triangles: ", m_show_triangles); + + int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return tr.valid; }); + imgui->text("Valid triangles: " + std::to_string(valid_triangles) + + "/" + std::to_string(m_triangles.size())); + imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + + imgui->end(); + + if (m_show_triangles) { + ::glColor3f(0.f, 0.f, 1.f); + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + ::glBegin( GL_TRIANGLES); + for (int tr_id=0; tr_id +#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + + namespace Slic3r { enum class FacetSupportType : int8_t; @@ -25,7 +28,7 @@ class ClippingPlane; class TriangleSelector { public: void set_edge_limit(float edge_limit) { m_edge_limit_sqr = std::pow(edge_limit, 2.f); } - void render() const; + // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); @@ -40,10 +43,19 @@ public: void unselect_all(); + // Render current selection. Transformation matrices are supposed + // to be already set. + void render(ImGuiWrapper* imgui = nullptr); + // Remove all unnecessary data (such as vertices that are not needed // because the selection has been made larger. void garbage_collect(); +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + void render_debug(ImGuiWrapper* imgui); + bool m_show_triangles{true}; +#endif + private: // Triangle and info about how it's split. struct Triangle { @@ -112,13 +124,13 @@ private: // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, - bool cursor_inside = false); + bool recursive_call = false); bool is_point_inside_cursor(const Vec3f& point) const; int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); bool split_triangle(int facet_idx); - void remove_if_needless(int child_facet); + void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; }; From b9321856f387d6ef07ee9d769fe9674d83b8e31b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 24 Jun 2020 12:24:32 +0200 Subject: [PATCH 195/826] TriangleSelector: Reusing of previously calculated triangle divisions, partial garbage collection implementation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 144 ++++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 38 ++--- 2 files changed, 117 insertions(+), 65 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 8972e92467..2296dd570e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -800,51 +800,29 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const } - +// sides_to_split==-1 : just restore previous split void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) { - assert(sides_to_split >=0 && sides_to_split <= 3); + assert(sides_to_split >=-1 && sides_to_split <= 3); assert(special_side_idx >=-1 && special_side_idx < 3); // If splitting one or two sides, second argument must be provided. assert(sides_to_split != 1 || special_side_idx != -1); assert(sides_to_split != 2 || special_side_idx != -1); - division_type = sides_to_split | ((special_side_idx != -1 ? special_side_idx : 0 ) <<2); -} - - - -void TriangleSelector::Triangle::set_state(FacetSupportType type) -{ - // If this is not a leaf-node, this makes no sense and - // the bits are used for storing index of an edge. - assert(! is_split()); - division_type = (int8_t(type) << 2); -} - - - -int TriangleSelector::Triangle::side_to_keep() const -{ - assert(number_of_split_sides() == 2); - return division_type >> 2; -} - - - -int TriangleSelector::Triangle::side_to_split() const -{ - assert(number_of_split_sides() == 1); - return division_type >> 2; -} - - - -FacetSupportType TriangleSelector::Triangle::get_state() const -{ - assert(! is_split()); // this must be leaf - return FacetSupportType(division_type >> 2); + if (sides_to_split != -1) { + this->number_of_splits = sides_to_split; + if (sides_to_split != 0) { + assert(old_number_of_splits == 0); + this->special_side_idx = special_side_idx; + this->old_number_of_splits = sides_to_split; + } + } + else { + assert(old_number_of_splits != 0); + this->number_of_splits = old_number_of_splits; + // indices of children should still be there. + } } @@ -938,17 +916,29 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } -bool TriangleSelector::split_triangle(int facet_idx) +void TriangleSelector::split_triangle(int facet_idx) { if (m_triangles[facet_idx].is_split()) { - // The triangle was divided already. - return false; + // The triangle is divided already. + return; } Triangle* tr = &m_triangles[facet_idx]; FacetSupportType old_type = tr->get_state(); + if (tr->was_split_before() != 0) { + // This triangle is not split at the moment, but was at one point + // in history. We can just restore it and resurrect its children. + tr->set_division(-1); + for (int i=0; i<=tr->number_of_split_sides(); ++i) { + m_triangles[tr->children[i]].set_state(old_type); + m_triangles[tr->children[i]].valid = true; + } + return; + } + + // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; stl_triangle_vertex_indices& facet = tr->verts_idxs; @@ -964,8 +954,9 @@ bool TriangleSelector::split_triangle(int facet_idx) side_to_keep = pt_idx; } if (sides_to_split.empty()) { + // This shall be unselected. tr->set_division(0); - return false; + return; } // indices of triangle vertices @@ -1023,11 +1014,8 @@ bool TriangleSelector::split_triangle(int facet_idx) assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); for (int i=0; i<=int(sides_to_split.size()); ++i) { tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].parent = facet_idx; m_triangles[tr->children[i]].set_state(old_type); } - - return true; } @@ -1166,6 +1154,45 @@ void TriangleSelector::remove_useless_children(int facet_idx) } + +void TriangleSelector::garbage_collect() +{ + // First make a map from old to new triangle indices. + int new_idx = m_orig_size_indices; + std::vector new_triangle_indices(m_triangles.size(), -1); + std::vector invalid_vertices(m_vertices.size(), false); + for (int i = m_orig_size_indices; itext("Edge limit (mm): "); imgui->slider_float("", &edge_limit, 0.1f, 8.f); set_edge_limit(edge_limit); - imgui->checkbox("Show triangles: ", m_show_triangles); + imgui->checkbox("Show split triangles: ", m_show_triangles); + imgui->checkbox("Show invalid triangles: ", m_show_invalid); int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), [](const Triangle& tr) { return tr.valid; }); imgui->text("Valid triangles: " + std::to_string(valid_triangles) + "/" + std::to_string(m_triangles.size())); imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + if (imgui->button("Force garbage collection")) + garbage_collect(); imgui->end(); if (m_show_triangles) { - ::glColor3f(0.f, 0.f, 1.f); ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); ::glBegin( GL_TRIANGLES); for (int tr_id=0; tr_id children; @@ -79,22 +80,25 @@ private: void set_division(int sides_to_split, int special_side_idx = -1); // Get/set current state. - void set_state(FacetSupportType state); - FacetSupportType get_state() const; + void set_state(FacetSupportType type) { assert(! is_split()); state = type; } + FacetSupportType get_state() const { assert(! is_split()); return state; } // Get info on how it's split. bool is_split() const { return number_of_split_sides() != 0; } - int number_of_split_sides() const { return division_type & 0b11; } - int side_to_keep() const; - int side_to_split() const; + int number_of_split_sides() const { return number_of_splits; } + int side_to_keep() const { assert(number_of_split_sides() == 2); return special_side_idx; } + int side_to_split() const { assert(number_of_split_sides() == 1); return special_side_idx; } + bool was_split_before() const { return old_number_of_splits != 0; } + void forget_history() { old_number_of_splits = 0; } private: - // Bitmask encoding which sides are split. - int8_t division_type; - // bits 0, 1 : decimal 0, 1, 2 or 3 (how many sides are split) - // bits 2, 3 (non-leaf): decimal 0, 1 or 2 identifying the special edge - // (one that splits in one-edge split or one that stays in two-edge split). - // bits 2, 3 (leaf): FacetSupportType value + int number_of_splits; + int special_side_idx; + FacetSupportType state; + + // How many children were spawned during last split? + // Is not reset on remerging the triangle. + int old_number_of_splits; }; // Lists of vertices and triangles, both original and new @@ -129,7 +133,7 @@ private: int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); - bool split_triangle(int facet_idx); + void split_triangle(int facet_idx); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; From da6acd73e21407a8956d64b332b8fba47a94465f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 24 Jun 2020 14:47:53 +0200 Subject: [PATCH 196/826] TriangleSelector: Vertices are reference-counted and garbage collected Garbage collection is triggered automatically when more than half of all triangles are invalid --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 133 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 44 +++--- 2 files changed, 120 insertions(+), 57 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 2296dd570e..10d0459205 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -908,10 +908,20 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } } } - // In case that all children are leafs and have the same state now, - // they may be removed and substituted by the parent triangle. - if (! recursive_call) + + if (! recursive_call) { + // In case that all children are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. remove_useless_children(facet_idx); + + // Make sure that we did not lose track of invalid triangles. + assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; })); + + // Do garbage collection maybe? + if (2*m_invalid_triangles > int(m_triangles.size())) + garbage_collect(); + } return true; } @@ -934,6 +944,7 @@ void TriangleSelector::split_triangle(int facet_idx) for (int i=0; i<=tr->number_of_split_sides(); ++i) { m_triangles[tr->children[i]].set_state(old_type); m_triangles[tr->children[i]].valid = true; + --m_invalid_triangles; } return; } @@ -941,9 +952,11 @@ void TriangleSelector::split_triangle(int facet_idx) // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; - stl_triangle_vertex_indices& facet = tr->verts_idxs; - const stl_vertex* pts[3] = { &m_vertices[facet[0]], &m_vertices[facet[1]], &m_vertices[facet[2]]}; - double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), (*pts[0]-*pts[2]).squaredNorm(), (*pts[1]-*pts[0]).squaredNorm() }; + std::array& facet = tr->verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), + (*pts[0]-*pts[2]).squaredNorm(), + (*pts[1]-*pts[0]).squaredNorm() }; std::vector sides_to_split; int side_to_keep = -1; @@ -970,37 +983,37 @@ void TriangleSelector::split_triangle(int facet_idx) if (sides_to_split.size() == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]] + m_vertices[verts_idxs[2]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); } if (sides_to_split.size() == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[3]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - m_triangles.emplace_back(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); } if (sides_to_split.size() == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]] + m_vertices[verts_idxs[1]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]] + m_vertices[verts_idxs[3]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]] + m_vertices[verts_idxs[0]])/2.); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - m_triangles.emplace_back(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - m_triangles.emplace_back(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - m_triangles.emplace_back(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); } tr = &m_triangles[facet_idx]; // may have been invalidated @@ -1035,9 +1048,9 @@ bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const return ((b-a).cross(c-a)).dot(d-a) > 0.; }; - const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]]; - const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]]; - const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]]; + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; const Vec3f& q1 = m_cursor.center + m_cursor.dir; const Vec3f q2 = m_cursor.center - m_cursor.dir; @@ -1065,7 +1078,7 @@ int TriangleSelector::vertices_inside(int facet_idx) const { int inside = 0; for (size_t i=0; i<3; ++i) { - if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]])) + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) ++inside; } return inside; @@ -1077,7 +1090,7 @@ bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const { Vec3f pts[3]; for (int i=0; i<3; ++i) - pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]]; + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; const Vec3f& p = m_cursor.center; @@ -1109,6 +1122,7 @@ void TriangleSelector::undivide_triangle(int facet_idx) for (int i=0; i<=tr.number_of_split_sides(); ++i) { undivide_triangle(tr.children[i]); m_triangles[tr.children[i]].valid = false; + ++m_invalid_triangles; } tr.set_division(0); // not split } @@ -1138,7 +1152,7 @@ void TriangleSelector::remove_useless_children(int facet_idx) // Return if a child is not leaf or two children differ in type. - FacetSupportType first_child_type; + FacetSupportType first_child_type = FacetSupportType::NONE; for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) return; @@ -1160,13 +1174,26 @@ void TriangleSelector::garbage_collect() // First make a map from old to new triangle indices. int new_idx = m_orig_size_indices; std::vector new_triangle_indices(m_triangles.size(), -1); - std::vector invalid_vertices(m_vertices.size(), false); for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); + for (int i=m_orig_size_vertices; i= 0); + if (m_vertices[i].ref_cnt != 0) { + new_vertices_indices[i] = new_idx; + ++new_idx; } } @@ -1174,6 +1201,9 @@ void TriangleSelector::garbage_collect() m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), [](const Triangle& tr) { return ! tr.valid; }), m_triangles.end()); + m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), + [](const Vertex& vert) { return vert.ref_cnt == 0; }), + m_vertices.end()); // Now go through all remaining triangles and update changed indices. for (Triangle& tr : m_triangles) { @@ -1187,20 +1217,32 @@ void TriangleSelector::garbage_collect() } } + // Update indices into m_vertices. The original vertices are never + // touched and need not be reindexed. + for (int& idx : tr.verts_idxs) { + if (idx >= m_orig_size_vertices) { + assert(new_vertices_indices[idx] != -1); + idx = new_vertices_indices[idx]; + } + } + // If this triangle was split before, forget it. // Children referenced in the cache are dead by now. tr.forget_history(); } + + m_invalid_triangles = 0; } TriangleSelector::TriangleSelector(const TriangleMesh& mesh) { for (const stl_vertex& vert : mesh.its.vertices) - m_vertices.push_back(vert); + m_vertices.emplace_back(vert); for (const stl_triangle_vertex_indices& ind : mesh.its.indices) - m_triangles.emplace_back(Triangle(ind[0], ind[1], ind[2])); + push_triangle(ind[0], ind[1], ind[2]); m_orig_size_vertices = m_vertices.size(); m_orig_size_indices = m_triangles.size(); + m_invalid_triangles = 0; m_mesh = &mesh; } @@ -1222,9 +1264,9 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glColor4f(1.f, 0.f, 0.f, 0.2f); for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], - m_vertices[tr.verts_idxs[i]][1], - m_vertices[tr.verts_idxs[i]][2]); + ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], + m_vertices[tr.verts_idxs[i]].v[1], + m_vertices[tr.verts_idxs[i]].v[2]); } ::glEnd(); @@ -1250,6 +1292,18 @@ void TriangleSelector::set_edge_limit(float edge_limit) } } + + +void TriangleSelector::push_triangle(int a, int b, int c) +{ + for (int i : {a, b, c}) { + assert(i >= 0 && i < int(m_vertices.size())); + ++m_vertices[i].ref_cnt; + } + m_triangles.emplace_back(a, b, c); +} + + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) { @@ -1262,11 +1316,10 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) imgui->checkbox("Show split triangles: ", m_show_triangles); imgui->checkbox("Show invalid triangles: ", m_show_invalid); - int valid_triangles = std::count_if(m_triangles.begin(), m_triangles.end(), - [](const Triangle& tr) { return tr.valid; }); + int valid_triangles = m_triangles.size() - m_invalid_triangles; imgui->text("Valid triangles: " + std::to_string(valid_triangles) + "/" + std::to_string(m_triangles.size())); - imgui->text("Number of vertices: " + std::to_string(m_vertices.size())); + imgui->text("Vertices: " + std::to_string(m_vertices.size())); if (imgui->button("Force garbage collection")) garbage_collect(); @@ -1290,9 +1343,9 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) ::glColor3f(0.f, 0.f, 1.f); for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]][0], - m_vertices[tr.verts_idxs[i]][1], - m_vertices[tr.verts_idxs[i]][2]); + ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], + m_vertices[tr.verts_idxs[i]].v[1], + m_vertices[tr.verts_idxs[i]].v[2]); } ::glEnd(); ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index fa03f7987b..fb14f3c5ee 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -33,7 +33,7 @@ public: // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh); - // Select all triangles inside the circle, subdivide where needed. + // Select all triangles fully inside the circle, subdivide where needed. void select_patch(const Vec3f& hit, // point where to start int facet_start, // facet that point belongs to const Vec3f& source, // camera position (mesh coords) @@ -41,14 +41,11 @@ public: float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? - void unselect_all(); - // Render current selection. Transformation matrices are supposed // to be already set. void render(ImGuiWrapper* imgui = nullptr); - // Remove all unnecessary data (such as vertices that are not needed - // because the selection has been made larger. + // Remove all unnecessary data. void garbage_collect(); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG @@ -59,21 +56,24 @@ public: private: // Triangle and info about how it's split. - struct Triangle { + class Triangle { public: + // Use TriangleSelector::push_triangle to create a new triangle. + // It increments/decrements reference counter on vertices. Triangle(int a, int b, int c) - : verts_idxs{stl_triangle_vertex_indices(a, b, c)}, + : verts_idxs{a, b, c}, state{FacetSupportType(0)}, number_of_splits{0}, special_side_idx{0}, old_number_of_splits{0} {} - stl_triangle_vertex_indices verts_idxs; + // Indices into m_vertices. + std::array verts_idxs; - // Is this triangle valid or marked to remove? + // Is this triangle valid or marked to be removed? bool valid{true}; - // Children triangles (0 = no child) + // Children triangles. std::array children; // Set the division type. @@ -101,22 +101,31 @@ private: int old_number_of_splits; }; + struct Vertex { + explicit Vertex(const stl_vertex& vert) + : v{vert}, + ref_cnt{0} + {} + stl_vertex v; + int ref_cnt; + }; + // Lists of vertices and triangles, both original and new - std::vector m_vertices; + std::vector m_vertices; std::vector m_triangles; const TriangleMesh* m_mesh; + // Number of invalid triangles (to trigger garbage collection). + int m_invalid_triangles; + + // Limiting length of triangle side (squared). float m_edge_limit_sqr = 1.f; // Number of original vertices and triangles. int m_orig_size_vertices; int m_orig_size_indices; - // Limits for stopping the recursion. - float m_max_edge_length; - int m_max_recursion_depth; - - // Caches for cursor position, radius and direction. + // Cache for cursor position, radius and direction. struct Cursor { Vec3f center; Vec3f source; @@ -130,13 +139,14 @@ private: bool select_triangle(int facet_idx, FacetSupportType type, bool recursive_call = false); bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; + int vertices_inside(int facet_idx) const; bool faces_camera(int facet) const; void undivide_triangle(int facet_idx); void split_triangle(int facet_idx); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; + void push_triangle(int a, int b, int c); }; From 814f8be92f410e2cd5a7928a64a5a032072da342 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 1 Jul 2020 09:09:25 +0200 Subject: [PATCH 197/826] TriangleSelector: getting ready for frontend/backend separation --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 279 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 11 +- 2 files changed, 207 insertions(+), 83 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 10d0459205..f2a6bb8adc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -972,63 +972,12 @@ void TriangleSelector::split_triangle(int facet_idx) return; } - // indices of triangle vertices - std::vector verts_idxs; - int idx = sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]; - for (int j=0; j<3; ++j) { - verts_idxs.push_back(facet[idx++]); - if (idx == 3) - idx = 0; - } - - - if (sides_to_split.size() == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); - } - - if (sides_to_split.size() == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); - } - - if (sides_to_split.size() == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); - } - - tr = &m_triangles[facet_idx]; // may have been invalidated - - // Save how the triangle was split. Second argument makes sense only for one + // Save how the triangle will be split. Second argument makes sense only for one // or two split sides, otherwise the value is ignored. tr->set_division(sides_to_split.size(), sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); - // And save the children. All children should start in the same state as the triangle we just split. - assert(! sides_to_split.empty() && int(sides_to_split.size()) <= 3); - for (int i=0; i<=int(sides_to_split.size()); ++i) { - tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].set_state(old_type); - } + perform_split(facet_idx, old_type); } @@ -1253,22 +1202,45 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glScalef(1.005f, 1.005f, 1.005f); - ::glBegin( GL_TRIANGLES); + int enf_cnt = 0; + int blc_cnt = 0; + for (const Triangle& tr : m_triangles) { if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; - if (tr.get_state() == FacetSupportType::ENFORCER) - ::glColor4f(0.f, 0.f, 1.f, 0.2f); - else - ::glColor4f(1.f, 0.f, 0.f, 0.2f); + GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER + ? m_iva_enforcers + : m_iva_blockers; + int& cnt = tr.get_state() == FacetSupportType::ENFORCER + ? enf_cnt + : blc_cnt; for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], - m_vertices[tr.verts_idxs[i]].v[1], - m_vertices[tr.verts_idxs[i]].v[2]); + va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va.push_triangle(cnt, + cnt+1, + cnt+2); + cnt += 3; } - ::glEnd(); + + m_iva_enforcers.finalize_geometry(true); + m_iva_blockers.finalize_geometry(true); + + if (m_iva_enforcers.has_VBOs()) { + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + m_iva_enforcers.render(); + } + m_iva_enforcers.release_geometry(); + + if (m_iva_blockers.has_VBOs()) { + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + m_iva_blockers.render(); + } + m_iva_blockers.release_geometry(); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) @@ -1304,6 +1276,110 @@ void TriangleSelector::push_triangle(int a, int b, int c) } +void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +{ + Triangle* tr = &m_triangles[facet_idx]; + + assert(tr->is_split()); + + // Read info about how to split this triangle. + int sides_to_split = tr->number_of_split_sides(); + + // indices of triangle vertices + std::vector verts_idxs; + int idx = tr->special_side(); + for (int j=0; j<3; ++j) { + verts_idxs.push_back(tr->verts_idxs[idx++]); + if (idx == 3) + idx = 0; + } + + if (sides_to_split == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + tr = &m_triangles[facet_idx]; // may have been invalidated + + // And save the children. All children should start in the same state as the triangle we just split. + assert(sides_to_split <= 3); + for (int i=0; i<=sides_to_split; ++i) { + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].set_state(old_state); + } +} + + +std::map TriangleSelector::serialize() const +{ + std::map out; + for (int i=0; i serialize_recursive; + serialize_recursive = [this, &stored_triangles, &data, &serialize_recursive](int facet_idx) { + const Triangle& tr = m_triangles[facet_idx]; + int split_sides = tr.number_of_split_sides(); + assert( split_sides > 0 && split_sides <= 3); + data |= (split_sides << (stored_triangles * 4)); + + if (tr.is_split()) { + assert(split_sides > 0); + assert(tr.special_side() >= 0 && tr.special_side() <= 3); + data |= (tr.special_side() << (stored_triangles * 4 + 2)); + ++stored_triangles; + for (int child_idx=0; child_idx<=split_sides; ++child_idx) + serialize_recursive(tr.children[child_idx]); + } else { + assert(int8_t(tr.get_state()) <= 3); + data |= (int8_t(tr.get_state()) << (stored_triangles * 4 + 2)); + ++stored_triangles; + } + }; + + serialize_recursive(i); + out[i] = data; + } + + return out; +} + + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) { @@ -1323,33 +1399,74 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) if (imgui->button("Force garbage collection")) garbage_collect(); + if (imgui->button("Serialize")) { + auto map = serialize(); + for (auto& [idx, data] : map) + std::cout << idx << "\t" << data << std::endl; + } + imgui->end(); - if (m_show_triangles) { - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + if (! m_show_triangles) + return; - ::glBegin( GL_TRIANGLES); - for (int tr_id=0; tr_id cnts; - for (int i=0; i<3; ++i) - ::glVertex3f(m_vertices[tr.verts_idxs[i]].v[0], - m_vertices[tr.verts_idxs[i]].v[1], - m_vertices[tr.verts_idxs[i]].v[2]); + ::glScalef(1.01f, 1.01f, 1.01f); + + for (int tr_id=0; tr_idpush_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va->push_triangle(*cnt, + *cnt+1, + *cnt+2); + *cnt += 3; } + + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + for (vtype i : {ORIGINAL, SPLIT, INVALID}) { + GLIndexedVertexArray& va = m_varrays[i]; + va.finalize_geometry(true); + if (va.has_VBOs()) { + switch (i) { + case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; + case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; + case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; + } + va.render(); + } + } + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); } #endif diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index fb14f3c5ee..f3a66eca93 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -48,6 +48,9 @@ public: // Remove all unnecessary data. void garbage_collect(); + // Store the division trees in compact form. + std::map serialize() const; + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); bool m_show_triangles{true}; @@ -86,8 +89,7 @@ private: // Get info on how it's split. bool is_split() const { return number_of_split_sides() != 0; } int number_of_split_sides() const { return number_of_splits; } - int side_to_keep() const { assert(number_of_split_sides() == 2); return special_side_idx; } - int side_to_split() const { assert(number_of_split_sides() == 1); return special_side_idx; } + int special_side() const { assert(is_split()); return special_side_idx; } bool was_split_before() const { return old_number_of_splits != 0; } void forget_history() { old_number_of_splits = 0; } @@ -115,6 +117,10 @@ private: std::vector m_triangles; const TriangleMesh* m_mesh; + GLIndexedVertexArray m_iva_enforcers; + GLIndexedVertexArray m_iva_blockers; + std::array m_varrays; + // Number of invalid triangles (to trigger garbage collection). int m_invalid_triangles; @@ -147,6 +153,7 @@ private: bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; void push_triangle(int a, int b, int c); + void perform_split(int facet_idx, FacetSupportType old_state); }; From b250c08ec9c76fe3e1895e3ed40536c4c884ecb4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 2 Jul 2020 12:30:12 +0200 Subject: [PATCH 198/826] TriangleSelector: Serialization and deserialization --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 153 ++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 11 +- 2 files changed, 143 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index f2a6bb8adc..01427ec098 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -1184,15 +1184,26 @@ void TriangleSelector::garbage_collect() } TriangleSelector::TriangleSelector(const TriangleMesh& mesh) + : m_mesh{&mesh} { - for (const stl_vertex& vert : mesh.its.vertices) + reset(); + +} + + +void TriangleSelector::reset() +{ + if (! m_orig_size_indices != 0) // unless this is run from constructor + garbage_collect(); + m_vertices.clear(); + m_triangles.clear(); + for (const stl_vertex& vert : m_mesh->its.vertices) m_vertices.emplace_back(vert); - for (const stl_triangle_vertex_indices& ind : mesh.its.indices) + for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) push_triangle(ind[0], ind[1], ind[2]); m_orig_size_vertices = m_vertices.size(); m_orig_size_indices = m_triangles.size(); m_invalid_triangles = 0; - m_mesh = &mesh; } void TriangleSelector::render(ImGuiWrapper* imgui) @@ -1339,35 +1350,56 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) } -std::map TriangleSelector::serialize() const +std::map> TriangleSelector::serialize() const { - std::map out; + // Each original triangle of the mesh is assigned a number encoding its state + // or how it is split. Each triangle is encoded by 4 bits (xxyy): + // leaf triangle: xx = FacetSupportType, yy = 0 + // non-leaf: xx = special side, yy = number of split sides + // These are bitwise appended and formed into one 64-bit integer. + + // The function returns a map from original triangle indices to + // stream of bits encoding state and offsprings. + + std::map> out; for (int i=0; i data; // complete encoding of this mesh triangle + int stored_triangles = 0; // how many have been already encoded std::function serialize_recursive; - serialize_recursive = [this, &stored_triangles, &data, &serialize_recursive](int facet_idx) { + serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { const Triangle& tr = m_triangles[facet_idx]; + + // Always save number of split sides. It is zero for unsplit triangles. int split_sides = tr.number_of_split_sides(); - assert( split_sides > 0 && split_sides <= 3); - data |= (split_sides << (stored_triangles * 4)); + assert(split_sides >= 0 && split_sides <= 3); + + //data |= (split_sides << (stored_triangles * 4)); + data.push_back(split_sides & 0b01); + data.push_back(split_sides & 0b10); if (tr.is_split()) { + // If this triangle is split, save which side is split (in case + // of one split) or kept (in case of two splits). The value will + // be ignored for 3-side split. assert(split_sides > 0); assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data |= (tr.special_side() << (stored_triangles * 4 + 2)); + data.push_back(tr.special_side() & 0b01); + data.push_back(tr.special_side() & 0b10); ++stored_triangles; + // Now save all children. for (int child_idx=0; child_idx<=split_sides; ++child_idx) serialize_recursive(tr.children[child_idx]); } else { - assert(int8_t(tr.get_state()) <= 3); - data |= (int8_t(tr.get_state()) << (stored_triangles * 4 + 2)); + // In case this is leaf, we better save information about its state. + assert(int(tr.get_state()) <= 3); + data.push_back(int(tr.get_state()) & 0b01); + data.push_back(int(tr.get_state()) & 0b10); ++stored_triangles; } }; @@ -1379,6 +1411,90 @@ std::map TriangleSelector::serialize() const return out; } +void TriangleSelector::deserialize(const std::map> data) +{ + reset(); // dump any current state + for (const auto& [triangle_id, code] : data) { + assert(triangle_id < int(m_triangles.size())); + int processed_triangles = 0; + struct ProcessingInfo { + int facet_id = 0; + int processed_children = 0; + int total_children = 0; + }; + + // Vector to store all parents that have offsprings. + std::vector parents; + + while (true) { + // Read next triangle info. + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[4 * processed_triangles + i]); + } + ++processed_triangles; + + int num_of_split_sides = (next_code & 0b11); + int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; + bool is_split = num_of_children != 0; + FacetSupportType state = FacetSupportType(next_code >> 2); + int special_side = (next_code >> 2); + + // Take care of the first iteration separately, so handling of the others is simpler. + if (parents.empty()) { + if (! is_split) { + // root is not split. just set the state and that's it. + m_triangles[triangle_id].set_state(state); + break; + } else { + // root is split, add it into list of parents and split it. + // then go to the next. + parents.push_back({triangle_id, 0, num_of_children}); + m_triangles[triangle_id].set_division(num_of_children-1, special_side); + perform_split(triangle_id, FacetSupportType::NONE); + continue; + } + } + + // This is not the first iteration. This triangle is a child of last seen parent. + assert(! parents.empty()); + assert(parents.back().processed_children < parents.back().total_children); + + if (is_split) { + // split the triangle and save it as parent of the next ones. + const ProcessingInfo& last = parents.back(); + int this_idx = m_triangles[last.facet_id].children[last.processed_children]; + m_triangles[this_idx].set_division(num_of_children-1, special_side); + perform_split(this_idx, FacetSupportType::NONE); + parents.push_back({this_idx, 0, num_of_children}); + } else { + // this triangle belongs to last split one + m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); + ++parents.back().processed_children; + } + + + // If all children of the past parent triangle are claimed, move to grandparent. + while (parents.back().processed_children == parents.back().total_children) { + parents.pop_back(); + + if (parents.empty()) + break; + + // And increment the grandparent children counter, because + // we have just finished that branch and got back here. + ++parents.back().processed_children; + } + + // In case we popped back the root, we should be done. + if (parents.empty()) + break; + } + + } +} + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void TriangleSelector::render_debug(ImGuiWrapper* imgui) @@ -1399,10 +1515,9 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) if (imgui->button("Force garbage collection")) garbage_collect(); - if (imgui->button("Serialize")) { + if (imgui->button("Serialize - deserialize")) { auto map = serialize(); - for (auto& [idx, data] : map) - std::cout << idx << "\t" << data << std::endl; + deserialize(map); } imgui->end(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index f3a66eca93..099c9a30cb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -45,11 +45,18 @@ public: // to be already set. void render(ImGuiWrapper* imgui = nullptr); + // Clear everything and make the tree empty. + void reset(); + // Remove all unnecessary data. void garbage_collect(); - // Store the division trees in compact form. - std::map serialize() const; + // Store the division trees in compact form (a long stream of + // bits for each triangle of the original mesh). + std::map> serialize() const; + + // Load serialized data. Assumes that correct mesh is loaded. + void deserialize(const std::map> data); #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); From 6baff45759bc9dd69e2a836bef59f62101d58f77 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 9 Jul 2020 16:25:34 +0200 Subject: [PATCH 199/826] TriangleSelector: Separated frontend/backend, support of multiple volumes, etc. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Model.cpp | 19 +- src/libslic3r/Model.hpp | 6 +- src/libslic3r/TriangleSelector.cpp | 654 ++++++++++++++++ src/libslic3r/TriangleSelector.hpp | 149 ++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 736 +------------------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 145 +--- 7 files changed, 856 insertions(+), 855 deletions(-) create mode 100644 src/libslic3r/TriangleSelector.cpp create mode 100644 src/libslic3r/TriangleSelector.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 881466b399..9f566b4051 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -187,6 +187,8 @@ add_library(libslic3r STATIC Utils.hpp Time.cpp Time.hpp + TriangleSelector.cpp + TriangleSelector.hpp MTUtils.hpp VoronoiOffset.cpp VoronoiOffset.hpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0719cac8cf..b6bae489b4 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2,6 +2,7 @@ #include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" +#include "TriangleSelector.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -1833,25 +1834,21 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const std::vector FacetsAnnotation::get_facets(FacetSupportType type) const { std::vector out; - for (auto& [facet_idx, this_type] : m_data) + /*for (auto& [facet_idx, this_type] : m_data) if (this_type == type) out.push_back(facet_idx); - return out; + */return out; } -void FacetsAnnotation::set_facet(int idx, FacetSupportType type) +void FacetsAnnotation::set(const TriangleSelector& selector) { - bool changed = true; - - if (type == FacetSupportType::NONE) - changed = m_data.erase(idx) != 0; - else - m_data[idx] = type; - - if (changed) + std::map> sel_map = selector.serialize(); + if (sel_map != m_data) { + m_data = sel_map; update_timestamp(); + } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index be298ae4bb..de20e0fdcc 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -39,6 +39,7 @@ class ModelVolume; class ModelWipeTower; class Print; class SLAPrint; +class TriangleSelector; namespace UndoRedo { class StackImpl; @@ -404,8 +405,9 @@ class FacetsAnnotation { public: using ClockType = std::chrono::steady_clock; + const std::map>& get_data() const { return m_data; } + void set(const TriangleSelector& selector); std::vector get_facets(FacetSupportType type) const; - void set_facet(int idx, FacetSupportType type); void clear(); ClockType::time_point get_timestamp() const { return timestamp; } @@ -419,7 +421,7 @@ public: } private: - std::map m_data; + std::map> m_data; ClockType::time_point timestamp; void update_timestamp() { diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp new file mode 100644 index 0000000000..340f5a29a4 --- /dev/null +++ b/src/libslic3r/TriangleSelector.cpp @@ -0,0 +1,654 @@ +#include "TriangleSelector.hpp" +#include "Model.hpp" + + +namespace Slic3r { + + + +// sides_to_split==-1 : just restore previous split +void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) +{ + assert(sides_to_split >=-1 && sides_to_split <= 3); + assert(special_side_idx >=-1 && special_side_idx < 3); + + // If splitting one or two sides, second argument must be provided. + assert(sides_to_split != 1 || special_side_idx != -1); + assert(sides_to_split != 2 || special_side_idx != -1); + + if (sides_to_split != -1) { + this->number_of_splits = sides_to_split; + if (sides_to_split != 0) { + assert(old_number_of_splits == 0); + this->special_side_idx = special_side_idx; + this->old_number_of_splits = sides_to_split; + } + } + else { + assert(old_number_of_splits != 0); + this->number_of_splits = old_number_of_splits; + // indices of children should still be there. + } +} + + + +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, + const Vec3f& source, const Vec3f& dir, + float radius_sqr, FacetSupportType new_state) +{ + assert(facet_start < m_orig_size_indices); + assert(is_approx(dir.norm(), 1.f)); + + // Save current cursor center, squared radius and camera direction, + // so we don't have to pass it around. + m_cursor = {hit, source, dir, radius_sqr}; + + // Now start with the facet the pointer points to and check all adjacent facets. + std::vector facets_to_check{facet_start}; + std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed + int facet_idx = 0; // index into facets_to_check + while (facet_idx < int(facets_to_check.size())) { + int facet = facets_to_check[facet_idx]; + if (! visited[facet]) { + if (select_triangle(facet, new_state)) { + // add neighboring facets to list to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } + } + } + visited[facet] = true; + ++facet_idx; + } +} + + + +// Selects either the whole triangle (discarding any children it had), or divides +// the triangle recursively, selecting just subtriangles truly inside the circle. +// This is done by an actual recursive call. Returns false if the triangle is +// outside the cursor. +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) +{ + assert(facet_idx < int(m_triangles.size())); + + Triangle* tr = &m_triangles[facet_idx]; + if (! tr->valid) + return false; + + int num_of_inside_vertices = vertices_inside(facet_idx); + + if (num_of_inside_vertices == 0 + && ! is_pointer_in_triangle(facet_idx) + && ! is_edge_inside_cursor(facet_idx)) + return false; + + if (num_of_inside_vertices == 3) { + // dump any subdivision and select whole triangle + undivide_triangle(facet_idx); + tr->set_state(type); + } else { + // the triangle is partially inside, let's recursively divide it + // (if not already) and try selecting its children. + + if (! tr->is_split() && tr->get_state() == type) { + // This is leaf triangle that is already of correct type as a whole. + // No need to split, all children would end up selected anyway. + return true; + } + + split_triangle(facet_idx); + tr = &m_triangles[facet_idx]; // might have been invalidated + + + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i=0; ichildren.size())); + assert(tr->children[i] < int(m_triangles.size())); + + select_triangle(tr->children[i], type, true); + tr = &m_triangles[facet_idx]; // might have been invalidated + } + } + } + + if (! recursive_call) { + // In case that all children are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. + remove_useless_children(facet_idx); + + // Make sure that we did not lose track of invalid triangles. + assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; })); + + // Do garbage collection maybe? + if (2*m_invalid_triangles > int(m_triangles.size())) + garbage_collect(); + } + return true; +} + + +void TriangleSelector::split_triangle(int facet_idx) +{ + if (m_triangles[facet_idx].is_split()) { + // The triangle is divided already. + return; + } + + Triangle* tr = &m_triangles[facet_idx]; + + FacetSupportType old_type = tr->get_state(); + + if (tr->was_split_before() != 0) { + // This triangle is not split at the moment, but was at one point + // in history. We can just restore it and resurrect its children. + tr->set_division(-1); + for (int i=0; i<=tr->number_of_split_sides(); ++i) { + m_triangles[tr->children[i]].set_state(old_type); + m_triangles[tr->children[i]].valid = true; + --m_invalid_triangles; + } + return; + } + + // If we got here, we are about to actually split the triangle. + const double limit_squared = m_edge_limit_sqr; + + std::array& facet = tr->verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), + (*pts[0]-*pts[2]).squaredNorm(), + (*pts[1]-*pts[0]).squaredNorm() }; + + std::vector sides_to_split; + int side_to_keep = -1; + for (int pt_idx = 0; pt_idx<3; ++pt_idx) { + if (sides[pt_idx] > limit_squared) + sides_to_split.push_back(pt_idx); + else + side_to_keep = pt_idx; + } + if (sides_to_split.empty()) { + // This shall be unselected. + tr->set_division(0); + return; + } + + // Save how the triangle will be split. Second argument makes sense only for one + // or two split sides, otherwise the value is ignored. + tr->set_division(sides_to_split.size(), + sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); + + perform_split(facet_idx, old_type); +} + + +// Calculate distance of a point from a line. +bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const +{ + Vec3f diff = m_cursor.center - point; + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; +} + + +// Is pointer in a triangle? +bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const +{ + auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, + const Vec3f& c, const Vec3f& d) -> bool { + return ((b-a).cross(c-a)).dot(d-a) > 0.; + }; + + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; + const Vec3f& q1 = m_cursor.center + m_cursor.dir; + const Vec3f q2 = m_cursor.center - m_cursor.dir; + + if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { + bool pos = signed_volume_sign(q1,q2,p1,p2); + if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) + return true; + } + return false; +} + + + +// Determine whether this facet is potentially visible (still can be obscured). +bool TriangleSelector::faces_camera(int facet) const +{ + assert(facet < m_orig_size_indices); + // The normal is cached in mesh->stl, use it. + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); +} + + +// How many vertices of a triangle are inside the circle? +int TriangleSelector::vertices_inside(int facet_idx) const +{ + int inside = 0; + for (size_t i=0; i<3; ++i) { + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) + ++inside; + } + return inside; +} + + +// Is edge inside cursor? +bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const +{ + Vec3f pts[3]; + for (int i=0; i<3; ++i) + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; + + const Vec3f& p = m_cursor.center; + + for (int side = 0; side < 3; ++side) { + const Vec3f& a = pts[side]; + const Vec3f& b = pts[side<2 ? side+1 : 0]; + Vec3f s = (b-a).normalized(); + float t = (p-a).dot(s); + Vec3f vector = a+t*s - p; + + // vector is 3D vector from center to the intersection. What we want to + // measure is length of its projection onto plane perpendicular to dir. + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); + if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + return true; + } + return false; +} + + + +// Recursively remove all subtriangles. +void TriangleSelector::undivide_triangle(int facet_idx) +{ + assert(facet_idx < int(m_triangles.size())); + Triangle& tr = m_triangles[facet_idx]; + + if (tr.is_split()) { + for (int i=0; i<=tr.number_of_split_sides(); ++i) { + undivide_triangle(tr.children[i]); + m_triangles[tr.children[i]].valid = false; + ++m_invalid_triangles; + } + tr.set_division(0); // not split + } +} + + +void TriangleSelector::remove_useless_children(int facet_idx) +{ + // Check that all children are leafs of the same type. If not, try to + // make them (recursive call). Remove them if sucessful. + + assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); + Triangle& tr = m_triangles[facet_idx]; + + if (! tr.is_split()) { + // This is a leaf, there nothing to do. This can happen during the + // first (non-recursive call). Shouldn't otherwise. + return; + } + + // Call this for all non-leaf children. + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); + if (m_triangles[tr.children[child_idx]].is_split()) + remove_useless_children(tr.children[child_idx]); + } + + + // Return if a child is not leaf or two children differ in type. + FacetSupportType first_child_type = FacetSupportType::NONE; + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + if (m_triangles[tr.children[child_idx]].is_split()) + return; + if (child_idx == 0) + first_child_type = m_triangles[tr.children[0]].get_state(); + else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) + return; + } + + // If we got here, the children can be removed. + undivide_triangle(facet_idx); + tr.set_state(first_child_type); +} + + + +void TriangleSelector::garbage_collect() +{ + // First make a map from old to new triangle indices. + int new_idx = m_orig_size_indices; + std::vector new_triangle_indices(m_triangles.size(), -1); + for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); + for (int i=m_orig_size_vertices; i= 0); + if (m_vertices[i].ref_cnt != 0) { + new_vertices_indices[i] = new_idx; + ++new_idx; + } + } + + // We can remove all invalid triangles and vertices that are no longer referenced. + m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; }), + m_triangles.end()); + m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), + [](const Vertex& vert) { return vert.ref_cnt == 0; }), + m_vertices.end()); + + // Now go through all remaining triangles and update changed indices. + for (Triangle& tr : m_triangles) { + assert(tr.valid); + + if (tr.is_split()) { + // There are children. Update their indices. + for (int j=0; j<=tr.number_of_split_sides(); ++j) { + assert(new_triangle_indices[tr.children[j]] != -1); + tr.children[j] = new_triangle_indices[tr.children[j]]; + } + } + + // Update indices into m_vertices. The original vertices are never + // touched and need not be reindexed. + for (int& idx : tr.verts_idxs) { + if (idx >= m_orig_size_vertices) { + assert(new_vertices_indices[idx] != -1); + idx = new_vertices_indices[idx]; + } + } + + // If this triangle was split before, forget it. + // Children referenced in the cache are dead by now. + tr.forget_history(); + } + + m_invalid_triangles = 0; +} + +TriangleSelector::TriangleSelector(const TriangleMesh& mesh) + : m_mesh{&mesh} +{ + reset(); +} + + +void TriangleSelector::reset() +{ + if (! m_orig_size_indices != 0) // unless this is run from constructor + garbage_collect(); + m_vertices.clear(); + m_triangles.clear(); + for (const stl_vertex& vert : m_mesh->its.vertices) + m_vertices.emplace_back(vert); + for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) + push_triangle(ind[0], ind[1], ind[2]); + m_orig_size_vertices = m_vertices.size(); + m_orig_size_indices = m_triangles.size(); + m_invalid_triangles = 0; +} + + + + + +void TriangleSelector::set_edge_limit(float edge_limit) +{ + float new_limit_sqr = std::pow(edge_limit, 2.f); + + if (new_limit_sqr != m_edge_limit_sqr) { + m_edge_limit_sqr = new_limit_sqr; + + // The way how triangles split may be different now, forget + // all cached splits. + garbage_collect(); + } +} + + + +void TriangleSelector::push_triangle(int a, int b, int c) +{ + for (int i : {a, b, c}) { + assert(i >= 0 && i < int(m_vertices.size())); + ++m_vertices[i].ref_cnt; + } + m_triangles.emplace_back(a, b, c); +} + + +void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +{ + Triangle* tr = &m_triangles[facet_idx]; + + assert(tr->is_split()); + + // Read info about how to split this triangle. + int sides_to_split = tr->number_of_split_sides(); + + // indices of triangle vertices + std::vector verts_idxs; + int idx = tr->special_side(); + for (int j=0; j<3; ++j) { + verts_idxs.push_back(tr->verts_idxs[idx++]); + if (idx == 3) + idx = 0; + } + + if (sides_to_split == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + tr = &m_triangles[facet_idx]; // may have been invalidated + + // And save the children. All children should start in the same state as the triangle we just split. + assert(sides_to_split <= 3); + for (int i=0; i<=sides_to_split; ++i) { + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].set_state(old_state); + } +} + + +std::map> TriangleSelector::serialize() const +{ + // Each original triangle of the mesh is assigned a number encoding its state + // or how it is split. Each triangle is encoded by 4 bits (xxyy): + // leaf triangle: xx = FacetSupportType, yy = 0 + // non-leaf: xx = special side, yy = number of split sides + // These are bitwise appended and formed into one 64-bit integer. + + // The function returns a map from original triangle indices to + // stream of bits encoding state and offsprings. + + std::map> out; + for (int i=0; i data; // complete encoding of this mesh triangle + int stored_triangles = 0; // how many have been already encoded + + std::function serialize_recursive; + serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { + const Triangle& tr = m_triangles[facet_idx]; + + // Always save number of split sides. It is zero for unsplit triangles. + int split_sides = tr.number_of_split_sides(); + assert(split_sides >= 0 && split_sides <= 3); + + //data |= (split_sides << (stored_triangles * 4)); + data.push_back(split_sides & 0b01); + data.push_back(split_sides & 0b10); + + if (tr.is_split()) { + // If this triangle is split, save which side is split (in case + // of one split) or kept (in case of two splits). The value will + // be ignored for 3-side split. + assert(split_sides > 0); + assert(tr.special_side() >= 0 && tr.special_side() <= 3); + data.push_back(tr.special_side() & 0b01); + data.push_back(tr.special_side() & 0b10); + ++stored_triangles; + // Now save all children. + for (int child_idx=0; child_idx<=split_sides; ++child_idx) + serialize_recursive(tr.children[child_idx]); + } else { + // In case this is leaf, we better save information about its state. + assert(int(tr.get_state()) <= 3); + data.push_back(int(tr.get_state()) & 0b01); + data.push_back(int(tr.get_state()) & 0b10); + ++stored_triangles; + } + }; + + serialize_recursive(i); + out[i] = data; + } + + return out; +} + +void TriangleSelector::deserialize(const std::map> data) +{ + reset(); // dump any current state + for (const auto& [triangle_id, code] : data) { + assert(triangle_id < int(m_triangles.size())); + int processed_triangles = 0; + struct ProcessingInfo { + int facet_id = 0; + int processed_children = 0; + int total_children = 0; + }; + + // Vector to store all parents that have offsprings. + std::vector parents; + + while (true) { + // Read next triangle info. + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[4 * processed_triangles + i]); + } + ++processed_triangles; + + int num_of_split_sides = (next_code & 0b11); + int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; + bool is_split = num_of_children != 0; + FacetSupportType state = FacetSupportType(next_code >> 2); + int special_side = (next_code >> 2); + + // Take care of the first iteration separately, so handling of the others is simpler. + if (parents.empty()) { + if (! is_split) { + // root is not split. just set the state and that's it. + m_triangles[triangle_id].set_state(state); + break; + } else { + // root is split, add it into list of parents and split it. + // then go to the next. + parents.push_back({triangle_id, 0, num_of_children}); + m_triangles[triangle_id].set_division(num_of_children-1, special_side); + perform_split(triangle_id, FacetSupportType::NONE); + continue; + } + } + + // This is not the first iteration. This triangle is a child of last seen parent. + assert(! parents.empty()); + assert(parents.back().processed_children < parents.back().total_children); + + if (is_split) { + // split the triangle and save it as parent of the next ones. + const ProcessingInfo& last = parents.back(); + int this_idx = m_triangles[last.facet_id].children[last.processed_children]; + m_triangles[this_idx].set_division(num_of_children-1, special_side); + perform_split(this_idx, FacetSupportType::NONE); + parents.push_back({this_idx, 0, num_of_children}); + } else { + // this triangle belongs to last split one + m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); + ++parents.back().processed_children; + } + + + // If all children of the past parent triangle are claimed, move to grandparent. + while (parents.back().processed_children == parents.back().total_children) { + parents.pop_back(); + + if (parents.empty()) + break; + + // And increment the grandparent children counter, because + // we have just finished that branch and got back here. + ++parents.back().processed_children; + } + + // In case we popped back the root, we should be done. + if (parents.empty()) + break; + } + + } +} + + + + +} // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp new file mode 100644 index 0000000000..943029548c --- /dev/null +++ b/src/libslic3r/TriangleSelector.hpp @@ -0,0 +1,149 @@ +#ifndef libslic3r_TriangleSelector_hpp_ +#define libslic3r_TriangleSelector_hpp_ + +#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + + +#include "Point.hpp" +#include "TriangleMesh.hpp" + +namespace Slic3r { + +enum class FacetSupportType : int8_t; + + + +// Following class holds information about selected triangles. It also has power +// to recursively subdivide the triangles and make the selection finer. +class TriangleSelector { +public: + void set_edge_limit(float edge_limit); + + // Create new object on a TriangleMesh. The referenced mesh must + // stay valid, a ptr to it is saved and used. + explicit TriangleSelector(const TriangleMesh& mesh); + + // Select all triangles fully inside the circle, subdivide where needed. + void select_patch(const Vec3f& hit, // point where to start + int facet_start, // facet that point belongs to + const Vec3f& source, // camera position (mesh coords) + const Vec3f& dir, // direction of the ray (mesh coords) + float radius_sqr, // squared radius of the cursor + FacetSupportType new_state); // enforcer or blocker? + + + // Clear everything and make the tree empty. + void reset(); + + // Remove all unnecessary data. + void garbage_collect(); + + // Store the division trees in compact form (a long stream of + // bits for each triangle of the original mesh). + std::map> serialize() const; + + // Load serialized data. Assumes that correct mesh is loaded. + void deserialize(const std::map> data); + + +protected: + // Triangle and info about how it's split. + class Triangle { + public: + // Use TriangleSelector::push_triangle to create a new triangle. + // It increments/decrements reference counter on vertices. + Triangle(int a, int b, int c) + : verts_idxs{a, b, c}, + state{FacetSupportType(0)}, + number_of_splits{0}, + special_side_idx{0}, + old_number_of_splits{0} + {} + // Indices into m_vertices. + std::array verts_idxs; + + // Is this triangle valid or marked to be removed? + bool valid{true}; + + // Children triangles. + std::array children; + + // Set the division type. + void set_division(int sides_to_split, int special_side_idx = -1); + + // Get/set current state. + void set_state(FacetSupportType type) { assert(! is_split()); state = type; } + FacetSupportType get_state() const { assert(! is_split()); return state; } + + // Get info on how it's split. + bool is_split() const { return number_of_split_sides() != 0; } + int number_of_split_sides() const { return number_of_splits; } + int special_side() const { assert(is_split()); return special_side_idx; } + bool was_split_before() const { return old_number_of_splits != 0; } + void forget_history() { old_number_of_splits = 0; } + + private: + int number_of_splits; + int special_side_idx; + FacetSupportType state; + + // How many children were spawned during last split? + // Is not reset on remerging the triangle. + int old_number_of_splits; + }; + + struct Vertex { + explicit Vertex(const stl_vertex& vert) + : v{vert}, + ref_cnt{0} + {} + stl_vertex v; + int ref_cnt; + }; + + // Lists of vertices and triangles, both original and new + std::vector m_vertices; + std::vector m_triangles; + const TriangleMesh* m_mesh; + + // Number of invalid triangles (to trigger garbage collection). + int m_invalid_triangles; + + // Limiting length of triangle side (squared). + float m_edge_limit_sqr = 1.f; + + // Number of original vertices and triangles. + int m_orig_size_vertices; + int m_orig_size_indices; + + // Cache for cursor position, radius and direction. + struct Cursor { + Vec3f center; + Vec3f source; + Vec3f dir; + float radius_sqr; + }; + + Cursor m_cursor; + + // Private functions: + bool select_triangle(int facet_idx, FacetSupportType type, + bool recursive_call = false); + bool is_point_inside_cursor(const Vec3f& point) const; + int vertices_inside(int facet_idx) const; + bool faces_camera(int facet) const; + void undivide_triangle(int facet_idx); + void split_triangle(int facet_idx); + void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. + bool is_pointer_in_triangle(int facet_idx) const; + bool is_edge_inside_cursor(int facet_idx) const; + void push_triangle(int a, int b, int c); + void perform_split(int facet_idx, FacetSupportType old_state); +}; + + + + +} // namespace Slic3r + +#endif // libslic3r_TriangleSelector_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 01427ec098..ca7695907e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -89,15 +89,12 @@ void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const S void GLGizmoFdmSupports::on_render() const { - //const Selection& selection = m_parent.get_selection(); + const Selection& selection = m_parent.get_selection(); glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); - //render_triangles(selection); - - if (m_triangle_selector && ! m_setting_angle) - m_triangle_selector->render(m_imgui); + render_triangles(selection); m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -148,14 +145,9 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); - // Now render both enforcers and blockers. - //for (int i=0; i<2; ++i) { - // glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - // for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { - if (m_iva.has_VBOs()) - m_iva.render(); - // } - //} + if (! m_setting_angle) + m_triangle_selectors[mesh_id]->render(m_imgui); + glsafe(::glPopMatrix()); if (is_left_handed) glsafe(::glFrontFace(GL_CCW)); @@ -212,16 +204,14 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { - return; - /*ModelObject* mo = m_c->selection_info()->model_object(); + ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { ++idx; if (! mv->is_model_part()) continue; - for (int i=0; im_supported_facets.set_facet(i, m_selected_facets[idx][i]); - }*/ + mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + } } @@ -230,21 +220,7 @@ void GLGizmoFdmSupports::update_from_model_object() wxBusyCursor wait; const ModelObject* mo = m_c->selection_info()->model_object(); - /*size_t num_of_volumes = 0; - for (const ModelVolume* mv : mo->volumes) - if (mv->is_model_part()) - ++num_of_volumes; - m_selected_facets.resize(num_of_volumes);*/ - - m_triangle_selector = std::make_unique(mo->volumes.front()->mesh()); - - /*m_ivas.clear(); - m_ivas.resize(num_of_volumes); - for (size_t i=0; ivolumes) { @@ -256,17 +232,9 @@ void GLGizmoFdmSupports::update_from_model_object() // This mesh does not account for the possible Z up SLA offset. const TriangleMesh* mesh = &mv->mesh(); - m_selected_facets[volume_id].assign(mesh->its.indices.size(), FacetSupportType::NONE); - - // Load current state from ModelVolume. - for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) { - const std::vector& list = mv->m_supported_facets.get_facets(type); - for (int i : list) - m_selected_facets[volume_id][i] = type; - } - update_vertex_buffers(mesh, volume_id, FacetSupportType::ENFORCER); - update_vertex_buffers(mesh, volume_id, FacetSupportType::BLOCKER); - }*/ + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); + } } @@ -321,7 +289,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous || action == SLAGizmoEventType::RightDown || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { - if (! m_triangle_selector) + if (m_triangle_selectors.empty()) return false; FacetSupportType new_state = FacetSupportType::NONE; @@ -426,23 +394,20 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } } - // FIXME: just for now, only process first mesh - if (mesh_id != 0) - return false; - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; // Calculate how far can a point be from the line (in mesh coords). // FIXME: The scaling of the mesh can be non-uniform. const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = pow(m_cursor_radius/avg_scaling , 2.f); + const float limit = std::pow(m_cursor_radius/avg_scaling , 2.f); // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); Vec3f dir = (closest_hit - camera_pos).normalized(); - m_triangle_selector->select_patch(closest_hit, closest_facet, camera_pos, + assert(mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, dir, limit, new_state); return true; @@ -468,7 +433,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, +/*void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, int mesh_id, FacetSupportType type, const std::vector* new_facets) @@ -506,7 +471,7 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, if (pushed) m_iva.finalize_geometry(true); - /*} else { + } else { // we are only appending - let's make new vertex array and let the old ones live ivas.push_back(GLIndexedVertexArray()); for (size_t facet_idx : *new_facets) @@ -516,9 +481,9 @@ void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, ivas.back().finalize_geometry(true); else ivas.pop_back(); - }*/ + } -} +}*/ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block) @@ -761,8 +726,8 @@ void GLGizmoFdmSupports::on_set_state() } activate_internal_undo_redo_stack(false); m_old_mo_id = -1; - m_iva.release_geometry(); - m_selected_facets.clear(); + //m_iva.release_geometry(); + m_triangle_selectors.clear(); } m_old_state = m_state; } @@ -800,422 +765,14 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const } -// sides_to_split==-1 : just restore previous split -void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) +void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { - assert(sides_to_split >=-1 && sides_to_split <= 3); - assert(special_side_idx >=-1 && special_side_idx < 3); - - // If splitting one or two sides, second argument must be provided. - assert(sides_to_split != 1 || special_side_idx != -1); - assert(sides_to_split != 2 || special_side_idx != -1); - - if (sides_to_split != -1) { - this->number_of_splits = sides_to_split; - if (sides_to_split != 0) { - assert(old_number_of_splits == 0); - this->special_side_idx = special_side_idx; - this->old_number_of_splits = sides_to_split; - } - } - else { - assert(old_number_of_splits != 0); - this->number_of_splits = old_number_of_splits; - // indices of children should still be there. - } -} - - - -void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, - const Vec3f& source, const Vec3f& dir, - float radius_sqr, FacetSupportType new_state) -{ - assert(facet_start < m_orig_size_indices); - assert(is_approx(dir.norm(), 1.f)); - - // Save current cursor center, squared radius and camera direction, - // so we don't have to pass it around. - m_cursor = {hit, source, dir, radius_sqr}; - - // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_check{facet_start}; - std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed - int facet_idx = 0; // index into facets_to_check - while (facet_idx < int(facets_to_check.size())) { - int facet = facets_to_check[facet_idx]; - if (! visited[facet]) { - if (select_triangle(facet, new_state)) { - // add neighboring facets to list to be proccessed later - for (int n=0; n<3; ++n) { - if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); - } - } - } - visited[facet] = true; - ++facet_idx; - } -} - - - -// Selects either the whole triangle (discarding any children it had), or divides -// the triangle recursively, selecting just subtriangles truly inside the circle. -// This is done by an actual recursive call. Returns false if the triangle is -// outside the cursor. -bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) -{ - assert(facet_idx < int(m_triangles.size())); - - Triangle* tr = &m_triangles[facet_idx]; - if (! tr->valid) - return false; - - int num_of_inside_vertices = vertices_inside(facet_idx); - - if (num_of_inside_vertices == 0 - && ! is_pointer_in_triangle(facet_idx) - && ! is_edge_inside_cursor(facet_idx)) - return false; - - if (num_of_inside_vertices == 3) { - // dump any subdivision and select whole triangle - undivide_triangle(facet_idx); - tr->set_state(type); - } else { - // the triangle is partially inside, let's recursively divide it - // (if not already) and try selecting its children. - - if (! tr->is_split() && tr->get_state() == type) { - // This is leaf triangle that is already of correct type as a whole. - // No need to split, all children would end up selected anyway. - return true; - } - - split_triangle(facet_idx); - tr = &m_triangles[facet_idx]; // might have been invalidated - - - int num_of_children = tr->number_of_split_sides() + 1; - if (num_of_children != 1) { - for (int i=0; ichildren.size())); - assert(tr->children[i] < int(m_triangles.size())); - - select_triangle(tr->children[i], type, true); - tr = &m_triangles[facet_idx]; // might have been invalidated - } - } - } - - if (! recursive_call) { - // In case that all children are leafs and have the same state now, - // they may be removed and substituted by the parent triangle. - remove_useless_children(facet_idx); - - // Make sure that we did not lose track of invalid triangles. - assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), - [](const Triangle& tr) { return ! tr.valid; })); - - // Do garbage collection maybe? - if (2*m_invalid_triangles > int(m_triangles.size())) - garbage_collect(); - } - return true; -} - - -void TriangleSelector::split_triangle(int facet_idx) -{ - if (m_triangles[facet_idx].is_split()) { - // The triangle is divided already. - return; - } - - Triangle* tr = &m_triangles[facet_idx]; - - FacetSupportType old_type = tr->get_state(); - - if (tr->was_split_before() != 0) { - // This triangle is not split at the moment, but was at one point - // in history. We can just restore it and resurrect its children. - tr->set_division(-1); - for (int i=0; i<=tr->number_of_split_sides(); ++i) { - m_triangles[tr->children[i]].set_state(old_type); - m_triangles[tr->children[i]].valid = true; - --m_invalid_triangles; - } - return; - } - - // If we got here, we are about to actually split the triangle. - const double limit_squared = m_edge_limit_sqr; - - std::array& facet = tr->verts_idxs; - const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; - double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), - (*pts[0]-*pts[2]).squaredNorm(), - (*pts[1]-*pts[0]).squaredNorm() }; - - std::vector sides_to_split; - int side_to_keep = -1; - for (int pt_idx = 0; pt_idx<3; ++pt_idx) { - if (sides[pt_idx] > limit_squared) - sides_to_split.push_back(pt_idx); - else - side_to_keep = pt_idx; - } - if (sides_to_split.empty()) { - // This shall be unselected. - tr->set_division(0); - return; - } - - // Save how the triangle will be split. Second argument makes sense only for one - // or two split sides, otherwise the value is ignored. - tr->set_division(sides_to_split.size(), - sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); - - perform_split(facet_idx, old_type); -} - - -// Calculate distance of a point from a line. -bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const -{ - Vec3f diff = m_cursor.center - point; - return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; -} - - -// Is pointer in a triangle? -bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const -{ - auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, - const Vec3f& c, const Vec3f& d) -> bool { - return ((b-a).cross(c-a)).dot(d-a) > 0.; - }; - - const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; - const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; - const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; - const Vec3f& q1 = m_cursor.center + m_cursor.dir; - const Vec3f q2 = m_cursor.center - m_cursor.dir; - - if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { - bool pos = signed_volume_sign(q1,q2,p1,p2); - if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) - return true; - } - return false; -} - - - -// Determine whether this facet is potentially visible (still can be obscured). -bool TriangleSelector::faces_camera(int facet) const -{ - assert(facet < m_orig_size_indices); - // The normal is cached in mesh->stl, use it. - return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); -} - - -// How many vertices of a triangle are inside the circle? -int TriangleSelector::vertices_inside(int facet_idx) const -{ - int inside = 0; - for (size_t i=0; i<3; ++i) { - if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) - ++inside; - } - return inside; -} - - -// Is edge inside cursor? -bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const -{ - Vec3f pts[3]; - for (int i=0; i<3; ++i) - pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; - - const Vec3f& p = m_cursor.center; - - for (int side = 0; side < 3; ++side) { - const Vec3f& a = pts[side]; - const Vec3f& b = pts[side<2 ? side+1 : 0]; - Vec3f s = (b-a).normalized(); - float t = (p-a).dot(s); - Vec3f vector = a+t*s - p; - - // vector is 3D vector from center to the intersection. What we want to - // measure is length of its projection onto plane perpendicular to dir. - float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); - if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) - return true; - } - return false; -} - - - -// Recursively remove all subtriangles. -void TriangleSelector::undivide_triangle(int facet_idx) -{ - assert(facet_idx < int(m_triangles.size())); - Triangle& tr = m_triangles[facet_idx]; - - if (tr.is_split()) { - for (int i=0; i<=tr.number_of_split_sides(); ++i) { - undivide_triangle(tr.children[i]); - m_triangles[tr.children[i]].valid = false; - ++m_invalid_triangles; - } - tr.set_division(0); // not split - } -} - - -void TriangleSelector::remove_useless_children(int facet_idx) -{ - // Check that all children are leafs of the same type. If not, try to - // make them (recursive call). Remove them if sucessful. - - assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); - Triangle& tr = m_triangles[facet_idx]; - - if (! tr.is_split()) { - // This is a leaf, there nothing to do. This can happen during the - // first (non-recursive call). Shouldn't otherwise. - return; - } - - // Call this for all non-leaf children. - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { - assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); - if (m_triangles[tr.children[child_idx]].is_split()) - remove_useless_children(tr.children[child_idx]); - } - - - // Return if a child is not leaf or two children differ in type. - FacetSupportType first_child_type = FacetSupportType::NONE; - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { - if (m_triangles[tr.children[child_idx]].is_split()) - return; - if (child_idx == 0) - first_child_type = m_triangles[tr.children[0]].get_state(); - else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) - return; - } - - // If we got here, the children can be removed. - undivide_triangle(facet_idx); - tr.set_state(first_child_type); -} - - - -void TriangleSelector::garbage_collect() -{ - // First make a map from old to new triangle indices. - int new_idx = m_orig_size_indices; - std::vector new_triangle_indices(m_triangles.size(), -1); - for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); - for (int i=m_orig_size_vertices; i= 0); - if (m_vertices[i].ref_cnt != 0) { - new_vertices_indices[i] = new_idx; - ++new_idx; - } - } - - // We can remove all invalid triangles and vertices that are no longer referenced. - m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), - [](const Triangle& tr) { return ! tr.valid; }), - m_triangles.end()); - m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), - [](const Vertex& vert) { return vert.ref_cnt == 0; }), - m_vertices.end()); - - // Now go through all remaining triangles and update changed indices. - for (Triangle& tr : m_triangles) { - assert(tr.valid); - - if (tr.is_split()) { - // There are children. Update their indices. - for (int j=0; j<=tr.number_of_split_sides(); ++j) { - assert(new_triangle_indices[tr.children[j]] != -1); - tr.children[j] = new_triangle_indices[tr.children[j]]; - } - } - - // Update indices into m_vertices. The original vertices are never - // touched and need not be reindexed. - for (int& idx : tr.verts_idxs) { - if (idx >= m_orig_size_vertices) { - assert(new_vertices_indices[idx] != -1); - idx = new_vertices_indices[idx]; - } - } - - // If this triangle was split before, forget it. - // Children referenced in the cache are dead by now. - tr.forget_history(); - } - - m_invalid_triangles = 0; -} - -TriangleSelector::TriangleSelector(const TriangleMesh& mesh) - : m_mesh{&mesh} -{ - reset(); - -} - - -void TriangleSelector::reset() -{ - if (! m_orig_size_indices != 0) // unless this is run from constructor - garbage_collect(); - m_vertices.clear(); - m_triangles.clear(); - for (const stl_vertex& vert : m_mesh->its.vertices) - m_vertices.emplace_back(vert); - for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) - push_triangle(ind[0], ind[1], ind[2]); - m_orig_size_vertices = m_vertices.size(); - m_orig_size_indices = m_triangles.size(); - m_invalid_triangles = 0; -} - -void TriangleSelector::render(ImGuiWrapper* imgui) -{ - Vec3d offset = wxGetApp().model().objects.front()->instances.front()->get_transformation().get_offset(); - ::glTranslatef(offset.x(), offset.y(), offset.z()); - ::glScalef(1.005f, 1.005f, 1.005f); - - int enf_cnt = 0; int blc_cnt = 0; + m_iva_enforcers.release_geometry(); + m_iva_blockers.release_geometry(); + for (const Triangle& tr : m_triangles) { if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) continue; @@ -1245,13 +802,13 @@ void TriangleSelector::render(ImGuiWrapper* imgui) ::glColor4f(0.f, 0.f, 1.f, 0.2f); m_iva_enforcers.render(); } - m_iva_enforcers.release_geometry(); + if (m_iva_blockers.has_VBOs()) { ::glColor4f(1.f, 0.f, 0.f, 0.2f); m_iva_blockers.render(); } - m_iva_blockers.release_geometry(); + #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG if (imgui) @@ -1262,242 +819,9 @@ void TriangleSelector::render(ImGuiWrapper* imgui) } -void TriangleSelector::set_edge_limit(float edge_limit) -{ - float new_limit_sqr = std::pow(edge_limit, 2.f); - - if (new_limit_sqr != m_edge_limit_sqr) { - m_edge_limit_sqr = new_limit_sqr; - - // The way how triangles split may be different now, forget - // all cached splits. - garbage_collect(); - } -} - - - -void TriangleSelector::push_triangle(int a, int b, int c) -{ - for (int i : {a, b, c}) { - assert(i >= 0 && i < int(m_vertices.size())); - ++m_vertices[i].ref_cnt; - } - m_triangles.emplace_back(a, b, c); -} - - -void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) -{ - Triangle* tr = &m_triangles[facet_idx]; - - assert(tr->is_split()); - - // Read info about how to split this triangle. - int sides_to_split = tr->number_of_split_sides(); - - // indices of triangle vertices - std::vector verts_idxs; - int idx = tr->special_side(); - for (int j=0; j<3; ++j) { - verts_idxs.push_back(tr->verts_idxs[idx++]); - if (idx == 3) - idx = 0; - } - - if (sides_to_split == 1) { - m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); - } - - if (sides_to_split == 2) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); - push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); - } - - if (sides_to_split == 3) { - m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); - m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); - verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); - - push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); - push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); - push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); - } - - tr = &m_triangles[facet_idx]; // may have been invalidated - - // And save the children. All children should start in the same state as the triangle we just split. - assert(sides_to_split <= 3); - for (int i=0; i<=sides_to_split; ++i) { - tr->children[i] = m_triangles.size()-1-i; - m_triangles[tr->children[i]].set_state(old_state); - } -} - - -std::map> TriangleSelector::serialize() const -{ - // Each original triangle of the mesh is assigned a number encoding its state - // or how it is split. Each triangle is encoded by 4 bits (xxyy): - // leaf triangle: xx = FacetSupportType, yy = 0 - // non-leaf: xx = special side, yy = number of split sides - // These are bitwise appended and formed into one 64-bit integer. - - // The function returns a map from original triangle indices to - // stream of bits encoding state and offsprings. - - std::map> out; - for (int i=0; i data; // complete encoding of this mesh triangle - int stored_triangles = 0; // how many have been already encoded - - std::function serialize_recursive; - serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { - const Triangle& tr = m_triangles[facet_idx]; - - // Always save number of split sides. It is zero for unsplit triangles. - int split_sides = tr.number_of_split_sides(); - assert(split_sides >= 0 && split_sides <= 3); - - //data |= (split_sides << (stored_triangles * 4)); - data.push_back(split_sides & 0b01); - data.push_back(split_sides & 0b10); - - if (tr.is_split()) { - // If this triangle is split, save which side is split (in case - // of one split) or kept (in case of two splits). The value will - // be ignored for 3-side split. - assert(split_sides > 0); - assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data.push_back(tr.special_side() & 0b01); - data.push_back(tr.special_side() & 0b10); - ++stored_triangles; - // Now save all children. - for (int child_idx=0; child_idx<=split_sides; ++child_idx) - serialize_recursive(tr.children[child_idx]); - } else { - // In case this is leaf, we better save information about its state. - assert(int(tr.get_state()) <= 3); - data.push_back(int(tr.get_state()) & 0b01); - data.push_back(int(tr.get_state()) & 0b10); - ++stored_triangles; - } - }; - - serialize_recursive(i); - out[i] = data; - } - - return out; -} - -void TriangleSelector::deserialize(const std::map> data) -{ - reset(); // dump any current state - for (const auto& [triangle_id, code] : data) { - assert(triangle_id < int(m_triangles.size())); - int processed_triangles = 0; - struct ProcessingInfo { - int facet_id = 0; - int processed_children = 0; - int total_children = 0; - }; - - // Vector to store all parents that have offsprings. - std::vector parents; - - while (true) { - // Read next triangle info. - int next_code = 0; - for (int i=3; i>=0; --i) { - next_code = next_code << 1; - next_code |= int(code[4 * processed_triangles + i]); - } - ++processed_triangles; - - int num_of_split_sides = (next_code & 0b11); - int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; - bool is_split = num_of_children != 0; - FacetSupportType state = FacetSupportType(next_code >> 2); - int special_side = (next_code >> 2); - - // Take care of the first iteration separately, so handling of the others is simpler. - if (parents.empty()) { - if (! is_split) { - // root is not split. just set the state and that's it. - m_triangles[triangle_id].set_state(state); - break; - } else { - // root is split, add it into list of parents and split it. - // then go to the next. - parents.push_back({triangle_id, 0, num_of_children}); - m_triangles[triangle_id].set_division(num_of_children-1, special_side); - perform_split(triangle_id, FacetSupportType::NONE); - continue; - } - } - - // This is not the first iteration. This triangle is a child of last seen parent. - assert(! parents.empty()); - assert(parents.back().processed_children < parents.back().total_children); - - if (is_split) { - // split the triangle and save it as parent of the next ones. - const ProcessingInfo& last = parents.back(); - int this_idx = m_triangles[last.facet_id].children[last.processed_children]; - m_triangles[this_idx].set_division(num_of_children-1, special_side); - perform_split(this_idx, FacetSupportType::NONE); - parents.push_back({this_idx, 0, num_of_children}); - } else { - // this triangle belongs to last split one - m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); - ++parents.back().processed_children; - } - - - // If all children of the past parent triangle are claimed, move to grandparent. - while (parents.back().processed_children == parents.back().total_children) { - parents.pop_back(); - - if (parents.empty()) - break; - - // And increment the grandparent children counter, because - // we have just finished that branch and got back here. - ++parents.back().processed_children; - } - - // In case we popped back the root, we should be done. - if (parents.empty()) - break; - } - - } -} - #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -void TriangleSelector::render_debug(ImGuiWrapper* imgui) +void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) { imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); @@ -1585,5 +909,7 @@ void TriangleSelector::render_debug(ImGuiWrapper* imgui) } #endif + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 099c9a30cb..7020bb7d42 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -6,11 +6,11 @@ #include "slic3r/GUI/3DScene.hpp" #include "libslic3r/ObjectID.hpp" +#include "libslic3r/TriangleSelector.hpp" #include -#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG namespace Slic3r { @@ -23,144 +23,26 @@ enum class SLAGizmoEventType : unsigned char; class ClippingPlane; -// Following class holds information about selected triangles. It also has power -// to recursively subdivide the triangles and make the selection finer. -class TriangleSelector { + +class TriangleSelectorGUI : public TriangleSelector { public: - void set_edge_limit(float edge_limit); - - // Create new object on a TriangleMesh. The referenced mesh must - // stay valid, a ptr to it is saved and used. - explicit TriangleSelector(const TriangleMesh& mesh); - - // Select all triangles fully inside the circle, subdivide where needed. - void select_patch(const Vec3f& hit, // point where to start - int facet_start, // facet that point belongs to - const Vec3f& source, // camera position (mesh coords) - const Vec3f& dir, // direction of the ray (mesh coords) - float radius_sqr, // squared radius of the cursor - FacetSupportType new_state); // enforcer or blocker? + explicit TriangleSelectorGUI(const TriangleMesh& mesh) + : TriangleSelector(mesh) {} // Render current selection. Transformation matrices are supposed // to be already set. void render(ImGuiWrapper* imgui = nullptr); - // Clear everything and make the tree empty. - void reset(); - - // Remove all unnecessary data. - void garbage_collect(); - - // Store the division trees in compact form (a long stream of - // bits for each triangle of the original mesh). - std::map> serialize() const; - - // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::map> data); - #ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG void render_debug(ImGuiWrapper* imgui); - bool m_show_triangles{true}; + bool m_show_triangles{false}; bool m_show_invalid{false}; #endif private: - // Triangle and info about how it's split. - class Triangle { - public: - // Use TriangleSelector::push_triangle to create a new triangle. - // It increments/decrements reference counter on vertices. - Triangle(int a, int b, int c) - : verts_idxs{a, b, c}, - state{FacetSupportType(0)}, - number_of_splits{0}, - special_side_idx{0}, - old_number_of_splits{0} - {} - // Indices into m_vertices. - std::array verts_idxs; - - // Is this triangle valid or marked to be removed? - bool valid{true}; - - // Children triangles. - std::array children; - - // Set the division type. - void set_division(int sides_to_split, int special_side_idx = -1); - - // Get/set current state. - void set_state(FacetSupportType type) { assert(! is_split()); state = type; } - FacetSupportType get_state() const { assert(! is_split()); return state; } - - // Get info on how it's split. - bool is_split() const { return number_of_split_sides() != 0; } - int number_of_split_sides() const { return number_of_splits; } - int special_side() const { assert(is_split()); return special_side_idx; } - bool was_split_before() const { return old_number_of_splits != 0; } - void forget_history() { old_number_of_splits = 0; } - - private: - int number_of_splits; - int special_side_idx; - FacetSupportType state; - - // How many children were spawned during last split? - // Is not reset on remerging the triangle. - int old_number_of_splits; - }; - - struct Vertex { - explicit Vertex(const stl_vertex& vert) - : v{vert}, - ref_cnt{0} - {} - stl_vertex v; - int ref_cnt; - }; - - // Lists of vertices and triangles, both original and new - std::vector m_vertices; - std::vector m_triangles; - const TriangleMesh* m_mesh; - GLIndexedVertexArray m_iva_enforcers; GLIndexedVertexArray m_iva_blockers; std::array m_varrays; - - // Number of invalid triangles (to trigger garbage collection). - int m_invalid_triangles; - - // Limiting length of triangle side (squared). - float m_edge_limit_sqr = 1.f; - - // Number of original vertices and triangles. - int m_orig_size_vertices; - int m_orig_size_indices; - - // Cache for cursor position, radius and direction. - struct Cursor { - Vec3f center; - Vec3f source; - Vec3f dir; - float radius_sqr; - }; - - Cursor m_cursor; - - // Private functions: - bool select_triangle(int facet_idx, FacetSupportType type, - bool recursive_call = false); - bool is_point_inside_cursor(const Vec3f& point) const; - int vertices_inside(int facet_idx) const; - bool faces_camera(int facet) const; - void undivide_triangle(int facet_idx); - void split_triangle(int facet_idx); - void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. - bool is_pointer_in_triangle(int facet_idx) const; - bool is_edge_inside_cursor(int facet_idx) const; - void push_triangle(int a, int b, int c); - void perform_split(int facet_idx, FacetSupportType old_state); }; @@ -178,17 +60,8 @@ private: static constexpr float CursorRadiusMax = 8.f; static constexpr float CursorRadiusStep = 0.2f; - // For each model-part volume, store a list of statuses of - // individual facets (one of the enum values above). - std::vector> m_selected_facets; - - GLIndexedVertexArray m_iva; - - void update_vertex_buffers(const TriangleMesh* mesh, - int mesh_id, - FacetSupportType type, // enforcers / blockers - const std::vector* new_facets = nullptr); // nullptr -> regenerate all - + // For each model-part volume, store status and division of the triangles. + std::vector> m_triangle_selectors; public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); @@ -220,8 +93,6 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; - - std::unique_ptr m_triangle_selector; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. From 0756a7e4b3b8776c5885c6589a376e0fd191335b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 14 Jul 2020 14:02:39 +0200 Subject: [PATCH 200/826] TriangleSelector: 'Select by angle' and 'reset selection' functions fixed --- src/libslic3r/TriangleSelector.cpp | 9 ++ src/libslic3r/TriangleSelector.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 94 ++++---------------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 3 +- 4 files changed, 31 insertions(+), 78 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 340f5a29a4..9210bde08e 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -132,6 +132,15 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo } + +void TriangleSelector::set_facet(int facet_idx, FacetSupportType state) +{ + assert(facet_idx < m_orig_size_indices); + undivide_triangle(facet_idx); + assert(! m_triangles[facet_idx].is_split()); + m_triangles[facet_idx].set_state(state); +} + void TriangleSelector::split_triangle(int facet_idx) { if (m_triangles[facet_idx].is_split()) { diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 943029548c..f3e23bea20 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -32,6 +32,9 @@ public: FacetSupportType new_state); // enforcer or blocker? + // Set facet of the mesh to a given state. Only works for original triangles. + void set_facet(int facet_idx, FacetSupportType state); + // Clear everything and make the tree empty. void reset(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index ca7695907e..417d101ea0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -48,7 +48,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["block"] = _L("Block supports"); m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all"); + m_desc["remove_all"] = _L("Remove all selection"); return true; } @@ -207,9 +207,9 @@ void GLGizmoFdmSupports::update_model_object() const ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (! mv->is_model_part()) continue; + ++idx; mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); } } @@ -433,63 +433,9 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -/*void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh, - int mesh_id, - FacetSupportType type, - const std::vector* new_facets) + +void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) { - //std::vector& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1]; - - // lambda to push facet into vertex buffer - auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) { - for (int i=0; i<3; ++i) - iva.push_geometry( - mesh->its.vertices[mesh->its.indices[idx](i)].cast(), - m_c->raycaster()->raycasters()[mesh_id]->get_triangle_normal(idx).cast() - ); - size_t num = iva.triangle_indices_size; - iva.push_triangle(num, num+1, num+2); - }; - - - //if (ivas.size() == MaxVertexBuffers || ! new_facets) { - // If there are too many or they should be regenerated, make one large - // GLVertexBufferArray. - //ivas.clear(); // destructors release geometry - //ivas.push_back(GLIndexedVertexArray()); - - m_iva.release_geometry(); - m_iva.clear(); - - bool pushed = false; - for (size_t facet_idx=0; facet_idxempty()) - ivas.back().finalize_geometry(true); - else - ivas.pop_back(); - } - -}*/ - - -void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block) -{ - return; -/* float threshold = (M_PI/180.)*threshold_deg; const Selection& selection = m_parent.get_selection(); const ModelObject* mo = m_c->selection_info()->model_object(); @@ -512,13 +458,12 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr int idx = -1; for (const stl_facet& facet : mv->mesh().stl.facet_start) { ++idx; - if (facet.normal.dot(down) > dot_limit && (overwrite || m_selected_facets[mesh_id][idx] == FacetSupportType::NONE)) - m_selected_facets[mesh_id][idx] = block - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER; + if (facet.normal.dot(down) > dot_limit) + m_triangle_selectors[mesh_id]->set_facet(idx, + block + ? FacetSupportType::BLOCKER + : FacetSupportType::ENFORCER); } - update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::ENFORCER); - update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER); } activate_internal_undo_redo_stack(true); @@ -528,7 +473,6 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr update_model_object(); m_parent.set_as_dirty(); m_setting_angle = false; -*/ } @@ -584,18 +528,17 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->button(m_desc.at("remove_all"))) { - /*ModelObject* mo = m_c->selection_info()->model_object(); + Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (mv->is_model_part()) { - m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE); - mv->m_supported_facets.clear(); - update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::ENFORCER); - update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER); - m_parent.set_as_dirty(); + ++idx; + m_triangle_selectors[idx]->reset(); } - }*/ + } + update_model_object(); + m_parent.set_as_dirty(); } const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -651,12 +594,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - m_imgui->checkbox(wxString("Overwrite already selected facets"), m_overwrite_selected); if (m_imgui->button("Enforce")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, false); + select_facets_by_angle(m_angle_threshold_deg, false); ImGui::SameLine(); if (m_imgui->button("Block")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, true); + select_facets_by_angle(m_angle_threshold_deg, true); ImGui::SameLine(); if (m_imgui->button("Cancel")) m_setting_angle = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 7020bb7d42..2d1442164a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -82,8 +82,7 @@ private: void update_from_model_object(); void activate_internal_undo_redo_stack(bool activate); - void select_facets_by_angle(float threshold, bool overwrite, bool block); - bool m_overwrite_selected = false; + void select_facets_by_angle(float threshold, bool block); float m_angle_threshold_deg = 45.f; bool is_mesh_point_clipped(const Vec3d& point) const; From 3b91d11ddfcc362552a62e8fe03c11b1115db3fd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 15 Jul 2020 10:28:20 +0200 Subject: [PATCH 201/826] TriangleSelector: backend is aware of divided triangles --- src/libslic3r/Model.cpp | 13 ++++++------- src/libslic3r/Model.hpp | 2 +- src/libslic3r/PrintObject.cpp | 10 +++++----- src/libslic3r/TriangleSelector.cpp | 19 +++++++++++++++++++ src/libslic3r/TriangleSelector.hpp | 2 ++ 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b6bae489b4..d0410c2d4e 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1831,13 +1831,12 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const } -std::vector FacetsAnnotation::get_facets(FacetSupportType type) const +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const { - std::vector out; - /*for (auto& [facet_idx, this_type] : m_data) - if (this_type == type) - out.push_back(facet_idx); - */return out; + TriangleSelector selector(mv.mesh()); + selector.deserialize(m_data); + indexed_triangle_set out = selector.get_facets(type); + return out; } @@ -1932,7 +1931,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject return true; } return false; -}; +} extern bool model_has_multi_part_objects(const Model &model) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index de20e0fdcc..3127af5ba7 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -407,7 +407,7 @@ public: const std::map>& get_data() const { return m_data; } void set(const TriangleSelector& selector); - std::vector get_facets(FacetSupportType type) const; + indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); ClockType::time_point get_timestamp() const { return timestamp; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d2bdb6d531..1a2edcf6e8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2673,8 +2673,8 @@ void PrintObject::project_and_append_custom_supports( FacetSupportType type, std::vector& expolys) const { for (const ModelVolume* mv : this->model_object()->volumes) { - const std::vector custom_facets = mv->m_supported_facets.get_facets(type); - if (custom_facets.empty()) + const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type); + if (custom_facets.indices.empty()) continue; const TriangleMesh& mesh = mv->mesh(); @@ -2705,11 +2705,11 @@ void PrintObject::project_and_append_custom_supports( }; // Vector to collect resulting projections from each triangle. - std::vector projections_of_triangles(custom_facets.size()); + std::vector projections_of_triangles(custom_facets.indices.size()); // Iterate over all triangles. tbb::parallel_for( - tbb::blocked_range(0, custom_facets.size()), + tbb::blocked_range(0, custom_facets.indices.size()), [&](const tbb::blocked_range& range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { @@ -2717,7 +2717,7 @@ void PrintObject::project_and_append_custom_supports( // Transform the triangle into worlds coords. for (int i=0; i<3; ++i) - facet[i] = tr * mesh.its.vertices[mesh.its.indices[custom_facets[idx]](i)]; + facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; // Ignore triangles with upward-pointing normal. if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 9210bde08e..6e3f9f5185 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -512,6 +512,25 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) } + +indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const +{ + indexed_triangle_set out; + for (const Triangle& tr : m_triangles) { + if (tr.valid && ! tr.is_split() && tr.get_state() == state) { + stl_triangle_vertex_indices indices; + for (int i=0; i<3; ++i) { + out.vertices.emplace_back(m_vertices[tr.verts_idxs[i]].v); + indices[i] = out.vertices.size() - 1; + } + out.indices.emplace_back(indices); + } + } + return out; +} + + + std::map> TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index f3e23bea20..dc2ad9127d 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -31,6 +31,8 @@ public: float radius_sqr, // squared radius of the cursor FacetSupportType new_state); // enforcer or blocker? + // Get facets currently in the given state. + indexed_triangle_set get_facets(FacetSupportType state) const; // Set facet of the mesh to a given state. Only works for original triangles. void set_facet(int facet_idx, FacetSupportType state); From afb5d929c47b54567c8ef5b2d1d8376621b60048 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 15 Jul 2020 12:28:52 +0200 Subject: [PATCH 202/826] TriangleSelector: Schedule restarting background process after edit --- src/libslic3r/Model.cpp | 4 +++- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d0410c2d4e..4ff0a5c1bf 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1841,13 +1841,15 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSu -void FacetsAnnotation::set(const TriangleSelector& selector) +bool FacetsAnnotation::set(const TriangleSelector& selector) { std::map> sel_map = selector.serialize(); if (sel_map != m_data) { m_data = sel_map; update_timestamp(); + return true; } + return false; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3127af5ba7..16f3f00ad5 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -406,7 +406,7 @@ public: using ClockType = std::chrono::steady_clock; const std::map>& get_data() const { return m_data; } - void set(const TriangleSelector& selector); + bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 417d101ea0..13c9cfef87 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -204,14 +204,18 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { + bool updated = false; ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { if (! mv->is_model_part()) continue; ++idx; - mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); } + + if (updated) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } From 5a1d9aee15f507be774f96c2a98ca0adda8cc1e4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 21 Jul 2020 09:01:17 +0200 Subject: [PATCH 203/826] TriangleSelector: Fix of a macOS crash Calling reset() from constructor relied on uninitialized variable --- src/libslic3r/TriangleSelector.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index dc2ad9127d..877bc122cd 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -118,8 +118,8 @@ protected: float m_edge_limit_sqr = 1.f; // Number of original vertices and triangles. - int m_orig_size_vertices; - int m_orig_size_indices; + int m_orig_size_vertices = 0; + int m_orig_size_indices = 0; // Cache for cursor position, radius and direction. struct Cursor { From 74a1aeff8e8421fd9523bd9ff788e33e5692c6e1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 23 Jul 2020 08:17:04 +0200 Subject: [PATCH 204/826] TriangleSelector: bugfix - backend did not correctly account for mirrorring --- src/libslic3r/PrintObject.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1a2edcf6e8..273fc9c108 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2677,10 +2677,10 @@ void PrintObject::project_and_append_custom_supports( if (custom_facets.indices.empty()) continue; - const TriangleMesh& mesh = mv->mesh(); const Transform3f& tr1 = mv->get_matrix().cast(); const Transform3f& tr2 = this->trafo().cast(); const Transform3f tr = tr2 * tr1; + const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f); // The projection will be at most a pentagon. Let's minimize heap @@ -2719,8 +2719,9 @@ void PrintObject::project_and_append_custom_supports( for (int i=0; i<3; ++i) facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; - // Ignore triangles with upward-pointing normal. - if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.) + // Ignore triangles with upward-pointing normal. Don't forget about mirroring. + float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); + if (tr_det_sign * z_comp > 0.) continue; // Sort the three vertices according to z-coordinate. From 7ddb64783b6094e3b1c5a8006e977c5308a6b841 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 24 Jul 2020 17:45:49 +0200 Subject: [PATCH 205/826] TriangleSelector: edge limit is derived from cursor size --- src/libslic3r/TriangleSelector.cpp | 10 ++++++++-- src/libslic3r/TriangleSelector.hpp | 5 +++-- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 6e3f9f5185..50d775ed86 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -35,14 +35,20 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, const Vec3f& dir, - float radius_sqr, FacetSupportType new_state) + float radius, FacetSupportType new_state) { assert(facet_start < m_orig_size_indices); assert(is_approx(dir.norm(), 1.f)); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. - m_cursor = {hit, source, dir, radius_sqr}; + m_cursor = {hit, source, dir, radius*radius}; + + // In case user changed cursor size since last time, update triangle edge limit. + if (m_old_cursor_radius != radius) { + set_edge_limit(radius / 5.f); + m_old_cursor_radius = radius; + } // Now start with the facet the pointer points to and check all adjacent facets. std::vector facets_to_check{facet_start}; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 877bc122cd..fb90cff769 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -1,7 +1,7 @@ #ifndef libslic3r_TriangleSelector_hpp_ #define libslic3r_TriangleSelector_hpp_ -#define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +// #define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG #include "Point.hpp" @@ -28,7 +28,7 @@ public: int facet_start, // facet that point belongs to const Vec3f& source, // camera position (mesh coords) const Vec3f& dir, // direction of the ray (mesh coords) - float radius_sqr, // squared radius of the cursor + float radius, // radius of the cursor FacetSupportType new_state); // enforcer or blocker? // Get facets currently in the given state. @@ -130,6 +130,7 @@ protected: }; Cursor m_cursor; + float m_old_cursor_radius; // Private functions: bool select_triangle(int facet_idx, FacetSupportType type, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 13c9cfef87..3769e96605 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -404,7 +404,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // FIXME: The scaling of the mesh can be non-uniform. const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = std::pow(m_cursor_radius/avg_scaling , 2.f); + const float limit = m_cursor_radius/avg_scaling; // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 2d1442164a..ce24ea8d28 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -56,7 +56,7 @@ private: GLUquadricObj* m_quadric; float m_cursor_radius = 2.f; - static constexpr float CursorRadiusMin = 0.f; + static constexpr float CursorRadiusMin = 0.4f; // cannot be zero static constexpr float CursorRadiusMax = 8.f; static constexpr float CursorRadiusStep = 0.2f; From 248fba82a4892e85ffcfecd24b82bd0f9ddd8135 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 24 Jul 2020 16:53:05 +0200 Subject: [PATCH 206/826] TriangleSelector: 3MF loading and saving --- src/libslic3r/Format/3mf.cpp | 17 +++++++++ src/libslic3r/Model.cpp | 58 ++++++++++++++++++++++++++++++ src/libslic3r/Model.hpp | 2 ++ src/libslic3r/TriangleSelector.cpp | 1 + 4 files changed, 78 insertions(+) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index edf55ba37e..3612e6898c 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -86,6 +86,7 @@ const char* OBJECTID_ATTR = "objectid"; const char* TRANSFORM_ATTR = "transform"; const char* PRINTABLE_ATTR = "printable"; const char* INSTANCESCOUNT_ATTR = "instances_count"; +const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; const char* KEY_ATTR = "key"; const char* VALUE_ATTR = "value"; @@ -283,6 +284,7 @@ namespace Slic3r { { std::vector vertices; std::vector triangles; + std::vector custom_supports; bool empty() { @@ -293,6 +295,7 @@ namespace Slic3r { { vertices.clear(); triangles.clear(); + custom_supports.clear(); } }; @@ -1539,6 +1542,8 @@ namespace Slic3r { m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + + m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); return true; } @@ -1872,6 +1877,13 @@ namespace Slic3r { volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->calculate_convex_hull(); + // recreate custom supports from previously loaded attribute + assert(geometry.custom_supports.size() == triangles_count); + for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]); + } + // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { @@ -2383,6 +2395,11 @@ namespace Slic3r { { stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" "; } + + std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i); + if (! custom_supports_data_string.empty()) + stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" "; + stream << "/>\n"; } } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 4ff0a5c1bf..3beb74f235 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1862,6 +1862,64 @@ void FacetsAnnotation::clear() +// Following function takes data from a triangle and encodes it as string +// of hexadecimal numbers (one digit per triangle). Used for 3MF export, +// changing it may break backwards compatibility !!!!! +std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const +{ + std::string out; + + auto triangle_it = m_data.find(triangle_idx); + if (triangle_it != m_data.end()) { + const std::vector& code = triangle_it->second; + int offset = 0; + while (offset < int(code.size())) { + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[offset + i]); + } + offset += 4; + + assert(next_code >=0 && next_code <= 15); + char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A'; + out.insert(out.begin(), digit); + } + } + return out; +} + + + +// Recover triangle splitting & state from string of hexadecimal values previously +// generated by get_triangle_as_string. Used to load from 3MF. +void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) +{ + assert(! str.empty()); + m_data[triangle_id] = std::vector(); // zero current state or create new + std::vector& code = m_data[triangle_id]; + + for (auto it = str.crbegin(); it != str.crend(); ++it) { + const char ch = *it; + int dec = 0; + if (ch >= '0' && ch<='9') + dec = int(ch - '0'); + else if (ch >='A' && ch <= 'F') + dec = 10 + int(ch - 'A'); + else + assert(false); + + // Convert to binary and append into code. + for (int i=0; i<4; ++i) { + code.insert(code.end(), bool(dec & (1 << i))); + } + } + + +} + + + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 16f3f00ad5..92dc84d17a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -409,6 +409,8 @@ public: bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); + std::string get_triangle_as_string(int i) const; + void set_triangle_from_string(int triangle_id, const std::string& str); ClockType::time_point get_timestamp() const { return timestamp; } bool is_same_as(const FacetsAnnotation& other) const { diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 50d775ed86..763bf58616 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -603,6 +603,7 @@ void TriangleSelector::deserialize(const std::map> data) reset(); // dump any current state for (const auto& [triangle_id, code] : data) { assert(triangle_id < int(m_triangles.size())); + assert(! code.empty()); int processed_triangles = 0; struct ProcessingInfo { int facet_id = 0; From 67d2f438458c2185d01d8db02fe74f5f56e209db Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Jun 2020 23:14:44 +0200 Subject: [PATCH 207/826] Showing Eject button only after exporting is finished. Fix of #4212 --- src/slic3r/GUI/Plater.cpp | 5 ++++- src/slic3r/GUI/RemovableDriveManager.cpp | 2 ++ src/slic3r/GUI/RemovableDriveManager.hpp | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec13610b8c..e4e43bb7c7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3450,7 +3450,7 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) break; default: break; } -} +} void Plater::priv::on_process_completed(wxCommandEvent &evt) { @@ -3510,7 +3510,10 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) show_action_buttons(true); } else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) + { + wxGetApp().removable_drive_manager()->set_exporting_finished(true); show_action_buttons(false); + } this->writing_to_removable_device = false; } diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index d67ac4a22f..d865fe3476 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -393,6 +393,7 @@ bool RemovableDriveManager::set_and_verify_last_save_path(const std::string &pat #endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS m_last_save_path = this->get_removable_drive_from_path(path); + m_exporting_finished = false; return ! m_last_save_path.empty(); } @@ -407,6 +408,7 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() } if (! out.has_eject) m_last_save_path.clear(); + out.has_eject = out.has_eject && m_exporting_finished; return out; } diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp index e1a8d6faf1..26ee12e40c 100644 --- a/src/slic3r/GUI/RemovableDriveManager.hpp +++ b/src/slic3r/GUI/RemovableDriveManager.hpp @@ -83,7 +83,7 @@ public: // Public to be accessible from RemovableDriveManagerMM::on_device_unmount OSX notification handler. // It would be better to make this method private and friend to RemovableDriveManagerMM, but RemovableDriveManagerMM is an ObjectiveC class. void update(); - + void set_exporting_finished(bool b) { m_exporting_finished = b; } #ifdef _WIN32 // Called by Win32 Volume arrived / detached callback. void volumes_changed(); @@ -121,7 +121,9 @@ private: std::vector::const_iterator find_last_save_path_drive_data() const; // Set with set_and_verify_last_save_path() to a removable drive path to be ejected. std::string m_last_save_path; - + // Verifies that exporting was finished so drive can be ejected. + // Set false by set_and_verify_last_save_path() that is called just before exporting. + bool m_exporting_finished; #if __APPLE__ void register_window_osx(); void unregister_window_osx(); From 1dc3561e2cc52fbe32aa13b2e74c09f6de621a1b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 14 Jun 2020 23:53:17 +0200 Subject: [PATCH 208/826] added 's' in https when pointing users to our github --- src/slic3r/GUI/MainFrame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 64a1319d44..687634d11f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -106,7 +106,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_statusbar->embed(this); m_statusbar->set_status_text(_(L("Version")) + " " + SLIC3R_VERSION + - _(L(" - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/releases"))); + _(L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases"))); /* Load default preset bitmaps before a tabpanel initialization, * but after filling of an em_unit value @@ -1141,7 +1141,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")), [this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/PrusaSlicer/releases"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); @@ -1158,7 +1158,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")), [this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")), [this](wxCommandEvent&) { Slic3r::GUI::about(); }); helpMenu->AppendSeparator(); From 864ecf750cd1dab68d50ac8ee5d4bdac77d4f4c8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 15 Jun 2020 00:34:12 +0200 Subject: [PATCH 209/826] Deleted default value in Plater::export_gcode(bool prefer_removable). Only place where it is not sure what value should be is in btn_reslice - i chose true --- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Plater.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 687634d11f..521dcab804 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -909,7 +909,7 @@ void MainFrame::init_menubar() wxMenu* export_menu = new wxMenu(); wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode", nullptr, + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "export_gcode", nullptr, [this](){return can_export_gcode(); }, this); m_changeable_menu_items.push_back(item_export_gcode); wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e4e43bb7c7..761f574e15 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -929,7 +929,7 @@ Sidebar::Sidebar(Plater *parent) { const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); if (export_gcode_after_slicing) - p->plater->export_gcode(); + p->plater->export_gcode(true); else p->plater->reslice(); p->plater->select_view_3D("Preview"); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5d60e006b0..a08b19fa35 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -220,7 +220,7 @@ public: void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); - void export_gcode(bool prefer_removable = true); + void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); From 18594261d293651bf5f8234cfefe692e7709a103 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Jul 2020 12:18:21 +0200 Subject: [PATCH 210/826] Added handling of mouse wheel events to ImGuiWrapper --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/ImGuiWrapper.cpp | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b4e672c4fb..2d45277849 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3300,6 +3300,11 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) if (evt.MiddleIsDown()) return; + if (wxGetApp().imgui()->update_mouse_data(evt)) { + m_dirty = true; + return; + } + #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor(); evt.SetX(evt.GetX() * scale); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 51a9a6d4eb..88dd02ccb7 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -176,6 +176,9 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) io.MouseDown[0] = evt.LeftIsDown(); io.MouseDown[1] = evt.RightIsDown(); io.MouseDown[2] = evt.MiddleIsDown(); + float wheel_delta = static_cast(evt.GetWheelDelta()); + if (wheel_delta != 0.0f) + io.MouseWheel = static_cast(evt.GetWheelRotation()) / wheel_delta; unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; From 14366800e2feac3ce075a317e0895e43411dfc4c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 27 Jul 2020 15:45:29 +0200 Subject: [PATCH 211/826] GCodeProcessor -> Added parsing of 3d part generated gcodes --- src/libslic3r/GCode/GCodeProcessor.cpp | 311 +++++++++++++++++++++++++ src/libslic3r/GCode/GCodeProcessor.hpp | 23 ++ src/slic3r/GUI/Plater.cpp | 1 + 3 files changed, 335 insertions(+) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 83cbb876ef..42d1c07575 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -296,6 +296,14 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast(ETimeMode::Normal)].enabled = true; } +const std::vector> GCodeProcessor::Producers = { + { EProducer::PrusaSlicer, "PrusaSlicer" }, + { EProducer::Cura, "Cura" }, + { EProducer::Simplify3D, "Simplify3D" }, + { EProducer::CraftWare, "CraftWare" }, + { EProducer::ideaMaker, "ideaMaker" } +}; + unsigned int GCodeProcessor::s_result_id = 0; void GCodeProcessor::apply_config(const PrintConfig& config) @@ -364,6 +372,9 @@ void GCodeProcessor::reset() m_extruders_color = ExtrudersColor(); m_cp_color.reset(); + m_producer = EProducer::Unknown; + m_producers_enabled = false; + m_time_processor.reset(); m_result.reset(); @@ -539,6 +550,13 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_tags(const std::string& comment) { + if (m_producers_enabled && m_producer == EProducer::Unknown && detect_producer(comment)) + return; + else if (m_producers_enabled && m_producer != EProducer::Unknown) { + if (process_producers_tags(comment)) + return; + } + // extrusion role tag size_t pos = comment.find(Extrusion_Role_Tag); if (pos != comment.npos) { @@ -645,6 +663,299 @@ void GCodeProcessor::process_tags(const std::string& comment) } } +bool GCodeProcessor::process_producers_tags(const std::string& comment) +{ + switch (m_producer) + { + case EProducer::PrusaSlicer: { return process_prusaslicer_tags(comment); } + case EProducer::Cura: { return process_cura_tags(comment); } + case EProducer::Simplify3D: { return process_simplify3d_tags(comment); } + case EProducer::CraftWare: { return process_craftware_tags(comment); } + case EProducer::ideaMaker: { return process_ideamaker_tags(comment); } + default: { return false; } + } +} + +bool GCodeProcessor::process_prusaslicer_tags(const std::string& comment) +{ + std::cout << comment << "\n"; + return false; +} + +bool GCodeProcessor::process_cura_tags(const std::string& comment) +{ + // TYPE -> extrusion role + std::string tag = "TYPE:"; + size_t pos = comment.find(tag); + if (pos != comment.npos) { + std::string type = comment.substr(pos + tag.length()); + if (type == "SKIRT") + m_extrusion_role = erSkirt; + else if (type == "WALL-OUTER") + m_extrusion_role = erExternalPerimeter; + else if (type == "WALL-INNER") + m_extrusion_role = erPerimeter; + else if (type == "SKIN") + m_extrusion_role = erSolidInfill; + else if (type == "FILL") + m_extrusion_role = erInternalInfill; + else if (type == "SUPPORT") + m_extrusion_role = erSupportMaterial; + else if (type == "SUPPORT-INTERFACE") + m_extrusion_role = erSupportMaterialInterface; + else if (type == "PRIME-TOWER") + m_extrusion_role = erWipeTower; + else { + m_extrusion_role = erNone; + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; + } + + return true; + } + + return false; +} + +bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) +{ + // extrusion roles + + // ; skirt + size_t pos = comment.find(" skirt"); + if (pos == 0) { + m_extrusion_role = erSkirt; + return true; + } + + // ; outer perimeter + pos = comment.find(" outer perimeter"); + if (pos == 0) { + m_extrusion_role = erExternalPerimeter; + return true; + } + + // ; inner perimeter + pos = comment.find(" inner perimeter"); + if (pos == 0) { + m_extrusion_role = erPerimeter; + return true; + } + + // ; gap fill + pos = comment.find(" gap fill"); + if (pos == 0) { + m_extrusion_role = erGapFill; + return true; + } + + // ; infill + pos = comment.find(" infill"); + if (pos == 0) { + m_extrusion_role = erInternalInfill; + return true; + } + + // ; solid layer + pos = comment.find(" solid layer"); + if (pos == 0) { + m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + return true; + } + + // ; bridge + pos = comment.find(" bridge"); + if (pos == 0) { + m_extrusion_role = erBridgeInfill; + return true; + } + + // ; support + pos = comment.find(" support"); + if (pos == 0) { + m_extrusion_role = erSupportMaterial; + return true; + } + + // ; prime pillar + pos = comment.find(" prime pillar"); + if (pos == 0) { + m_extrusion_role = erWipeTower; + return true; + } + + // ; ooze shield + pos = comment.find(" ooze shield"); + if (pos == 0) { + m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + return true; + } + + // ; raft + pos = comment.find(" raft"); + if (pos == 0) { + m_extrusion_role = erSkirt; + return true; + } + + // geometry + + // ; tool + std::string tag = " tool"; + pos = comment.find(tag); + if (pos == 0) { + std::string data = comment.substr(pos + tag.length()); + std::string h_tag = "H"; + size_t h_start = data.find(h_tag); + size_t h_end = data.find_first_of(' ', h_start); + std::string w_tag = "W"; + size_t w_start = data.find(w_tag); + size_t w_end = data.find_first_of(' ', w_start); + if (h_start != data.npos) { + try + { + std::string test = data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end); + m_height = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + } + } + if (w_start != data.npos) { + try + { + std::string test = data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end); + m_width = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + } + } + + return true; + } + + std::cout << comment << "\n"; + return false; +} + +bool GCodeProcessor::process_craftware_tags(const std::string& comment) +{ + // segType -> extrusion role + std::string tag = "segType:"; + size_t pos = comment.find(tag); + if (pos != comment.npos) { + std::string type = comment.substr(pos + tag.length()); + if (type == "Skirt") + m_extrusion_role = erSkirt; + else if (type == "Perimeter") + m_extrusion_role = erExternalPerimeter; + else if (type == "HShell") + m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + else if (type == "InnerHair") + m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + else if (type == "Loop") + m_extrusion_role = erNone; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + else if (type == "Infill") + m_extrusion_role = erInternalInfill; + else if (type == "Raft") + m_extrusion_role = erSkirt; + else if (type == "Support") + m_extrusion_role = erSupportMaterial; + else if (type == "SupportTouch") + m_extrusion_role = erSupportMaterial; + else if (type == "SoftSupport") + m_extrusion_role = erSupportMaterialInterface; + else if (type == "Pillar") + m_extrusion_role = erWipeTower; + else { + m_extrusion_role = erNone; + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; + } + + return true; + } + + return false; +} + +bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) +{ + // TYPE -> extrusion role + std::string tag = "TYPE:"; + size_t pos = comment.find(tag); + if (pos != comment.npos) { + std::string type = comment.substr(pos + tag.length()); + if (type == "RAFT") + m_extrusion_role = erSkirt; + else if (type == "WALL-OUTER") + m_extrusion_role = erExternalPerimeter; + else if (type == "WALL-INNER") + m_extrusion_role = erPerimeter; + else if (type == "SOLID-FILL") + m_extrusion_role = erSolidInfill; + else if (type == "FILL") + m_extrusion_role = erInternalInfill; + else if (type == "BRIDGE") + m_extrusion_role = erBridgeInfill; + else if (type == "SUPPORT") + m_extrusion_role = erSupportMaterial; + else { + m_extrusion_role = erNone; + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown extrusion role: " << type; + } + return true; + } + + // geometry + + // width + tag = "WIDTH:"; + pos = comment.find(tag); + if (pos != comment.npos) { + try + { + m_width = std::stof(comment.substr(pos + tag.length())); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; + } + return true; + } + + // height + tag = "HEIGHT:"; + pos = comment.find(tag); + if (pos != comment.npos) { + try + { + m_height = std::stof(comment.substr(pos + tag.length())); + } + catch (...) + { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + } + return true; + } + + return false; +} + +bool GCodeProcessor::detect_producer(const std::string& comment) +{ + for (const auto& [id, search_string] : Producers) { + size_t pos = comment.find(search_string); + if (pos != comment.npos) { + m_producer = id; + BOOST_LOG_TRIVIAL(info) << "Detected gcode producer: " << search_string; + return true; + } + } + return false; +} + void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line) { process_G1(line); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index d19d363f95..9507adf4d5 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -243,6 +243,20 @@ namespace Slic3r { ExtrudersColor m_extruders_color; CpColor m_cp_color; + enum class EProducer + { + Unknown, + PrusaSlicer, + Cura, + Simplify3D, + CraftWare, + ideaMaker + }; + + static const std::vector> Producers; + EProducer m_producer; + bool m_producers_enabled; + TimeProcessor m_time_processor; Result m_result; @@ -253,6 +267,7 @@ namespace Slic3r { void apply_config(const PrintConfig& config); void enable_stealth_time_estimator(bool enabled); + void enable_producers(bool enabled) { m_producers_enabled = enabled; } void reset(); const Result& get_result() const { return m_result; } @@ -274,6 +289,14 @@ namespace Slic3r { // Process tags embedded into comments void process_tags(const std::string& comment); + bool process_producers_tags(const std::string& comment); + bool process_prusaslicer_tags(const std::string& comment); + bool process_cura_tags(const std::string& comment); + bool process_simplify3d_tags(const std::string& comment); + bool process_craftware_tags(const std::string& comment); + bool process_ideamaker_tags(const std::string& comment); + + bool detect_producer(const std::string& comment); // Move void process_G0(const GCodeReader::GCodeLine& line); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8a5863616e..193ac8e0c1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4662,6 +4662,7 @@ void Plater::load_gcode(const wxString& filename) // process gcode GCodeProcessor processor; // processor.apply_config(config); + processor.enable_producers(true); processor.process_file(filename.ToUTF8().data()); p->gcode_result = std::move(processor.extract_result()); From 924bda6ec08f8940347423b7f8b3e1e1d3115e88 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 27 Jul 2020 20:06:10 +0200 Subject: [PATCH 212/826] ChangePresetForPhysicalPrinterDialog and SavePresetWindow are merged to SavePresetDialog --- src/slic3r/GUI/PresetComboBoxes.cpp | 301 ++++++++++++++++++++-------- src/slic3r/GUI/PresetComboBoxes.hpp | 55 +++-- src/slic3r/GUI/Tab.cpp | 61 +----- 3 files changed, 253 insertions(+), 164 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 3edc3947a1..01c83921ab 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1005,106 +1005,135 @@ void TabPresetComboBox::update_dirty() #endif /* __APPLE __ */ } -void TabPresetComboBox::update_physical_printers( const std::string& preset_name) -{ - if (m_type != Preset::TYPE_PRINTER || !m_allow_to_update_physical_printers) - return; - - m_allow_to_update_physical_printers = false; - - PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; - if (!physical_printers.has_selection()) - return; - - std::string printer_preset_name = physical_printers.get_selected_printer_preset_name(); - - if (Preset::remove_suffix_modified(preset_name) == printer_preset_name) { - if (!this->is_selected_physical_printer()) - physical_printers.unselect_printer(); - } - else - { - ChangePresetForPhysicalPrinterDialog dlg(Preset::remove_suffix_modified(preset_name)); - if(dlg.ShowModal() == wxID_OK) - { - if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::Switch) - // unselect physical printer, if it was selected - m_preset_bundle->physical_printers.unselect_printer(); - else - { - PhysicalPrinter printer = physical_printers.get_selected_printer(); - - if (dlg.m_selection == ChangePresetForPhysicalPrinterDialog::ChangePreset) - printer.delete_preset(printer_preset_name); - - if (printer.add_preset(preset_name)) - physical_printers.save_printer(printer); - else { - wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - } - - physical_printers.select_printer(printer.get_full_name(preset_name)); - } - } - else - wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printer_preset_name); - } -} - //----------------------------------------------- -// ChangePresetForPhysicalPrinterDialog +// SavePresetDialog //----------------------------------------------- -ChangePresetForPhysicalPrinterDialog::ChangePresetForPhysicalPrinterDialog(const std::string& preset_name) - : DPIDialog(nullptr, wxID_ANY, _L("Warning"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING/* | wxRESIZE_BORDER*/) +SavePresetDialog::SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix) + : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), + m_preset_cb(preset_cb) { SetFont(wxGetApp().normal_font()); - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - - PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; - std::string printer_name = printers.get_selected_printer_name(); - std::string old_preset_name = printers.get_selected_printer_preset_name(); - - wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\"\n" - "with related printer preset \"%2%\"")) % - printer_name % old_preset_name).str()); - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, msg_text); - label_top->SetFont(wxGetApp().bold_font()); - - wxString choices[] = { from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer")) % old_preset_name % preset_name).str()), - from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer")) % preset_name).str()), - from_u8((boost::format(_u8L("Just switch to \"%1%\"")) % preset_name).str()) }; - - wxRadioBox* selection_type_box = new wxRadioBox(this, wxID_ANY, _L("What would you like to do?"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, - 3, wxRA_SPECIFY_ROWS); - selection_type_box->SetFont(wxGetApp().normal_font()); - selection_type_box->SetSelection(0); - - selection_type_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { - int selection = e.GetSelection(); - m_selection = (SelectionType)selection; - }); - - auto radio_sizer = new wxBoxSizer(wxHORIZONTAL); - radio_sizer->Add(selection_type_box, 1, wxALIGN_CENTER_VERTICAL); - - wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); - wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); - btnOK->Bind(wxEVT_BUTTON, &ChangePresetForPhysicalPrinterDialog::OnOK, this); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(label_top, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); + add_common_items(topSizer, suffix); + add_items_for_edit_ph_printer(topSizer); + + // add dialog's buttons + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); + btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + evt.Enable(!m_combo->GetValue().IsEmpty()); }); + + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); SetSizer(topSizer); topSizer->SetSizeHints(this); } -void ChangePresetForPhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) +void SavePresetDialog::add_common_items(wxBoxSizer* sizer, const std::string& suffix) +{ + const PresetCollection* presets = m_preset_cb->presets(); + const Preset& sel_preset = presets->get_selected_preset(); + std::string preset_name = sel_preset.is_default ? "Untitled" : + sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : + sel_preset.name; + + // if name contains extension + if (boost::iends_with(preset_name, ".ini")) { + size_t len = preset_name.length() - 4; + preset_name.resize(len); + } + + std::vector values; + for (const Preset& preset : *presets) { + if (preset.is_default || preset.is_system || preset.is_external) + continue; + values.push_back(preset.name); + } + + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(wxGetApp().get_tab(m_preset_cb->type())->title())).str())); + m_combo = new wxComboBox(this, wxID_ANY, from_u8(preset_name), + wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); + for (auto value : values) + m_combo->Append(from_u8(value)); + + m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); + m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { + update(normalize_utf8_nfc(m_combo->GetValue().ToUTF8())); + this->Layout(); + this->Fit(); + }); + + sizer->Add(label_top, 0, wxEXPAND | wxALL, BORDER_W); + sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, BORDER_W); +} + +void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) +{ + if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) + return; + + PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; + m_ph_printer_name = printers.get_selected_printer_name(); + m_old_preset_name = printers.get_selected_printer_preset_name(); + + wxString msg_text = from_u8((boost::format(_u8L("You have selected physical printer \"%1%\" \n" + "with related printer preset \"%2%\"")) % + m_ph_printer_name % m_old_preset_name).str()); + m_label = new wxStaticText(this, wxID_ANY, msg_text); + m_label->SetFont(wxGetApp().bold_font()); + + wxString choices[] = {"","",""}; + + m_action_radio_box = new wxRadioBox(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, + WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); + m_action_radio_box->SetFont(wxGetApp().normal_font()); + m_action_radio_box->SetSelection(0); + m_action_radio_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { + m_action = (ActionType)e.GetSelection(); }); + m_action = ChangePreset; + + m_radio_sizer = new wxBoxSizer(wxHORIZONTAL); + m_radio_sizer->Add(m_action_radio_box, 1, wxALIGN_CENTER_VERTICAL); + + sizer->Add(m_label, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + + update(m_preset_name); +} + +void SavePresetDialog::update(const std::string& preset_name) +{ + if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) + return; + + bool show = m_old_preset_name != preset_name; + + m_label->Show(show); + m_radio_sizer->ShowItems(show); + if (!show) { + this->SetMinSize(wxSize(100,50)); + return; + } + + wxString msg_text = from_u8((boost::format(_u8L("What would you like to do with \"%1%\" preset after saving?")) % preset_name).str()); + m_action_radio_box->SetLabel(msg_text); + + wxString choices[] = { from_u8((boost::format(_u8L("Change \"%1%\" to \"%2%\" for this physical printer \"%3%\"")) % m_old_preset_name % preset_name % m_ph_printer_name).str()), + from_u8((boost::format(_u8L("Add \"%1%\" as a next preset for the the physical printer \"%2%\"")) % preset_name % m_ph_printer_name).str()), + from_u8((boost::format(_u8L("Just switch to \"%1%\" preset")) % preset_name).str()) }; + + int n = 0; + for(const wxString& label: choices) + m_action_radio_box->SetString(n++, label); +} + +void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -1117,10 +1146,106 @@ void ChangePresetForPhysicalPrinterDialog::on_dpi_changed(const wxRect& suggeste Refresh(); } -void ChangePresetForPhysicalPrinterDialog::OnOK(wxEvent& event) +bool SavePresetDialog::preset_name_is_accepted() { - event.Skip(); + const char* unusable_symbols = "<>[]:/\\|?*\""; + const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (m_preset_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + show_error(this, _L("The supplied name is not valid;") + "\n" + + _L("the following characters are not allowed:") + " " + unusable_symbols); + return false; + } + } + + if (m_preset_name.find(unusable_suffix) != std::string::npos) { + show_error(this, _L("The supplied name is not valid;") + "\n" + + _L("the following suffix is not allowed:") + "\n\t" + + from_u8(PresetCollection::get_suffix_modified())); + return false; + } + + if (m_preset_name == "- default -") { + show_error(this, _L("The supplied name is not available.")); + return false; + } + return true; +} + +bool SavePresetDialog::preset_is_possible_to_save() +{ + const Preset* existing = m_preset_cb->presets()->find_preset(m_preset_name, false); + if (existing && (existing->is_default || existing->is_system)) { + show_error(this, _L("Cannot overwrite a system profile.")); + return false; + } + if (existing && (existing->is_external)) { + show_error(this, _(L("Cannot overwrite an external profile."))); + return false; + } + if (existing && m_preset_name != m_preset_cb->presets()->get_selected_preset_name()) + { + wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exists."))) % m_preset_name).str()); + msg_text += "\n" + _L("Replace?"); + wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + if (dialog.ShowModal() == wxID_NO) + return false; + + // Remove the preset from the list. + m_preset_cb->presets()->delete_preset(m_preset_name); + } + return true; +} + +void SavePresetDialog::update_physical_printers() +{ + if (m_action == UndefAction) + return; + + PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers; + if (!physical_printers.has_selection()) + return; + + std::string printer_preset_name = physical_printers.get_selected_printer_preset_name(); + + if (m_action == Switch) + // unselect physical printer, if it was selected + physical_printers.unselect_printer(); + else + { + PhysicalPrinter printer = physical_printers.get_selected_printer(); + + if (m_action == ChangePreset) + printer.delete_preset(printer_preset_name); + + if (printer.add_preset(m_preset_name)) + physical_printers.save_printer(printer); + else { + wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + } + + physical_printers.select_printer(printer.get_full_name(m_preset_name)); + } +} + +void SavePresetDialog::accept() +{ + m_preset_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); + + if (m_preset_name.empty()) + return; + + if (!preset_name_is_accepted() || + !preset_is_possible_to_save()) + return; + + update_physical_printers(); + + EndModal(wxID_OK); } + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 6b8017f311..fa4554a5ea 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -13,6 +13,7 @@ class wxTextCtrl; class wxStaticText; class ScalableButton; class wxBoxSizer; +class wxComboBox; namespace Slic3r { @@ -167,50 +168,68 @@ class TabPresetComboBox : public PresetComboBox bool show_incompatible {false}; bool m_enable_all {false}; - bool m_allow_to_update_physical_printers {false}; - public: TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); ~TabPresetComboBox() {} void set_show_incompatible_presets(bool show_incompatible_presets) { show_incompatible = show_incompatible_presets; } - void allow_to_update_physical_printers() { - m_allow_to_update_physical_printers = m_type == Preset::TYPE_PRINTER; - } void update() override; void update_dirty(); - void update_physical_printers(const std::string& preset_name); void msw_rescale() override; void set_enable_all(bool enable=true) { m_enable_all = enable; } + + PresetCollection* presets() const { return m_collection; } + Preset::Type type() const { return m_type; } }; //------------------------------------------------ -// ChangePresetForPhysicalPrinterDialog +// SavePresetDialog //------------------------------------------------ -class ChangePresetForPhysicalPrinterDialog : public DPIDialog +class SavePresetDialog : public DPIDialog { - void OnOK(wxEvent& event); + enum ActionType + { + ChangePreset, + AddPreset, + Switch, + UndefAction + }; + + TabPresetComboBox* m_preset_cb {nullptr}; + std::string m_preset_name; + wxComboBox* m_combo {nullptr}; + wxStaticText* m_label {nullptr}; + wxRadioBox* m_action_radio_box {nullptr}; + wxBoxSizer* m_radio_sizer {nullptr}; + ActionType m_action {UndefAction}; + + std::string m_ph_printer_name; + std::string m_old_preset_name; public: - enum SelectionType - { - Switch, - ChangePreset, - AddPreset - } m_selection {Switch}; + SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix); + ~SavePresetDialog() {} - ChangePresetForPhysicalPrinterDialog(const std::string& preset_name); - ~ChangePresetForPhysicalPrinterDialog() {} + std::string get_name() { return m_preset_name; } protected: void on_dpi_changed(const wxRect& suggested_rect) override; - void on_sys_color_changed() override {}; + void on_sys_color_changed() override {} + +private: + void add_common_items(wxBoxSizer *sizer, const std::string &suffix); + void add_items_for_edit_ph_printer(wxBoxSizer *sizer); + void update(const std::string &preset_name); + bool preset_name_is_accepted(); + bool preset_is_possible_to_save(); + void update_physical_printers(); + void accept(); }; } // namespace GUI diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3f566eacb3..a97c10f7d5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -164,11 +164,8 @@ void Tab::create_preset_tab() m_presets_choice->set_selection_changed_function([this](int selection) { if (!m_presets_choice->selection_is_changed_according_to_physical_printers()) { - // For the printer presets allow to update a physical printer if it is needed. - // After call of the update_physical_printers() this possibility will be disabled again to avoid a case, - // when select_preset is called from the others than this place - if (m_type == Preset::TYPE_PRINTER) - m_presets_choice->allow_to_update_physical_printers(); + if (m_type == Preset::TYPE_PRINTER && !m_presets_choice->is_selected_physical_printer()) + m_preset_bundle->physical_printers.unselect_printer(); // select preset select_preset(m_presets_choice->GetString(selection).ToUTF8().data()); @@ -3148,9 +3145,6 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } - //update physical printer's related printer preset if it's needed - m_presets_choice->update_physical_printers(preset_name); - load_current_preset(); } } @@ -3290,56 +3284,10 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) std::string suffix = detach ? _utf8(L("Detached")) : _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); if (name.empty()) { - const Preset &preset = m_presets->get_selected_preset(); - auto default_name = preset.is_default ? "Untitled" : -// preset.is_system ? (boost::format(_CTX_utf8(L_CONTEXT("%1% - Copy", "PresetName"), "PresetName")) % preset.name).str() : - preset.is_system ? (boost::format(("%1% - %2%")) % preset.name % suffix).str() : - preset.name; - - bool have_extention = boost::iends_with(default_name, ".ini"); - if (have_extention) { - size_t len = default_name.length()-4; - default_name.resize(len); - } - //[map $_->name, grep !$_->default && !$_->external, @{$self->{presets}}], - std::vector values; - for (size_t i = 0; i < m_presets->size(); ++i) { - const Preset &preset = m_presets->preset(i); - if (preset.is_default || preset.is_system || preset.is_external) - continue; - values.push_back(preset.name); - } - - SavePresetWindow dlg(parent()); - dlg.build(title(), default_name, values); + SavePresetDialog dlg(m_presets_choice, suffix); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); - if (name == "") { - show_error(this, _(L("The supplied name is empty. It can't be saved."))); - return; - } - const Preset *existing = m_presets->find_preset(name, false); - if (existing && (existing->is_default || existing->is_system)) { - show_error(this, _(L("Cannot overwrite a system profile."))); - return; - } - if (existing && (existing->is_external)) { - show_error(this, _(L("Cannot overwrite an external profile."))); - return; - } - if (existing && name != preset.name) - { - wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exists."))) % name).str()); - msg_text += "\n" + _(L("Replace?")); - wxMessageDialog dialog(nullptr, msg_text, _(L("Warning")), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_NO) - return; - - // Remove the preset from the list. - m_presets->delete_preset(name); - } } // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini @@ -3347,9 +3295,6 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // Mark the print & filament enabled if they are compatible with the currently selected preset. // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); - //update physical printer's related printer preset if it's needed - m_presets_choice->allow_to_update_physical_printers(); - m_presets_choice->update_physical_printers(name); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); // Update the selection boxes at the plater. From d9228ee82cc541f997da7a731893c44a59023445 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 28 Jul 2020 09:48:55 +0200 Subject: [PATCH 213/826] GCodeProcessor -> Human readable extrusion roles in gcode --- src/libslic3r/ExtrusionEntity.cpp | 36 ++++++++++++++++++++++++++ src/libslic3r/ExtrusionEntity.hpp | 1 + src/libslic3r/GCode.cpp | 6 ++--- src/libslic3r/GCode/GCodeProcessor.cpp | 23 ++++------------ src/libslic3r/GCode/WipeTower.cpp | 2 +- 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index d629a3c89b..b2c5e1350f 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -331,4 +331,40 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) return ""; } +ExtrusionRole ExtrusionEntity::string_to_role(const std::string& role) +{ + if (role == L("Perimeter")) + return erPerimeter; + else if (role == L("External perimeter")) + return erExternalPerimeter; + else if (role == L("Overhang perimeter")) + return erOverhangPerimeter; + else if (role == L("Internal infill")) + return erInternalInfill; + else if (role == L("Solid infill")) + return erSolidInfill; + else if (role == L("Top solid infill")) + return erTopSolidInfill; + else if (role == L("Ironing")) + return erIroning; + else if (role == L("Bridge infill")) + return erBridgeInfill; + else if (role == L("Gap fill")) + return erGapFill; + else if (role == L("Skirt")) + return erSkirt; + else if (role == L("Support material")) + return erSupportMaterial; + else if (role == L("Support material interface")) + return erSupportMaterialInterface; + else if (role == L("Wipe tower")) + return erWipeTower; + else if (role == L("Custom")) + return erCustom; + else if (role == L("Mixed")) + return erMixed; + else + return erNone; +} + } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 879f564b6c..3d7e581128 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -106,6 +106,7 @@ public: virtual double total_volume() const = 0; static std::string role_to_string(ExtrusionRole role); + static ExtrusionRole string_to_role(const std::string& role); }; typedef std::vector ExtrusionEntitiesPtr; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8a835e07d5..742498baf8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1388,7 +1388,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu #if ENABLE_GCODE_VIEWER // adds tag for processor - _write_format(file, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erCustom); + _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); #else if (m_enable_analyzer) // adds tag for analyzer @@ -1546,7 +1546,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu #if ENABLE_GCODE_VIEWER // adds tag for processor - _write_format(file, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erCustom); + _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); #else if (m_enable_analyzer) // adds tag for analyzer @@ -3226,7 +3226,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, #if ENABLE_GCODE_VIEWER if (path.role() != m_last_processor_extrusion_role) { m_last_processor_extrusion_role = path.role(); - sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), int(m_last_processor_extrusion_role)); + sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); gcode += buf; } #else diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 42d1c07575..44598d2408 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -21,13 +21,13 @@ static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 namespace Slic3r { -const std::string GCodeProcessor::Extrusion_Role_Tag = "PrusaSlicer__EXTRUSION_ROLE:"; +const std::string GCodeProcessor::Extrusion_Role_Tag = "ExtrType:"; const std::string GCodeProcessor::Width_Tag = "PrusaSlicer__WIDTH:"; const std::string GCodeProcessor::Height_Tag = "PrusaSlicer__HEIGHT:"; const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "PrusaSlicer__MM3_PER_MM:"; -const std::string GCodeProcessor::Color_Change_Tag = "PrusaSlicer__COLOR_CHANGE"; -const std::string GCodeProcessor::Pause_Print_Tag = "PrusaSlicer__PAUSE_PRINT"; -const std::string GCodeProcessor::Custom_Code_Tag = "PrusaSlicer__CUSTOM_CODE"; +const std::string GCodeProcessor::Color_Change_Tag = "COLOR_CHANGE"; +const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE_PRINT"; +const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM_CODE"; static bool is_valid_extrusion_role(int value) { @@ -560,20 +560,7 @@ void GCodeProcessor::process_tags(const std::string& comment) // extrusion role tag size_t pos = comment.find(Extrusion_Role_Tag); if (pos != comment.npos) { - try - { - int role = std::stoi(comment.substr(pos + Extrusion_Role_Tag.length())); - if (is_valid_extrusion_role(role)) - m_extrusion_role = static_cast(role); - else { - // todo: show some error ? - } - } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Extrusion Role (" << comment << ")."; - } - + m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(pos + Extrusion_Role_Tag.length())); return; } diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 73ac36dc41..e4f995aba1 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -64,7 +64,7 @@ public: #endif // ENABLE_GCODE_VIEWER m_gcode += buf; #if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%d\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), erWipeTower); + sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str()); #else sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); #endif // ENABLE_GCODE_VIEWER From 68ae95509fc4410baa9069cb407b8bb756e2be10 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 28 Jul 2020 11:31:16 +0200 Subject: [PATCH 214/826] Improved InfoMsg for a delete preset: * Now we show a list of printers name with selected preset + Added a edit_button for the editing of the physical printer fro the Settings Tab + Show whole list of the loaded presets with "Print host upload" --- src/libslic3r/Preset.cpp | 65 ++++++---- src/libslic3r/Preset.hpp | 11 +- src/slic3r/GUI/GUI_App.cpp | 38 ++---- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 2 + src/slic3r/GUI/PresetComboBoxes.cpp | 15 ++- src/slic3r/GUI/PresetComboBoxes.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 153 ++++++++++++++--------- src/slic3r/GUI/Tab.hpp | 3 +- 8 files changed, 173 insertions(+), 116 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7e30831fe5..c7acf8f495 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1383,13 +1383,14 @@ const std::vector& PhysicalPrinter::print_host_options() return s_opts; } -bool PhysicalPrinter::has_print_host_information(const PrinterPresetCollection& printer_presets) +std::vector PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets) { + std::vector presets; for (const Preset& preset : printer_presets) if (has_print_host_information(preset.config)) - return true; + presets.emplace_back(preset.name); - return false; + return presets; } bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config) @@ -1564,7 +1565,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const // if there is saved user presets, contains information about "Print Host upload", // Create default printers with this presets // Note! "Print Host upload" options will be cleared after physical printer creations -void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets, std::string def_printer_name) +void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets) { int cnt=0; for (Preset& preset: printer_presets) { @@ -1579,8 +1580,12 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti // just add preset for this printer existed_printer->add_preset(preset.name); else { + std::string new_printer_name = (boost::format("Printer %1%") % ++cnt ).str(); + while (find_printer(new_printer_name)) + new_printer_name = (boost::format("Printer %1%") % ++cnt).str(); + // create new printer from this preset - PhysicalPrinter printer((boost::format("%1% %2%") % def_printer_name % ++cnt ).str(), preset); + PhysicalPrinter printer(new_printer_name, preset); printer.loaded = true; save_printer(printer); } @@ -1699,33 +1704,51 @@ bool PhysicalPrinterCollection::delete_selected_printer() return true; } -bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name, bool first_check /*=true*/) +bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name) { - if (first_check) { - for (auto printer: m_printers) - if (printer.preset_names.size()==1 && *printer.preset_names.begin() == preset_name) - return false; - } - std::vector printers_for_delete; - for (PhysicalPrinter& printer : m_printers) + for (PhysicalPrinter& printer : m_printers) { if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) printers_for_delete.emplace_back(printer.name); - else if (printer.delete_preset(preset_name)) { - if (printer.name == get_selected_printer_name() && - preset_name == get_selected_printer_preset_name()) - select_printer(printer); + else if (printer.delete_preset(preset_name)) save_printer(printer); - } + } - if (!printers_for_delete.empty()) { + if (!printers_for_delete.empty()) for (const std::string& printer_name : printers_for_delete) delete_printer(printer_name); - unselect_printer(); - } + + unselect_printer(); return true; } +// Get list of printers which have more than one preset and "preset_name" preset is one of them +std::vector PhysicalPrinterCollection::get_printers_with_preset(const std::string& preset_name) +{ + std::vector printers; + + for (auto printer : m_printers) { + if (printer.preset_names.size() == 1) + continue; + if (printer.preset_names.find(preset_name) != printer.preset_names.end()) + printers.emplace_back(printer.name); + } + + return printers; +} + +// Get list of printers which has only "preset_name" preset +std::vector PhysicalPrinterCollection::get_printers_with_only_preset(const std::string& preset_name) +{ + std::vector printers; + + for (auto printer : m_printers) + if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name) + printers.emplace_back(printer.name); + + return printers; +} + std::string PhysicalPrinterCollection::get_selected_full_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6b5a2a5112..e34fca4dd7 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -555,7 +555,7 @@ public: static std::string separator(); static const std::vector& printer_options(); static const std::vector& print_host_options(); - static bool has_print_host_information(const PrinterPresetCollection& printer_presets); + static std::vector presets_with_print_host_information(const PrinterPresetCollection& printer_presets); static bool has_print_host_information(const DynamicPrintConfig& config); const std::set& get_preset_names() const; @@ -629,7 +629,7 @@ public: // Load ini files of the particular type from the provided directory path. void load_printers(const std::string& dir_path, const std::string& subdir); - void load_printers_from_presets(PrinterPresetCollection &printer_presets, std::string def_printer_name); + void load_printers_from_presets(PrinterPresetCollection &printer_presets); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. @@ -645,7 +645,12 @@ public: // Delete preset_name preset from all printers: // If there is last preset for the printer and first_check== false, then delete this printer // returns true if all presets were deleted successfully. - bool delete_preset_from_printers(const std::string& preset_name, bool first_check = true); + bool delete_preset_from_printers(const std::string& preset_name); + + // Get list of printers which have more than one preset and "preset_name" preset is one of them + std::vector get_printers_with_preset( const std::string &preset_name); + // Get list of printers which has only "preset_name" preset + std::vector get_printers_with_only_preset( const std::string &preset_name); // Return the selected preset, without the user modifications applied. PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 1b7278bd7f..410614b948 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -635,38 +635,22 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const // check user printer_presets for the containing information about "Print Host upload" void GUI_App::check_printer_presets() { - if (!PhysicalPrinter::has_print_host_information(preset_bundle->printers)) + std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); + if (preset_names.empty()) return; - wxString msg_text = _L("You have presets with saved options for \"Print Host upload\".\n" - "But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" + wxString msg_text = _L("You have next presets with saved options for \"Print Host upload\"") + ":"; + for (const std::string& preset_name : preset_names) + msg_text += "\n \"" + from_u8(preset_name) + "\","; + msg_text.RemoveLast(); + msg_text += "\n\n" + _L("But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n" "Now, this information will be exposed in physical printers settings.") + "\n\n" + - _L("Enter the name for the Printer device used by default during its creation.\n" - "Note: This name can be changed later from the physical printers settings") + ":"; - wxString msg_header = _L("Name for printer device"); + _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" + "Note: This name can be changed later from the physical printers settings"); - // get custom gcode - wxTextEntryDialog dlg(nullptr, msg_text, msg_header, _L("Printer"), wxTextEntryDialogStyle); + wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - // detect TextCtrl and OK button - wxTextCtrl* textctrl{ nullptr }; - wxWindowList& dlg_items = dlg.GetChildren(); - for (auto item : dlg_items) { - textctrl = dynamic_cast(item); - if (textctrl) - break; - } - - if (textctrl) { - textctrl->SetSelection(0, textctrl->GetLastPosition()); - - wxButton* btn_OK = static_cast(dlg.FindWindowById(wxID_OK)); - btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { - evt.Enable(!textctrl->IsEmpty()); - }, btn_OK->GetId()); - } - if (dlg.ShowModal() == wxID_OK) - preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers, into_u8(dlg.GetValue())); + preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); } void GUI_App::recreate_GUI(const wxString& msg_name) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 7d3c92c138..f14f498010 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -510,6 +510,8 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) // refresh preset list on Printer Settings Tab wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name()); } + else + wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice(); event.Skip(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 01c83921ab..666f10194c 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -171,7 +171,7 @@ void PresetComboBox::update_selection() SetToolTip(GetString(m_last_selected)); } -void PresetComboBox::update(const std::string& select_preset_name) +void PresetComboBox::update(std::string select_preset_name) { Freeze(); Clear(); @@ -192,6 +192,8 @@ void PresetComboBox::update(const std::string& select_preset_name) // marker used for disable incompatible printer models for the selected physical printer bool is_enabled = m_type == Preset::TYPE_PRINTER && printer_technology != ptAny ? preset.printer_technology() == printer_technology : true; + if (select_preset_name.empty() && is_enabled) + select_preset_name = preset.name; std::string bitmap_key = "cb"; if (m_type == Preset::TYPE_PRINTER) { @@ -208,7 +210,7 @@ void PresetComboBox::update(const std::string& select_preset_name) int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); if (!is_enabled) set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)); + validate_selection(preset.name == select_preset_name); } else { @@ -659,6 +661,13 @@ void PlaterPresetComboBox::show_edit_menu() wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); }); }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "", + [this](wxCommandEvent&) { + PhysicalPrinterDialog dlg(wxEmptyString); + if (dlg.ShowModal() == wxID_OK) + update(); + }, "edit_uni", menu, []() { return true; }, wxGetApp().plater()); + wxGetApp().plater()->PopupMenu(menu); } @@ -784,7 +793,7 @@ void PlaterPresetComboBox::update() } } - if (/*m_type == Preset::TYPE_PRINTER || */m_type == Preset::TYPE_SLA_MATERIAL) { + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) { wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index fa4554a5ea..a30d9f6e90 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -55,7 +55,7 @@ public: // and next internal selection was accomplished bool selection_is_changed_according_to_physical_printers(); - void update(const std::string& select_preset); + void update(std::string select_preset); virtual void update(); virtual void msw_rescale(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a97c10f7d5..4dbe7cc742 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -35,6 +35,7 @@ #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" +#include "PhysicalPrinterDialog.hpp" namespace Slic3r { namespace GUI { @@ -180,6 +181,8 @@ void Tab::create_preset_tab() add_scaled_button(panel, &m_btn_save_preset, "save"); add_scaled_button(panel, &m_btn_delete_preset, "cross"); + if (m_type == Preset::Type::TYPE_PRINTER) + add_scaled_button(panel, &m_btn_edit_ph_printer, "cog"); m_show_incompatible_presets = false; add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); @@ -191,6 +194,8 @@ void Tab::create_preset_tab() m_btn_save_preset->SetToolTip(from_u8((boost::format(_utf8(L("Save current %s"))) % m_title).str())); m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); m_btn_delete_preset->Disable(); + if (m_btn_edit_ph_printer) + m_btn_edit_ph_printer->Disable(); add_scaled_button(panel, &m_question_btn, "question"); m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" @@ -245,6 +250,10 @@ void Tab::create_preset_tab() m_hsizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(4 * scale_factor)); m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); + if (m_btn_edit_ph_printer) { + m_hsizer->AddSpacer(int(4 * scale_factor)); + m_hsizer->Add(m_btn_edit_ph_printer, 0, wxALIGN_CENTER_VERTICAL); + } m_hsizer->AddSpacer(int(/*16*/8 * scale_factor)); m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(8 * scale_factor)); @@ -291,6 +300,13 @@ void Tab::create_preset_tab() toggle_show_hide_incompatible(); })); + if (m_btn_edit_ph_printer) + m_btn_edit_ph_printer->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { + PhysicalPrinterDialog dlg(m_presets_choice->GetString(m_presets_choice->GetSelection())); + if (dlg.ShowModal() == wxID_OK) + update_tab_ui(); + })); + // Fill cache for mode bitmaps m_mode_bitmap_cache.reserve(3); m_mode_bitmap_cache.push_back(ScalableBitmap(this, "mode_simple" , mode_icon_px_size())); @@ -2786,8 +2802,7 @@ void Tab::load_current_preset() { const Preset& preset = m_presets->get_edited_preset(); -// (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); - update_delete_preset_btn(); + update_btns_enabling(); update(); if (m_type == Slic3r::Preset::TYPE_PRINTER) { @@ -2924,23 +2939,25 @@ void Tab::update_page_tree_visibility() } -void Tab::update_delete_preset_btn() +void Tab::update_btns_enabling() { - if (m_type == Preset::TYPE_PRINTER && m_presets_choice->is_selected_physical_printer() && - m_preset_bundle->physical_printers.has_selection()) { - // we can't delete last preset from the physical printer + // we can't delete last preset from the physical printer + if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) m_btn_delete_preset->Enable(m_preset_bundle->physical_printers.get_selected_printer().preset_names.size() > 1); - } else { const Preset& preset = m_presets->get_edited_preset(); m_btn_delete_preset->Enable(!preset.is_default && !preset.is_system); } + + // we can edit physical printer only if it's selected in the list + if (m_btn_edit_ph_printer) + m_btn_edit_ph_printer->Enable(m_preset_bundle->physical_printers.has_selection()); } void Tab::update_preset_choice() { m_presets_choice->update(); - update_delete_preset_btn(); + update_btns_enabling(); } // Called by the UI combo box when the user switches profiles, and also to delete the current profile. @@ -2950,55 +2967,16 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, { if (preset_name.empty()) { if (delete_current) { - PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; - if (m_presets_choice->is_selected_physical_printer()) { - PhysicalPrinter& printer = physical_printers.get_selected_printer(); - - if (printer.preset_names.size()==1) { - wxMessageDialog dialog(nullptr, _L("It's a last for this physical printer. We can't delete it"), _L("Information"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - } - else { - // just delete this preset from the current physical printer - printer.delete_preset(m_presets->get_edited_preset().name); - // select first from the possible presets for this printer - physical_printers.select_printer(printer); - - preset_name = physical_printers.get_selected_printer_preset_name(); - // revert delete_current value to avoid deleting of the new selected preset - delete_current = false; - } - } - else { - // Check preset for delete in physical printers - // Ask a customer about next action , if there is a printer with just one preset and this preset is equal to delete - if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty() ) - { - // try to delete selected preset from the all printers it has - if (!physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name)) - { - wxMessageDialog dialog(nullptr, _L("There is/are a physical printer(s), which has/have one and only this printer preset.\n" - "This/Those printer(s) will be deleted after deleting of the selected preset.\n" - "Are you sure you want to delete the selected preset?"), _L("Warning"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - if (dialog.ShowModal() == wxID_NO) - return; - - // delete selected preset from printers and printer, if it's needed - physical_printers.delete_preset_from_printers(m_presets->get_edited_preset().name, false); - } - } - - // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_current = this->m_presets->get_idx_selected(); - // Find the next visible preset. - size_t idx_new = idx_current + 1; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - if (idx_new == presets.size()) - for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); - preset_name = presets[idx_new].name; - } + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_current = this->m_presets->get_idx_selected(); + // Find the next visible preset. + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); + preset_name = presets[idx_new].name; } else { // If no name is provided, select the "-- default --" preset. preset_name = m_presets->default_preset().name; @@ -3349,15 +3327,70 @@ void Tab::delete_preset() // Don't let the user delete the ' - default - ' configuration. std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); // TRN remove/delete - const wxString msg = m_presets_choice->is_selected_physical_printer() ? - from_u8((boost::format(_utf8(L("Are you sure you want to delete \"%1%\" preset from the physical printer?"))) % current_preset.name).str()) : - from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); + + PhysicalPrinterCollection& physical_printers = m_preset_bundle->physical_printers; + wxString msg; + if (m_presets_choice->is_selected_physical_printer()) + msg = from_u8((boost::format(_u8L("Are you sure you want to delete \"%1%\" preset from the physical printer \"%2%\"?")) + % current_preset.name % physical_printers.get_selected_printer_name()).str()); + else + { + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) + { + // Check preset for delete in physical printers + // Ask a customer about next action, if there is a printer with just one preset and this preset is equal to delete + std::vector ph_printers = physical_printers.get_printers_with_preset(current_preset.name); + std::vector ph_printers_only = physical_printers.get_printers_with_only_preset(current_preset.name); + + if (!ph_printers.empty()) { + msg += _L("Next physical printer(s) has/have selected preset") + ":"; + for (const std::string& printer : ph_printers) + msg += "\n \"" + from_u8(printer) + "\","; + msg.RemoveLast(); + msg += "\n" + _L("Note, that selected preset will be deleted from this/those printer(s) too.")+ "\n\n"; + } + + if (!ph_printers_only.empty()) { + msg += _L("Next physical printer(s) has/have one and only selected preset") + ":"; + for (const std::string& printer : ph_printers_only) + msg += "\n \"" + from_u8(printer) + "\","; + msg.RemoveLast(); + msg += "\n" + _L("Note, that this/those printer(s) will be deleted after deleting of the selected preset.") + "\n\n"; + } + } + + msg += from_u8((boost::format(_u8L("Are you sure you want to %1% the selected preset?")) % action).str()); + } + action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); // TRN Remove/Delete wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); //action + _(L(" Preset")); if (current_preset.is_default || wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) return; + + // if we just delete preset from the physical printer + if (m_presets_choice->is_selected_physical_printer()) { + PhysicalPrinter& printer = physical_printers.get_selected_printer(); + + if (printer.preset_names.size() == 1) { + wxMessageDialog dialog(nullptr, _L("It's a last for this physical printer. We can't delete it"), _L("Information"), wxICON_INFORMATION | wxOK); + dialog.ShowModal(); + return; + } + // just delete this preset from the current physical printer + printer.delete_preset(m_presets->get_edited_preset().name); + // select first from the possible presets for this printer + physical_printers.select_printer(printer); + + this->select_preset(physical_printers.get_selected_printer_preset_name()); + return; + } + + // delete selected preset from printers and printer, if it's needed + if (m_type == Preset::TYPE_PRINTER && !physical_printers.empty()) + physical_printers.delete_preset_from_printers(current_preset.name); + // Select will handle of the preset dependencies, of saving & closing the depending profiles, and // finally of deleting the preset. this->select_preset("", true); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 57129955b0..24f25e2d74 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -119,6 +119,7 @@ protected: ScalableButton* m_search_btn; ScalableButton* m_btn_save_preset; ScalableButton* m_btn_delete_preset; + ScalableButton* m_btn_edit_ph_printer {nullptr}; ScalableButton* m_btn_hide_incompatible_presets; wxBoxSizer* m_hsizer; wxBoxSizer* m_left_sizer; @@ -275,7 +276,7 @@ public: void load_current_preset(); void rebuild_page_tree(); void update_page_tree_visibility(); - void update_delete_preset_btn(); + void update_btns_enabling(); void update_preset_choice(); // Select a new preset, possibly delete the current one. void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); From 11cf9a87f143dcc18c82779ce63a7cdf768521e4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Jul 2020 10:04:10 +0200 Subject: [PATCH 215/826] GCodeProcessor -> Calculate mm3 per mm on the fly --- src/libslic3r/GCode.cpp | 8 ++------ src/libslic3r/GCode.hpp | 7 ++++++- src/libslic3r/GCode/GCodeProcessor.cpp | 28 ++++++++++++-------------- src/libslic3r/GCode/GCodeProcessor.hpp | 2 +- src/libslic3r/GCode/WipeTower.cpp | 26 ++++++++++++------------ 5 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 742498baf8..ced52207d0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1178,7 +1178,6 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // resets analyzer's tracking data #if ENABLE_GCODE_VIEWER - m_last_mm3_per_mm = 0.0f; m_last_width = 0.0f; m_last_height = 0.0f; #else @@ -3237,16 +3236,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } #endif // ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; -#if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); - gcode += buf; -#else sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); gcode += buf; -#endif // ENABLE_GCODE_VIEWER } +#endif // !ENABLE_GCODE_VIEWER if (last_was_wipe_tower || (m_last_width != path.width)) { m_last_width = path.width; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 2732427620..443c25bb2f 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -173,7 +173,6 @@ public: m_last_pos_defined(false), m_last_extrusion_role(erNone), #if ENABLE_GCODE_VIEWER - m_last_mm3_per_mm(0.0f), m_last_width(0.0f), m_last_height(0.0f), #else @@ -378,10 +377,16 @@ private: double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? ExtrusionRole m_last_extrusion_role; +#if ENABLE_GCODE_VIEWER + // Support for G-Code Processor + float m_last_width; + float m_last_height; +#else // Support for G-Code Analyzer double m_last_mm3_per_mm; float m_last_width; float m_last_height; +#endif // ENABLE_GCODE_VIEWER Point m_last_pos; bool m_last_pos_defined; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 44598d2408..ef9c64880d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -24,7 +24,6 @@ namespace Slic3r { const std::string GCodeProcessor::Extrusion_Role_Tag = "ExtrType:"; const std::string GCodeProcessor::Width_Tag = "PrusaSlicer__WIDTH:"; const std::string GCodeProcessor::Height_Tag = "PrusaSlicer__HEIGHT:"; -const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "PrusaSlicer__MM3_PER_MM:"; const std::string GCodeProcessor::Color_Change_Tag = "COLOR_CHANGE"; const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE_PRINT"; const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM_CODE"; @@ -325,6 +324,10 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_extruders_color[id] = static_cast(id); } + for (double diam : config.filament_diameter.values) { + m_filament_diameters.push_back(static_cast(diam)); + } + m_time_processor.machine_limits = reinterpret_cast(config); // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they @@ -370,6 +373,7 @@ void GCodeProcessor::reset() m_extrusion_role = erNone; m_extruder_id = 0; m_extruders_color = ExtrudersColor(); + m_filament_diameters = std::vector(); m_cp_color.reset(); m_producer = EProducer::Unknown; @@ -592,20 +596,6 @@ void GCodeProcessor::process_tags(const std::string& comment) return; } - // mm3 per mm tag - pos = comment.find(Mm3_Per_Mm_Tag); - if (pos != comment.npos) { - try - { - m_mm3_per_mm = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length())); - } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; - } - return; - } - // color change tag pos = comment.find(Color_Change_Tag); if (pos != comment.npos) { @@ -1019,6 +1009,14 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) EMoveType type = move_type(delta_pos); + if (type == EMoveType::Extrude) { + if (delta_pos[E] > 0.0f) { + float ds = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + if (ds > 0.0f && static_cast(m_extruder_id) < m_filament_diameters.size()) + m_mm3_per_mm = round_nearest(delta_pos[E] * static_cast(M_PI) * sqr(static_cast(m_filament_diameters[m_extruder_id])) / (4.0f * ds), 3); + } + } + // time estimate section auto move_length = [](const AxisCoords& delta_pos) { float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 9507adf4d5..d59fc7bb9b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -21,7 +21,6 @@ namespace Slic3r { static const std::string Extrusion_Role_Tag; static const std::string Width_Tag; static const std::string Height_Tag; - static const std::string Mm3_Per_Mm_Tag; static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; static const std::string Custom_Code_Tag; @@ -241,6 +240,7 @@ namespace Slic3r { ExtrusionRole m_extrusion_role; unsigned char m_extruder_id; ExtrudersColor m_extruders_color; + std::vector m_filament_diameters; CpColor m_cp_color; enum class EProducer diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index e4f995aba1..c20009a487 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -84,19 +84,17 @@ public: return *this; } - WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { - static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; - float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); - // adds tag for analyzer: - char buf[64]; -#if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); -#else - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); -#endif // ENABLE_GCODE_VIEWER - m_gcode += buf; - return *this; +#if !ENABLE_GCODE_VIEWER + WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { + static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; + float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); + m_gcode += buf; + return *this; } +#endif // !ENABLE_GCODE_VIEWER WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { m_wipe_tower_width = width; @@ -169,8 +167,10 @@ public: Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.f) { +#if !ENABLE_GCODE_VIEWER change_analyzer_mm3_per_mm(len, e); - // Width of a squished extrusion, corrected for the roundings of the squished extrusions. +#endif // !ENABLE_GCODE_VIEWER + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width = e * m_filpar[0].filament_area / (len * m_layer_height); // Correct for the roundings of a squished extrusion. From 16e282110d2fc8ce503c0635397ad0e8300c78c3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Jul 2020 11:13:48 +0200 Subject: [PATCH 216/826] GCodeProcessor -> Load config data from gcode files generated by PrusaSlicer --- src/libslic3r/GCode/GCodeProcessor.cpp | 45 +++++++++++++++++++++++--- src/libslic3r/GCode/GCodeProcessor.hpp | 1 + src/libslic3r/GCodeReader.cpp | 5 +++ src/libslic3r/GCodeReader.hpp | 6 ++++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index ef9c64880d..ae414ad441 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -346,6 +346,18 @@ void GCodeProcessor::apply_config(const PrintConfig& config) } } +void GCodeProcessor::apply_config(const DynamicPrintConfig& config) +{ + m_parser.apply_config(config); + + const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); + if (filament_diameters != nullptr) { + for (double diam : filament_diameters->values) { + m_filament_diameters.push_back(static_cast(diam)); + } + } +} + void GCodeProcessor::enable_stealth_time_estimator(bool enabled) { m_time_processor.machines[static_cast(ETimeMode::Stealth)].enabled = enabled; @@ -391,6 +403,28 @@ void GCodeProcessor::process_file(const std::string& filename) auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS + // pre-processing + // parse the gcode file to detect its producer + if (m_producers_enabled) { + m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + std::string cmd = line.cmd(); + if (cmd.length() == 0) { + std::string comment = line.comment(); + if (comment.length() > 1 && detect_producer(comment)) + m_parser.quit_parsing_file(); + } + }); + + // if the gcode was produced by PrusaSlicer, + // extract the config from it + if (m_producer == EProducer::PrusaSlicer) { + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + config.load_from_gcode_file(filename); + apply_config(config); + } + } + m_result.id = ++s_result_id; m_result.moves.emplace_back(MoveVertex()); m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); @@ -554,11 +588,12 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_tags(const std::string& comment) { - if (m_producers_enabled && m_producer == EProducer::Unknown && detect_producer(comment)) - return; - else if (m_producers_enabled && m_producer != EProducer::Unknown) { - if (process_producers_tags(comment)) - return; + // producers tags + if (m_producers_enabled) { + if (m_producer != EProducer::Unknown) { + if (process_producers_tags(comment)) + return; + } } // extrusion role tag diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index d59fc7bb9b..b2d702f7f3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -266,6 +266,7 @@ namespace Slic3r { GCodeProcessor() { reset(); } void apply_config(const PrintConfig& config); + void apply_config(const DynamicPrintConfig& config); void enable_stealth_time_estimator(bool enabled); void enable_producers(bool enabled) { m_producers_enabled = enabled; } void reset(); diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index e68bc5ad29..ab77b01413 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -115,7 +115,12 @@ void GCodeReader::parse_file(const std::string &file, callback_t callback) { std::ifstream f(file); std::string line; +#if ENABLE_GCODE_VIEWER + m_parsing_file = true; + while (m_parsing_file && std::getline(f, line)) +#else while (std::getline(f, line)) +#endif // ENABLE_GCODE_VIEWER this->parse_line(line, callback); } diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 9503ddcc16..7e0793cd9b 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -107,6 +107,9 @@ public: { GCodeLine gline; this->parse_line(line.c_str(), gline, callback); } void parse_file(const std::string &file, callback_t callback); +#if ENABLE_GCODE_VIEWER + void quit_parsing_file() { m_parsing_file = false; } +#endif // ENABLE_GCODE_VIEWER float& x() { return m_position[X]; } float x() const { return m_position[X]; } @@ -145,6 +148,9 @@ private: char m_extrusion_axis; float m_position[NUM_AXES]; bool m_verbose; +#if ENABLE_GCODE_VIEWER + bool m_parsing_file{ false }; +#endif // ENABLE_GCODE_VIEWER }; } /* namespace Slic3r */ From 9d4344a78ce56ecf722e946f2c1796a9e1df96e8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Jul 2020 12:47:42 +0200 Subject: [PATCH 217/826] GCodeProcessor/GCodeViewer -> Extract bed shape from gcode files generated by PrusaSlicer --- src/libslic3r/GCode/GCodeProcessor.cpp | 4 ++++ src/libslic3r/GCode/GCodeProcessor.hpp | 20 ++++++++++++++++++-- src/slic3r/GUI/GCodeViewer.cpp | 22 ++++++++++++++-------- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index ae414ad441..beb340a5da 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -356,6 +356,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_filament_diameters.push_back(static_cast(diam)); } } + + const ConfigOptionPoints* bed_shape = config.option("bed_shape"); + if (bed_shape != nullptr) + m_result.bed_shape = bed_shape->values; } void GCodeProcessor::enable_stealth_time_estimator(bool enabled) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index b2d702f7f3..1def93e74b 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -210,11 +210,27 @@ namespace Slic3r { { unsigned int id; std::vector moves; +#if ENABLE_GCODE_VIEWER_AS_STATE + Pointfs bed_shape; +#endif // ENABLE_GCODE_VIEWER_AS_STATE #if ENABLE_GCODE_VIEWER_STATISTICS long long time{ 0 }; - void reset() { time = 0; moves = std::vector(); } + void reset() + { + time = 0; + moves = std::vector(); +#if ENABLE_GCODE_VIEWER_AS_STATE + bed_shape = Pointfs(); +#endif // ENABLE_GCODE_VIEWER_AS_STATE + } #else - void reset() { moves = std::vector(); } + void reset() + { + moves = std::vector(); +#if ENABLE_GCODE_VIEWER_AS_STATE + bed_shape = Pointfs(); +#endif // ENABLE_GCODE_VIEWER_AS_STATE + } #endif // ENABLE_GCODE_VIEWER_STATISTICS }; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 418223503b..817e7e650c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -323,14 +323,20 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& #if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { - // adjust printbed size in dependence of toolpaths bbox - const double margin = 10.0; - Vec2d min(m_paths_bounding_box.min(0) - margin, m_paths_bounding_box.min(1) - margin); - Vec2d max(m_paths_bounding_box.max(0) + margin, m_paths_bounding_box.max(1) + margin); - Pointfs bed_shape = { { min(0), min(1) }, - { max(0), min(1) }, - { max(0), max(1) }, - { min(0), max(1) } }; + Pointfs bed_shape; + if (!gcode_result.bed_shape.empty()) + // bed shape detected in the gcode + bed_shape = gcode_result.bed_shape; + else { + // adjust printbed size in dependence of toolpaths bbox + const double margin = 10.0; + Vec2d min(m_paths_bounding_box.min(0) - margin, m_paths_bounding_box.min(1) - margin); + Vec2d max(m_paths_bounding_box.max(0) + margin, m_paths_bounding_box.max(1) + margin); + bed_shape = { { min(0), min(1) }, + { max(0), min(1) }, + { max(0), max(1) }, + { min(0), max(1) } }; + } wxGetApp().plater()->set_bed_shape(bed_shape, "", ""); } #endif // ENABLE_GCODE_VIEWER_AS_STATE From 54a434063167a87b9d57665b604b15914e190630 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Jul 2020 13:05:16 +0200 Subject: [PATCH 218/826] GCodeViewer -> Hexagonal icons as default --- src/slic3r/GUI/GCodeViewer.cpp | 57 +--------------------------------- 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 817e7e650c..fed9a30d57 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1348,8 +1348,6 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); } -#define USE_ICON_HEXAGON 1 - void GCodeViewer::render_legend() const { if (!m_legend_enabled) @@ -1444,11 +1442,7 @@ void GCodeViewer::render_legend() const auto append_range_item = [this, draw_list, &imgui, append_item](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, Range_Colors[i], buf); -#else - append_item(EItemType::Rect, Range_Colors[i], buf); -#endif // USE_ICON_HEXAGON }; float step_size = range.step_size(); @@ -1533,13 +1527,8 @@ void GCodeViewer::render_legend() const if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { -#else - append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { -#endif // USE_ICON_HEXAGON - if (role < erCount) - { + if (role < erCount) { m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); @@ -1562,11 +1551,7 @@ void GCodeViewer::render_legend() const { // shows only extruders actually used for (unsigned char i : m_extruder_ids) { -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); -#else - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); -#endif // USE_ICON_HEXAGON } break; } @@ -1578,36 +1563,20 @@ void GCodeViewer::render_legend() const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, m_tool_colors.front(), _u8L("Default color")); -#else - append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); -#endif // USE_ICON_HEXAGON } else { for (int i = items_cnt; i >= 0; --i) { // create label for color change item if (i == 0) { -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, m_tool_colors[0], upto_label(cp_values.front().second.first)); -#else - append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first); -#endif // USE_ICON_HEXAGON break; } else if (i == items_cnt) { -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); -#else - append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second); -#endif // USE_ICON_HEXAGON continue; } -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); -#else - append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); -#endif // USE_ICON_HEXAGON } } } @@ -1618,11 +1587,7 @@ void GCodeViewer::render_legend() const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); -#else - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); -#endif // USE_ICON_HEXAGON } else { for (int j = items_cnt; j >= 0; --j) { @@ -1630,29 +1595,17 @@ void GCodeViewer::render_legend() const std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); if (j == 0) { label += " " + upto_label(cp_values.front().second.first); -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, m_tool_colors[i], label); -#else - append_item(EItemType::Rect, m_tool_colors[i], label); -#endif // USE_ICON_HEXAGON break; } else if (j == items_cnt) { label += " " + above_label(cp_values[j - 1].second.second); -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, cp_values[j - 1].first, label); -#else - append_item(EItemType::Rect, cp_values[j - 1].first, label); -#endif // USE_ICON_HEXAGON continue; } label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); -#if USE_ICON_HEXAGON append_item(EItemType::Hexagon, cp_values[j - 1].first, label); -#else - append_item(EItemType::Rect, cp_values[j - 1].first, label); -#endif // USE_ICON_HEXAGON } } } @@ -1826,18 +1779,10 @@ void GCodeViewer::render_time_estimate() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 pos = ImGui::GetCursorScreenPos(); pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; -#if USE_ICON_HEXAGON ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6); center.x += icon_size; draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6); -#else - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); - pos.x += icon_size; - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); -#endif // USE_ICON_HEXAGON ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second - times.first))); }; From 0348986bda0e20c26a99a452be5402146303680c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Jul 2020 14:20:01 +0200 Subject: [PATCH 219/826] Follow-up of 9d4344a78ce56ecf722e946f2c1796a9e1df96e8 -> ensure printbed always rendered as custom in gcode preview mode --- src/libslic3r/GCode/GCodeProcessor.cpp | 1 - src/slic3r/GUI/3DBed.cpp | 41 +++++++++++++++----------- src/slic3r/GUI/3DBed.hpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 13 ++++---- src/slic3r/GUI/Plater.hpp | 2 +- 7 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index beb340a5da..7a6b1ecb42 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -694,7 +694,6 @@ bool GCodeProcessor::process_producers_tags(const std::string& comment) bool GCodeProcessor::process_prusaslicer_tags(const std::string& comment) { - std::cout << comment << "\n"; return false; } diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index ca075fb372..9d16bead71 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -255,7 +255,7 @@ Bed3D::Bed3D() { } -bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) +bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { auto check_texture = [](const std::string& texture) { return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture); @@ -265,30 +265,39 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model); }; - auto [new_type, system_model, system_texture] = detect_type(shape); + EType type; + std::string model; + std::string texture; + if (force_as_custom) + type = Custom; + else { + auto [new_type, system_model, system_texture] = detect_type(shape); + type = new_type; + model = system_model; + texture = system_texture; + } - std::string texture_filename = custom_texture.empty() ? system_texture : custom_texture; + std::string texture_filename = custom_texture.empty() ? texture : custom_texture; if (!check_texture(texture_filename)) texture_filename.clear(); - std::string model_filename = custom_model.empty() ? system_model : custom_model; + std::string model_filename = custom_model.empty() ? model : custom_model; if (!check_model(model_filename)) model_filename.clear(); - if ((m_shape == shape) && (m_type == new_type) && (m_texture_filename == texture_filename) && (m_model_filename == model_filename)) + if (m_shape == shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename) // No change, no need to update the UI. return false; m_shape = shape; m_texture_filename = texture_filename; m_model_filename = model_filename; - m_type = new_type; + m_type = type; calc_bounding_boxes(); ExPolygon poly; - for (const Vec2d& p : m_shape) - { + for (const Vec2d& p : m_shape) { poly.contour.append(Point(scale_(p(0)), scale_(p(1)))); } @@ -435,19 +444,15 @@ static std::string system_print_bed_texture(const Preset &preset) std::tuple Bed3D::detect_type(const Pointfs& shape) const { auto bundle = wxGetApp().preset_bundle; - if (bundle != nullptr) - { + if (bundle != nullptr) { const Preset* curr = &bundle->printers.get_selected_preset(); - while (curr != nullptr) - { - if (curr->config.has("bed_shape")) - { - if (shape == dynamic_cast(curr->config.option("bed_shape"))->values) - { + while (curr != nullptr) { + if (curr->config.has("bed_shape")) { + if (shape == dynamic_cast(curr->config.option("bed_shape"))->values) { std::string model_filename = system_print_bed_model(*curr); std::string texture_filename = system_print_bed_texture(*curr); if (!model_filename.empty() && !texture_filename.empty()) - return std::make_tuple(System, model_filename, texture_filename); + return { System, model_filename, texture_filename }; } } @@ -455,7 +460,7 @@ std::tuple Bed3D::detect_type(const Poin } } - return std::make_tuple(Custom, "", ""); + return { Custom, "", "" }; } void Bed3D::render_axes() const diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index b9e952c4a4..fbfc3078c1 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -144,7 +144,7 @@ public: const Pointfs& get_shape() const { return m_shape; } // Return true if the bed shape changed, so the calee will update the UI. - bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model); + bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index fed9a30d57..af3d8d901c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -337,7 +337,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& { max(0), max(1) }, { min(0), max(1) } }; } - wxGetApp().plater()->set_bed_shape(bed_shape, "", ""); + wxGetApp().plater()->set_bed_shape(bed_shape, "", "", true); } #endif // ENABLE_GCODE_VIEWER_AS_STATE } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index c43563c8bf..0d527f48b3 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1490,7 +1490,7 @@ void MainFrame::set_mode(EMode mode) m_plater->select_view("iso"); // switch printbed - m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", ""); + m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); // switch menubar SetMenuBar(m_gcodeviewer_menubar); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 193ac8e0c1..43cf27e30f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1867,7 +1867,7 @@ struct Plater::priv // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. - void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model); + void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); bool can_delete() const; bool can_delete_all() const; @@ -4182,11 +4182,10 @@ bool Plater::priv::can_reload_from_disk() const return !paths.empty(); } -void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) +void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { - bool new_shape = bed.set_shape(shape, custom_texture, custom_model); - if (new_shape) - { + bool new_shape = bed.set_shape(shape, custom_texture, custom_model, force_as_custom); + if (new_shape) { if (view3D) view3D->bed_shape_changed(); if (preview) preview->bed_shape_changed(); } @@ -5456,9 +5455,9 @@ void Plater::set_bed_shape() const } #if ENABLE_GCODE_VIEWER_AS_STATE -void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) const +void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const { - p->set_bed_shape(shape, custom_texture, custom_model); + p->set_bed_shape(shape, custom_texture, custom_model, force_as_custom); } #endif // ENABLE_GCODE_VIEWER_AS_STATE diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 085ed0e69c..59d595bbe4 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -363,7 +363,7 @@ public: void set_bed_shape() const; #if ENABLE_GCODE_VIEWER_AS_STATE - void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) const; + void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; #endif // ENABLE_GCODE_VIEWER_AS_STATE // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. From 39d08441cc4bf103bfc240e8609935aded72ffa7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 29 Jul 2020 15:40:28 +0200 Subject: [PATCH 220/826] ENABLE_GCODE_VIEWER_AS_STATE -> Fixed collapse toolbar showing-up when presing [T] in gcode preview mode --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/GLToolbar.cpp | 13 +------------ src/slic3r/GUI/GLToolbar.hpp | 4 ++-- src/slic3r/GUI/MainFrame.cpp | 6 +++++- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2c13c752e6..175eecd8bd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4295,8 +4295,13 @@ void GLCanvas3D::update_ui_from_settings() } #endif // ENABLE_RETINA_GL +#if ENABLE_GCODE_VIEWER_AS_STATE + if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) + wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); +#else bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse); +#endif // ENABLE_GCODE_VIEWER_AS_STATE } diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 4ab282b066..46371b037a 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -230,24 +230,13 @@ void GLToolbar::set_icons_size(float size) void GLToolbar::set_scale(float scale) { - if (m_layout.scale != scale) - { + if (m_layout.scale != scale) { m_layout.scale = scale; m_layout.dirty = true; m_icons_texture_dirty = true; } } -bool GLToolbar::is_enabled() const -{ - return m_enabled; -} - -void GLToolbar::set_enabled(bool enable) -{ - m_enabled = enable;//true; etFIXME -} - bool GLToolbar::add_item(const GLToolbarItem::Data& data) { GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 41c2735c9a..74e18de975 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -276,8 +276,8 @@ public: void set_icons_size(float size); void set_scale(float scale); - bool is_enabled() const; - void set_enabled(bool enable); + bool is_enabled() const { return m_enabled; } + void set_enabled(bool enable) { m_enabled = enable; } bool add_item(const GLToolbarItem::Data& data); bool add_separator(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 0d527f48b3..7d6acad069 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -279,8 +279,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S update_ui_from_settings(); // FIXME (?) - if (m_plater != nullptr) + if (m_plater != nullptr) { +#if ENABLE_GCODE_VIEWER_AS_STATE + m_plater->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); +#endif // ENABLE_GCODE_VIEWER_AS_STATE m_plater->show_action_buttons(true); + } } #if ENABLE_LAYOUT_NO_RESTART From 96a364c3e6356d7107d6c51b4c1763c81d0d3435 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 29 Jul 2020 16:05:30 +0200 Subject: [PATCH 221/826] SavePresetDialog: Improvements --- src/slic3r/GUI/PresetComboBoxes.cpp | 303 ++++++++++++++++------------ src/slic3r/GUI/PresetComboBoxes.hpp | 64 ++++-- src/slic3r/GUI/Tab.cpp | 2 +- 3 files changed, 229 insertions(+), 140 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 666f10194c..35acbfd3ae 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1016,41 +1016,21 @@ void TabPresetComboBox::update_dirty() //----------------------------------------------- -// SavePresetDialog +// SavePresetDialog::Item //----------------------------------------------- -SavePresetDialog::SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix) - : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER), - m_preset_cb(preset_cb) +SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent): + m_type(type), + m_parent(parent) { - SetFont(wxGetApp().normal_font()); - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + Tab* tab = wxGetApp().get_tab(m_type); + assert(tab); + m_presets = tab->get_presets(); - wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - - add_common_items(topSizer, suffix); - add_items_for_edit_ph_printer(topSizer); - - // add dialog's buttons - wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); - wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); - btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); - btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { - evt.Enable(!m_combo->GetValue().IsEmpty()); }); - - topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); - - SetSizer(topSizer); - topSizer->SetSizeHints(this); -} - -void SavePresetDialog::add_common_items(wxBoxSizer* sizer, const std::string& suffix) -{ - const PresetCollection* presets = m_preset_cb->presets(); - const Preset& sel_preset = presets->get_selected_preset(); - std::string preset_name = sel_preset.is_default ? "Untitled" : - sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : - sel_preset.name; + const Preset& sel_preset = m_presets->get_selected_preset(); + std::string preset_name = sel_preset.is_default ? "Untitled" : + sel_preset.is_system ? (boost::format(("%1% - %2%")) % sel_preset.name % suffix).str() : + sel_preset.name; // if name contains extension if (boost::iends_with(preset_name, ".ini")) { @@ -1059,34 +1039,166 @@ void SavePresetDialog::add_common_items(wxBoxSizer* sizer, const std::string& su } std::vector values; - for (const Preset& preset : *presets) { + for (const Preset& preset : *m_presets) { if (preset.is_default || preset.is_system || preset.is_external) continue; values.push_back(preset.name); } - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(wxGetApp().get_tab(m_preset_cb->type())->title())).str())); - m_combo = new wxComboBox(this, wxID_ANY, from_u8(preset_name), - wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); + wxStaticText* label_top = new wxStaticText(m_parent, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(tab->title())).str())); + + m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); + + m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)/*, + wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER*/); for (auto value : values) m_combo->Append(from_u8(value)); - m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); - m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { - update(normalize_utf8_nfc(m_combo->GetValue().ToUTF8())); - this->Layout(); - this->Fit(); - }); + m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); - sizer->Add(label_top, 0, wxEXPAND | wxALL, BORDER_W); - sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, BORDER_W); + m_valid_label = new wxStaticText(m_parent, wxID_ANY, ""); + m_valid_label->SetFont(wxGetApp().bold_font()); + + wxBoxSizer* combo_sizer = new wxBoxSizer(wxHORIZONTAL); + combo_sizer->Add(m_valid_bmp, 0, wxEXPAND | wxRIGHT, BORDER_W); + combo_sizer->Add(m_combo, 1, wxEXPAND, BORDER_W); + + sizer->Add(label_top, 0, wxEXPAND | wxTOP| wxBOTTOM, BORDER_W); + sizer->Add(combo_sizer, 0, wxEXPAND | wxBOTTOM, BORDER_W); + sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W/* + m_valid_bmp->GetBitmap().GetWidth()*/); + + if (m_type == Preset::TYPE_PRINTER) + m_parent->add_info_for_edit_ph_printer(sizer); + + update(); } -void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) +void SavePresetDialog::Item::update() { - if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) - return; + m_preset_name = into_u8(m_combo->GetValue()); + m_valid_type = Valid; + wxString info_line; + + const char* unusable_symbols = "<>[]:/\\|?*\""; + + const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (m_preset_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + info_line = _L("The supplied name is not valid;") + "\n" + + _L("the following characters are not allowed:") + " " + unusable_symbols; + m_valid_type = NoValid; + break; + } + } + + if (m_valid_type == Valid && m_preset_name.find(unusable_suffix) != std::string::npos) { + info_line = _L("The supplied name is not valid;") + "\n" + + _L("the following suffix is not allowed:") + "\n\t" + + from_u8(PresetCollection::get_suffix_modified()); + m_valid_type = NoValid; + } + + if (m_valid_type == Valid && m_preset_name == "- default -") { + info_line = _L("The supplied name is not available."); + m_valid_type = NoValid; + } + + const Preset* existing = m_presets->find_preset(m_preset_name, false); + if (m_valid_type == Valid && existing && (existing->is_default || existing->is_system)) { + info_line = _L("Cannot overwrite a system profile."); + m_valid_type = NoValid; + } + if (m_valid_type == Valid && existing && (existing->is_external)) { + info_line = _L("Cannot overwrite an external profile."); + m_valid_type = NoValid; + } + if (m_valid_type == Valid && existing && m_preset_name != m_presets->get_selected_preset_name()) + { + info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()) + "\n" + + _L("Note: This preset will be replaced after saving"); + m_valid_type = Warning; + } + + m_valid_label->SetLabel(info_line); + m_valid_label->Show(!info_line.IsEmpty()); + + std::string bmp_name = m_valid_type == Warning ? "exclamation" : + m_valid_type == NoValid ? "cross" : "tick_mark" ; + m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); + + if (m_type == Preset::TYPE_PRINTER) + m_parent->update_info_for_edit_ph_printer(m_preset_name); + + m_parent->layout(); +} + +void SavePresetDialog::Item::accept() +{ + if (m_valid_type == Warning) + m_presets->delete_preset(m_preset_name); +} + + +//----------------------------------------------- +// SavePresetDialog +//----------------------------------------------- + +SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) + : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) +{ + SetFont(wxGetApp().normal_font()); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + m_presets_sizer = new wxBoxSizer(wxVERTICAL); + + // Add first item + m_items.emplace_back(type, suffix, m_presets_sizer, this); + + // Add dialog's buttons + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); + btnOK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); + btnOK->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(enable_ok_btn()); }); + + topSizer->Add(m_presets_sizer, 0, wxEXPAND | wxALL, BORDER_W); + topSizer->Add(btns, 0, wxEXPAND | wxALL, BORDER_W); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void SavePresetDialog::AddItem(Preset::Type type, const std::string& suffix) +{ + m_items.emplace_back(type, suffix, m_presets_sizer, this); +} + +std::string SavePresetDialog::get_name() +{ + return m_items.front().preset_name(); +} + +std::string SavePresetDialog::get_name(Preset::Type type) +{ + for (Item& item : m_items) + if (item.type() == type) + return item.preset_name(); + return ""; +} + +bool SavePresetDialog::enable_ok_btn() const +{ + for (Item item : m_items) + if (!item.is_valid()) + return false; + + return true; +} + +void SavePresetDialog::add_info_for_edit_ph_printer(wxBoxSizer* sizer) +{ PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; m_ph_printer_name = printers.get_selected_printer_name(); m_old_preset_name = printers.get_selected_printer_preset_name(); @@ -1102,6 +1214,7 @@ void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) m_action_radio_box = new wxRadioBox(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); m_action_radio_box->SetFont(wxGetApp().normal_font()); + m_action_radio_box->SetLabelFont(wxGetApp().normal_font()); m_action_radio_box->SetSelection(0); m_action_radio_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { m_action = (ActionType)e.GetSelection(); }); @@ -1110,18 +1223,13 @@ void SavePresetDialog::add_items_for_edit_ph_printer(wxBoxSizer* sizer) m_radio_sizer = new wxBoxSizer(wxHORIZONTAL); m_radio_sizer->Add(m_action_radio_box, 1, wxALIGN_CENTER_VERTICAL); - sizer->Add(m_label, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - - update(m_preset_name); + sizer->Add(m_label, 0, wxEXPAND | wxALL, 2*BORDER_W); + sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT, 2*BORDER_W); } -void SavePresetDialog::update(const std::string& preset_name) +void SavePresetDialog::update_info_for_edit_ph_printer(const std::string& preset_name) { - if (m_preset_cb->type() != Preset::TYPE_PRINTER || !m_preset_cb->is_selected_physical_printer()) - return; - - bool show = m_old_preset_name != preset_name; + bool show = wxGetApp().preset_bundle->physical_printers.has_selection() && m_old_preset_name != preset_name; m_label->Show(show); m_radio_sizer->ShowItems(show); @@ -1142,6 +1250,12 @@ void SavePresetDialog::update(const std::string& preset_name) m_action_radio_box->SetString(n++, label); } +void SavePresetDialog::layout() +{ + this->Layout(); + this->Fit(); +} + void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); @@ -1149,65 +1263,13 @@ void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); const wxSize& size = wxSize(45 * em, 35 * em); - SetMinSize(size); + SetMinSize(/*size*/wxSize(100, 50)); Fit(); Refresh(); } -bool SavePresetDialog::preset_name_is_accepted() -{ - const char* unusable_symbols = "<>[]:/\\|?*\""; - const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (m_preset_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - show_error(this, _L("The supplied name is not valid;") + "\n" + - _L("the following characters are not allowed:") + " " + unusable_symbols); - return false; - } - } - - if (m_preset_name.find(unusable_suffix) != std::string::npos) { - show_error(this, _L("The supplied name is not valid;") + "\n" + - _L("the following suffix is not allowed:") + "\n\t" + - from_u8(PresetCollection::get_suffix_modified())); - return false; - } - - if (m_preset_name == "- default -") { - show_error(this, _L("The supplied name is not available.")); - return false; - } - return true; -} - -bool SavePresetDialog::preset_is_possible_to_save() -{ - const Preset* existing = m_preset_cb->presets()->find_preset(m_preset_name, false); - if (existing && (existing->is_default || existing->is_system)) { - show_error(this, _L("Cannot overwrite a system profile.")); - return false; - } - if (existing && (existing->is_external)) { - show_error(this, _(L("Cannot overwrite an external profile."))); - return false; - } - if (existing && m_preset_name != m_preset_cb->presets()->get_selected_preset_name()) - { - wxString msg_text = GUI::from_u8((boost::format(_utf8(L("Preset with name \"%1%\" already exists."))) % m_preset_name).str()); - msg_text += "\n" + _L("Replace?"); - wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - if (dialog.ShowModal() == wxID_NO) - return false; - - // Remove the preset from the list. - m_preset_cb->presets()->delete_preset(m_preset_name); - } - return true; -} - -void SavePresetDialog::update_physical_printers() +void SavePresetDialog::update_physical_printers(const std::string& preset_name) { if (m_action == UndefAction) return; @@ -1228,29 +1290,20 @@ void SavePresetDialog::update_physical_printers() if (m_action == ChangePreset) printer.delete_preset(printer_preset_name); - if (printer.add_preset(m_preset_name)) + if (printer.add_preset(preset_name)) physical_printers.save_printer(printer); - else { - wxMessageDialog dialog(nullptr, _L("This preset is already exist for this physical printer. Please, select another one."), _L("Information"), wxICON_INFORMATION | wxOK); - dialog.ShowModal(); - } - physical_printers.select_printer(printer.get_full_name(m_preset_name)); + physical_printers.select_printer(printer.get_full_name(preset_name)); } } void SavePresetDialog::accept() { - m_preset_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); - - if (m_preset_name.empty()) - return; - - if (!preset_name_is_accepted() || - !preset_is_possible_to_save()) - return; - - update_physical_printers(); + for (Item& item : m_items) { + item.accept(); + if (item.type() == Preset::TYPE_PRINTER) + update_physical_printers(item.preset_name()); + } EndModal(wxID_OK); } diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index a30d9f6e90..c0de645dfe 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -14,6 +14,7 @@ class wxStaticText; class ScalableButton; class wxBoxSizer; class wxComboBox; +class wxStaticBitmap; namespace Slic3r { @@ -200,35 +201,70 @@ class SavePresetDialog : public DPIDialog UndefAction }; - TabPresetComboBox* m_preset_cb {nullptr}; - std::string m_preset_name; - wxComboBox* m_combo {nullptr}; - wxStaticText* m_label {nullptr}; + struct Item + { + enum ValidationType + { + Valid, + NoValid, + Warning + }; + + Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent); + + void accept(); + + bool is_valid() const { return m_valid_type != NoValid; } + Preset::Type type() const { return m_type; } + std::string preset_name() const { return m_preset_name; } + + private: + Preset::Type m_type; + ValidationType m_valid_type; + std::string m_preset_name; + + SavePresetDialog* m_parent {nullptr}; + wxStaticBitmap* m_valid_bmp {nullptr}; + wxComboBox* m_combo {nullptr}; + wxStaticText* m_valid_label {nullptr}; + + PresetCollection* m_presets {nullptr}; + + void update(); + }; + + std::vector m_items; + + wxBoxSizer* m_presets_sizer {nullptr}; + wxStaticText* m_label {nullptr}; wxRadioBox* m_action_radio_box {nullptr}; wxBoxSizer* m_radio_sizer {nullptr}; ActionType m_action {UndefAction}; - std::string m_ph_printer_name; - std::string m_old_preset_name; + std::string m_ph_printer_name; + std::string m_old_preset_name; public: - SavePresetDialog(TabPresetComboBox* preset_cb, const std::string& suffix); + SavePresetDialog(Preset::Type type, const std::string& suffix); ~SavePresetDialog() {} - std::string get_name() { return m_preset_name; } + void AddItem(Preset::Type type, const std::string& suffix); + + std::string get_name(); + std::string get_name(Preset::Type type); + + bool enable_ok_btn() const; + void add_info_for_edit_ph_printer(wxBoxSizer *sizer); + void update_info_for_edit_ph_printer(const std::string &preset_name); + void layout(); protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {} private: - void add_common_items(wxBoxSizer *sizer, const std::string &suffix); - void add_items_for_edit_ph_printer(wxBoxSizer *sizer); - void update(const std::string &preset_name); - bool preset_name_is_accepted(); - bool preset_is_possible_to_save(); - void update_physical_printers(); + void update_physical_printers(const std::string& preset_name); void accept(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4dbe7cc742..9d37362ba4 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3262,7 +3262,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) std::string suffix = detach ? _utf8(L("Detached")) : _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); if (name.empty()) { - SavePresetDialog dlg(m_presets_choice, suffix); + SavePresetDialog dlg(m_type, suffix); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); From d84e70f59afaf7c052462f6a7cadf1fa28876eb9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 Jul 2020 09:43:13 +0200 Subject: [PATCH 222/826] SavePresetDialog: Fixed OSX and Linux build + Added scaling of the validation icons --- src/slic3r/GUI/PresetComboBoxes.cpp | 34 ++++++++++++++++------------- src/slic3r/GUI/PresetComboBoxes.hpp | 1 + 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 35acbfd3ae..77bdb38122 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1049,9 +1049,8 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); - m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)/*, - wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER*/); - for (auto value : values) + m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)); + for (const std::string& value : values) m_combo->Append(from_u8(value)); m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); @@ -1060,12 +1059,12 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_valid_label->SetFont(wxGetApp().bold_font()); wxBoxSizer* combo_sizer = new wxBoxSizer(wxHORIZONTAL); - combo_sizer->Add(m_valid_bmp, 0, wxEXPAND | wxRIGHT, BORDER_W); + combo_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); combo_sizer->Add(m_combo, 1, wxEXPAND, BORDER_W); sizer->Add(label_top, 0, wxEXPAND | wxTOP| wxBOTTOM, BORDER_W); sizer->Add(combo_sizer, 0, wxEXPAND | wxBOTTOM, BORDER_W); - sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W/* + m_valid_bmp->GetBitmap().GetWidth()*/); + sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W); if (m_type == Preset::TYPE_PRINTER) m_parent->add_info_for_edit_ph_printer(sizer); @@ -1123,9 +1122,7 @@ void SavePresetDialog::Item::update() m_valid_label->SetLabel(info_line); m_valid_label->Show(!info_line.IsEmpty()); - std::string bmp_name = m_valid_type == Warning ? "exclamation" : - m_valid_type == NoValid ? "cross" : "tick_mark" ; - m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); + update_valid_bmp(); if (m_type == Preset::TYPE_PRINTER) m_parent->update_info_for_edit_ph_printer(m_preset_name); @@ -1133,6 +1130,13 @@ void SavePresetDialog::Item::update() m_parent->layout(); } +void SavePresetDialog::Item::update_valid_bmp() +{ + std::string bmp_name = m_valid_type == Warning ? "exclamation" : + m_valid_type == NoValid ? "cross" : "tick_mark" ; + m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); +} + void SavePresetDialog::Item::accept() { if (m_valid_type == Warning) @@ -1147,7 +1151,6 @@ void SavePresetDialog::Item::accept() SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) { - SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); @@ -1213,18 +1216,16 @@ void SavePresetDialog::add_info_for_edit_ph_printer(wxBoxSizer* sizer) m_action_radio_box = new wxRadioBox(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); - m_action_radio_box->SetFont(wxGetApp().normal_font()); - m_action_radio_box->SetLabelFont(wxGetApp().normal_font()); m_action_radio_box->SetSelection(0); m_action_radio_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { m_action = (ActionType)e.GetSelection(); }); m_action = ChangePreset; m_radio_sizer = new wxBoxSizer(wxHORIZONTAL); - m_radio_sizer->Add(m_action_radio_box, 1, wxALIGN_CENTER_VERTICAL); + m_radio_sizer->Add(m_action_radio_box, 1, wxEXPAND | wxTOP, 2*BORDER_W); - sizer->Add(m_label, 0, wxEXPAND | wxALL, 2*BORDER_W); - sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT, 2*BORDER_W); + sizer->Add(m_label, 0, wxEXPAND | wxLEFT | wxTOP, 3*BORDER_W); + sizer->Add(m_radio_sizer, 1, wxEXPAND | wxLEFT, 3*BORDER_W); } void SavePresetDialog::update_info_for_edit_ph_printer(const std::string& preset_name) @@ -1262,7 +1263,10 @@ void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - const wxSize& size = wxSize(45 * em, 35 * em); + for (Item& item : m_items) + item.update_valid_bmp(); + + //const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(/*size*/wxSize(100, 50)); Fit(); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index c0de645dfe..f31b67fbe6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -212,6 +212,7 @@ class SavePresetDialog : public DPIDialog Item(Preset::Type type, const std::string& suffix, wxBoxSizer* sizer, SavePresetDialog* parent); + void update_valid_bmp(); void accept(); bool is_valid() const { return m_valid_type != NoValid; } From 5eb3b21be791003fa575066c4d1096e9b879f509 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 Jul 2020 09:45:45 +0200 Subject: [PATCH 223/826] Added missed icons/tick_mark.svg --- resources/icons/tick_mark.svg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 resources/icons/tick_mark.svg diff --git a/resources/icons/tick_mark.svg b/resources/icons/tick_mark.svg new file mode 100644 index 0000000000..4ccab2192d --- /dev/null +++ b/resources/icons/tick_mark.svg @@ -0,0 +1,6 @@ + + + + + + From 534e8bb909a7533020256104b220e93842e8ad02 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 30 Jul 2020 13:49:57 +0200 Subject: [PATCH 224/826] ENABLE_GCODE_VIEWER -> Export to gcode layer z and layer height at each layer change --- src/libslic3r/GCode.cpp | 42 +++++++++++++++++--------- src/libslic3r/GCode.hpp | 12 +++----- src/libslic3r/GCode/GCodeProcessor.cpp | 2 +- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ced52207d0..2dea4fb22b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1180,6 +1180,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu #if ENABLE_GCODE_VIEWER m_last_width = 0.0f; m_last_height = 0.0f; + m_last_layer_z = 0.0f; #else m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; m_last_width = GCodeAnalyzer::Default_Width; @@ -2067,6 +2068,20 @@ void GCode::process_layer( std::string gcode; +#if ENABLE_GCODE_VIEWER + // export layer z + char buf[64]; + sprintf(buf, ";Z%g\n", print_z); + gcode += buf; + // export layer height + float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; + sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), height); + gcode += buf; + // update caches + m_last_layer_z = static_cast(print_z); + m_last_height = height; +#endif // ENABLE_GCODE_VIEWER + // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (! print.config().before_layer_gcode.value.empty()) { DynamicConfig config; @@ -3207,7 +3222,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } } - // adds analyzer tags and updates analyzer's tracking data + // adds processor tags and updates processor tracking data #if !ENABLE_GCODE_VIEWER if (m_enable_analyzer) { #endif // !ENABLE_GCODE_VIEWER @@ -3234,40 +3249,39 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role)); gcode += buf; } -#endif // ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); gcode += buf; } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER - if (last_was_wipe_tower || (m_last_width != path.width)) { + if (last_was_wipe_tower || m_last_width != path.width) { m_last_width = path.width; #if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); - gcode += buf; + sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); #else sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); - gcode += buf; #endif // ENABLE_GCODE_VIEWER + gcode += buf; } - if (last_was_wipe_tower || (m_last_height != path.height)) { - m_last_height = path.height; + #if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_last_height); + if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) { + m_last_height = path.height; + sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), m_last_height); gcode += buf; + } #else + if (last_was_wipe_tower || m_last_height != path.height) { + m_last_height = path.height; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height); gcode += buf; -#endif // ENABLE_GCODE_VIEWER } -#if !ENABLE_GCODE_VIEWER } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER std::string comment; if (m_enable_cooling_markers) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 443c25bb2f..69f98bfa52 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -172,14 +172,11 @@ public: m_volumetric_speed(0), m_last_pos_defined(false), m_last_extrusion_role(erNone), -#if ENABLE_GCODE_VIEWER - m_last_width(0.0f), - m_last_height(0.0f), -#else +#if !ENABLE_GCODE_VIEWER m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm), m_last_width(GCodeAnalyzer::Default_Width), m_last_height(GCodeAnalyzer::Default_Height), -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER m_brim_done(false), m_second_layer_things_done(false), m_normal_time_estimator(GCodeTimeEstimator::Normal), @@ -379,8 +376,9 @@ private: ExtrusionRole m_last_extrusion_role; #if ENABLE_GCODE_VIEWER // Support for G-Code Processor - float m_last_width; - float m_last_height; + float m_last_width{ 0.0f }; + float m_last_height{ 0.0f }; + float m_last_layer_z{ 0.0f }; #else // Support for G-Code Analyzer double m_last_mm3_per_mm; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7a6b1ecb42..c10552482d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -23,7 +23,7 @@ namespace Slic3r { const std::string GCodeProcessor::Extrusion_Role_Tag = "ExtrType:"; const std::string GCodeProcessor::Width_Tag = "PrusaSlicer__WIDTH:"; -const std::string GCodeProcessor::Height_Tag = "PrusaSlicer__HEIGHT:"; +const std::string GCodeProcessor::Height_Tag = "Height:"; const std::string GCodeProcessor::Color_Change_Tag = "COLOR_CHANGE"; const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE_PRINT"; const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM_CODE"; From 2dee3abea033e57fe79fd986799726b38ac80483 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 30 Jul 2020 14:15:00 +0200 Subject: [PATCH 225/826] Revert titles in legend dialog to previous format --- src/slic3r/GUI/ImGuiWrapper.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 00d69b4ccb..6aef521667 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -762,16 +762,10 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co void ImGuiWrapper::title(const std::string& str) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); - - ImRect frame_bb; - frame_bb.Min = { window->WorkRect.Min.x, window->DC.CursorPos.y }; - frame_bb.Max = { window->WorkRect.Max.x, window->DC.CursorPos.y + ImGui::CalcTextSize(str.c_str(), nullptr, false).y }; - frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); - frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); - - window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(COL_ORANGE_DARK), 0.0f, 0); + ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); text(str); + ImGui::PopStyleColor(); + ImGui::Separator(); } void ImGuiWrapper::disabled_begin(bool disabled) From 3cf2914a9e574691ef9fe24f4ba1a735305a2bb2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 Jul 2020 16:16:56 +0200 Subject: [PATCH 226/826] UnsavedChangesDialog: first implementation --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_App.hpp | 6 + src/slic3r/GUI/Search.cpp | 10 +- src/slic3r/GUI/Tab.cpp | 5 + src/slic3r/GUI/UnsavedChangesDialog.cpp | 271 ++++++++++++++++++++++++ src/slic3r/GUI/UnsavedChangesDialog.hpp | 151 +++++++++++++ 6 files changed, 437 insertions(+), 8 deletions(-) create mode 100644 src/slic3r/GUI/UnsavedChangesDialog.cpp create mode 100644 src/slic3r/GUI/UnsavedChangesDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 20ea4e33a9..cd28d6eb20 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -163,6 +163,8 @@ set(SLIC3R_GUI_SOURCES GUI/InstanceCheck.hpp GUI/Search.cpp GUI/Search.hpp + GUI/UnsavedChangesDialog.cpp + GUI/UnsavedChangesDialog.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index db551610b7..6fa9429157 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -86,6 +86,12 @@ class ConfigWizard; static wxString dots("…", wxConvUTF8); +// Does our wxWidgets version support markup? +// https://github.com/prusa3d/PrusaSlicer/issues/4282#issuecomment-634676371 +#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) + #define SUPPORTS_MARKUP +#endif + class GUI_App : public wxApp { bool m_initialized { false }; diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 2a2af5336b..6eab6b72ac 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -28,12 +28,6 @@ using GUI::into_u8; namespace Search { -// Does our wxWidgets version support markup? -// https://github.com/prusa3d/PrusaSlicer/issues/4282#issuecomment-634676371 -#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) - #define SEARCH_SUPPORTS_MARKUP -#endif - static char marker_by_type(Preset::Type type, PrinterTechnology pt) { switch(type) { @@ -264,7 +258,7 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) std::string label_u8 = into_u8(label); std::string label_plain = label_u8; -#ifdef SEARCH_SUPPORTS_MARKUP +#ifdef SUPPORTS_MARKUP boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)), ""); boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)), ""); #else @@ -442,7 +436,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer(); -#ifdef SEARCH_SUPPORTS_MARKUP +#ifdef SUPPORTS_MARKUP markupRenderer->EnableMarkup(); #endif diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9d37362ba4..49317f802f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -36,6 +36,7 @@ #include "MainFrame.hpp" #include "format.hpp" #include "PhysicalPrinterDialog.hpp" +#include "UnsavedChangesDialog.hpp" namespace Slic3r { namespace GUI { @@ -3131,6 +3132,10 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, // if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { + UnsavedChangesDialog dlg(m_type); + dlg.ShowModal(); + + if (presets == nullptr) presets = m_presets; // Display a dialog showing the dirty options in a human readable form. const Preset& old_preset = presets->get_edited_preset(); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp new file mode 100644 index 0000000000..21da295d40 --- /dev/null +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -0,0 +1,271 @@ +#include "UnsavedChangesDialog.hpp" + +#include +#include +#include +#include +#include + +#include "wx/dataview.h" + +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "Tab.hpp" + +#define FTS_FUZZY_MATCH_IMPLEMENTATION +#include "fts_fuzzy_match.h" + +#include "imgui/imconfig.h" + +using boost::optional; + +namespace Slic3r { + +namespace GUI { + +// ---------------------------------------------------------------------------- +// ModelNode: a node inside UnsavedChangesModel +// ---------------------------------------------------------------------------- + +// preset(root) node +ModelNode::ModelNode(const wxString& text, Preset::Type preset_type) : + m_parent(nullptr), + m_preset_type(preset_type), + m_text(text) +{ +} + +// group node +ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name) : + m_parent(parent), + m_text(text) +{ +} + +// group node +ModelNode::ModelNode(ModelNode* parent, const wxString& text, bool is_option) : + m_parent(parent), + m_text(text), + m_container(!is_option) +{ +} + + +// ---------------------------------------------------------------------------- +// UnsavedChangesModel +// ---------------------------------------------------------------------------- + +UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) +{ + int icon_id = 0; + for (const std::string& icon : { "cog", "printer", "sla_printer", "spool", "resin" }) + m_icon[icon_id++] = ScalableBitmap(parent, icon); + + m_root = new ModelNode("Preset", Preset::TYPE_INVALID); +} + +UnsavedChangesModel::~UnsavedChangesModel() +{ + delete m_root; +} + +void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const +{ + wxASSERT(item.IsOk()); + + ModelNode* node = (ModelNode*)item.GetID(); + switch (col) + { + case colToggle: + variant = node->m_toggle; + break; + case colTypeIcon: + variant << node->m_type_icon; + break; + case colGroupIcon: + variant << node->m_group_icon; + break; + case colMarkedText: + variant =node->m_text; + break; + case colOldValue: + variant =node->m_text; + break; + case colNewValue: + variant =node->m_text; + break; + + default: + wxLogError("UnsavedChangesModel::GetValue: wrong column %d", col); + } +} + +bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) +{ + assert(item.IsOk()); + + ModelNode* node = (ModelNode*)item.GetID(); + switch (col) + { + case colToggle: + node->m_toggle = variant.GetBool(); + return true; + case colTypeIcon: + node->m_type_icon << variant; + return true; + case colGroupIcon: + node->m_group_icon << variant; + return true; + case colMarkedText: + node->m_text = variant.GetString(); + return true; + case colOldValue: + node->m_text = variant.GetString(); + return true; + case colNewValue: + node->m_text = variant.GetString(); + return true; + default: + wxLogError("UnsavedChangesModel::SetValue: wrong column"); + } + return false; +} + +bool UnsavedChangesModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const +{ + assert(item.IsOk()); + + ModelNode* node = (ModelNode*)item.GetID(); + + // disable unchecked nodes + return !node->IsToggle(); +} + +wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(nullptr); + + ModelNode* node = (ModelNode*)item.GetID(); + + // "MyMusic" also has no parent + if (node == m_root) + return wxDataViewItem(nullptr); + + return wxDataViewItem((void*)node->GetParent()); +} + +bool UnsavedChangesModel::IsContainer(const wxDataViewItem& item) const +{ + // the invisble root node can have children + if (!item.IsOk()) + return true; + + ModelNode* node = (ModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int UnsavedChangesModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const +{ + ModelNode* node = (ModelNode*)parent.GetID(); + if (!node) { + array.Add(wxDataViewItem((void*)m_root)); + return 1; + } + + if (node->GetChildCount() == 0) + return 0; + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) { + ModelNode* child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + + +wxString UnsavedChangesModel::GetColumnType(unsigned int col) const +{ + if (col == colToggle) + return "bool"; + + if (col < colMarkedText) + return "wxBitmap"; + + return "string"; +} + + +//------------------------------------------ +// UnsavedChangesDialog +//------------------------------------------ + +UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) + : DPIDialog(NULL, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + SetBackgroundColour(bgr_clr); + + int border = 10; + int em = em_unit(); + + changes_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 60), wxBORDER_SIMPLE); + changes_tree_model = new UnsavedChangesModel(this); + changes_tree->AssociateModel(changes_tree_model); + + changes_tree->AppendToggleColumn(L"\u2610", UnsavedChangesModel::colToggle);//2610,11,12 //2714 + changes_tree->AppendBitmapColumn("", UnsavedChangesModel::colTypeIcon); + changes_tree->AppendBitmapColumn("", UnsavedChangesModel::colGroupIcon); + + wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer(); + +#ifdef SUPPORTS_MARKUP + markupRenderer->EnableMarkup(); +#endif + + changes_tree->AppendColumn(new wxDataViewColumn("", markupRenderer, UnsavedChangesModel::colMarkedText, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); + changes_tree->AppendColumn(new wxDataViewColumn("Old value", markupRenderer, UnsavedChangesModel::colOldValue, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); + changes_tree->AppendColumn(new wxDataViewColumn("New value", markupRenderer, UnsavedChangesModel::colNewValue, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); + + wxStdDialogButtonSizer* cancel_btn = this->CreateStdDialogButtonSizer(wxCANCEL); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There is unsaved changes for the current preset") + ":"), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(changes_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(cancel_btn, 0, wxEXPAND | wxALL, border); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + +void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_CANCEL }); + + const wxSize& size = wxSize(80 * em, 60 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + +void UnsavedChangesDialog::on_sys_color_changed() +{ + // msw_rescale updates just icons, so use it +// changes_tree_model->msw_rescale(); + + Refresh(); +} + + +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp new file mode 100644 index 0000000000..a3ee7d984f --- /dev/null +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -0,0 +1,151 @@ +#ifndef slic3r_UnsavedChangesDialog_hpp_ +#define slic3r_UnsavedChangesDialog_hpp_ + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include "GUI_Utils.hpp" +#include "wxExtensions.hpp" +#include "libslic3r/Preset.hpp" + +namespace Slic3r { + +namespace GUI{ + +// ---------------------------------------------------------------------------- +// ModelNode: a node inside UnsavedChangesModel +// ---------------------------------------------------------------------------- + +class ModelNode; +WX_DEFINE_ARRAY_PTR(ModelNode*, ModelNodePtrArray); + +class ModelNode +{ + ModelNode* m_parent; + ModelNodePtrArray m_children; + wxBitmap m_empty_bmp; + Preset::Type m_preset_type {Preset::TYPE_INVALID}; + + // TODO/FIXME: + // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) + // needs to know in advance if a node is or _will be_ a container. + // Thus implementing: + // bool IsContainer() const + // { return m_children.GetCount()>0; } + // doesn't work with wxGTK when UnsavedChangesModel::AddToClassical is called + // AND the classical node was removed (a new node temporary without children + // would be added to the control) + bool m_container {true}; + +public: + + bool m_toggle {true}; + wxBitmap m_type_icon; + wxBitmap m_group_icon; + wxString m_text; + wxString m_old_value; + wxString m_new_value; + + // preset(root) node + ModelNode(const wxString& text, Preset::Type preset_type); + + // group node + ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name); + + // group node + ModelNode(ModelNode* parent, const wxString& text, bool is_option); + + ~ModelNode() { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) { + ModelNode* child = m_children[i]; + delete child; + } + } + + bool IsContainer() const { return m_container; } + bool IsToggle() const { return m_toggle; } + + ModelNode* GetParent() { return m_parent; } + ModelNodePtrArray& GetChildren() { return m_children; } + ModelNode* GetNthChild(unsigned int n) { return m_children.Item(n); } + unsigned int GetChildCount() const { return m_children.GetCount(); } + + void Insert(ModelNode* child, unsigned int n) { m_children.Insert(child, n); } + void Append(ModelNode* child) { m_children.Add(child); } +}; + + +// ---------------------------------------------------------------------------- +// UnsavedChangesModel +// ---------------------------------------------------------------------------- + +class UnsavedChangesModel : public wxDataViewModel +{ + ModelNode* m_root; + ScalableBitmap m_icon[5]; + +public: + enum { + colToggle, + colTypeIcon, + colGroupIcon, + colMarkedText, + colOldValue, + colNewValue, + colMax + }; + + UnsavedChangesModel(wxWindow* parent); + ~UnsavedChangesModel(); + + virtual unsigned int GetColumnCount() const override { return colMax; } + virtual wxString GetColumnType(unsigned int col) const override; + + virtual wxDataViewItem GetParent(const wxDataViewItem& item) const override; + virtual unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + + virtual void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; + virtual bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + + virtual bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; + virtual bool IsContainer(const wxDataViewItem& item) const override; + +}; + + +//------------------------------------------ +// UnsavedChangesDialog +//------------------------------------------ +class UnsavedChangesDialog : public DPIDialog +{ + wxDataViewCtrl* changes_tree{ nullptr }; + UnsavedChangesModel* changes_tree_model{ nullptr }; + +public: + UnsavedChangesDialog(Preset::Type type); + ~UnsavedChangesDialog() {} + + void ProcessSelection(wxDataViewItem selection); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override; +}; + + +} +} + +#endif //slic3r_UnsavedChangesDialog_hpp_ From a29b00a0b46b390a1ae195534e094faab49824d2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 Aug 2020 08:28:43 +0200 Subject: [PATCH 227/826] Use ImGui::TextColored() --- src/slic3r/GUI/GLCanvas3D.cpp | 73 ++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 +- src/slic3r/GUI/ImGuiWrapper.cpp | 16 +++++ src/slic3r/GUI/ImGuiWrapper.hpp | 3 + src/slic3r/GUI/Mouse3DController.cpp | 63 ++++++----------- 5 files changed, 70 insertions(+), 89 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2d45277849..5109d24264 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -225,56 +225,44 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); const Size& cnv_size = canvas.get_canvas_size(); - float canvas_w = (float)cnv_size.get_width(); - float canvas_h = (float)cnv_size.get_height(); ImGuiWrapper& imgui = *wxGetApp().imgui(); - imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f); + imgui.set_next_window_pos(static_cast(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, + static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - imgui.begin(_(L("Variable layer height")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); + imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Left mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Left mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Add detail"))); + imgui.text(_L("Add detail")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Right mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Right mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Remove detail"))); + imgui.text(_L("Remove detail")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Shift + Left mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Shift + Left mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Reset to base"))); + imgui.text(_L("Reset to base")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Shift + Right mouse button:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Shift + Right mouse button:")); ImGui::SameLine(); - imgui.text(_(L("Smoothing"))); + imgui.text(_L("Smoothing")); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - imgui.text(_(L("Mouse wheel:"))); - ImGui::PopStyleColor(); + imgui.text_colored(ORANGE, _L("Mouse wheel:")); ImGui::SameLine(); - imgui.text(_(L("Increase/decrease edit area"))); + imgui.text(_L("Increase/decrease edit area")); ImGui::Separator(); - if (imgui.button(_(L("Adaptive")))) + if (imgui.button(_L("Adaptive"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality)); ImGui::SameLine(); float text_align = ImGui::GetCursorPosX(); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Quality / Speed"))); - if (ImGui::IsItemHovered()) - { + imgui.text(_L("Quality / Speed")); + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); - ImGui::TextUnformatted(_(L("Higher print quality versus higher print speed.")).ToUTF8()); + ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8()); ImGui::EndTooltip(); } @@ -285,13 +273,13 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const ImGui::SliderFloat("", &m_adaptive_quality, 0.0f, 1.f, "%.2f"); ImGui::Separator(); - if (imgui.button(_(L("Smooth")))) + if (imgui.button(_L("Smooth"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params)); ImGui::SameLine(); ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Radius"))); + imgui.text(_L("Radius")); ImGui::SameLine(); ImGui::SetCursorPosX(widget_align); ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f); @@ -301,7 +289,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const ImGui::SetCursorPosX(text_align); ImGui::AlignTextToFramePadding(); - imgui.text(_(L("Keep min"))); + imgui.text(_L("Keep min")); ImGui::SameLine(); if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization ImGui::SetCursorPosX(widget_align); @@ -310,7 +298,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const imgui.checkbox("##2", m_smooth_params.keep_min); ImGui::Separator(); - if (imgui.button(_(L("Reset")))) + if (imgui.button(_L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); imgui.end(); @@ -1430,8 +1418,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas #if ENABLE_SLOPE_RENDERING void GLCanvas3D::Slope::render() const { - if (m_dialog_shown) - { + if (m_dialog_shown) { const std::array& z_range = m_volumes.get_slope_z_range(); std::array angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f }; bool modified = false; @@ -1439,9 +1426,9 @@ void GLCanvas3D::Slope::render() const ImGuiWrapper& imgui = *wxGetApp().imgui(); const Size& cnv_size = m_canvas.get_canvas_size(); imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f); - imgui.begin(_(L("Slope visualization")), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.begin(_L("Slope visualization"), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text(_(L("Facets' slope range (degrees)")) + ":"); + imgui.text(_L("Facets' slope range (degrees)") + ":"); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f)); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f)); @@ -1453,8 +1440,7 @@ void GLCanvas3D::Slope::render() const float slope_bound = 90.f - angle_range[1]; bool mod = ImGui::SliderFloat("##red", &slope_bound, 0.0f, 90.0f, "%.1f"); angle_range[1] = 90.f - slope_bound; - if (mod) - { + if (mod) { modified = true; if (angle_range[0] > angle_range[1]) angle_range[0] = angle_range[1]; @@ -1462,15 +1448,14 @@ void GLCanvas3D::Slope::render() const ImGui::PopStyleColor(4); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f)); slope_bound = 90.f - angle_range[0]; mod = ImGui::SliderFloat("##yellow", &slope_bound, 0.0f, 90.0f, "%.1f"); angle_range[0] = 90.f - slope_bound; - if (mod) - { + if (mod) { modified = true; if (angle_range[1] < angle_range[0]) angle_range[1] = angle_range[0]; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 3769e96605..4bbd52c307 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -513,9 +513,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ORANGE); - m_imgui->text(caption); - ImGui::PopStyleColor(); + m_imgui->text_colored(ORANGE, caption); ImGui::SameLine(caption_max); m_imgui->text(text); }; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 88dd02ccb7..a21194d94e 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -354,6 +354,22 @@ void ImGuiWrapper::text(const wxString &label) this->text(label_utf8.c_str()); } +void ImGuiWrapper::text_colored(const ImVec4& color, const char* label) +{ + ImGui::TextColored(color, label); +} + +void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label) +{ + this->text_colored(color, label.c_str()); +} + +void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label) +{ + auto label_utf8 = into_u8(label); + this->text_colored(color, label_utf8.c_str()); +} + bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/) { return ImGui::SliderFloat(label, v, v_min, v_max, format, power); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index bf542e1381..dc62e57a08 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -73,6 +73,9 @@ public: void text(const char *label); void text(const std::string &label); void text(const wxString &label); + void text_colored(const ImVec4& color, const char* label); + void text_colored(const ImVec4& color, const std::string& label); + void text_colored(const ImVec4& color, const wxString& label); bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index baa9356b69..ec7cd8d457 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -239,8 +239,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const // when the user clicks on [X] or [Close] button we need to trigger // an extra frame to let the dialog disappear - if (m_settings_dialog_closed_by_user) - { + if (m_settings_dialog_closed_by_user) { m_show_settings_dialog = false; m_settings_dialog_closed_by_user = false; canvas.request_extra_frame(); @@ -261,13 +260,10 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const static ImVec2 last_win_size(0.0f, 0.0f); bool shown = true; - if (imgui.begin(_(L("3Dconnexion settings")), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) - { - if (shown) - { + if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) { + if (shown) { ImVec2 win_size = ImGui::GetWindowSize(); - if ((last_win_size.x != win_size.x) || (last_win_size.y != win_size.y)) - { + if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) { // when the user clicks on [X] button, the next time the dialog is shown // has a dummy size, so we trigger an extra frame to let it have the correct size last_win_size = win_size; @@ -275,59 +271,51 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Device:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Device:")); ImGui::SameLine(); imgui.text(m_device_str); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Speed:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Speed:")); float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale; - if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) { + if (imgui.slider_float(_L("Translation") + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) { params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale; params_changed = true; } float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale; - if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) { + if (imgui.slider_float(_L("Rotation") + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) { params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale; params_changed = true; } float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale; - if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) { + if (imgui.slider_float(_L("Zoom"), &zoom_scale, 0.1f, 10.0f, "%.1f")) { params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale; params_changed = true; } ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Deadzone:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Deadzone:")); float translation_deadzone = (float)params_copy.translation.deadzone; - if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) { + if (imgui.slider_float(_L("Translation") + "/" + _L("Zoom"), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) { params_copy.translation.deadzone = (double)translation_deadzone; params_changed = true; } float rotation_deadzone = params_copy.rotation.deadzone; - if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) { + if (imgui.slider_float(_L("Rotation") + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) { params_copy.rotation.deadzone = rotation_deadzone; params_changed = true; } ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text(_(L("Options:"))); - ImGui::PopStyleColor(); + imgui.text_colored(color, _L("Options:")); bool swap_yz = params_copy.swap_yz; - if (imgui.checkbox("Swap Y/Z axes", swap_yz)) { + if (imgui.checkbox(_L("Swap Y/Z axes"), swap_yz)) { params_copy.swap_yz = swap_yz; params_changed = true; } @@ -335,25 +323,20 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text("DEBUG:"); - imgui.text("Vectors:"); - ImGui::PopStyleColor(); + imgui.text_colored(color, "DEBUG:"); + imgui.text_colored(color, "Vectors:"); Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast(); Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast(); ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text("Queue size:"); - ImGui::PopStyleColor(); + imgui.text_colored(color, "Queue size:"); int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) }; ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly); int input_queue_size_param = int(params_copy.input_queue_max_size); - if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) - { + if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) { if (input_queue_size_param > 0) { params_copy.input_queue_max_size = input_queue_size_param; params_changed = true; @@ -361,23 +344,19 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, color); - imgui.text("Camera:"); - ImGui::PopStyleColor(); + imgui.text_colored(color, "Camera:"); Vec3f target = wxGetApp().plater()->get_camera().get_target().cast(); ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); - if (imgui.button(_(L("Close")))) - { + if (imgui.button(_L("Close"))) { // the user clicked on the [Close] button m_settings_dialog_closed_by_user = true; canvas.set_as_dirty(); } } - else - { + else { // the user clicked on the [X] button m_settings_dialog_closed_by_user = true; canvas.set_as_dirty(); From 1532920d81411f07ecb80edf6dbb6396fce73fab Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 Aug 2020 08:46:32 +0200 Subject: [PATCH 228/826] GCodeProcessor -> Extended import of config data from gcode saved by PrusaSlicer --- src/libslic3r/GCode/GCodeProcessor.cpp | 207 ++++++++++++++++++++++--- src/libslic3r/GCode/GCodeProcessor.hpp | 8 +- src/slic3r/GUI/GCodeViewer.cpp | 17 +- src/slic3r/GUI/KBShortcutsDialog.cpp | 6 + 4 files changed, 205 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index c10552482d..f3e07da881 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -24,9 +24,9 @@ namespace Slic3r { const std::string GCodeProcessor::Extrusion_Role_Tag = "ExtrType:"; const std::string GCodeProcessor::Width_Tag = "PrusaSlicer__WIDTH:"; const std::string GCodeProcessor::Height_Tag = "Height:"; -const std::string GCodeProcessor::Color_Change_Tag = "COLOR_CHANGE"; -const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE_PRINT"; -const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM_CODE"; +const std::string GCodeProcessor::Color_Change_Tag = "Color change"; +const std::string GCodeProcessor::Pause_Print_Tag = "Pause print"; +const std::string GCodeProcessor::Custom_Code_Tag = "Custom gcode"; static bool is_valid_extrusion_role(int value) { @@ -297,7 +297,7 @@ void GCodeProcessor::TimeProcessor::reset() const std::vector> GCodeProcessor::Producers = { { EProducer::PrusaSlicer, "PrusaSlicer" }, - { EProducer::Cura, "Cura" }, + { EProducer::Cura, "Cura_SteamEngine" }, { EProducer::Simplify3D, "Simplify3D" }, { EProducer::CraftWare, "CraftWare" }, { EProducer::ideaMaker, "ideaMaker" } @@ -314,32 +314,34 @@ void GCodeProcessor::apply_config(const PrintConfig& config) size_t extruders_count = config.nozzle_diameter.values.size(); m_extruder_offsets.resize(extruders_count); - for (size_t id = 0; id < extruders_count; ++id) { - Vec2f offset = config.extruder_offset.get_at(id).cast(); - m_extruder_offsets[id] = Vec3f(offset(0), offset(1), 0.0f); + for (size_t i = 0; i < extruders_count; ++i) { + Vec2f offset = config.extruder_offset.get_at(i).cast(); + m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; } - m_extruders_color.resize(extruders_count); - for (size_t id = 0; id < extruders_count; ++id) { - m_extruders_color[id] = static_cast(id); + m_extruder_colors.resize(extruders_count); + for (size_t i = 0; i < extruders_count; ++i) { + m_extruder_colors[i] = static_cast(i); } - for (double diam : config.filament_diameter.values) { - m_filament_diameters.push_back(static_cast(diam)); + m_filament_diameters.resize(config.filament_diameter.values.size()); + for (size_t i = 0; i < config.filament_diameter.values.size(); ++i) { + m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } m_time_processor.machine_limits = reinterpret_cast(config); // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they // are considered to be active for the single extruder multi-material printers only. - m_time_processor.filament_load_times.clear(); - for (double d : config.filament_load_time.values) { - m_time_processor.filament_load_times.push_back(static_cast(d)); + m_time_processor.filament_load_times.resize(config.filament_load_time.values.size()); + for (size_t i = 0; i < config.filament_load_time.values.size(); ++i) { + m_time_processor.filament_load_times[i] = static_cast(config.filament_load_time.values[i]); } - m_time_processor.filament_unload_times.clear(); - for (double d : config.filament_unload_time.values) { - m_time_processor.filament_unload_times.push_back(static_cast(d)); + m_time_processor.filament_unload_times.resize(config.filament_unload_time.values.size()); + for (size_t i = 0; i < config.filament_unload_time.values.size(); ++i) { + m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); } + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -350,6 +352,14 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) { m_parser.apply_config(config); + const ConfigOptionEnum* gcode_flavor = config.option>("gcode_flavor"); + if (gcode_flavor != nullptr) + m_flavor = gcode_flavor->value; + + const ConfigOptionPoints* bed_shape = config.option("bed_shape"); + if (bed_shape != nullptr) + m_result.bed_shape = bed_shape->values; + const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); if (filament_diameters != nullptr) { for (double diam : filament_diameters->values) { @@ -357,9 +367,127 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } - const ConfigOptionPoints* bed_shape = config.option("bed_shape"); - if (bed_shape != nullptr) - m_result.bed_shape = bed_shape->values; + const ConfigOptionPoints* extruder_offset = config.option("extruder_offset"); + if (extruder_offset != nullptr) { + m_extruder_offsets.resize(extruder_offset->values.size()); + for (size_t i = 0; i < extruder_offset->values.size(); ++i) { + Vec2f offset = extruder_offset->values[i].cast(); + m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; + } + } + + // ensure at least one (default) color is defined + std::string default_color = "#FF8000"; + m_result.extruder_colors = std::vector(1, default_color); + const ConfigOptionStrings* extruder_colour = config.option("extruder_colour"); + if (extruder_colour != nullptr) { + // takes colors from config + m_result.extruder_colors = extruder_colour->values; + // try to replace missing values with filament colors + const ConfigOptionStrings* filament_colour = config.option("filament_colour"); + if (filament_colour != nullptr && filament_colour->values.size() == m_result.extruder_colors.size()) { + for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { + if (m_result.extruder_colors[i].empty()) + m_result.extruder_colors[i] = filament_colour->values[i]; + } + } + } + + // replace missing values with default + for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { + if (m_result.extruder_colors[i].empty()) + m_result.extruder_colors[i] = default_color; + } + + m_extruder_colors.resize(m_result.extruder_colors.size()); + for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { + m_extruder_colors[i] = static_cast(i); + } + + const ConfigOptionFloats* filament_load_time = config.option("filament_load_time"); + if (filament_load_time != nullptr) { + m_time_processor.filament_load_times.resize(filament_load_time->values.size()); + for (size_t i = 0; i < filament_load_time->values.size(); ++i) { + m_time_processor.filament_load_times[i] = static_cast(filament_load_time->values[i]); + } + } + + const ConfigOptionFloats* filament_unload_time = config.option("filament_unload_time"); + if (filament_unload_time != nullptr) { + m_time_processor.filament_unload_times.resize(filament_unload_time->values.size()); + for (size_t i = 0; i < filament_unload_time->values.size(); ++i) { + m_time_processor.filament_unload_times[i] = static_cast(filament_unload_time->values[i]); + } + } + + const ConfigOptionFloats* machine_max_acceleration_x = config.option("machine_max_acceleration_x"); + if (machine_max_acceleration_x != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values; + + const ConfigOptionFloats* machine_max_acceleration_y = config.option("machine_max_acceleration_y"); + if (machine_max_acceleration_y != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_y.values = machine_max_acceleration_y->values; + + const ConfigOptionFloats* machine_max_acceleration_z = config.option("machine_max_acceleration_z"); + if (machine_max_acceleration_z != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_z.values = machine_max_acceleration_z->values; + + const ConfigOptionFloats* machine_max_acceleration_e = config.option("machine_max_acceleration_e"); + if (machine_max_acceleration_e != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_e.values = machine_max_acceleration_e->values; + + const ConfigOptionFloats* machine_max_feedrate_x = config.option("machine_max_feedrate_x"); + if (machine_max_feedrate_x != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_x.values = machine_max_feedrate_x->values; + + const ConfigOptionFloats* machine_max_feedrate_y = config.option("machine_max_feedrate_y"); + if (machine_max_feedrate_y != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_y.values = machine_max_feedrate_y->values; + + const ConfigOptionFloats* machine_max_feedrate_z = config.option("machine_max_feedrate_z"); + if (machine_max_feedrate_z != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_z.values = machine_max_feedrate_z->values; + + const ConfigOptionFloats* machine_max_feedrate_e = config.option("machine_max_feedrate_e"); + if (machine_max_feedrate_e != nullptr) + m_time_processor.machine_limits.machine_max_feedrate_e.values = machine_max_feedrate_e->values; + + const ConfigOptionFloats* machine_max_jerk_x = config.option("machine_max_jerk_x"); + if (machine_max_jerk_x != nullptr) + m_time_processor.machine_limits.machine_max_jerk_x.values = machine_max_jerk_x->values; + + const ConfigOptionFloats* machine_max_jerk_y = config.option("machine_max_jerk_y"); + if (machine_max_jerk_y != nullptr) + m_time_processor.machine_limits.machine_max_jerk_y.values = machine_max_jerk_y->values; + + const ConfigOptionFloats* machine_max_jerk_z = config.option("machine_max_jerkz"); + if (machine_max_jerk_z != nullptr) + m_time_processor.machine_limits.machine_max_jerk_z.values = machine_max_jerk_z->values; + + const ConfigOptionFloats* machine_max_jerk_e = config.option("machine_max_jerk_e"); + if (machine_max_jerk_e != nullptr) + m_time_processor.machine_limits.machine_max_jerk_e.values = machine_max_jerk_e->values; + + const ConfigOptionFloats* machine_max_acceleration_extruding = config.option("machine_max_acceleration_extruding"); + if (machine_max_acceleration_extruding != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_extruding.values = machine_max_acceleration_extruding->values; + + const ConfigOptionFloats* machine_max_acceleration_retracting = config.option("machine_max_acceleration_retracting"); + if (machine_max_acceleration_retracting != nullptr) + m_time_processor.machine_limits.machine_max_acceleration_retracting.values = machine_max_acceleration_retracting->values; + + const ConfigOptionFloats* machine_min_extruding_rate = config.option("machine_min_extruding_rate"); + if (machine_min_extruding_rate != nullptr) + m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values; + + const ConfigOptionFloats* machine_min_travel_rate = config.option("machine_min_travel_rate"); + if (machine_min_travel_rate != nullptr) + m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; + + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); + m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; + } } void GCodeProcessor::enable_stealth_time_estimator(bool enabled) @@ -388,7 +516,7 @@ void GCodeProcessor::reset() m_extrusion_role = erNone; m_extruder_id = 0; - m_extruders_color = ExtrudersColor(); + m_extruder_colors = ExtruderColors(); m_filament_diameters = std::vector(); m_cp_color.reset(); @@ -643,13 +771,13 @@ void GCodeProcessor::process_tags(const std::string& comment) { unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1))); - m_extruders_color[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview ++m_cp_color.counter; if (m_cp_color.counter == UCHAR_MAX) m_cp_color.counter = 0; if (m_extruder_id == extruder_id) { - m_cp_color.current = m_extruders_color[extruder_id]; + m_cp_color.current = m_extruder_colors[extruder_id]; store_move_vertex(EMoveType::Color_change); } @@ -728,6 +856,35 @@ bool GCodeProcessor::process_cura_tags(const std::string& comment) return true; } + // flavor + tag = "FLAVOR:"; + pos = comment.find(tag); + if (pos != comment.npos) { + std::string flavor = comment.substr(pos + tag.length()); + if (flavor == "BFB") + m_flavor = gcfMarlin; // << ??????????????????????? + else if (flavor == "Mach3") + m_flavor = gcfMach3; + else if (flavor == "Makerbot") + m_flavor = gcfMakerWare; + else if (flavor == "UltiGCode") + m_flavor = gcfMarlin; // << ??????????????????????? + else if (flavor == "Marlin(Volumetric)") + m_flavor = gcfMarlin; // << ??????????????????????? + else if (flavor == "Griffin") + m_flavor = gcfMarlin; // << ??????????????????????? + else if (flavor == "Repetier") + m_flavor = gcfRepetier; + else if (flavor == "RepRap") + m_flavor = gcfRepRap; + else if (flavor == "Marlin") + m_flavor = gcfMarlin; + else + BOOST_LOG_TRIVIAL(warning) << "GCodeProcessor found unknown flavor: " << flavor; + + return true; + } + return false; } @@ -1582,7 +1739,7 @@ void GCodeProcessor::process_T(const std::string& command) else { unsigned char old_extruder_id = m_extruder_id; m_extruder_id = id; - m_cp_color.current = m_extruders_color[id]; + m_cp_color.current = m_extruder_colors[id]; // Specific to the MK3 MMU2: // The initial value of extruder_unloaded is set to true indicating // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet. diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 1def93e74b..bad04100ef 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -10,6 +10,7 @@ #include #include +#include namespace Slic3r { @@ -27,7 +28,7 @@ namespace Slic3r { private: using AxisCoords = std::array; - using ExtrudersColor = std::vector; + using ExtruderColors = std::vector; enum class EUnits : unsigned char { @@ -212,6 +213,7 @@ namespace Slic3r { std::vector moves; #if ENABLE_GCODE_VIEWER_AS_STATE Pointfs bed_shape; + std::vector extruder_colors; #endif // ENABLE_GCODE_VIEWER_AS_STATE #if ENABLE_GCODE_VIEWER_STATISTICS long long time{ 0 }; @@ -221,6 +223,7 @@ namespace Slic3r { moves = std::vector(); #if ENABLE_GCODE_VIEWER_AS_STATE bed_shape = Pointfs(); + extruder_colors = std::vector(); #endif // ENABLE_GCODE_VIEWER_AS_STATE } #else @@ -229,6 +232,7 @@ namespace Slic3r { moves = std::vector(); #if ENABLE_GCODE_VIEWER_AS_STATE bed_shape = Pointfs(); + extruder_colors = std::vector(); #endif // ENABLE_GCODE_VIEWER_AS_STATE } #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -255,7 +259,7 @@ namespace Slic3r { float m_fan_speed; // percentage ExtrusionRole m_extrusion_role; unsigned char m_extruder_id; - ExtrudersColor m_extruders_color; + ExtruderColors m_extruder_colors; std::vector m_filament_diameters; CpColor m_cp_color; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index af3d8d901c..8d4fb6f4cb 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -43,13 +43,13 @@ static GCodeProcessor::EMoveType buffer_type(unsigned char id) { std::array decode_color(const std::string& color) { static const float INV_255 = 1.0f / 255.0f; - std::array ret; + std::array ret = { 0.0f, 0.0f, 0.0f }; const char* c = color.data() + 1; - if ((color.size() == 7) && (color.front() == '#')) { + if (color.size() == 7 && color.front() == '#') { for (size_t j = 0; j < 3; ++j) { int digit1 = hex_digit_to_int(*c++); int digit2 = hex_digit_to_int(*c++); - if ((digit1 == -1) || (digit2 == -1)) + if (digit1 == -1 || digit2 == -1) break; ret[j] = float(digit1 * 16 + digit2) * INV_255; @@ -351,8 +351,14 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: if (m_vertices_count == 0) return; - // update tool colors - m_tool_colors = decode_colors(str_tool_colors); +#if ENABLE_GCODE_VIEWER_AS_STATE + if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) + // update tool colors from config stored in the gcode + m_tool_colors = decode_colors(gcode_result.extruder_colors); + else +#endif // ENABLE_GCODE_VIEWER_AS_STATE + // update tool colors + m_tool_colors = decode_colors(str_tool_colors); // update ranges for coloring / legend m_extrusions.reset_ranges(); @@ -1708,7 +1714,6 @@ void GCodeViewer::render_time_estimate() const } #endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG - using Times = std::pair; using TimesList = std::vector>; using Headers = std::vector; diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 66e5ac4878..fc6bc98914 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -204,7 +204,13 @@ void KBShortcutsDialog::fill_shortcuts() { "U", L("Upper Layer") }, { "D", L("Lower Layer") }, { "L", L("Show/Hide Legend") }, +#if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG + { "T", L("Show Estimated printing time") } +#else { "T", L("Show/Hide Estimated printing time") } +#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // ENABLE_GCODE_VIEWER }; m_full_shortcuts.push_back(std::make_pair(_L("Preview"), preview_shortcuts)); From 757572b760d4062f780bc9b2568f5f0a8c410763 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 Aug 2020 11:08:17 +0200 Subject: [PATCH 229/826] Tech ENABLE_LAYOUT_NO_RESTART set as default --- src/libslic3r/Technologies.hpp | 3 - src/slic3r/GUI/GUI_App.cpp | 13 --- src/slic3r/GUI/GUI_Utils.hpp | 22 +---- src/slic3r/GUI/MainFrame.cpp | 164 +-------------------------------- src/slic3r/GUI/MainFrame.hpp | 18 ---- src/slic3r/GUI/Preferences.cpp | 24 ----- 6 files changed, 5 insertions(+), 239 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c6991c057b..e4b71697d8 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -54,8 +54,5 @@ // Enable built-in DPI changed event handler of wxWidgets 3.1.3 #define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1) -// Enable changing application layout without the need to restart -#define ENABLE_LAYOUT_NO_RESTART (1 && ENABLE_2_3_0_ALPHA1) - #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a7b562bd75..bfb1586195 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1061,34 +1061,21 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuPreferences: { -#if ENABLE_LAYOUT_NO_RESTART bool app_layout_changed = false; -#else - bool recreate_app = false; -#endif // ENABLE_LAYOUT_NO_RESTART { // the dialog needs to be destroyed before the call to recreate_GUI() // or sometimes the application crashes into wxDialogBase() destructor // so we put it into an inner scope PreferencesDialog dlg(mainframe); dlg.ShowModal(); -#if ENABLE_LAYOUT_NO_RESTART app_layout_changed = dlg.settings_layout_changed(); -#else - recreate_app = dlg.settings_layout_changed(); -#endif // ENABLE_LAYOUT_NO_RESTART } -#if ENABLE_LAYOUT_NO_RESTART if (app_layout_changed) { mainframe->GetSizer()->Hide((size_t)0); mainframe->update_layout(); mainframe->select_tab(0); mainframe->GetSizer()->Show((size_t)0); } -#else - if (recreate_app) - recreate_GUI(_L("Changing of the settings layout") + dots); -#endif // ENABLE_LAYOUT_NO_RESTART break; } case ConfigMenuLanguage: diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 2737b3edbf..6ce3f62a67 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -110,13 +110,8 @@ public: if (!m_can_rescale) return; -#if ENABLE_LAYOUT_NO_RESTART if (m_force_rescale || is_new_scale_factor()) rescale(wxRect()); -#else - if (is_new_scale_factor()) - rescale(wxRect()); -#endif // ENABLE_LAYOUT_NO_RESTART }); #else this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { @@ -127,13 +122,8 @@ public: if (!m_can_rescale) return; -#if ENABLE_LAYOUT_NO_RESTART if (m_force_rescale || is_new_scale_factor()) rescale(evt.rect); -#else - if (is_new_scale_factor()) - rescale(evt.rect); -#endif // ENABLE_LAYOUT_NO_RESTART }); #endif // wxVERSION_EQUAL_OR_GREATER_THAN @@ -175,9 +165,7 @@ public: int em_unit() const { return m_em_unit; } // int font_size() const { return m_font_size; } const wxFont& normal_font() const { return m_normal_font; } -#if ENABLE_LAYOUT_NO_RESTART void enable_force_rescale() { m_force_rescale = true; } -#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect) = 0; @@ -191,9 +179,7 @@ private: wxFont m_normal_font; float m_prev_scale_factor; bool m_can_rescale{ true }; -#if ENABLE_LAYOUT_NO_RESTART bool m_force_rescale{ false }; -#endif // ENABLE_LAYOUT_NO_RESTART int m_new_font_point_size; @@ -233,17 +219,17 @@ private: { this->Freeze(); -#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) if (m_force_rescale) { -#endif // ENABLE_LAYOUT_NO_RESTART +#endif // wxVERSION_EQUAL_OR_GREATER_THAN // rescale fonts of all controls scale_controls_fonts(this, m_new_font_point_size); // rescale current window font scale_win_font(this, m_new_font_point_size); -#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) m_force_rescale = false; } -#endif // ENABLE_LAYOUT_NO_RESTART +#endif // wxVERSION_EQUAL_OR_GREATER_THAN // set normal application font as a current window font m_normal_font = this->GetFont(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 521dcab804..628aee2aa6 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -42,7 +42,6 @@ namespace Slic3r { namespace GUI { -#if ENABLE_LAYOUT_NO_RESTART enum class ERescaleTarget { Mainframe, @@ -71,15 +70,12 @@ static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog } } } -#endif // ENABLE_LAYOUT_NO_RESTART MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) , m_recent_projects(9) -#if ENABLE_LAYOUT_NO_RESTART , m_settings_dialog(this) -#endif // ENABLE_LAYOUT_NO_RESTART { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); @@ -124,43 +120,15 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_loaded = true; -#if !ENABLE_LAYOUT_NO_RESTART -#ifdef __APPLE__ - // Using SetMinSize() on Mac messes up the window position in some cases - // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 - // So, if we haven't possibility to set MinSize() for the MainFrame, - // set the MinSize() as a half of regular for the m_plater and m_tabpanel, when settings layout is in slNew mode - // Otherwise, MainFrame will be maximized by height - if (slNew) { - wxSize size = wxGetApp().get_min_size(); - size.SetHeight(int(0.5*size.GetHeight())); - m_plater->SetMinSize(size); - m_tabpanel->SetMinSize(size); - } -#endif -#endif // !ENABLE_LAYOUT_NO_RESTART - // initialize layout m_main_sizer = new wxBoxSizer(wxVERTICAL); wxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_main_sizer, 1, wxEXPAND); -#if ENABLE_LAYOUT_NO_RESTART SetSizer(sizer); // initialize layout from config update_layout(); sizer->SetSizeHints(this); Fit(); -#else - if (m_plater && m_layout != slOld) - sizer->Add(m_plater, 1, wxEXPAND); - - if (m_tabpanel && m_layout != slDlg) - sizer->Add(m_tabpanel, 1, wxEXPAND); - - sizer->SetSizeHints(this); - SetSizer(sizer); - Fit(); -#endif // !ENABLE_LAYOUT_NO_RESTART const wxSize min_size = wxGetApp().get_min_size(); //wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit()); #ifdef __APPLE__ @@ -252,12 +220,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S }); wxGetApp().persist_window_geometry(this, true); -#if ENABLE_LAYOUT_NO_RESTART wxGetApp().persist_window_geometry(&m_settings_dialog, true); -#else - if (m_settings_dialog != nullptr) - wxGetApp().persist_window_geometry(m_settings_dialog, true); -#endif // ENABLE_LAYOUT_NO_RESTART update_ui_from_settings(); // FIXME (?) @@ -265,7 +228,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_plater->show_action_buttons(true); } -#if ENABLE_LAYOUT_NO_RESTART void MainFrame::update_layout() { auto restore_to_creation = [this]() { @@ -380,7 +342,6 @@ void MainFrame::update_layout() Layout(); Thaw(); } -#endif // ENABLE_LAYOUT_NO_RESTART // Called when closing the application and when switching the application language. void MainFrame::shutdown() @@ -414,20 +375,9 @@ void MainFrame::shutdown() // In addition, there were some crashes due to the Paint events sent to already destructed windows. this->Show(false); -#if ENABLE_LAYOUT_NO_RESTART if (m_settings_dialog.IsShown()) // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry() m_settings_dialog.Close(); -#else - if (m_settings_dialog != nullptr) - { - if (m_settings_dialog->IsShown()) - // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry() - m_settings_dialog->Close(); - - m_settings_dialog->Destroy(); - } -#endif // ENABLE_LAYOUT_NO_RESTART // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). @@ -486,7 +436,6 @@ void MainFrame::update_title() void MainFrame::init_tabpanel() { -#if ENABLE_LAYOUT_NO_RESTART // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 // with multiple high resolution displays connected. m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); @@ -495,27 +444,6 @@ void MainFrame::init_tabpanel() #endif m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); -#else - m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld : - wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew : - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld; - - // From the very beginning the Print settings should be selected - m_last_selected_tab = m_layout == slDlg ? 0 : 1; - - if (m_layout == slDlg) { - m_settings_dialog = new SettingsDialog(this); - m_tabpanel = m_settings_dialog->get_tabpanel(); - } - else { - // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 - // with multiple high resolution displays connected. - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); -#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList - m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); -#endif - } -#endif // ENABLE_LAYOUT_NO_RESTART m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { wxWindow* panel = m_tabpanel->GetCurrentPage(); @@ -536,20 +464,9 @@ void MainFrame::init_tabpanel() select_tab(0); // select Plater }); -#if ENABLE_LAYOUT_NO_RESTART m_plater = new Plater(this, this); m_plater->Hide(); -#else - if (m_layout == slOld) { - m_plater = new Plater(m_tabpanel, this); - m_tabpanel->AddPage(m_plater, _L("Plater")); - } - else { - m_plater = new Plater(this, this); - if (m_layout == slNew) - m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab - } -#endif // ENABLE_LAYOUT_NO_RESTART + wxGetApp().plater_ = m_plater; wxGetApp().obj_list()->create_popup_menus(); @@ -691,7 +608,6 @@ bool MainFrame::can_slice() const bool MainFrame::can_change_view() const { -#if ENABLE_LAYOUT_NO_RESTART switch (m_layout) { default: { return false; } @@ -702,15 +618,6 @@ bool MainFrame::can_change_view() const return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; } } -#else - if (m_layout == slNew) - return m_plater->IsShown(); - if (m_layout == slDlg) - return true; - // slOld layout mode - int page_id = m_tabpanel->GetSelection(); - return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; -#endif // ENABLE_LAYOUT_NO_RESTART } bool MainFrame::can_select() const @@ -756,11 +663,7 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) wxGetApp().plater()->msw_rescale(); // update Tabs -#if ENABLE_LAYOUT_NO_RESTART if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog -#else - if (m_layout != slDlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog -#endif // ENABLE_LAYOUT_NO_RESTART for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); @@ -789,10 +692,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) this->Maximize(is_maximized); -#if ENABLE_LAYOUT_NO_RESTART if (m_layout == ESettingsLayout::Dlg) rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); -#endif // ENABLE_LAYOUT_NO_RESTART } void MainFrame::on_sys_color_changed() @@ -1528,25 +1429,15 @@ void MainFrame::load_config(const DynamicPrintConfig& config) void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { bool tabpanel_was_hidden = false; -#if ENABLE_LAYOUT_NO_RESTART if (m_layout == ESettingsLayout::Dlg) { -#else - if (m_layout == slDlg) { -#endif // ENABLE_LAYOUT_NO_RESTART if (tab==0) { -#if ENABLE_LAYOUT_NO_RESTART if (m_settings_dialog.IsShown()) this->SetFocus(); -#else - if (m_settings_dialog->IsShown()) - this->SetFocus(); -#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (m_plater->canvas3D()->is_search_pressed()) m_plater->SetFocus(); return; } -#if ENABLE_LAYOUT_NO_RESTART // Show/Activate Settings Dialog #ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList if (m_settings_dialog.IsShown()) @@ -1563,28 +1454,11 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) m_settings_dialog.Show(); } #endif -#else - // Show/Activate Settings Dialog - if (m_settings_dialog->IsShown()) -#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList - m_settings_dialog->Hide(); -#else - m_settings_dialog->SetFocus(); - else -#endif - m_settings_dialog->Show(); -#endif // ENABLE_LAYOUT_NO_RESTART } -#if ENABLE_LAYOUT_NO_RESTART else if (m_layout == ESettingsLayout::New) { m_main_sizer->Show(m_plater, tab == 0); tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel); m_main_sizer->Show(m_tabpanel, tab != 0); -#else - else if (m_layout == slNew) { - m_plater->Show(tab == 0); - m_tabpanel->Show(tab != 0); -#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (tab == 0 && m_plater->canvas3D()->is_search_pressed()) @@ -1601,11 +1475,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) tab->update_changed_tree_ui(); // when tab == -1, it means we should show the last selected tab -#if ENABLE_LAYOUT_NO_RESTART m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab); -#else - m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab); -#endif // ENABLE_LAYOUT_NO_RESTART } // Set a camera direction, zoom to all objects. @@ -1734,34 +1604,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); #endif // _WIN32 -#if !ENABLE_LAYOUT_NO_RESTART - // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 - // with multiple high resolution displays connected. - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size(), wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); -#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList - m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); -#endif - - m_tabpanel->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& evt) { - if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) { - switch (evt.GetKeyCode()) { - case '1': { m_main_frame->select_tab(0); break; } - case '2': { m_main_frame->select_tab(1); break; } - case '3': { m_main_frame->select_tab(2); break; } - case '4': { m_main_frame->select_tab(3); break; } -#ifdef __APPLE__ - case 'f': -#else /* __APPLE__ */ - case WXK_CONTROL_F: -#endif /* __APPLE__ */ - case 'F': { m_main_frame->plater()->search(false); break; } - default:break; - } - } - }); -#endif // !ENABLE_LAYOUT_NO_RESTART - -#if ENABLE_LAYOUT_NO_RESTART this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) { auto key_up_handker = [this](wxKeyEvent& evt) { @@ -1791,13 +1633,9 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) m_tabpanel->Unbind(wxEVT_KEY_UP, key_up_handker); } }); -#endif // ENABLE_LAYOUT_NO_RESTART // initialize layout auto sizer = new wxBoxSizer(wxVERTICAL); -#if !ENABLE_LAYOUT_NO_RESTART - sizer->Add(m_tabpanel, 1, wxEXPAND); -#endif // !ENABLE_LAYOUT_NO_RESTART sizer->SetSizeHints(this); SetSizer(sizer); Fit(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 4514b8f50f..3c93f6b58d 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -55,11 +55,7 @@ class SettingsDialog : public DPIDialog public: SettingsDialog(MainFrame* mainframe); ~SettingsDialog() {} -#if ENABLE_LAYOUT_NO_RESTART void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; } -#else - wxNotebook* get_tabpanel() { return m_tabpanel; } -#endif // ENABLE_LAYOUT_NO_RESTART protected: void on_dpi_changed(const wxRect& suggested_rect) override; @@ -119,7 +115,6 @@ class MainFrame : public DPIFrame wxFileHistory m_recent_projects; -#if ENABLE_LAYOUT_NO_RESTART enum class ESettingsLayout { Unknown, @@ -129,13 +124,6 @@ class MainFrame : public DPIFrame }; ESettingsLayout m_layout{ ESettingsLayout::Unknown }; -#else - enum SettingsLayout { - slOld = 0, - slNew, - slDlg, - } m_layout; -#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -145,9 +133,7 @@ public: MainFrame(); ~MainFrame() = default; -#if ENABLE_LAYOUT_NO_RESTART void update_layout(); -#endif // ENABLE_LAYOUT_NO_RESTART // Called when closing the application and when switching the application language. void shutdown(); @@ -190,12 +176,8 @@ public: Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; -#if ENABLE_LAYOUT_NO_RESTART SettingsDialog m_settings_dialog; wxWindow* m_plater_page{ nullptr }; -#else - SettingsDialog* m_settings_dialog { nullptr }; -#endif // ENABLE_LAYOUT_NO_RESTART wxProgressDialog* m_progress_dialog { nullptr }; std::shared_ptr m_statusbar; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 02e4a899d2..4b5808e16a 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -234,30 +234,6 @@ void PreferencesDialog::accept() } } -#if !ENABLE_LAYOUT_NO_RESTART - if (m_settings_layout_changed) { - // the dialog needs to be destroyed before the call to recreate_gui() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - wxMessageDialog dialog(nullptr, - _L("Switching the settings layout mode will trigger application restart.\n" - "You will lose content of the plater.") + "\n\n" + - _L("Do you want to proceed?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switching the settings layout mode"), - wxICON_QUESTION | wxOK | wxCANCEL); - - if (dialog.ShowModal() == wxID_CANCEL) - { - int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : - app_config->get("new_settings_layout_mode") == "1" ? 1 : - app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; - - m_layout_mode_box->SetSelection(selection); - return; - } - } -#endif // !ENABLE_LAYOUT_NO_RESTART - for (std::map::iterator it = m_values.begin(); it != m_values.end(); ++it) app_config->set(it->first, it->second); From 4bfb69eb1482238e6ee975e905ae49b4b3acb106 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 3 Aug 2020 12:20:46 +0200 Subject: [PATCH 230/826] Added an icon for 'ironing' category --- resources/icons/ironing.svg | 27 +++++++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 resources/icons/ironing.svg diff --git a/resources/icons/ironing.svg b/resources/icons/ironing.svg new file mode 100644 index 0000000000..94917d6bfe --- /dev/null +++ b/resources/icons/ironing.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f3ff264ced..c10853f690 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -94,7 +94,7 @@ ObjectList::ObjectList(wxWindow* parent) : // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); @@ -645,7 +645,7 @@ void ObjectList::msw_rescale_icons() // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); - CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing"); CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); From 5249b3e018f84d232a578e4992844da3ce1ba6fc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 Aug 2020 13:57:10 +0200 Subject: [PATCH 231/826] ENABLE_GCODE_VIEWER -> Estimated print time statistics moved from PrintStatistics to GCodeProcessor --- src/libslic3r/GCode.cpp | 5 +- src/libslic3r/GCode/GCodeProcessor.cpp | 41 ++- src/libslic3r/GCode/GCodeProcessor.hpp | 68 +++-- src/libslic3r/Print.hpp | 24 -- src/slic3r/GUI/GCodeViewer.cpp | 334 +++++++++++-------------- src/slic3r/GUI/GCodeViewer.hpp | 9 +- src/slic3r/GUI/MainFrame.cpp | 4 - 7 files changed, 227 insertions(+), 258 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2dea4fb22b..880572b2bb 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -775,12 +775,9 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ } #if ENABLE_GCODE_VIEWER - print->m_print_statistics.clear_time_estimates(); m_processor.process_file(path_tmp); - if (result != nullptr) { + if (result != nullptr) *result = std::move(m_processor.extract_result()); - m_processor.update_print_stats_estimated_times(print->m_print_statistics); - } #endif // ENABLE_GCODE_VIEWER GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index f3e07da881..5d9644457d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -570,31 +570,13 @@ void GCodeProcessor::process_file(const std::string& filename) gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); } + update_estimated_times_stats(); + #if ENABLE_GCODE_VIEWER_STATISTICS m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } -void GCodeProcessor::update_print_stats_estimated_times(PrintStatistics& print_statistics) -{ - print_statistics.estimated_normal_print_time = get_time(GCodeProcessor::ETimeMode::Normal); - print_statistics.estimated_normal_custom_gcode_print_times = get_custom_gcode_times(GCodeProcessor::ETimeMode::Normal, true); - print_statistics.estimated_normal_moves_times = get_moves_time(GCodeProcessor::ETimeMode::Normal); - print_statistics.estimated_normal_roles_times = get_roles_time(GCodeProcessor::ETimeMode::Normal); - if (m_time_processor.machines[static_cast(GCodeProcessor::ETimeMode::Stealth)].enabled) { - print_statistics.estimated_silent_print_time = get_time(GCodeProcessor::ETimeMode::Stealth); - print_statistics.estimated_silent_custom_gcode_print_times = get_custom_gcode_times(GCodeProcessor::ETimeMode::Stealth, true); - print_statistics.estimated_silent_moves_times = get_moves_time(GCodeProcessor::ETimeMode::Stealth); - print_statistics.estimated_silent_roles_times = get_roles_time(GCodeProcessor::ETimeMode::Stealth); - } - else { - print_statistics.estimated_silent_print_time = 0.0f; - print_statistics.estimated_silent_custom_gcode_print_times.clear(); - print_statistics.estimated_silent_moves_times.clear(); - print_statistics.estimated_silent_roles_times.clear(); - } -} - float GCodeProcessor::get_time(ETimeMode mode) const { return (mode < ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; @@ -620,7 +602,7 @@ std::vector>> GCodeProcesso return ret; } -std::vector> GCodeProcessor::get_moves_time(ETimeMode mode) const +std::vector> GCodeProcessor::get_moves_time(ETimeMode mode) const { std::vector> ret; if (mode < ETimeMode::Count) { @@ -1892,6 +1874,23 @@ void GCodeProcessor::simulate_st_synchronize(float additional_time) } } +void GCodeProcessor::update_estimated_times_stats() +{ + auto update_mode = [this](GCodeProcessor::ETimeMode mode) { + PrintEstimatedTimeStatistics::Mode& data = m_result.time_statistics.modes[static_cast(mode)]; + data.time = get_time(mode); + data.custom_gcode_times = get_custom_gcode_times(mode, true); + data.moves_times = get_moves_time(mode); + data.roles_times = get_roles_time(mode); + }; + + update_mode(GCodeProcessor::ETimeMode::Normal); + if (m_time_processor.machines[static_cast(GCodeProcessor::ETimeMode::Stealth)].enabled) + update_mode(GCodeProcessor::ETimeMode::Stealth); + else + m_result.time_statistics.modes[static_cast(GCodeProcessor::ETimeMode::Stealth)].reset(); +} + } /* namespace Slic3r */ #endif // ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index bad04100ef..526300e55a 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -14,7 +14,54 @@ namespace Slic3r { - struct PrintStatistics; + enum class EMoveType : unsigned char + { + Noop, + Retract, + Unretract, + Tool_change, + Color_change, + Pause_Print, + Custom_GCode, + Travel, + Extrude, + Count + }; + + struct PrintEstimatedTimeStatistics + { + enum class ETimeMode : unsigned char + { + Normal, + Stealth, + Count + }; + + struct Mode + { + float time; + std::vector>> custom_gcode_times; + std::vector> moves_times; + std::vector> roles_times; + + void reset() { + time = 0.0f; + custom_gcode_times.clear(); + moves_times.clear(); + roles_times.clear(); + } + }; + + std::array(ETimeMode::Count)> modes; + + PrintEstimatedTimeStatistics() { reset(); } + + void reset() { + for (auto m : modes) { + m.reset(); + } + } + }; class GCodeProcessor { @@ -59,20 +106,6 @@ namespace Slic3r { }; public: - enum class EMoveType : unsigned char - { - Noop, - Retract, - Unretract, - Tool_change, - Color_change, - Pause_Print, - Custom_GCode, - Travel, - Extrude, - Count - }; - struct FeedrateProfile { float entry{ 0.0f }; // mm/s @@ -215,6 +248,8 @@ namespace Slic3r { Pointfs bed_shape; std::vector extruder_colors; #endif // ENABLE_GCODE_VIEWER_AS_STATE + PrintEstimatedTimeStatistics time_statistics; + #if ENABLE_GCODE_VIEWER_STATISTICS long long time{ 0 }; void reset() @@ -297,7 +332,6 @@ namespace Slic3r { // Process the gcode contained in the file with the given filename void process_file(const std::string& filename); - void update_print_stats_estimated_times(PrintStatistics& print_statistics); float get_time(ETimeMode mode) const; std::string get_time_dhm(ETimeMode mode) const; std::vector>> get_custom_gcode_times(ETimeMode mode, bool include_remaining) const; @@ -422,6 +456,8 @@ namespace Slic3r { // Simulates firmware st_synchronize() call void simulate_st_synchronize(float additional_time = 0.0f); + + void update_estimated_times_stats(); }; } /* namespace Slic3r */ diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 22af935d3e..62c00aa88d 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -304,22 +304,12 @@ struct PrintStatistics { PrintStatistics() { clear(); } #if ENABLE_GCODE_VIEWER - float estimated_normal_print_time; - float estimated_silent_print_time; #if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::string estimated_normal_print_time_str; std::string estimated_silent_print_time_str; -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - std::vector>> estimated_normal_custom_gcode_print_times; - std::vector>> estimated_silent_custom_gcode_print_times; -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR std::vector>> estimated_normal_custom_gcode_print_times_str; std::vector>> estimated_silent_custom_gcode_print_times_str; #endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - std::vector> estimated_normal_moves_times; - std::vector> estimated_silent_moves_times; - std::vector> estimated_normal_roles_times; - std::vector> estimated_silent_roles_times; #else std::string estimated_normal_print_time; std::string estimated_silent_print_time; @@ -344,7 +334,6 @@ struct PrintStatistics void clear() { #if ENABLE_GCODE_VIEWER - clear_time_estimates(); #if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR estimated_normal_print_time_str.clear(); estimated_silent_print_time_str.clear(); @@ -366,19 +355,6 @@ struct PrintStatistics total_wipe_tower_filament = 0.; filament_stats.clear(); } - -#if ENABLE_GCODE_VIEWER - void clear_time_estimates() { - estimated_normal_print_time = 0.0f; - estimated_silent_print_time = 0.0f; - estimated_normal_custom_gcode_print_times.clear(); - estimated_silent_custom_gcode_print_times.clear(); - estimated_normal_moves_times.clear(); - estimated_silent_moves_times.clear(); - estimated_normal_roles_times.clear(); - estimated_silent_roles_times.clear(); - } -#endif //ENABLE_GCODE_VIEWER }; typedef std::vector PrintObjectPtrs; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 8d4fb6f4cb..4faa0486ff 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -32,12 +32,12 @@ namespace Slic3r { namespace GUI { -static unsigned char buffer_id(GCodeProcessor::EMoveType type) { - return static_cast(type) - static_cast(GCodeProcessor::EMoveType::Retract); +static unsigned char buffer_id(EMoveType type) { + return static_cast(type) - static_cast(EMoveType::Retract); } -static GCodeProcessor::EMoveType buffer_type(unsigned char id) { - return static_cast(static_cast(GCodeProcessor::EMoveType::Retract) + id); +static EMoveType buffer_type(unsigned char id) { + return static_cast(static_cast(EMoveType::Retract) + id); } std::array decode_color(const std::string& color) { @@ -92,19 +92,19 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const { switch (move.type) { - case GCodeProcessor::EMoveType::Tool_change: - case GCodeProcessor::EMoveType::Color_change: - case GCodeProcessor::EMoveType::Pause_Print: - case GCodeProcessor::EMoveType::Custom_GCode: - case GCodeProcessor::EMoveType::Retract: - case GCodeProcessor::EMoveType::Unretract: - case GCodeProcessor::EMoveType::Extrude: + case EMoveType::Tool_change: + case EMoveType::Color_change: + case EMoveType::Pause_Print: + case EMoveType::Custom_GCode: + case EMoveType::Retract: + case EMoveType::Unretract: + case EMoveType::Extrude: { return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; } - case GCodeProcessor::EMoveType::Travel: + case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; } @@ -198,9 +198,7 @@ void GCodeViewer::SequentialView::Marker::render() const ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.25f); imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(_u8L("Tool position") + ":"); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":"); ImGui::SameLine(); char buf[1024]; sprintf(buf, "X: %.2f, Y: %.2f, Z: %.2f", m_world_position(0), m_world_position(1), m_world_position(2)); @@ -274,18 +272,18 @@ bool GCodeViewer::init() switch (buffer_type(i)) { default: { break; } - case GCodeProcessor::EMoveType::Tool_change: - case GCodeProcessor::EMoveType::Color_change: - case GCodeProcessor::EMoveType::Pause_Print: - case GCodeProcessor::EMoveType::Custom_GCode: - case GCodeProcessor::EMoveType::Retract: - case GCodeProcessor::EMoveType::Unretract: + case EMoveType::Tool_change: + case EMoveType::Color_change: + case EMoveType::Pause_Print: + case EMoveType::Custom_GCode: + case EMoveType::Retract: + case EMoveType::Unretract: { m_buffers[i].vertices.format = VBuffer::EFormat::Position; break; } - case GCodeProcessor::EMoveType::Extrude: - case GCodeProcessor::EMoveType::Travel: + case EMoveType::Extrude: + case EMoveType::Travel: { m_buffers[i].vertices.format = VBuffer::EFormat::PositionNormal; break; @@ -293,7 +291,7 @@ bool GCodeViewer::init() } } - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Extrude, true); + set_toolpath_move_type_visible(EMoveType::Extrude, true); m_sequential_view.marker.init(); init_shaders(); @@ -340,6 +338,8 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& wxGetApp().plater()->set_bed_shape(bed_shape, "", "", true); } #endif // ENABLE_GCODE_VIEWER_AS_STATE + + m_time_statistics = gcode_result.time_statistics; } void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) @@ -371,7 +371,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: switch (curr.type) { - case GCodeProcessor::EMoveType::Extrude: + case EMoveType::Extrude: { m_extrusions.ranges.height.update_from(curr.height); m_extrusions.ranges.width.update_from(curr.width); @@ -379,7 +379,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: m_extrusions.ranges.volumetric_rate.update_from(curr.volumetric_rate()); [[fallthrough]]; } - case GCodeProcessor::EMoveType::Travel: + case EMoveType::Travel: { if (m_buffers[buffer_id(curr.type)].visible) m_extrusions.ranges.feedrate.update_from(curr.feedrate); @@ -415,6 +415,7 @@ void GCodeViewer::reset() m_layers_zs = std::vector(); m_layers_z_range = { 0.0, 0.0 }; m_roles = std::vector(); + m_time_statistics.reset(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); @@ -452,13 +453,13 @@ void GCodeViewer::render() const #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR } -bool GCodeViewer::is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const +bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const { size_t id = static_cast(buffer_id(type)); return (id < m_buffers.size()) ? m_buffers[id].visible : false; } -void GCodeViewer::set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible) +void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible) { size_t id = static_cast(buffer_id(type)); if (id < m_buffers.size()) @@ -472,13 +473,13 @@ unsigned int GCodeViewer::get_options_visibility_flags() const }; unsigned int flags = 0; - flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel)); - flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract)); - flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract)); - flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change)); - flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print)); - flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode)); + flags = set_flag(flags, static_cast(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel)); + flags = set_flag(flags, static_cast(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract)); + flags = set_flag(flags, static_cast(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract)); + flags = set_flag(flags, static_cast(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change)); + flags = set_flag(flags, static_cast(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change)); + flags = set_flag(flags, static_cast(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print)); + flags = set_flag(flags, static_cast(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode)); flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); @@ -494,13 +495,13 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) return (flags & (1 << flag)) != 0; }; - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); - set_toolpath_move_type_visible(GCodeProcessor::EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); + set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast(Preview::OptionType::Travel))); + set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast(Preview::OptionType::Retractions))); + set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast(Preview::OptionType::Unretractions))); + set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast(Preview::OptionType::ToolChanges))); + set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast(Preview::OptionType::ColorChanges))); + set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast(Preview::OptionType::PausePrints))); + set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast(Preview::OptionType::CustomGCodes))); m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); @@ -537,7 +538,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const wxBusyCursor busy; // the data needed is contained into the Extrude TBuffer - const TBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Extrude)]; + const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Extrude)]; if (buffer.vertices.id == 0 || buffer.indices.id == 0) return; @@ -805,21 +806,21 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const void GCodeViewer::init_shaders() { - unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); - unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + unsigned char begin_id = buffer_id(EMoveType::Retract); + unsigned char end_id = buffer_id(EMoveType::Count); bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); for (unsigned char i = begin_id; i < end_id; ++i) { switch (buffer_type(i)) { - case GCodeProcessor::EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case GCodeProcessor::EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case GCodeProcessor::EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case GCodeProcessor::EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case GCodeProcessor::EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case GCodeProcessor::EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case GCodeProcessor::EMoveType::Extrude: { m_buffers[i].shader = "toolpaths"; break; } - case GCodeProcessor::EMoveType::Travel: { m_buffers[i].shader = "toolpaths"; break; } + case EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } + case EMoveType::Extrude: { m_buffers[i].shader = "toolpaths"; break; } + case EMoveType::Travel: { m_buffers[i].shader = "toolpaths"; break; } default: { break; } } } @@ -846,14 +847,17 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_paths_bounding_box.merge(move.position.cast()); else { #endif // ENABLE_GCODE_VIEWER_AS_STATE - if (move.type == GCodeProcessor::EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) + if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) m_paths_bounding_box.merge(move.position.cast()); #if ENABLE_GCODE_VIEWER_AS_STATE } #endif // ENABLE_GCODE_VIEWER_AS_STATE } - // max bounding box + // add origin + m_paths_bounding_box.merge(Vec3d::Zero()); + + // max bounding box (account for tool marker) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); @@ -875,12 +879,12 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) switch (curr.type) { - case GCodeProcessor::EMoveType::Tool_change: - case GCodeProcessor::EMoveType::Color_change: - case GCodeProcessor::EMoveType::Pause_Print: - case GCodeProcessor::EMoveType::Custom_GCode: - case GCodeProcessor::EMoveType::Retract: - case GCodeProcessor::EMoveType::Unretract: + case EMoveType::Tool_change: + case EMoveType::Color_change: + case EMoveType::Pause_Print: + case EMoveType::Custom_GCode: + case EMoveType::Retract: + case EMoveType::Unretract: { for (int j = 0; j < 3; ++j) { buffer_vertices.push_back(curr.position[j]); @@ -889,8 +893,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer_indices.push_back(static_cast(buffer_indices.size())); break; } - case GCodeProcessor::EMoveType::Extrude: - case GCodeProcessor::EMoveType::Travel: + case EMoveType::Extrude: + case EMoveType::Travel: { // x component of the normal to the current segment (the normal is parallel to the XY plane) float normal_x = (curr.position - prev.position).normalized()[1]; @@ -979,7 +983,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // layers zs / roles / extruder ids / cp color ids -> extract from result for (size_t i = 0; i < m_vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - if (move.type == GCodeProcessor::EMoveType::Extrude) + if (move.type == EMoveType::Extrude) m_layers_zs.emplace_back(static_cast(move.position[2])); m_extruder_ids.emplace_back(move.extruder_id); @@ -1127,14 +1131,14 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool for (size_t i = 0; i < buffer.paths.size(); ++i) { const Path& path = buffer.paths[i]; - if (path.type == GCodeProcessor::EMoveType::Travel) { + if (path.type == EMoveType::Travel) { if (!is_travel_in_z_range(i)) continue; } else if (!is_in_z_range(path)) continue; - if (path.type == GCodeProcessor::EMoveType::Extrude && !is_visible(path)) + if (path.type == EMoveType::Extrude && !is_visible(path)) continue; // store valid path @@ -1156,7 +1160,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool for (const Path& path : buffer.paths) { if (path.first.s_id <= m_sequential_view.current.last && m_sequential_view.current.last <= path.last.s_id) { size_t offset = m_sequential_view.current.last - path.first.s_id; - if (offset > 0 && (path.type == GCodeProcessor::EMoveType::Travel || path.type == GCodeProcessor::EMoveType::Extrude)) + if (offset > 0 && (path.type == EMoveType::Travel || path.type == EMoveType::Extrude)) offset = 1 + 2 * (offset - 1); offset += path.first.i_id; @@ -1182,8 +1186,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool Color color; switch (path.type) { - case GCodeProcessor::EMoveType::Extrude: { color = extrusion_color(path); break; } - case GCodeProcessor::EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } + case EMoveType::Extrude: { color = extrusion_color(path); break; } + case EMoveType::Travel: { color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); break; } default: { color = { 0.0f, 0.0f, 0.0f }; break; } } @@ -1195,7 +1199,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } unsigned int size = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; - if (path.type == GCodeProcessor::EMoveType::Extrude || path.type == GCodeProcessor::EMoveType::Travel) + if (path.type == EMoveType::Extrude || path.type == EMoveType::Travel) size = 2 * (size - 1); it->sizes.push_back(size); @@ -1278,8 +1282,8 @@ void GCodeViewer::render_toolpaths() const glsafe(::glLineWidth(static_cast(line_width(zoom)))); - unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); - unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Count); + unsigned char begin_id = buffer_id(EMoveType::Retract); + unsigned char end_id = buffer_id(EMoveType::Count); for (unsigned char i = begin_id; i < end_id; ++i) { const TBuffer& buffer = m_buffers[i]; @@ -1302,14 +1306,14 @@ void GCodeViewer::render_toolpaths() const switch (buffer_type(i)) { default: { break; } - case GCodeProcessor::EMoveType::Tool_change: { render_as_points(buffer, EOptionsColors::ToolChanges, *shader); break; } - case GCodeProcessor::EMoveType::Color_change: { render_as_points(buffer, EOptionsColors::ColorChanges, *shader); break; } - case GCodeProcessor::EMoveType::Pause_Print: { render_as_points(buffer, EOptionsColors::PausePrints, *shader); break; } - case GCodeProcessor::EMoveType::Custom_GCode: { render_as_points(buffer, EOptionsColors::CustomGCodes, *shader); break; } - case GCodeProcessor::EMoveType::Retract: { render_as_points(buffer, EOptionsColors::Retractions, *shader); break; } - case GCodeProcessor::EMoveType::Unretract: { render_as_points(buffer, EOptionsColors::Unretractions, *shader); break; } - case GCodeProcessor::EMoveType::Extrude: - case GCodeProcessor::EMoveType::Travel: + case EMoveType::Tool_change: { render_as_points(buffer, EOptionsColors::ToolChanges, *shader); break; } + case EMoveType::Color_change: { render_as_points(buffer, EOptionsColors::ColorChanges, *shader); break; } + case EMoveType::Pause_Print: { render_as_points(buffer, EOptionsColors::PausePrints, *shader); break; } + case EMoveType::Custom_GCode: { render_as_points(buffer, EOptionsColors::CustomGCodes, *shader); break; } + case EMoveType::Retract: { render_as_points(buffer, EOptionsColors::Retractions, *shader); break; } + case EMoveType::Unretract: { render_as_points(buffer, EOptionsColors::Unretractions, *shader); break; } + case EMoveType::Extrude: + case EMoveType::Travel: { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR std::array light_intensity = { @@ -1406,7 +1410,7 @@ void GCodeViewer::render_legend() const draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); #else ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(GCodeProcessor::EMoveType::Retract)].shader == "options_120_flat") { + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120_flat") { draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); float radius = 0.5f * icon_size; @@ -1623,7 +1627,7 @@ void GCodeViewer::render_legend() const } // travel paths - if (m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)].visible) { + if (m_buffers[buffer_id(EMoveType::Travel)].visible) { switch (m_view_type) { case EViewType::Feedrate: @@ -1649,20 +1653,20 @@ void GCodeViewer::render_legend() const } auto any_option_available = [this]() { - auto available = [this](GCodeProcessor::EMoveType type) { + auto available = [this](EMoveType type) { const TBuffer& buffer = m_buffers[buffer_id(type)]; return buffer.visible && buffer.indices.count > 0; }; - return available(GCodeProcessor::EMoveType::Color_change) || - available(GCodeProcessor::EMoveType::Custom_GCode) || - available(GCodeProcessor::EMoveType::Pause_Print) || - available(GCodeProcessor::EMoveType::Retract) || - available(GCodeProcessor::EMoveType::Tool_change) || - available(GCodeProcessor::EMoveType::Unretract); + return available(EMoveType::Color_change) || + available(EMoveType::Custom_GCode) || + available(EMoveType::Pause_Print) || + available(EMoveType::Retract) || + available(EMoveType::Tool_change) || + available(EMoveType::Unretract); }; - auto add_option = [this, append_item](GCodeProcessor::EMoveType move_type, EOptionsColors color, const std::string& text) { + auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { const TBuffer& buffer = m_buffers[buffer_id(move_type)]; if (buffer.visible && buffer.indices.count > 0) #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR @@ -1679,12 +1683,12 @@ void GCodeViewer::render_legend() const imgui.title(_u8L("Options")); // items - add_option(GCodeProcessor::EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); - add_option(GCodeProcessor::EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Unretractions")); - add_option(GCodeProcessor::EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); - add_option(GCodeProcessor::EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); - add_option(GCodeProcessor::EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Pause prints")); - add_option(GCodeProcessor::EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom GCodes")); + add_option(EMoveType::Retract, EOptionsColors::Retractions, _u8L("Retractions")); + add_option(EMoveType::Unretract, EOptionsColors::Unretractions, _u8L("Unretractions")); + add_option(EMoveType::Tool_change, EOptionsColors::ToolChanges, _u8L("Tool changes")); + add_option(EMoveType::Color_change, EOptionsColors::ColorChanges, _u8L("Color changes")); + add_option(EMoveType::Pause_Print, EOptionsColors::PausePrints, _u8L("Pause prints")); + add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom GCodes")); } imgui.end(); @@ -1700,10 +1704,6 @@ void GCodeViewer::render_time_estimate() const return; } - const PrintStatistics& ps = wxGetApp().plater()->fff_print().print_statistics(); - if (ps.estimated_normal_print_time <= 0.0f && ps.estimated_silent_print_time <= 0.0f) - return; - ImGuiWrapper& imgui = *wxGetApp().imgui(); #if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG @@ -1737,19 +1737,17 @@ void GCodeViewer::render_time_estimate() const using PartialTimes = std::vector; auto append_headers = [&imgui](const Headers& headers, const ColumnOffsets& offsets) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(headers[0]); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, headers[0]); ImGui::SameLine(offsets[0]); - imgui.text(headers[1]); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, headers[1]); ImGui::SameLine(offsets[1]); - imgui.text(headers[2]); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, headers[2]); ImGui::Separator(); }; auto append_mode = [this, &imgui, append_headers](float total_time, const PartialTimes& items, const Headers& partial_times_headers, - const std::vector>& moves_time, + const std::vector>& moves_time, const Headers& moves_headers, const std::vector>& roles_time, const Headers& roles_headers) { @@ -1775,9 +1773,7 @@ void GCodeViewer::render_time_estimate() const return ret; }; auto append_color = [this, &imgui](const Color& color1, const Color& color2, ColumnOffsets& offsets, const Times& times) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(_u8L("Color change")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Color change")); ImGui::SameLine(); float icon_size = ImGui::GetTextLineHeight(); @@ -1805,9 +1801,7 @@ void GCodeViewer::render_time_estimate() const { case PartialTime::EType::Print: { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(_u8L("Print")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Print")); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(item.times.second))); ImGui::SameLine(offsets[1]); @@ -1816,9 +1810,7 @@ void GCodeViewer::render_time_estimate() const } case PartialTime::EType::Pause: { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(_u8L("Pause")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Pause")); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); break; @@ -1832,26 +1824,24 @@ void GCodeViewer::render_time_estimate() const } }; - auto move_type_label = [](GCodeProcessor::EMoveType type) { + auto move_type_label = [](EMoveType type) { switch (type) { - case GCodeProcessor::EMoveType::Noop: { return _u8L("Noop"); } - case GCodeProcessor::EMoveType::Retract: { return _u8L("Retraction"); } - case GCodeProcessor::EMoveType::Unretract: { return _u8L("Unretraction"); } - case GCodeProcessor::EMoveType::Tool_change: { return _u8L("Tool change"); } - case GCodeProcessor::EMoveType::Color_change: { return _u8L("Color change"); } - case GCodeProcessor::EMoveType::Pause_Print: { return _u8L("Pause print"); } - case GCodeProcessor::EMoveType::Custom_GCode: { return _u8L("Custom GCode"); } - case GCodeProcessor::EMoveType::Travel: { return _u8L("Travel"); } - case GCodeProcessor::EMoveType::Extrude: { return _u8L("Extrusion"); } - default: { return _u8L("Unknown"); } + case EMoveType::Noop: { return _u8L("Noop"); } + case EMoveType::Retract: { return _u8L("Retraction"); } + case EMoveType::Unretract: { return _u8L("Unretraction"); } + case EMoveType::Tool_change: { return _u8L("Tool change"); } + case EMoveType::Color_change: { return _u8L("Color change"); } + case EMoveType::Pause_Print: { return _u8L("Pause print"); } + case EMoveType::Custom_GCode: { return _u8L("Custom GCode"); } + case EMoveType::Travel: { return _u8L("Travel"); } + case EMoveType::Extrude: { return _u8L("Extrusion"); } + default: { return _u8L("Unknown"); } } }; auto append_time_item = [&imgui] (const std::string& label, float time, float percentage, const ImVec4& color, const ColumnOffsets& offsets) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(label); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(time))); ImGui::SameLine(offsets[1]); @@ -1868,7 +1858,7 @@ void GCodeViewer::render_time_estimate() const }; auto append_move_times = [this, &imgui, move_type_label, append_headers, append_time_item](float total_time, - const std::vector>& moves_time, + const std::vector>& moves_time, const Headers& headers, const ColumnOffsets& offsets) { if (moves_time.empty()) @@ -1879,7 +1869,7 @@ void GCodeViewer::render_time_estimate() const append_headers(headers, offsets); - std::vector> sorted_moves_time(moves_time); + std::vector> sorted_moves_time(moves_time); std::sort(sorted_moves_time.begin(), sorted_moves_time.end(), [](const auto& p1, const auto& p2) { return p2.second < p1.second; }); for (const auto& [type, time] : sorted_moves_time) { @@ -1909,7 +1899,7 @@ void GCodeViewer::render_time_estimate() const }; auto calc_common_offsets = [move_type_label]( - const std::vector>& moves_time, const Headers& moves_headers, + const std::vector>& moves_time, const Headers& moves_headers, const std::vector>& roles_time, const Headers& roles_headers) { ColumnOffsets ret = { std::max(ImGui::CalcTextSize(moves_headers[0].c_str()).x, ImGui::CalcTextSize(roles_headers[0].c_str()).x), std::max(ImGui::CalcTextSize(moves_headers[1].c_str()).x, ImGui::CalcTextSize(roles_headers[1].c_str()).x) }; @@ -1930,9 +1920,7 @@ void GCodeViewer::render_time_estimate() const return ret; }; - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(_u8L("Time") + ":"); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Time") + ":"); ImGui::SameLine(); imgui.text(short_time(get_time_dhms(total_time))); append_partial_times(items, partial_times_headers); @@ -2032,21 +2020,23 @@ void GCodeViewer::render_time_estimate() const // mode tabs ImGui::BeginTabBar("mode_tabs"); - if (ps.estimated_normal_print_time > 0.0f) { + const PrintEstimatedTimeStatistics::Mode& normal_mode = m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)]; + if (normal_mode.time > 0.0f) { if (ImGui::BeginTabItem(_u8L("Normal").c_str())) { - append_mode(ps.estimated_normal_print_time, - generate_partial_times(ps.estimated_normal_custom_gcode_print_times), partial_times_headers, - ps.estimated_normal_moves_times, moves_headers, - ps.estimated_normal_roles_times, roles_headers); + append_mode(normal_mode.time, + generate_partial_times(normal_mode.custom_gcode_times), partial_times_headers, + normal_mode.moves_times, moves_headers, + normal_mode.roles_times, roles_headers); ImGui::EndTabItem(); } } - if (ps.estimated_silent_print_time > 0.0f) { + const PrintEstimatedTimeStatistics::Mode& stealth_mode = m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)]; + if (stealth_mode.time > 0.0f) { if (ImGui::BeginTabItem(_u8L("Stealth").c_str())) { - append_mode(ps.estimated_silent_print_time, - generate_partial_times(ps.estimated_silent_custom_gcode_print_times), partial_times_headers, - ps.estimated_silent_moves_times, moves_headers, - ps.estimated_silent_roles_times, roles_headers); + append_mode(stealth_mode.time, + generate_partial_times(stealth_mode.custom_gcode_times), partial_times_headers, + stealth_mode.moves_times, moves_headers, + stealth_mode.roles_times, roles_headers); ImGui::EndTabItem(); } } @@ -2082,93 +2072,67 @@ void GCodeViewer::render_statistics() const imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("GCodeProcessor time:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor time:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.results_time) + " ms"); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Load time:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Load time:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.load_time) + " ms"); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Resfresh time:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh time:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.refresh_time) + " ms"); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Resfresh paths time:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh paths time:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.refresh_paths_time) + " ms"); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Multi GL_POINTS calls:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_POINTS calls:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count)); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Multi GL_LINE_STRIP calls:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_LINE_STRIP calls:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.gl_multi_line_strip_calls_count)); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("GCodeProcessor results:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor results:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.results_size) + " bytes"); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Paths CPU:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Paths CPU:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.paths_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Render paths CPU:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Render paths CPU:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.render_paths_size) + " bytes"); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Vertices GPU:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Indices GPU:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); ImGui::Separator(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Travel segments:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.travel_segments_count)); - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); - imgui.text(std::string("Extrude segments:")); - ImGui::PopStyleColor(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Extrude segments:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.extrude_segments_count)); @@ -2242,7 +2206,7 @@ void GCodeViewer::render_shaders_editor() const bool GCodeViewer::is_travel_in_z_range(size_t id) const { - const TBuffer& buffer = m_buffers[buffer_id(GCodeProcessor::EMoveType::Travel)]; + const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; if (id >= buffer.paths.size()) return false; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 3b50062fbc..0a32a97e67 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -88,7 +88,7 @@ class GCodeViewer Vec3f position{ Vec3f::Zero() }; }; - GCodeProcessor::EMoveType type{ GCodeProcessor::EMoveType::Noop }; + EMoveType type{ EMoveType::Noop }; ExtrusionRole role{ erNone }; Endpoint first; Endpoint last; @@ -326,7 +326,7 @@ public: private: unsigned int m_last_result_id{ 0 }; size_t m_vertices_count{ 0 }; - mutable std::vector m_buffers{ static_cast(GCodeProcessor::EMoveType::Extrude) }; + mutable std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; // bounding box of toolpaths + marker tools @@ -341,6 +341,7 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; + PrintEstimatedTimeStatistics m_time_statistics; #if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG mutable bool m_time_estimate_enabled{ false }; mutable unsigned int m_time_estimate_frames_count{ 0 }; @@ -391,8 +392,8 @@ public: m_view_type = type; } - bool is_toolpath_move_type_visible(GCodeProcessor::EMoveType type) const; - void set_toolpath_move_type_visible(GCodeProcessor::EMoveType type, bool visible); + bool is_toolpath_move_type_visible(EMoveType type) const; + void set_toolpath_move_type_visible(EMoveType type, bool visible); unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; } void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } unsigned int get_options_visibility_flags() const; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7d6acad069..f7f6285c65 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1432,8 +1432,6 @@ void MainFrame::set_mode(EMode mode) select_tab(0); #endif // ENABLE_LAYOUT_NO_RESTART - m_plater->fff_print().print_statistics().clear_time_estimates(); - m_plater->reset(); m_plater->reset_gcode_toolpaths(); @@ -1477,8 +1475,6 @@ void MainFrame::set_mode(EMode mode) update_layout(); #endif // ENABLE_LAYOUT_NO_RESTART - m_plater->fff_print().print_statistics().clear_time_estimates(); - m_plater->reset(); m_plater->reset_last_loaded_gcode(); m_plater->reset_gcode_toolpaths(); From 0840b2328a668f648de117b4743e5769f31990af Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 3 Aug 2020 15:00:19 +0200 Subject: [PATCH 232/826] Tech ENABLE_GCODE_VIEWER_AS_STATE set as default --- src/libslic3r/GCode/GCodeProcessor.cpp | 5 -- src/libslic3r/GCode/GCodeProcessor.hpp | 6 --- src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/GCodeViewer.cpp | 15 +----- src/slic3r/GUI/GLCanvas3D.cpp | 29 +++-------- src/slic3r/GUI/GLCanvas3D.hpp | 4 +- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_App.hpp | 4 +- src/slic3r/GUI/GUI_Preview.cpp | 16 +++--- src/slic3r/GUI/MainFrame.cpp | 68 +++++++++++++------------- src/slic3r/GUI/MainFrame.hpp | 16 +++--- src/slic3r/GUI/Plater.cpp | 38 +++++++------- src/slic3r/GUI/Plater.hpp | 29 +++++------ 13 files changed, 91 insertions(+), 144 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 5d9644457d..7612af0e90 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1147,7 +1147,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) type = EMoveType::Travel; -#if ENABLE_GCODE_VIEWER_AS_STATE if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) { if (m_extrusion_role != erCustom) { m_width = 0.5f; @@ -1155,10 +1154,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) } type = EMoveType::Travel; } -#else - if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f || !is_valid_extrusion_role(m_extrusion_role))) - type = EMoveType::Travel; -#endif // ENABLE_GCODE_VIEWER_AS_STATE return type; }; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 526300e55a..101c320db8 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -244,10 +244,8 @@ namespace Slic3r { { unsigned int id; std::vector moves; -#if ENABLE_GCODE_VIEWER_AS_STATE Pointfs bed_shape; std::vector extruder_colors; -#endif // ENABLE_GCODE_VIEWER_AS_STATE PrintEstimatedTimeStatistics time_statistics; #if ENABLE_GCODE_VIEWER_STATISTICS @@ -256,19 +254,15 @@ namespace Slic3r { { time = 0; moves = std::vector(); -#if ENABLE_GCODE_VIEWER_AS_STATE bed_shape = Pointfs(); extruder_colors = std::vector(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE } #else void reset() { moves = std::vector(); -#if ENABLE_GCODE_VIEWER_AS_STATE bed_shape = Pointfs(); extruder_colors = std::vector(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE } #endif // ENABLE_GCODE_VIEWER_STATISTICS }; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 569a73bab1..c14e2df52f 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,7 +58,6 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_AS_STATE (1 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR (1 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG (1 && ENABLE_GCODE_VIEWER) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 4faa0486ff..e746635ee2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -7,9 +7,7 @@ #include "libslic3r/Model.hpp" #include "libslic3r/Utils.hpp" #include "GUI_App.hpp" -#if ENABLE_GCODE_VIEWER_AS_STATE #include "MainFrame.hpp" -#endif // ENABLE_GCODE_VIEWER_AS_STATE #include "Plater.hpp" #include "PresetBundle.hpp" #include "Camera.hpp" @@ -314,13 +312,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& reset(); load_toolpaths(gcode_result); -#if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) -#endif // ENABLE_GCODE_VIEWER_AS_STATE load_shells(print, initialized); - -#if ENABLE_GCODE_VIEWER_AS_STATE - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { + else { Pointfs bed_shape; if (!gcode_result.bed_shape.empty()) // bed shape detected in the gcode @@ -337,7 +331,6 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& } wxGetApp().plater()->set_bed_shape(bed_shape, "", "", true); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE m_time_statistics = gcode_result.time_statistics; } @@ -351,12 +344,10 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: if (m_vertices_count == 0) return; -#if ENABLE_GCODE_VIEWER_AS_STATE if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) // update tool colors from config stored in the gcode m_tool_colors = decode_colors(gcode_result.extruder_colors); else -#endif // ENABLE_GCODE_VIEWER_AS_STATE // update tool colors m_tool_colors = decode_colors(str_tool_colors); @@ -841,17 +832,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (size_t i = 0; i < m_vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; -#if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) // for the gcode viewer we need all moves to correctly size the printbed m_paths_bounding_box.merge(move.position.cast()); else { -#endif // ENABLE_GCODE_VIEWER_AS_STATE if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) m_paths_bounding_box.merge(move.position.cast()); -#if ENABLE_GCODE_VIEWER_AS_STATE } -#endif // ENABLE_GCODE_VIEWER_AS_STATE } // add origin diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dc8b21ce8d..2a744d72d7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1913,12 +1913,12 @@ void GLCanvas3D::zoom_to_selection() _zoom_to_box(m_selection.get_bounding_box()); } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void GLCanvas3D::zoom_to_gcode() { _zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER void GLCanvas3D::select_view(const std::string& direction) { @@ -2696,9 +2696,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); -#if ENABLE_GCODE_VIEWER_AS_STATE if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) -#endif // ENABLE_GCODE_VIEWER_AS_STATE _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); } @@ -4280,17 +4278,15 @@ void GLCanvas3D::update_ui_from_settings() } #endif // ENABLE_RETINA_GL -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); #else bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER } - - GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const { WipeTowerInfo wti; @@ -5385,22 +5381,16 @@ static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) void GLCanvas3D::_render_background() const { #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STATE bool use_error_color = false; if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { use_error_color = m_dynamic_background_enabled; -#else - bool use_error_color = m_dynamic_background_enabled; -#endif // ENABLE_GCODE_VIEWER_AS_STATE if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); else { BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_paths_bounding_box()) : false; } -#if ENABLE_GCODE_VIEWER_AS_STATE } -#endif // ENABLE_GCODE_VIEWER_AS_STATE #endif // ENABLE_GCODE_VIEWER glsafe(::glPushMatrix()); @@ -7119,20 +7109,13 @@ void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning bool show = false; if (!m_volumes.empty()) show = _is_any_volume_outside(); - else - { -#if ENABLE_GCODE_VIEWER_AS_STATE - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) - { + else { + if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) show = !test_volume.contains(paths_volume); } -#else - BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); - show = (test_volume.radius() > 0.0) ? !test_volume.contains(m_gcode_viewer.get_bounding_box()) : false; -#endif // ENABLE_GCODE_VIEWER_AS_STATE } _set_warning_texture(warning, show); #else diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 0b5a357fdb..15bb3e6f06 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -625,9 +625,9 @@ public: void zoom_to_bed(); void zoom_to_volumes(); void zoom_to_selection(); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void zoom_to_gcode(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER void select_view(const std::string& direction); void update_volumes_colors_by_extruder(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 2fb60bf374..433e026f6c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -754,7 +754,7 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const dialog.GetPaths(input_files); } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const { input_file.Clear(); @@ -766,7 +766,7 @@ void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const if (dialog.ShowModal() == wxID_OK) input_file = dialog.GetPath(); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER bool GUI_App::switch_language() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index ad874bd1ef..aa94475319 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -156,9 +156,9 @@ public: void keyboard_shortcuts(); void load_project(wxWindow *parent, wxString& input_file) const; void import_model(wxWindow *parent, wxArrayString& input_files) const; -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void load_gcode(wxWindow* parent, wxString& input_file) const; -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER static bool catch_error(std::function cb, const std::string& err); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 556f5de8cd..98c02322ee 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -13,9 +13,9 @@ #include "PresetBundle.hpp" #include "DoubleSlider.hpp" #include "Plater.hpp" -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER #include "MainFrame.hpp" -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER #include #include @@ -1196,11 +1196,11 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent & event) void Preview::load_print_as_fff(bool keep_z_range) { -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER if (wxGetApp().mainframe == nullptr) // avoid proessing while mainframe is being constructed return; -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER if (m_loaded || m_process->current_printer_technology() != ptFFF) return; @@ -1225,11 +1225,11 @@ void Preview::load_print_as_fff(bool keep_z_range) } } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer && !has_layers) #else if (! has_layers) -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER { #if ENABLE_GCODE_VIEWER hide_layers_slider(); @@ -1265,11 +1265,7 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); -#if ENABLE_GCODE_VIEWER_AS_STATE bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); -#else - bool gcode_preview_data_valid = print->is_step_done(psGCodeExport); -#endif // ENABLE_GCODE_VIEWER_AS_STATE #else bool gcode_preview_data_valid = print->is_step_done(psGCodeExport) && ! m_gcode_preview_data->empty(); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 778ce2134f..f017900f46 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -111,7 +111,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // initialize tabpanel and menubar init_tabpanel(); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER init_editor_menubar(); init_gcodeviewer_menubar(); @@ -129,7 +129,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #endif // _WIN32 #else init_menubar(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER // set default tooltip timer in msec // SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values @@ -243,9 +243,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S update_ui_from_settings(); // FIXME (?) if (m_plater != nullptr) { -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER m_plater->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER m_plater->show_action_buttons(true); } } @@ -291,7 +291,7 @@ void MainFrame::update_layout() Layout(); }; -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER ESettingsLayout layout = (m_mode == EMode::GCodeViewer) ? ESettingsLayout::GCodeViewer : (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : @@ -300,7 +300,7 @@ void MainFrame::update_layout() ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old; -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER if (m_layout == layout) return; @@ -356,14 +356,14 @@ void MainFrame::update_layout() m_plater->Show(); break; } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER case ESettingsLayout::GCodeViewer: { m_main_sizer->Add(m_plater, 1, wxEXPAND); m_plater->Show(); break; } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER } //#ifdef __APPLE__ @@ -398,7 +398,7 @@ void MainFrame::shutdown() } #endif // _WIN32 -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER if (m_plater != nullptr) { m_plater->stop_jobs(); @@ -423,7 +423,7 @@ void MainFrame::shutdown() // Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours // see: https://github.com/prusa3d/PrusaSlicer/issues/3964 if (m_plater) m_plater->reset_canvas_volumes(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER // Weird things happen as the Paint messages are floating around the windows being destructed. // Avoid the Paint messages by hiding the main window. @@ -436,11 +436,11 @@ void MainFrame::shutdown() m_settings_dialog.Close(); if (m_plater != nullptr) { -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER // restore sidebar if it was hidden when switching to gcode viewer mode if (m_restore_from_gcode_viewer.collapsed_sidebar) m_plater->collapse_sidebar(false); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). m_plater->get_mouse3d_controller().shutdown(); @@ -781,7 +781,7 @@ void MainFrame::on_sys_color_changed() msw_rescale_menu(menu_bar->GetMenu(id)); } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER #ifdef _MSC_VER // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, // as the simple numeric accelerators spoil all numeric data entry. @@ -855,7 +855,7 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, void MainFrame::init_editor_menubar() #else void MainFrame::init_menubar() -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER { #ifdef __APPLE__ wxMenuBar::SetAutoWindowMenu(false); @@ -1015,7 +1015,7 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { @@ -1023,13 +1023,13 @@ void MainFrame::init_menubar() set_mode(EMode::GCodeViewer); }, "", nullptr, [this]() { return m_plater != nullptr && m_plater->printer_technology() != ptSLA; }, this); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); } -#if !ENABLE_GCODE_VIEWER_AS_STATE +#if !ENABLE_GCODE_VIEWER #ifdef _MSC_VER // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, // as the simple numeric accelerators spoil all numeric data entry. @@ -1039,7 +1039,7 @@ void MainFrame::init_menubar() wxString sep = " - "; wxString sep_space = ""; #endif -#endif // !ENABLE_GCODE_VIEWER_AS_STATE +#endif // !ENABLE_GCODE_VIEWER // Edit menu wxMenu* editMenu = nullptr; @@ -1123,7 +1123,7 @@ void MainFrame::init_menubar() [this](){return can_change_view(); }, this); } -#if !ENABLE_GCODE_VIEWER_AS_STATE +#if !ENABLE_GCODE_VIEWER #if _WIN32 // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad wxAcceleratorEntry entries[6]; @@ -1136,7 +1136,7 @@ void MainFrame::init_menubar() wxAcceleratorTable accel(6, entries); SetAcceleratorTable(accel); #endif // _WIN32 -#endif // !ENABLE_GCODE_VIEWER_AS_STATE +#endif // !ENABLE_GCODE_VIEWER windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), @@ -1148,7 +1148,7 @@ void MainFrame::init_menubar() wxMenu* viewMenu = nullptr; if (m_plater) { viewMenu = new wxMenu(); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this)); #else // The camera control accelerators are captured by GLCanvas3D::on_char(). @@ -1169,7 +1169,7 @@ void MainFrame::init_menubar() "", nullptr, [this](){return can_change_view(); }, this); append_menu_item(viewMenu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [this](wxCommandEvent&) { select_view("right"); }, "", nullptr, [this](){return can_change_view(); }, this); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER viewMenu->AppendSeparator(); #if ENABLE_SLOPE_RENDERING wxMenu* options_menu = new wxMenu(); @@ -1191,7 +1191,7 @@ void MainFrame::init_menubar() } // Help menu -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER auto helpMenu = generate_help_menu(); #else auto helpMenu = new wxMenu(); @@ -1228,12 +1228,12 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); }); #endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER // menubar // assign menubar to frame after appending items, otherwise special items // will not be handled correctly -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER m_editor_menubar = new wxMenuBar(); m_editor_menubar->Append(fileMenu, _L("&File")); if (editMenu) m_editor_menubar->Append(editMenu, _L("&Edit")); @@ -1253,16 +1253,16 @@ void MainFrame::init_menubar() wxGetApp().add_config_menu(menubar); menubar->Append(helpMenu, _(L("&Help"))); SetMenuBar(menubar); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER #ifdef __APPLE__ // This fixes a bug on Mac OS where the quit command doesn't emit window close events // wx bug: https://trac.wxwidgets.org/ticket/18328 -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu(); #else wxMenu *apple_menu = menubar->OSXGetAppleMenu(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER if (apple_menu != nullptr) { apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(); @@ -1271,14 +1271,14 @@ void MainFrame::init_menubar() #endif if (plater()->printer_technology() == ptSLA) -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER update_editor_menubar(); #else update_menubar(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void MainFrame::init_gcodeviewer_menubar() { wxMenu* fileMenu = new wxMenu; @@ -1412,13 +1412,13 @@ void MainFrame::set_mode(EMode mode) } } } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void MainFrame::update_editor_menubar() #else void MainFrame::update_menubar() -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER { const bool is_fff = plater()->printer_technology() == ptFFF; diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 8a3e4376b4..8961ad7172 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -68,7 +68,7 @@ class MainFrame : public DPIFrame wxString m_qs_last_input_file = wxEmptyString; wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER wxMenuBar* m_editor_menubar{ nullptr }; wxMenuBar* m_gcodeviewer_menubar{ nullptr }; @@ -79,7 +79,7 @@ class MainFrame : public DPIFrame }; RestoreFromGCodeViewer m_restore_from_gcode_viewer; -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER #if 0 wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now @@ -134,14 +134,14 @@ class MainFrame : public DPIFrame Old, New, Dlg, -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER GCodeViewer -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER }; ESettingsLayout m_layout{ ESettingsLayout::Unknown }; -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER public: enum class EMode : unsigned char { @@ -151,7 +151,7 @@ public: private: EMode m_mode{ EMode::Editor }; -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -173,7 +173,7 @@ public: void init_tabpanel(); void create_preset_tabs(); void add_created_tab(Tab* panel); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void init_editor_menubar(); void update_editor_menubar(); void init_gcodeviewer_menubar(); @@ -183,7 +183,7 @@ public: #else void init_menubar(); void update_menubar(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER void update_ui_from_settings(); bool is_loaded() const { return m_loaded; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 43cf27e30f..b87b3cce36 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -33,13 +33,11 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER #include "libslic3r/GCode/GCodeProcessor.hpp" #else -#if !ENABLE_GCODE_VIEWER #include "libslic3r/GCode/PreviewData.hpp" -#endif // !ENABLE_GCODE_VIEWER -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" @@ -2767,10 +2765,8 @@ void Plater::priv::reset() #if ENABLE_GCODE_VIEWER reset_gcode_toolpaths(); -#endif // ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STATE gcode_result.reset(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER // Stop and reset the Print content. this->background_process.reset(); @@ -4633,7 +4629,7 @@ void Plater::extract_config_from_project() load_files(input_paths, false, true); } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void Plater::load_gcode() { // Ask user for a gcode file name. @@ -4669,7 +4665,7 @@ void Plater::load_gcode(const wxString& filename) p->preview->reload_print(false); p->preview->get_canvas3d()->zoom_to_gcode(); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); } @@ -5443,7 +5439,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) void Plater::set_bed_shape() const { -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER set_bed_shape(p->config->option("bed_shape")->values, p->config->option("bed_custom_texture")->value, p->config->option("bed_custom_model")->value); @@ -5451,15 +5447,15 @@ void Plater::set_bed_shape() const p->set_bed_shape(p->config->option("bed_shape")->values, p->config->option("bed_custom_texture")->value, p->config->option("bed_custom_model")->value); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const { p->set_bed_shape(shape, custom_texture, custom_model, force_as_custom); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER void Plater::force_filament_colors_update() { @@ -5634,11 +5630,11 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); if (wxGetApp().mainframe != nullptr) -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER wxGetApp().mainframe->update_editor_menubar(); #else wxGetApp().mainframe->update_menubar(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER p->update_main_toolbar_tooltips(); @@ -5790,24 +5786,24 @@ bool Plater::init_view_toolbar() return p->init_view_toolbar(); } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void Plater::enable_view_toolbar(bool enable) { p->view_toolbar.set_enabled(enable); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER bool Plater::init_collapse_toolbar() { return p->init_collapse_toolbar(); } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void Plater::enable_collapse_toolbar(bool enable) { p->collapse_toolbar.set_enabled(enable); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER const Camera& Plater::get_camera() const { @@ -5936,9 +5932,9 @@ bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 59d595bbe4..8142a7c359 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -173,10 +173,10 @@ public: void add_model(bool imperial_units = false); void import_sl1_archive(); void extract_config_from_project(); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void load_gcode(); void load_gcode(const wxString& filename); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); // To be called when providing a list of files to the GUI slic3r on command line. @@ -256,9 +256,9 @@ public: bool search_string_getter(int idx, const char** label, const char** tooltip); // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void clear_undo_redo_stack_main(); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. void enter_gizmos_stack(); void leave_gizmos_stack(); @@ -322,13 +322,13 @@ public: void sys_color_changed(); bool init_view_toolbar(); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void enable_view_toolbar(bool enable); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER bool init_collapse_toolbar(); -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void enable_collapse_toolbar(bool enable); -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER const Camera& get_camera() const; Camera& get_camera(); @@ -352,19 +352,16 @@ public: void update_preview_moves_slider(); void reset_gcode_toolpaths(); -#endif // ENABLE_GCODE_VIEWER - -#if ENABLE_GCODE_VIEWER_AS_STATE void reset_last_loaded_gcode() { m_last_loaded_gcode = ""; } -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER const Mouse3DController& get_mouse3d_controller() const; Mouse3DController& get_mouse3d_controller(); void set_bed_shape() const; -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. class SuppressSnapshots @@ -415,9 +412,9 @@ private: bool m_tracking_popup_menu = false; wxString m_tracking_popup_menu_error_message; -#if ENABLE_GCODE_VIEWER_AS_STATE +#if ENABLE_GCODE_VIEWER wxString m_last_loaded_gcode; -#endif // ENABLE_GCODE_VIEWER_AS_STATE +#endif // ENABLE_GCODE_VIEWER void suppress_snapshots(); void allow_snapshots(); From b3f8ae5ca75b52ef74e8da2be2392ea8339dcab2 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 3 Aug 2020 15:36:55 +0200 Subject: [PATCH 233/826] Notifications & warning dialog notifications dialog with warnings produced by slicing is shown before exporting --- resources/icons/cancel.svg | 10 + resources/icons/cross_focus_large.svg | 81 ++ resources/icons/timer_dot.svg | 72 ++ resources/icons/timer_dot_empty.svg | 73 ++ src/imgui/imconfig.h | 7 +- src/libslic3r/GCode.cpp | 1 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/BackgroundSlicingProcess.cpp | 10 +- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 7 + src/slic3r/GUI/GLCanvas3D.cpp | 41 +- src/slic3r/GUI/GUI_App.cpp | 10 +- src/slic3r/GUI/GUI_App.hpp | 3 + src/slic3r/GUI/ImGuiWrapper.cpp | 32 +- src/slic3r/GUI/ImGuiWrapper.hpp | 3 + src/slic3r/GUI/Mouse3DController.cpp | 3 + src/slic3r/GUI/NotificationManager.cpp | 918 ++++++++++++++++++++ src/slic3r/GUI/NotificationManager.hpp | 268 ++++++ src/slic3r/GUI/Plater.cpp | 225 +++-- src/slic3r/GUI/Plater.hpp | 7 +- src/slic3r/Utils/PresetUpdater.cpp | 101 ++- src/slic3r/Utils/PresetUpdater.hpp | 6 +- 21 files changed, 1791 insertions(+), 89 deletions(-) create mode 100644 resources/icons/cancel.svg create mode 100644 resources/icons/cross_focus_large.svg create mode 100644 resources/icons/timer_dot.svg create mode 100644 resources/icons/timer_dot_empty.svg create mode 100644 src/slic3r/GUI/NotificationManager.cpp create mode 100644 src/slic3r/GUI/NotificationManager.hpp diff --git a/resources/icons/cancel.svg b/resources/icons/cancel.svg new file mode 100644 index 0000000000..da44606a08 --- /dev/null +++ b/resources/icons/cancel.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/resources/icons/cross_focus_large.svg b/resources/icons/cross_focus_large.svg new file mode 100644 index 0000000000..c246f2bd9e --- /dev/null +++ b/resources/icons/cross_focus_large.svg @@ -0,0 +1,81 @@ + +image/svg+xml + + + + + + + + + + + diff --git a/resources/icons/timer_dot.svg b/resources/icons/timer_dot.svg new file mode 100644 index 0000000000..3a77962b60 --- /dev/null +++ b/resources/icons/timer_dot.svg @@ -0,0 +1,72 @@ + +image/svg+xml + + + + diff --git a/resources/icons/timer_dot_empty.svg b/resources/icons/timer_dot_empty.svg new file mode 100644 index 0000000000..a8e776b49e --- /dev/null +++ b/resources/icons/timer_dot_empty.svg @@ -0,0 +1,73 @@ + + + +image/svg+xml + + + + \ No newline at end of file diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index d32f64aa4b..feda857ae2 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -113,7 +113,12 @@ namespace ImGui const char PrinterSlaIconMarker = 0x6; const char FilamentIconMarker = 0x7; const char MaterialIconMarker = 0x8; - + const char CloseIconMarker = 0xB; + const char CloseIconHoverMarker = 0xC; + const char TimerDotMarker = 0xE; + const char TimerDotEmptyMarker = 0xF; + const char WarningMarker = 0x10; + const char ErrorMarker = 0x11; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 35dc5a53bd..7d80677184 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -686,6 +686,7 @@ std::vector>> GCode::collec std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; }); std::vector>> layers_to_print; + // Merge numerically very close Z values. for (size_t i = 0; i < ordering.size();) { // Find the last layer with roughly the same print_z. diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 7e02c0fdd7..57e84f71ea 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -163,6 +163,8 @@ set(SLIC3R_GUI_SOURCES GUI/InstanceCheck.hpp GUI/Search.cpp GUI/Search.hpp + GUI/NotificationManager.cpp + GUI/NotificationManager.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 8d50998c48..7309654a85 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -89,11 +89,13 @@ void BackgroundSlicingProcess::process_fff() { assert(m_print == m_fff_print); m_print->process(); - wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); + wxCommandEvent evt(m_event_slicing_completed_id); + evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp)); + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb); - if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); //FIXME localize the messages // Perform the final post-processing of the export path by applying the print statistics over the file name. std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); @@ -124,6 +126,7 @@ void BackgroundSlicingProcess::process_fff() run_post_process_scripts(export_path, m_fff_print->config()); m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); } else if (! m_upload_job.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); prepare_upload(); } else { m_print->set_status(100, _utf8(L("Slicing complete"))); @@ -149,6 +152,8 @@ void BackgroundSlicingProcess::process_sla() m_print->process(); if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); + const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); Zipper zipper(export_path); @@ -170,6 +175,7 @@ void BackgroundSlicingProcess::process_sla() m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str()); } else if (! m_upload_job.empty()) { + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); prepare_upload(); } else { m_print->set_status(100, _utf8(L("Slicing complete"))); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 38e9e10755..c4672f1b40 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -60,6 +60,10 @@ public: // The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. void set_finished_event(int event_id) { m_event_finished_id = event_id; } + // The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to + // specified path or uploaded. + // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. + void set_export_began_event(int event_id) { m_event_export_began_id = event_id; } // Activate either m_fff_print or m_sla_print. // Return true if changed. @@ -190,6 +194,9 @@ private: int m_event_slicing_completed_id = 0; // wxWidgets command ID to be sent to the plater to inform that the task finished. int m_event_finished_id = 0; + // wxWidgets command ID to be sent to the plater to inform that the G-code is being exported. + int m_event_export_began_id = 0; + }; }; // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5109d24264..1edd7aa2b5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -31,6 +31,7 @@ #include "GUI_ObjectManipulation.hpp" #include "Mouse3DController.hpp" #include "I18N.hpp" +#include "NotificationManager.hpp" #if ENABLE_RETINA_GL #include "slic3r/Utils/RetinaHelper.hpp" @@ -651,19 +652,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool m_warnings.emplace_back(warning); std::sort(m_warnings.begin(), m_warnings.end()); + + std::string text; + switch (warning) { + case ObjectOutside: text = L("An object outside the print area was detected."); break; + case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; + case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; + case SomethingNotShown: text = L("Some objects are not visible."); break; + case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n" + "Resolve the current problem to continue slicing."), + *(wxGetApp().plater()->get_current_canvas3D())); + break; + } + if (!text.empty()) + wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D())); } else { if (it == m_warnings.end()) // deactivating something that is not active is an easy task return; m_warnings.erase(it); - if (m_warnings.empty()) { // nothing remains to be shown + + std::string text; + switch (warning) { + case ObjectOutside: text = L("An object outside the print area was detected."); break; + case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; + case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; + case SomethingNotShown: text = L("Some objects are not visibl.e"); break; + case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break; + } + if (!text.empty()) + wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text); + + /*if (m_warnings.empty()) { // nothing remains to be shown reset(); m_msg_text = "";// save information for rescaling return; - } + }*/ } - + /* // Look at the end of our vector and generate proper texture. std::string text; bool red_colored = false; @@ -685,6 +712,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool // save information for rescaling m_msg_text = text; m_is_colored_red = red_colored; + */ } @@ -2074,6 +2102,8 @@ void GLCanvas3D::render() std::string tooltip; + + // Negative coordinate means out of the window, likely because the window was deactivated. // In that case the tooltip should be hidden. if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) @@ -2103,6 +2133,8 @@ void GLCanvas3D::render() m_tooltip.render(m_mouse.position, *this); wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); + + wxGetApp().plater()->get_notification_manager()->render_notifications(*this); wxGetApp().imgui()->render(); @@ -3418,6 +3450,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #ifdef SLIC3R_DEBUG_MOUSE_EVENTS printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ + m_dirty = true; // do not return if dragging or tooltip not empty to allow for tooltip update if (!m_mouse.dragging && m_tooltip.is_empty()) return; @@ -3811,7 +3844,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_gizmos.reset_all_states(); // Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over. - if (m_picking_enabled) + //if (m_picking_enabled) m_dirty = true; } else diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index bfb1586195..49d08565ac 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -54,6 +54,7 @@ #include "Mouse3DController.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" +#include "NotificationManager.hpp" #ifdef __WXMSW__ #include @@ -384,7 +385,7 @@ bool GUI_App::on_init_inner() // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); -#ifdef __WXMSW__ +#ifdef __WXMSW__ associate_3mf_files(); #endif // __WXMSW__ @@ -392,6 +393,11 @@ bool GUI_App::on_init_inner() Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { app_config->set("version_online", into_u8(evt.GetString())); app_config->save(); + if(this->plater_ != nullptr) { + if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D())); + } + } }); // initialize label colors and fonts @@ -1439,7 +1445,7 @@ void GUI_App::check_updates(const bool verbose) PresetUpdater::UpdateResult updater_result; try { - updater_result = preset_updater->config_update(app_config->orig_version()); + updater_result = preset_updater->config_update(app_config->orig_version(), verbose); if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { mainframe->Close(); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index c2b257f458..922d6173df 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -194,12 +194,15 @@ public: Plater* plater(); Model& model(); + AppConfig* app_config{ nullptr }; PresetBundle* preset_bundle{ nullptr }; PresetUpdater* preset_updater{ nullptr }; MainFrame* mainframe{ nullptr }; Plater* plater_{ nullptr }; + PresetUpdater* get_preset_updater() { return preset_updater; } + wxNotebook* tab_panel() const ; int extruders_cnt() const; int extruders_edited_cnt() const; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index a21194d94e..266472ecaf 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -37,11 +37,17 @@ namespace GUI { static const std::map font_icons = { - {ImGui::PrintIconMarker , "cog" }, - {ImGui::PrinterIconMarker , "printer" }, - {ImGui::PrinterSlaIconMarker, "sla_printer"}, - {ImGui::FilamentIconMarker , "spool" }, - {ImGui::MaterialIconMarker , "resin" } + {ImGui::PrintIconMarker , "cog" }, + {ImGui::PrinterIconMarker , "printer" }, + {ImGui::PrinterSlaIconMarker, "sla_printer" }, + {ImGui::FilamentIconMarker , "spool" }, + {ImGui::MaterialIconMarker , "resin" }, + {ImGui::CloseIconMarker , "cross" }, + {ImGui::CloseIconHoverMarker, "cross_focus_large" }, + {ImGui::TimerDotMarker , "timer_dot" }, + {ImGui::TimerDotEmptyMarker , "timer_dot_empty" }, + {ImGui::WarningMarker , "flag_green" }, + {ImGui::ErrorMarker , "flag_red" } }; ImGuiWrapper::ImGuiWrapper() @@ -265,6 +271,11 @@ void ImGuiWrapper::set_next_window_bg_alpha(float alpha) ImGui::SetNextWindowBgAlpha(alpha); } +void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond) +{ + ImGui::SetNextWindowSize(ImVec2(x, y), cond); +} + bool ImGuiWrapper::begin(const std::string &name, int flags) { return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); @@ -296,12 +307,23 @@ bool ImGuiWrapper::button(const wxString &label) return ImGui::Button(label_utf8.c_str()); } +bool ImGuiWrapper::button(const wxString& label, float width, float height) +{ + auto label_utf8 = into_u8(label); + return ImGui::Button(label_utf8.c_str(), ImVec2(width, height)); +} + bool ImGuiWrapper::radio_button(const wxString &label, bool active) { auto label_utf8 = into_u8(label); return ImGui::RadioButton(label_utf8.c_str(), active); } +bool ImGuiWrapper::image_button() +{ + return false; +} + bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index dc62e57a08..ee553c4b6d 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -57,6 +57,7 @@ public: void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); + void set_next_window_size(float x, float y, ImGuiCond cond); bool begin(const std::string &name, int flags = 0); bool begin(const wxString &name, int flags = 0); @@ -65,7 +66,9 @@ public: void end(); bool button(const wxString &label); + bool button(const wxString& label, float width, float height); bool radio_button(const wxString &label, bool active); + bool image_button(); bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index ec7cd8d457..33f0d63793 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -7,6 +7,7 @@ #include "AppConfig.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" +#include "NotificationManager.hpp" #include @@ -403,6 +404,8 @@ void Mouse3DController::disconnected() m_params_by_device[m_device_str] = m_params_ui; m_device_str.clear(); m_connected = false; + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D())); + wxGetApp().plater()->CallAfter([]() { Plater *plater = wxGetApp().plater(); if (plater != nullptr) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp new file mode 100644 index 0000000000..b7301f3d82 --- /dev/null +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -0,0 +1,918 @@ +#include "NotificationManager.hpp" + +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" +#include "ImGuiWrapper.hpp" + +#include "wxExtensions.hpp" + +#include +#include +#include +#include + + + + +#define NOTIFICATION_MAX_MOVE 3.0f + +#define GAP_WIDTH 10.0f +#define SPACE_RIGHT_PANEL 10.0f + +namespace Slic3r { +namespace GUI { + +wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent); +wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); +wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent); + +namespace Notifications_Internal{ + void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) + { + if (fading_out) + ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity)); + else + ImGui::PushStyleColor(idx, col); + } +} +//ScalableBitmap bmp_icon; +//------PopNotification-------- +NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) : + m_data (n) + , m_id (id) + , m_remaining_time (n.duration) + , m_last_remaining_time (n.duration) + , m_counting_down (n.duration != 0) + , m_text1 (n.text1) + , m_hypertext (n.hypertext) + , m_text2 (n.text2) + , m_evt_handler (evt_handler) +{ + init(); +} +NotificationManager::PopNotification::~PopNotification() +{ +} +NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y) +{ + if (m_finished) + return RenderResult::Finished; + if (m_close_pending) { + // request of extra frame will be done in caller function by ret val ClosePending + m_finished = true; + return RenderResult::ClosePending; + } + if (m_hidden) { + m_top_y = initial_y - GAP_WIDTH; + return RenderResult::Static; + } + RenderResult ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static; + Size cnv_size = canvas.get_canvas_size(); + ImGuiWrapper& imgui = *wxGetApp().imgui(); + bool shown = true; + std::string name; + ImVec2 mouse_pos = ImGui::GetMousePos(); + + if (m_line_height != ImGui::CalcTextSize("A").y) + init(); + + set_next_window_size(imgui); + + //top y of window + m_top_y = initial_y + m_window_height; + //top right position + ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y); + imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); + imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); + + //find if hovered + if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height) + { + ImGui::SetNextWindowFocus(); + ret_val = RenderResult::Hovered; + //reset fading + m_fading_out = false; + m_current_fade_opacity = 1.f; + m_remaining_time = m_data.duration; + m_countdown_frame = 0; + } + + if (m_counting_down && m_remaining_time < 0) + m_close_pending = true; + + if (m_close_pending) { + // request of extra frame will be done in caller function by ret val ClosePending + m_finished = true; + return RenderResult::ClosePending; + } + + // color change based on fading out + bool fading_pop = false; + if (m_fading_out) { + if (!m_paused) + m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + fading_pop = true; + } + // background color + if (m_is_gray) { + ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } else if (m_data.level == NotificationLevel::ErrorNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } else if (m_data.level == NotificationLevel::WarningNotification) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + backcolor.y += 0.15f; + Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + } + + //name of window - probably indentifies window and is shown so last_end add whitespaces according to id + for (size_t i = 0; i < m_id; i++) + name += " "; + if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) { + if (shown) { + + ImVec2 win_size = ImGui::GetWindowSize(); + + + //FIXME: dont forget to us this for texts + //GUI::format(_utf8(L())); + + /* + //countdown numbers + ImGui::SetCursorPosX(15); + ImGui::SetCursorPosY(15); + imgui.text(std::to_string(m_remaining_time).c_str()); + */ + if(m_counting_down) + render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + render_left_sign(imgui); + render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y); + if (m_multiline && m_lines_count > 3) + render_minimize_button(imgui, win_pos.x, win_pos.y); + } else { + // the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button) + m_close_pending = true; + canvas.set_as_dirty(); + } + } + imgui.end(); + + if (fading_pop) { + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + } + if (m_is_gray) + ImGui::PopStyleColor(); + else if (m_data.level == NotificationLevel::ErrorNotification) + ImGui::PopStyleColor(); + else if (m_data.level == NotificationLevel::WarningNotification) + ImGui::PopStyleColor(); + return ret_val; +} +void NotificationManager::PopNotification::init() +{ + std::string text = m_text1 + " " + m_hypertext; + int last_end = 0; + m_lines_count = 0; + + //determine line width + m_line_height = ImGui::CalcTextSize("A").y; + + m_left_indentation = m_line_height; + if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + float picture_width = ImGui::CalcTextSize(text.c_str()).x; + m_left_indentation = picture_width + m_line_height / 2; + } + m_window_width_offset = m_left_indentation + m_line_height * 2; + m_window_width = m_line_height * 25; + + // count lines + m_endlines.clear(); + while (last_end < text.length() - 1) + { + int next_hard_end = text.find_first_of('\n', last_end); + if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) { + //next line is ended by '/n' + m_endlines.push_back(next_hard_end); + last_end = next_hard_end + 1; + } + else { + // find next suitable endline + if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) { + // more than one line till end + int next_space = text.find_first_of(' ', last_end); + if (next_space > 0) { + int next_space_candidate = text.find_first_of(' ', next_space + 1); + while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { + next_space = next_space_candidate; + next_space_candidate = text.find_first_of(' ', next_space + 1); + } + m_endlines.push_back(next_space); + last_end = next_space + 1; + } + } + else { + m_endlines.push_back(text.length()); + last_end = text.length(); + } + + } + m_lines_count++; + } +} +void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) +{ + if (m_multiline) { + m_window_height = m_lines_count * m_line_height; + }else + { + m_window_height = 2 * m_line_height; + } + m_window_height += 1 * m_line_height; // top and bottom +} + +void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + float x_offset = m_left_indentation; + std::string fulltext = m_text1 + m_hypertext; //+ m_text2; + ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); + // text posistions are calculated by lines count + // large texts has "more" button or are displayed whole + // smaller texts are divided as one liners and two liners + if (m_lines_count > 2) { + if (m_multiline) { + + int last_end = 0; + float starting_y = m_line_height/2;//10; + float shift_y = m_line_height;// -m_line_height / 20; + for (size_t i = 0; i < m_lines_count; i++) { + std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); + last_end = m_endlines[i] + 1; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + imgui.text(line.c_str()); + } + //hyperlink text + if (!m_hypertext.empty()) + { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); + } + + + } else { + // line1 + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + // line2 + std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1); + if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) + { + line = line.substr(0, line.length() - 6); + line += ".."; + }else + line += " "; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); + imgui.text(line.c_str()); + // "More" hypertext + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); + } + } else { + //text 1 + float cursor_y = win_size.y / 2 - text_size.y / 2; + float cursor_x = x_offset; + if(m_lines_count > 1) { + // line1 + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + // line2 + std::string line = m_text1.substr(m_endlines[0] + 1); + cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(line.c_str()); + cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; + } else { + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text1.c_str()); + cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x; + } + //hyperlink text + if (!m_hypertext.empty()) + { + render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext); + } + + //notification text 2 + //text 2 is suposed to be after the hyperlink - currently it is not used + /* + if (!m_text2.empty()) + { + ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str()); + ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text2.c_str()); + } + */ + } +} + +void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more) +{ + //invisible button + ImVec2 part_size = ImGui::CalcTextSize(text.c_str()); + ImGui::SetCursorPosX(text_x -4); + ImGui::SetCursorPosY(text_y -5); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + if (imgui.button(" ", part_size.x + 6, part_size.y + 10)) + { + if (more) + { + m_multiline = true; + set_next_window_size(imgui); + } + else { + on_text_click(); + m_close_pending = true; + } + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + //hover color + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) + orange_color.y += 0.2f; + + //text + Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); + ImGui::SetCursorPosX(text_x); + ImGui::SetCursorPosY(text_y); + imgui.text(text.c_str()); + ImGui::PopStyleColor(); + + //underline + ImVec2 lineEnd = ImGui::GetItemRectMax(); + lineEnd.y -= 2; + ImVec2 lineStart = lineEnd; + lineStart.x = ImGui::GetItemRectMin().x; + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f)))); + +} + +void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + orange_color.w = 0.8f; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + //button - if part if treggered + std::string button_text; + button_text = ImGui::CloseIconMarker; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_multiline? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CloseIconHoverMarker; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.25f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + m_close_pending = true; + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0))) + { + m_close_pending = true; + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} +void NotificationManager::PopNotification::render_countdown(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + /* + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + + //countdown dots + std::string dot_text; + dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; + ImGui::SetCursorPosX(win_size.x - m_line_height); + //ImGui::SetCursorPosY(win_size.y / 2 - 24); + ImGui::SetCursorPosY(0); + imgui.text(dot_text.c_str()); + + dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; + ImGui::SetCursorPosX(win_size.x - m_line_height); + //ImGui::SetCursorPosY(win_size.y / 2 - 9); + ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2); + imgui.text(dot_text.c_str()); + + dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker; + ImGui::SetCursorPosX(win_size.x - m_line_height); + //ImGui::SetCursorPosY(win_size.y / 2 + 6); + ImGui::SetCursorPosY(win_size.y - m_line_height); + imgui.text(dot_text.c_str()); + */ + if (!m_fading_out && m_remaining_time <= m_data.duration / 4) { + m_fading_out = true; + m_fading_time = m_remaining_time; + } + + if (m_last_remaining_time != m_remaining_time) { + m_last_remaining_time = m_remaining_time; + m_countdown_frame = 0; + } + /* + //countdown line + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x); + invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame); + ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5); + ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f); + if (!m_paused) + m_countdown_frame++; + */ +} +void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) +{ + if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + std::string text; + text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + ImGui::SetCursorPosX(m_line_height / 3); + ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2); + imgui.text(text.c_str()); + } +} +void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) +{ + ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + orange_color.w = 0.8f; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); + Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + + + //button - if part if treggered + std::string button_text; + button_text = ImGui::CloseIconMarker; + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1), + ImVec2(win_pos_x, win_pos_y + m_window_height), + true)) + { + button_text = ImGui::CloseIconHoverMarker; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(m_window_width - m_line_height * 2.25f); + ImGui::SetCursorPosY(m_window_height - button_size.y - 5); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + m_multiline = false; + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} +void NotificationManager::PopNotification::on_text_click() +{ + switch (m_data.type) { + case NotificationType::ExportToRemovableFinished : + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) + wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED)); + break; + case NotificationType::SlicingComplete : + //wxGetApp().plater()->export_gcode(false); + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) + wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); + break; + case NotificationType::PresetUpdateAviable : + //wxGetApp().plater()->export_gcode(false); + assert(m_evt_handler != nullptr); + if (m_evt_handler != nullptr) + wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED)); + break; + case NotificationType::NewAppAviable: + wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); + break; + default: + break; + } +} +void NotificationManager::PopNotification::update(const NotificationData& n) +{ + m_text1 = n.text1; + m_hypertext = n.hypertext; + m_text2 = n.text2; + init(); +} +bool NotificationManager::PopNotification::compare_text(const std::string& text) +{ + std::string t1(m_text1); + std::string t2(text); + t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end()); + t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end()); + if (t1.compare(t2) == 0) + return true; + return false; +} + +NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) : + NotificationManager::PopNotification(n, id, evt_handler) +{ + set_large(large); +} +void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (!m_is_large) + PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + else { + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + + ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); + float x_offset = m_left_indentation; + std::string fulltext = m_text1 + m_hypertext + m_text2; + ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); + float cursor_y = win_size.y / 2 - text_size.y / 2; + if (m_has_print_info) { + x_offset = 20; + cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_print_info.c_str()); + cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; + } + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text1.c_str()); + + render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); + + } +} +void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info) +{ + m_print_info = info; + m_has_print_info = true; + if(m_is_large) + m_lines_count = 2; +} +void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) +{ + m_is_large = l; + m_counting_down = !l; + m_hypertext = l ? _u8L("Export G-Code.") : std::string(); + m_hidden = !l; +} +//------NotificationManager-------- +NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : + m_evt_handler(evt_handler) +{ +} +NotificationManager::~NotificationManager() +{ + for (PopNotification* notification : m_pop_notifications) + { + delete notification; + } +} +void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp) +{ + auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(), + boost::bind(&NotificationData::type, _1) == type); + if (it != basic_notifications.end()) + push_notification_data( *it, canvas, timestamp); +} +void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp) +{ + push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp ); +} +void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp) +{ + switch (level) + { + case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification: + push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp); + break; + case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification: + push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp); + + break; + case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification: + push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp); + break; + default: + break; + } +} +void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas) +{ + set_all_slicing_errors_gray(false); + push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0); + close_notification_of_type(NotificationType::SlicingComplete); +} +void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step) +{ + NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + + NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler); + notification->set_object_id(oid); + notification->set_warning_step(warning_step); + if + (push_notification_data(notification, canvas, 0)) { + notification->set_gray(gray); + } + else { + delete notification; + } + +} +void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas) +{ + push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0); +} +void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas) +{ + push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, canvas, 0); +} +void NotificationManager::close_plater_error_notification() +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterError) { + notification->close(); + } + } +} +void NotificationManager::close_plater_warning_notification(const std::string& text) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) { + notification->close(); + } + } +} +void NotificationManager::set_all_slicing_errors_gray(bool g) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError) { + notification->set_gray(g); + } + } +} +void NotificationManager::set_all_slicing_warnings_gray(bool g) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning) { + notification->set_gray(g); + } + } +} +void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) { + notification->set_gray(g); + } + } +} +void NotificationManager::close_slicing_errors_and_warnings() +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) { + notification->close(); + } + } +} +void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large) +{ + std::string hypertext; + int time = 10; + if(large) + { + hypertext = _u8L("Export G-Code."); + time = 0; + } + NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext }; + + NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large); + if (push_notification_data(notification, canvas, timestamp)) { + } else { + delete notification; + } +} +void NotificationManager::set_slicing_complete_print_time(std::string info) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingComplete) { + dynamic_cast(notification)->set_print_info(info); + break; + } + } +} +void NotificationManager::set_slicing_complete_large(bool large) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingComplete) { + dynamic_cast(notification)->set_large(large); + break; + } + } +} +void NotificationManager::close_notification_of_type(const NotificationType type) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == type) { + notification->close(); + } + } +} +void NotificationManager::compare_warning_oids(const std::vector& living_oids) +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning) { + auto w = dynamic_cast(notification); + bool found = false; + for (size_t oid : living_oids) { + if (w->get_object_id() == oid) { + found = true; + break; + } + } + if (!found) + notification->close(); + } + } +} +bool NotificationManager::push_notification_data(const NotificationData ¬ification_data, GLCanvas3D& canvas, int timestamp) +{ + PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler); + bool r = push_notification_data(n, canvas, timestamp); + if (!r) + delete n; + return r; +} +bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp) +{ + // if timestamped notif, push only new one + if (timestamp != 0) { + if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) { + m_used_timestamps.insert(timestamp); + } else { + return false; + } + } + if (!this->find_older(notification)) { + m_pop_notifications.emplace_back(notification); + canvas.request_extra_frame(); + return true; + } else { + m_pop_notifications.back()->update(notification->get_data()); + canvas.request_extra_frame(); + return false; + } +} +void NotificationManager::render_notifications(GLCanvas3D& canvas) +{ + float last_x = 0.0f; + float current_height = 0.0f; + bool request_next_frame = false; + bool render_main = false; + bool hovered = false; + sort_notifications(); + // iterate thru notifications and render them / erease them + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) { + if ((*it)->get_finished()) { + delete (*it); + it = m_pop_notifications.erase(it); + } else { + (*it)->set_paused(m_hovered); + PopNotification::RenderResult res = (*it)->render(canvas, last_x); + if (res != PopNotification::RenderResult::Finished) { + last_x = (*it)->get_top() + GAP_WIDTH; + current_height = std::max(current_height, (*it)->get_current_top()); + render_main = true; + } + if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished) + request_next_frame = true; + if (res == PopNotification::RenderResult::Hovered) + hovered = true; + ++it; + } + } + m_hovered = hovered; + + //actualizate timers and request frame if needed + wxWindow* p = dynamic_cast (wxGetApp().plater()); + while (p->GetParent()) + p = p->GetParent(); + wxTopLevelWindow* top_level_wnd = dynamic_cast(p); + if (!top_level_wnd->IsActive()) + return; + + if (!m_hovered && m_last_time < wxGetLocalTime()) + { + if (wxGetLocalTime() - m_last_time == 1) + { + for(auto notification : m_pop_notifications) + { + notification->substract_remaining_time(); + } + } + m_last_time = wxGetLocalTime(); + } + + if (request_next_frame) + canvas.request_extra_frame(); +} + + +void NotificationManager::sort_notifications() +{ + std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) { + int n1l = (int)n1->get_data().level; + int n2l = (int)n2->get_data().level; + if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray()) + return true; + return (n1l < n2l); + }); +} + +bool NotificationManager::find_older(NotificationManager::PopNotification* notification) +{ + NotificationType type = notification->get_type(); + std::string text = notification->get_data().text1; + for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) { + if((*it)->get_type() == type && !(*it)->get_finished()) { + if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) { + if (!(*it)->compare_text(text)) + continue; + }else if (type == NotificationType::SlicingWarning) { + auto w1 = dynamic_cast(notification); + auto w2 = dynamic_cast(*it); + if (w1 != nullptr && w2 != nullptr) { + if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) { + continue; + } + } else { + continue; + } + } + + if (it != m_pop_notifications.end() - 1) + std::rotate(it, it + 1, m_pop_notifications.end()); + return true; + } + } + return false; +} + +void NotificationManager::dpi_changed() +{ + +} + +}//namespace GUI +}//namespace Slic3r diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp new file mode 100644 index 0000000000..d7037c53e4 --- /dev/null +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -0,0 +1,268 @@ +#ifndef slic3r_GUI_NotificationManager_hpp_ +#define slic3r_GUI_NotificationManager_hpp_ + +#include "Event.hpp" +#include "I18N.hpp" + +#include +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +using EjectDriveNotificationClickedEvent = SimpleEvent; +wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent); +using ExportGcodeNotificationClickedEvent = SimpleEvent; +wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); +using PresetUpdateAviableClickedEvent = SimpleEvent; +wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent); + +class GLCanvas3D; +class ImGuiWrapper; + +enum class NotificationType +{ + CustomNotification, + SlicingComplete, + SlicingNotPossible, + ExportToRemovableFinished, + Mouse3dDisconnected, + Mouse3dConnected, + NewPresetsAviable, + NewAppAviable, + PresetUpdateAviable, + LoadingFailed, + ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors + SlicingError, + SlicingWarning, + PlaterError, + PlaterWarning, + ApplyError + +}; +class NotificationManager +{ +public: + enum class NotificationLevel : int + { + ErrorNotification = 4, + WarningNotification = 3, + ImportantNotification = 2, + RegularNotification = 1, + }; + // duration 0 means not disapearing + struct NotificationData { + NotificationType type; + NotificationLevel level; + const int duration; + const std::string text1; + const std::string hypertext = std::string(); + const std::string text2 = std::string(); + }; + + //Pop notification - shows only once to user. + class PopNotification + { + public: + enum class RenderResult + { + Finished, + ClosePending, + Static, + Countdown, + Hovered + }; + PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler); + virtual ~PopNotification(); + RenderResult render(GLCanvas3D& canvas, const float& initial_y); + // close will dissapear notification on next render + void close() { m_close_pending = true; } + // data from newer notification of same type + void update(const NotificationData& n); + bool get_finished() const { return m_finished; } + // returns top after movement + float get_top() const { return m_top_y; } + //returns top in actual frame + float get_current_top() const { return m_top_y; } + const NotificationType get_type() const { return m_data.type; } + const NotificationData get_data() const { return m_data; } + const bool get_is_gray() const { return m_is_gray; } + // Call equals one second down + void substract_remaining_time() { m_remaining_time--; } + void set_gray(bool g) { m_is_gray = g; } + void set_paused(bool p) { m_paused = p; } + bool compare_text(const std::string& text); + protected: + // Call after every size change + void init(); + // Calculetes correct size but not se it in imgui! + virtual void set_next_window_size(ImGuiWrapper& imgui); + virtual void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x , const float win_pos_y); + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x , const float win_pos_y); + void render_countdown(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x , const float win_pos_y); + void render_hypertext(ImGuiWrapper& imgui, + const float text_x, const float text_y, + const std::string text, + bool more = false); + void render_left_sign(ImGuiWrapper& imgui); + void render_minimize_button(ImGuiWrapper& imgui, + const float win_pos_x, const float win_pos_y); + void on_text_click(); + + const NotificationData m_data; + + int m_id; + // Main text + std::string m_text1; + // Clickable text + std::string m_hypertext; + // Aditional text after hypertext - currently not used + std::string m_text2; + // Countdown variables + long m_remaining_time; + bool m_counting_down; + long m_last_remaining_time; + bool m_paused{ false }; + int m_countdown_frame{ 0 }; + bool m_fading_out{ false }; + // total time left when fading beggins + float m_fading_time{ 0.0f }; + float m_current_fade_opacity{ 1.f }; + // If hidden the notif is alive but not visible to user + bool m_hidden { false }; + // m_finished = true - does not render, marked to delete + bool m_finished { false }; + // Will go to m_finished next render + bool m_close_pending { false }; + // variables to count positions correctly + float m_window_width_offset; + float m_left_indentation; + // Total size of notification window - varies based on monitor + float m_window_height { 56.0f }; + float m_window_width { 450.0f }; + //Distance from bottom of notifications to top of this notification + float m_top_y { 0.0f }; + + // Height of text + // Used as basic scaling unit! + float m_line_height; + std::vector m_endlines; + // Gray are f.e. eorrors when its uknown if they are still valid + bool m_is_gray { false }; + //if multiline = true, notification is showing all lines(>2) + bool m_multiline { false }; + int m_lines_count{ 1 }; + wxEvtHandler* m_evt_handler; + }; + + class SlicingCompleteLargeNotification : public PopNotification + { + public: + SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds); + void set_large(bool l); + bool get_large() { return m_is_large; } + + void set_print_info(std::string info); + protected: + virtual void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) + override; + + bool m_is_large; + bool m_has_print_info { false }; + std::string m_print_info { std::string() }; + }; + + class SlicingWarningNotification : public PopNotification + { + public: + SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {} + void set_object_id(size_t id) { object_id = id; } + const size_t get_object_id() { return object_id; } + void set_warning_step(int ws) { warning_step = ws; } + const int get_warning_step() { return warning_step; } + protected: + size_t object_id; + int warning_step; + }; + + NotificationManager(wxEvtHandler* evt_handler); + ~NotificationManager(); + + + // only type means one of basic_notification (see below) + void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0); + // only text means Undefined type + void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0); + void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0); + // creates Slicing Error notification with custom text + void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas); + // creates Slicing Warning notification with custom text + void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step); + // marks slicing errors as gray + void set_all_slicing_errors_gray(bool g); + // marks slicing warings as gray + void set_all_slicing_warnings_gray(bool g); + void set_slicing_warning_gray(const std::string& text, bool g); + // imidietly stops showing slicing errors + void close_slicing_errors_and_warnings(); + void compare_warning_oids(const std::vector& living_oids); + void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas); + void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas); + void close_plater_error_notification(); + void close_plater_warning_notification(const std::string& text); + // creates special notification slicing complete + // if large = true prints printing time and export button + void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large); + void set_slicing_complete_print_time(std::string info); + void set_slicing_complete_large(bool large); + // renders notifications in queue and deletes expired ones + void render_notifications(GLCanvas3D& canvas); + // finds and closes all notifications of given type + void close_notification_of_type(const NotificationType type); + void dpi_changed(); +private: + //pushes notification into the queue of notifications that are rendered + //can be used to create custom notification + bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp); + bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp); + //finds older notification of same type and moves it to the end of queue. returns true if found + bool find_older(NotificationManager::PopNotification* notification); + void sort_notifications(); + + wxEvtHandler* m_evt_handler; + std::deque m_pop_notifications; + int m_next_id { 1 }; + long m_last_time { 0 }; + bool m_hovered { false }; + //timestamps used for slining finished - notification could be gone so it needs to be stored here + std::unordered_set m_used_timestamps; + + //prepared (basic) notifications + const std::vector basic_notifications = { + {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")}, + {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") }, + {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, + {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") }, + {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") }, + {NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more.")}, + {NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page.")}, + //{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, + //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, + //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification + }; +}; + +}//namespace GUI +}//namespace Slic3r + +#endif //slic3r_GUI_NotificationManager_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 761f574e15..9cfc717db9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -75,8 +75,10 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" +#include "../Utils/PresetUpdater.hpp" #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" +#include "NotificationManager.hpp" #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -102,6 +104,7 @@ wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); // Sidebar widgets @@ -716,7 +719,7 @@ struct Sidebar::priv wxButton *btn_export_gcode; wxButton *btn_reslice; ScalableButton *btn_send_gcode; - ScalableButton *btn_remove_device; + ScalableButton *btn_eject_device; ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected) bool is_collapsed {false}; @@ -889,12 +892,12 @@ Sidebar::Sidebar(Plater *parent) }; init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G"); - init_scalable_btn(&p->btn_remove_device, "eject_sd" , _L("Remove device") + "\tCtrl+T"); + init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device") + "\tCtrl+T"); init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U"); // regular buttons "Slice now" and "Export G-code" - const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4; + const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4; auto init_btn = [this](wxButton **btn, wxString label, const int button_height) { *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, wxSize(-1, button_height), wxBU_EXACTFIT); @@ -912,7 +915,7 @@ Sidebar::Sidebar(Plater *parent) complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND); complect_btns_sizer->Add(p->btn_send_gcode); complect_btns_sizer->Add(p->btn_export_gcode_removable); - complect_btns_sizer->Add(p->btn_remove_device); + complect_btns_sizer->Add(p->btn_eject_device); btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5); @@ -935,7 +938,7 @@ Sidebar::Sidebar(Plater *parent) p->plater->select_view_3D("Preview"); }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); - p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); + p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); }); p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); }); } @@ -1083,9 +1086,9 @@ void Sidebar::msw_rescale() p->object_info->msw_rescale(); p->btn_send_gcode->msw_rescale(); - p->btn_remove_device->msw_rescale(); + p->btn_eject_device->msw_rescale(); p->btn_export_gcode_removable->msw_rescale(); - const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4; + const int scaled_height = p->btn_eject_device->GetBitmap().GetHeight() + 4; p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height)); p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height)); @@ -1114,7 +1117,7 @@ void Sidebar::sys_color_changed() // btn...->msw_rescale() updates icon on button, so use it p->btn_send_gcode->msw_rescale(); - p->btn_remove_device->msw_rescale(); + p->btn_eject_device->msw_rescale(); p->btn_export_gcode_removable->msw_rescale(); p->scrolled->Layout(); @@ -1350,6 +1353,12 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); + + // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate + //if (p->plater->is_sidebar_collapsed()) + p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); + p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); + } if (ps.estimated_silent_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("stealth mode")); @@ -1385,15 +1394,16 @@ void Sidebar::enable_buttons(bool enable) p->btn_reslice->Enable(enable); p->btn_export_gcode->Enable(enable); p->btn_send_gcode->Enable(enable); - p->btn_remove_device->Enable(enable); + p->btn_eject_device->Enable(enable); p->btn_export_gcode_removable->Enable(enable); } -bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } -bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } -bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } -bool Sidebar::show_disconnect(bool show) const { return p->btn_remove_device->Show(show); } -bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); } +bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); } +bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); } +bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); } +bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); } +bool Sidebar::show_eject(bool show) const { return p->btn_eject_device->Show(show); } +bool Sidebar::get_eject_shown() const { return p->btn_eject_device->IsShown(); } bool Sidebar::is_multifilament() { @@ -1591,6 +1601,7 @@ struct Plater::priv GLToolbar view_toolbar; GLToolbar collapse_toolbar; Preview *preview; + NotificationManager* notification_manager; BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; @@ -1775,7 +1786,17 @@ struct Plater::priv void on_slicing_update(SlicingStatusEvent&); void on_slicing_completed(wxCommandEvent&); void on_process_completed(wxCommandEvent&); + void on_export_began(wxCommandEvent&); void on_layer_editing_toggled(bool enable); + void on_slicing_began(); + + void clear_warnings(); + void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); + void actualizate_warnings(const Model& model, size_t print_oid); + // Displays dialog window with list of warnings. + // Returns true if user clicks OK. + // Returns true if current_warnings vector is empty without showning the dialog + bool warnings_dialog(); void on_action_add(SimpleEvent&); void on_action_split_objects(SimpleEvent&); @@ -1826,7 +1847,7 @@ struct Plater::priv // Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes. bool writing_to_removable_device = { false }; bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } - + bool process_completed_with_error { false }; private: bool init_object_menu(); bool init_common_menu(wxMenu* menu, const bool is_part = false); @@ -1854,6 +1875,11 @@ private: * */ std::string m_last_fff_printer_profile_name; std::string m_last_sla_printer_profile_name; + + // vector of all warnings generated by last slicing + std::vector> current_warnings; + bool show_warning_dialog { false }; + }; const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); @@ -1899,6 +1925,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); + background_process.set_export_began_event(EVT_EXPORT_BEGAN); // Default printer technology for default config. background_process.select_technology(this->printer_technology); // Register progress callback from the Print class to the Plater. @@ -2010,8 +2037,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); - q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); + q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); + q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this); q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); }); q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); }); @@ -2038,16 +2066,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); #endif /* _WIN32 */ - this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) { + notification_manager = new NotificationManager(this->q); + this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); + this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); + this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); + + this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { if (evt.data.second) { this->show_action_buttons(this->ready_to_slice); - Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."), - evt.data.first.name, evt.data.first.path)); - } else - Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."), - evt.data.first.name, evt.data.first.path)); + notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); + } else { + notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); + } + }); + this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { + this->show_action_buttons(this->ready_to_slice); + if (!this->sidebar->get_eject_shown()) { + notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished); + } }); - this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); }); // Start the background thread and register this window as a target for update events. wxGetApp().removable_drive_manager()->init(this->q); #ifdef _WIN32 @@ -2675,6 +2714,8 @@ void Plater::priv::reset() { Plater::TakeSnapshot snapshot(q, _L("Reset Project")); + clear_warnings(); + set_project_filename(wxEmptyString); // Prevent toolpaths preview from rendering while we modify the Print object @@ -2844,22 +2885,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. std::string err = this->background_process.validate(); if (err.empty()) { + notification_manager->set_all_slicing_errors_gray(true); if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; } else { - // The print is not valid. - // Only show the error message immediately, if the top level parent of this window is active. - auto p = dynamic_cast(this->q); - while (p->GetParent()) - p = p->GetParent(); - auto *top_level_wnd = dynamic_cast(p); - if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) { - // The error returned from the Print needs to be translated into the local language. - GUI::show_error(this->q, err); - } else { - // Show the error message once the main window gets activated. - this->delayed_error_message = err; - } + // The print is not valid. + // Show error as notification. + notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D()); return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } } else if (! this->delayed_error_message.empty()) { @@ -2867,6 +2899,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; } + //actualizate warnings + if (invalidated != Print::APPLY_STATUS_UNCHANGED) { + actualizate_warnings(this->q->model(), this->background_process.current_print()->id().id); + notification_manager->set_all_slicing_warnings_gray(true); + show_warning_dialog = false; + process_completed_with_error = false; + } + if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { // The background processing was killed and it will not be restarted. @@ -2929,6 +2969,8 @@ bool Plater::priv::restart_background_process(unsigned int state) this->statusbar()->set_status_text(_L("Cancelling")); this->background_process.stop(); }); + if (!show_warning_dialog) + on_slicing_began(); return true; } } @@ -2955,6 +2997,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; + show_warning_dialog = true; if (! output_path.empty()) { background_process.schedule_export(output_path.string(), output_path_on_removable_media); } else { @@ -3433,11 +3476,20 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) state = print_object->step_state_with_warnings(static_cast(warning_step)); } // Now process state.warnings. + for (auto const& warning : state.warnings) { + if (warning.current) { + notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id.id, warning_step); + add_warning(warning, object_id.id); + } + } } } -void Plater::priv::on_slicing_completed(wxCommandEvent &) +void Plater::priv::on_slicing_completed(wxCommandEvent & evt) { + //notification_manager->push_notification(NotificationType::SlicingComplete, *q->get_current_canvas3D(), evt.GetInt()); + notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed()); + switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); @@ -3450,8 +3502,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) break; default: break; } -} +} +void Plater::priv::on_export_began(wxCommandEvent& evt) +{ + if (show_warning_dialog) + warnings_dialog(); +} +void Plater::priv::on_slicing_began() +{ + clear_warnings(); + notification_manager->close_notification_of_type(NotificationType::SlicingComplete); +} +void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) +{ + for (auto const& it : current_warnings) { + if (warning.message_id == it.first.message_id) { + if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message)) + return; + } + } + current_warnings.emplace_back(std::pair(warning, oid)); +} +void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid) +{ + std::vector living_oids; + living_oids.push_back(model.id().id); + living_oids.push_back(print_oid); + for (auto it = model.objects.begin(); it != model.objects.end(); ++it) { + living_oids.push_back((*it)->id().id); + } + notification_manager->compare_warning_oids(living_oids); +} +void Plater::priv::clear_warnings() +{ + notification_manager->close_slicing_errors_and_warnings(); + this->current_warnings.clear(); +} +bool Plater::priv::warnings_dialog() +{ + if (current_warnings.empty()) + return true; + std::string text = _u8L("There are active warnings concerning sliced models:\n"); + bool empt = true; + for (auto const& it : current_warnings) { + int next_n = it.first.message.find_first_of('\n', 0); + text += "\n"; + if (next_n != std::string::npos) + text += it.first.message.substr(0, next_n); + else + text += it.first.message; + } + //text += "\n\nDo you still wish to export?"; + wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK); + const auto res = msg_wingow.ShowModal(); + return res == wxID_OK; + +} void Plater::priv::on_process_completed(wxCommandEvent &evt) { // Stop the background task, wait until the thread goes into the "Idle" state. @@ -3470,14 +3577,13 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) if (error) { wxString message = evt.GetString(); if (message.IsEmpty()) - message = _L("Export failed"); - if (q->m_tracking_popup_menu) - // We don't want to pop-up a message box when tracking a pop-up menu. - // We postpone the error message instead. - q->m_tracking_popup_menu_error_message = message; - else - show_error(q, message); + message = _L("Export failed."); + notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D()); this->statusbar()->set_status_text(message); + const wxString invalid_str = _L("Invalid data"); + for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) + sidebar->set_btn_label(btn, invalid_str); + process_completed_with_error = true; } if (canceled) this->statusbar()->set_status_text(_L("Cancelled")); @@ -3503,18 +3609,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) default: break; } - if (canceled) { if (wxGetApp().get_mode() == comSimple) sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); show_action_buttons(true); } - else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) + else if (wxGetApp().get_mode() == comSimple) { - wxGetApp().removable_drive_manager()->set_exporting_finished(true); show_action_buttons(false); } - this->writing_to_removable_device = false; + else if (this->writing_to_removable_device) + { + show_action_buttons(false); + notification_manager->push_notification(NotificationType::ExportToRemovableFinished, *q->get_current_canvas3D()); + } + this->writing_to_removable_device = false; } void Plater::priv::on_layer_editing_toggled(bool enable) @@ -4156,7 +4265,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const sidebar->show_export(true) | sidebar->show_send(send_gcode_shown) | sidebar->show_export_removable(removable_media_status.has_removable_drives) | - sidebar->show_disconnect(removable_media_status.has_eject)) + sidebar->show_eject(removable_media_status.has_eject)) sidebar->Layout(); } else @@ -4168,7 +4277,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const sidebar->show_export(!ready_to_slice) | sidebar->show_send(send_gcode_shown && !ready_to_slice) | sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) | - sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject)) + sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject)) sidebar->Layout(); } } @@ -4731,6 +4840,9 @@ void Plater::export_gcode(bool prefer_removable) if (p->model.objects.empty()) return; + if (p->process_completed_with_error)//here + return; + // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. fs::path default_output_file; @@ -4990,7 +5102,6 @@ void Plater::export_toolpaths_to_obj() const p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); } - void Plater::reslice() { // Stop arrange and (or) optimize rotation tasks. @@ -5676,6 +5787,16 @@ Mouse3DController& Plater::get_mouse3d_controller() return p->mouse3d_controller; } +const NotificationManager* Plater::get_notification_manager() const +{ + return p->notification_manager; +} + +NotificationManager* Plater::get_notification_manager() +{ + return p->notification_manager; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a08b19fa35..24e93c80e7 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -47,6 +47,7 @@ class ObjectLayers; class ObjectList; class GLCanvas3D; class Mouse3DController; +class NotificationManager; struct Camera; class Bed3D; class GLToolbar; @@ -130,8 +131,9 @@ public: bool show_reslice(bool show) const; bool show_export(bool show) const; bool show_send(bool show) const; - bool show_disconnect(bool show)const; + bool show_eject(bool show)const; bool show_export_removable(bool show) const; + bool get_eject_shown() const; bool is_multifilament(); void update_mode(); bool is_collapsed(); @@ -338,6 +340,9 @@ public: Mouse3DController& get_mouse3d_controller(); void set_bed_shape() const; + + const NotificationManager* get_notification_manager() const; + NotificationManager* get_notification_manager(); // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. class SuppressSnapshots diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index c32613c468..7d316e77c2 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -27,6 +27,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/format.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Config/Version.hpp" #include "slic3r/Config/Snapshot.hpp" @@ -154,6 +155,9 @@ struct PresetUpdater::priv bool cancel; std::thread thread; + bool has_waiting_updates { false }; + Updates waiting_updates; + priv(); void set_download_prefs(AppConfig *app_config); @@ -165,6 +169,7 @@ struct PresetUpdater::priv void check_install_indices() const; Updates get_config_updates(const Semver& old_slic3r_version) const; void perform_updates(Updates &&updates, bool snapshot = true) const; + void set_waiting_updates(Updates u); }; PresetUpdater::priv::priv() @@ -326,7 +331,15 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) continue; } Slic3r::rename_file(idx_path_temp, idx_path); - index = std::move(new_index); + //if we rename path we need to change it in Index object too or create the object again + //index = std::move(new_index); + try { + index.load(idx_path); + } + catch (const std::exception& /* err */) { + BOOST_LOG_TRIVIAL(error) << format("Could not load downloaded index %1% for vendor %2%: invalid index?", idx_path, vendor.name); + continue; + } if (cancel) return; } @@ -632,6 +645,12 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons } } +void PresetUpdater::priv::set_waiting_updates(Updates u) +{ + waiting_updates = u; + has_waiting_updates = true; +} + PresetUpdater::PresetUpdater() : p(new priv()) {} @@ -690,9 +709,9 @@ void PresetUpdater::slic3r_update_notify() } } -PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3r_version) const +PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const { - if (! p->enabled_config_update) { return R_NOOP; } + if (! p->enabled_config_update) { return R_NOOP; } auto updates = p->get_config_updates(old_slic3r_version); if (updates.incompats.size() > 0) { @@ -779,30 +798,38 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver &old_slic3 } // regular update - BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", updates.updates.size()); + if (no_notification) { + BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size()); - std::vector updates_msg; - for (const auto &update : updates.updates) { - std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); - updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); - } + std::vector updates_msg; + for (const auto& update : updates.updates) { + std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); + updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); + } - GUI::MsgUpdateConfig dlg(updates_msg); + GUI::MsgUpdateConfig dlg(updates_msg); - const auto res = dlg.ShowModal(); - if (res == wxID_OK) { - BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; - p->perform_updates(std::move(updates)); + const auto res = dlg.ShowModal(); + if (res == wxID_OK) { + BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; + p->perform_updates(std::move(updates)); - // Reload global configuration - auto *app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); - return R_UPDATE_INSTALLED; + // Reload global configuration + auto* app_config = GUI::wxGetApp().app_config; + GUI::wxGetApp().preset_bundle->load_presets(*app_config); + GUI::wxGetApp().load_current_presets(); + return R_UPDATE_INSTALLED; + } + else { + BOOST_LOG_TRIVIAL(info) << "User refused the update"; + return R_UPDATE_REJECT; + } } else { - BOOST_LOG_TRIVIAL(info) << "User refused the update"; - return R_UPDATE_REJECT; + p->set_waiting_updates(updates); + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAviable, *(GUI::wxGetApp().plater()->get_current_canvas3D())); } + + // MsgUpdateConfig will show after the notificaation is clicked } else { BOOST_LOG_TRIVIAL(info) << "No configuration updates available."; } @@ -825,5 +852,37 @@ void PresetUpdater::install_bundles_rsrc(std::vector bundles, bool p->perform_updates(std::move(updates), snapshot); } +void PresetUpdater::on_update_notification_confirm() +{ + if (!p->has_waiting_updates) + return; + BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size()); + + std::vector updates_msg; + for (const auto& update : p->waiting_updates.updates) { + std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); + updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); + } + + GUI::MsgUpdateConfig dlg(updates_msg); + + const auto res = dlg.ShowModal(); + if (res == wxID_OK) { + BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; + p->perform_updates(std::move(p->waiting_updates)); + + // Reload global configuration + auto* app_config = GUI::wxGetApp().app_config; + GUI::wxGetApp().preset_bundle->load_presets(*app_config); + GUI::wxGetApp().load_current_presets(); + p->has_waiting_updates = false; + //return R_UPDATE_INSTALLED; + } + else { + BOOST_LOG_TRIVIAL(info) << "User refused the update"; + //return R_UPDATE_REJECT; + } + +} } diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index e186958284..0ca363c613 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -35,16 +35,20 @@ public: R_INCOMPAT_CONFIGURED, R_UPDATE_INSTALLED, R_UPDATE_REJECT, + R_UPDATE_NOTIFICATION }; // If updating is enabled, check if updates are available in cache, if so, ask about installation. // A false return value implies Slic3r should exit due to incompatibility of configuration. // Providing old slic3r version upgrade profiles on upgrade of an application even in case // that the config index installed from the Internet is equal to the index contained in the installation package. - UpdateResult config_update(const Semver &old_slic3r_version) const; + // no_notification = force modal textbox, otherwise some cases only shows notification + UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const; // "Update" a list of bundles from resources (behaves like an online update). void install_bundles_rsrc(std::vector bundles, bool snapshot = true) const; + + void on_update_notification_confirm(); private: struct priv; std::unique_ptr p; From 38239f09e3ea889aab14cc6c6bc2d6a27013981d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jun 2020 17:28:46 +0200 Subject: [PATCH 234/826] Fix remove_bottom_points function --- src/libslic3r/SLA/SupportPoint.hpp | 4 ++-- src/libslic3r/SLA/SupportPointGenerator.cpp | 11 ++++------ src/libslic3r/SLA/SupportPointGenerator.hpp | 2 +- src/libslic3r/SLAPrintSteps.cpp | 23 ++++++++++----------- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 202a614c32..455962cc40 100644 --- a/src/libslic3r/SLA/SupportPoint.hpp +++ b/src/libslic3r/SLA/SupportPoint.hpp @@ -29,13 +29,13 @@ struct SupportPoint float pos_y, float pos_z, float head_radius, - bool new_island) + bool new_island = false) : pos(pos_x, pos_y, pos_z) , head_front_radius(head_radius) , is_new_island(new_island) {} - SupportPoint(Vec3f position, float head_radius, bool new_island) + SupportPoint(Vec3f position, float head_radius, bool new_island = false) : pos(position) , head_front_radius(head_radius) , is_new_island(new_island) diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 78c2ced356..b598439cae 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -523,15 +523,12 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure } } -void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance) +void remove_bottom_points(std::vector &pts, float lvl) { // get iterator to the reorganized vector end - auto endit = - std::remove_if(pts.begin(), pts.end(), - [tolerance, gnd_lvl](const sla::SupportPoint &sp) { - double diff = std::abs(gnd_lvl - - double(sp.pos(Z))); - return diff <= tolerance; + auto endit = std::remove_if(pts.begin(), pts.end(), [lvl] + (const sla::SupportPoint &sp) { + return sp.pos.z() <= lvl; }); // erase all elements after the new end diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 2fe8e11fc7..1729230561 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -214,7 +214,7 @@ private: std::mt19937 m_rng; }; -void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance); +void remove_bottom_points(std::vector &pts, float lvl); }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index e421e9c1dc..ea016d5bb0 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -360,18 +360,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // removed them on purpose. No calculation will be done. po.m_supportdata->pts = po.transformed_support_points(); } - - // If the zero elevation mode is engaged, we have to filter out all the - // points that are on the bottom of the object - if (is_zero_elevation(po.config())) { - double tolerance = po.config().pad_enable.getBool() ? - po.m_config.pad_wall_thickness.getFloat() : - po.m_config.support_base_height.getFloat(); - - remove_bottom_points(po.m_supportdata->pts, - po.m_supportdata->emesh.ground_level(), - tolerance); - } } void SLAPrint::Steps::support_tree(SLAPrintObject &po) @@ -382,6 +370,17 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) if (pcfg.embed_object) po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); + + // If the zero elevation mode is engaged, we have to filter out all the + // points that are on the bottom of the object + if (is_zero_elevation(po.config())) { + double discard = po.config().pad_enable.getBool() ? + po.m_config.pad_wall_height.getFloat() : + po.m_config.support_base_height.getFloat() ; + + remove_bottom_points(po.m_supportdata->pts, + float(po.m_supportdata->emesh.ground_level() + discard)); + } po.m_supportdata->cfg = make_support_cfg(po.m_config); // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points()); From 06223221466508358ee210161b5872dae2f883e0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jun 2020 17:31:52 +0200 Subject: [PATCH 235/826] Create smaller supports in problematic areas with established strategies Completely remove the concept of CompactBridge. Replace it with Heads having the same back radius as front radius. Try to apply the same rules for mini supports as in the route_to_model step. Increased accuracy of bridge_mesh_intersect shot from support points Refining mini support integration --- src/libslic3r/SLA/SupportTree.cpp | 5 +- src/libslic3r/SLA/SupportTreeBuilder.cpp | 280 ++++++++----- src/libslic3r/SLA/SupportTreeBuilder.hpp | 102 +++-- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 426 ++++++++++---------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 11 +- tests/sla_print/CMakeLists.txt | 2 +- tests/sla_print/sla_test_utils.cpp | 6 +- tests/sla_print/sla_treebuilder_tests.cpp | 96 +++++ 8 files changed, 571 insertions(+), 357 deletions(-) create mode 100644 tests/sla_print/sla_treebuilder_tests.cpp diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 528778b68b..2edc4d21b1 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -103,9 +104,11 @@ SupportTree::UPtr SupportTree::create(const SupportableMesh &sm, builder->m_ctl = ctl; if (sm.cfg.enabled) { - builder->build(sm); + // Execute takes care about the ground_level + SupportTreeBuildsteps::execute(*builder, sm); builder->merge_and_cleanup(); // clean metadata, leave only the meshes. } else { + // If a pad gets added later, it will be in the right Z level builder->ground_level = sm.emesh.ground_level(); } diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index cf6e7e0206..8c9b54bb72 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -155,6 +155,65 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) return ret; } +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) +{ + assert(length > 0.); + assert(r_back > 0.); + assert(r_pin > 0.); + + Contour3D mesh; + + // We create two spheres which will be connected with a robe that fits + // both circles perfectly. + + // Set up the model detail level + const double detail = 2*PI/steps; + + // We don't generate whole circles. Instead, we generate only the + // portions which are visible (not covered by the robe) To know the + // exact portion of the bottom and top circles we need to use some + // rules of tangent circles from which we can derive (using simple + // triangles the following relations: + + // The height of the whole mesh + const double h = r_back + r_pin + length; + double phi = PI / 2. - std::acos((r_back - r_pin) / h); + + // To generate a whole circle we would pass a portion of (0, Pi) + // To generate only a half horizontal circle we can pass (0, Pi/2) + // The calculated phi is an offset to the half circles needed to smooth + // the transition from the circle to the robe geometry + + auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); + auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); + + for(auto& p : s2.points) p.z() += h; + + mesh.merge(s1); + mesh.merge(s2); + + for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; + idx1++, idx2++) + { + coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); + coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; + + mesh.faces3.emplace_back(i1s1, i2s1, i2s2); + mesh.faces3.emplace_back(i1s1, i2s2, i1s2); + } + + auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); + auto i2s1 = coord_t(s1.points.size()) - 1; + auto i1s2 = coord_t(s1.points.size()); + auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; + + mesh.faces3.emplace_back(i2s2, i2s1, i1s1); + mesh.faces3.emplace_back(i1s2, i2s2, i1s1); + + return mesh; +} + Head::Head(double r_big_mm, double r_small_mm, double length_mm, @@ -164,67 +223,17 @@ Head::Head(double r_big_mm, const size_t circlesteps) : steps(circlesteps) , dir(direction) - , tr(offset) + , pos(offset) , r_back_mm(r_big_mm) , r_pin_mm(r_small_mm) , width_mm(length_mm) , penetration_mm(penetration) { - assert(width_mm > 0.); - assert(r_back_mm > 0.); - assert(r_pin_mm > 0.); - - // We create two spheres which will be connected with a robe that fits - // both circles perfectly. - - // Set up the model detail level - const double detail = 2*PI/steps; - - // We don't generate whole circles. Instead, we generate only the - // portions which are visible (not covered by the robe) To know the - // exact portion of the bottom and top circles we need to use some - // rules of tangent circles from which we can derive (using simple - // triangles the following relations: - - // The height of the whole mesh - const double h = r_big_mm + r_small_mm + width_mm; - double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h ); - - // To generate a whole circle we would pass a portion of (0, Pi) - // To generate only a half horizontal circle we can pass (0, Pi/2) - // The calculated phi is an offset to the half circles needed to smooth - // the transition from the circle to the robe geometry - - auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); - - for(auto& p : s2.points) p.z() += h; - - mesh.merge(s1); - mesh.merge(s2); - - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { - coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); - coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; - - mesh.faces3.emplace_back(i1s1, i2s1, i2s2); - mesh.faces3.emplace_back(i1s1, i2s2, i1s2); - } - - auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); - auto i2s1 = coord_t(s1.points.size()) - 1; - auto i1s2 = coord_t(s1.points.size()); - auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; - - mesh.faces3.emplace_back(i2s2, i2s1, i1s1); - mesh.faces3.emplace_back(i1s2, i2s2, i1s1); + mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); // To simplify further processing, we translate the mesh so that the // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) - for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm); + for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st): @@ -305,34 +314,6 @@ Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): for(auto& p : mesh.points) p = quater * p + j1; } -CompactBridge::CompactBridge(const Vec3d &sp, - const Vec3d &ep, - const Vec3d &n, - double r, - bool endball, - size_t steps) -{ - Vec3d startp = sp + r * n; - Vec3d dir = (ep - startp).normalized(); - Vec3d endp = ep - r * dir; - - Bridge br(startp, endp, r, steps); - mesh.merge(br.mesh); - - // now add the pins - double fa = 2*PI/steps; - auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); - for(auto& p : upperball.points) p += startp; - - if(endball) { - auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); - for(auto& p : lowerball.points) p += endp; - mesh.merge(lowerball); - } - - mesh.merge(upperball); -} - Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, @@ -368,7 +349,6 @@ SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o) , m_pillars{std::move(o.m_pillars)} , m_bridges{std::move(o.m_bridges)} , m_crossbridges{std::move(o.m_crossbridges)} - , m_compact_bridges{std::move(o.m_compact_bridges)} , m_pad{std::move(o.m_pad)} , m_meshcache{std::move(o.m_meshcache)} , m_meshcache_valid{o.m_meshcache_valid} @@ -382,7 +362,6 @@ SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o) , m_pillars{o.m_pillars} , m_bridges{o.m_bridges} , m_crossbridges{o.m_crossbridges} - , m_compact_bridges{o.m_compact_bridges} , m_pad{o.m_pad} , m_meshcache{o.m_meshcache} , m_meshcache_valid{o.m_meshcache_valid} @@ -397,7 +376,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o) m_pillars = std::move(o.m_pillars); m_bridges = std::move(o.m_bridges); m_crossbridges = std::move(o.m_crossbridges); - m_compact_bridges = std::move(o.m_compact_bridges); m_pad = std::move(o.m_pad); m_meshcache = std::move(o.m_meshcache); m_meshcache_valid = o.m_meshcache_valid; @@ -413,7 +391,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) m_pillars = o.m_pillars; m_bridges = o.m_bridges; m_crossbridges = o.m_crossbridges; - m_compact_bridges = o.m_compact_bridges; m_pad = o.m_pad; m_meshcache = o.m_meshcache; m_meshcache_valid = o.m_meshcache_valid; @@ -443,12 +420,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const if (ctl().stopcondition()) break; merged.merge(j.mesh); } - - for (auto &cb : m_compact_bridges) { - if (ctl().stopcondition()) break; - merged.merge(cb.mesh); - } - + for (auto &bs : m_bridges) { if (ctl().stopcondition()) break; merged.merge(bs.mesh); @@ -499,7 +471,6 @@ const TriangleMesh &SupportTreeBuilder::merge_and_cleanup() m_pillars = {}; m_junctions = {}; m_bridges = {}; - m_compact_bridges = {}; return ret; } @@ -514,11 +485,130 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const return m_meshcache; } -bool SupportTreeBuilder::build(const SupportableMesh &sm) +template +static Hit min_hit(const C &hits) { - ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; - return SupportTreeBuildsteps::execute(*this, sm); + auto mit = std::min_element(hits.begin(), hits.end(), + [](const Hit &h1, const Hit &h2) { + return h1.distance() < h2.distance(); + }); + + return *mit; } +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) +{ + static const size_t SAMPLES = 8; + + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + + const double& sd = msh.cfg.safety_distance_mm; + + auto& m = msh.emesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + Vec3d s1 = h.pos, s2 = h.junction_point(); + + struct Rings { + double rpin; + double rback; + Vec3d spin; + Vec3d sback; + PointRing ring; + + Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } + Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } + } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; + + // We will shoot multiple rays from the head pinpoint in the direction + // of the pinhead robe (side) surface. The result will be the smallest + // hit distance. + + auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { + // Point on the circle on the pin sphere + Vec3d ps = rings.pinring(i); + // This is the point on the circle on the back sphere + Vec3d p = rings.backring(i); + + // Point ps is not on mesh but can be inside or + // outside as well. This would cause many problems + // with ray-casting. To detect the position we will + // use the ray-casting result (which has an is_inside + // predicate). + + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd * n, n); + + if (q.is_inside()) { // the hit is inside the model + if (q.distance() > rings.rpin) { + // If we are inside the model and the hit + // distance is bigger than our pin circle + // diameter, it probably indicates that the + // support point was already inside the + // model, or there is really no space + // around the point. We will assign a zero + // hit distance to these cases which will + // enforce the function return value to be + // an invalid ray with zero hit distance. + // (see min_element at the end) + hit = HitResult(0.0); + } else { + // re-cast the ray from the outside of the + // object. The starting point has an offset + // of 2*safety_distance because the + // original ray has also had an offset + auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); + hit = q2; + } + } else + hit = q; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); } + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) +{ + static const size_t SAMPLES = 8; + + Vec3d dir = (br.endp - br.startp).normalized(); + PointRing ring{dir}; + + using Hit = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + const double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; + bool ins_check = sd < msh.cfg.safety_distance_mm; + + auto hitfn = [&br, &ring, &msh, dir, sd, ins_check](Hit & hit, size_t i) { + // Point on the circle on the pin sphere + Vec3d p = ring.get(i, br.startp, br.r + sd); + + auto hr = msh.emesh.query_ray_hit(p + sd * dir, dir); + + if (ins_check && hr.is_inside()) { + if (hr.distance() > 2 * br.r + sd) + hit = Hit(0.0); + else { + // re-cast the ray from the outside of the object + hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, + dir); + } + } else + hit = hr; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); } + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 90cf417c83..aec2a7a585 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -76,6 +76,8 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), // sp: starting point Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0}); +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); + const constexpr long ID_UNSET = -1; struct Head { @@ -83,7 +85,7 @@ struct Head { size_t steps = 45; Vec3d dir = {0, 0, -1}; - Vec3d tr = {0, 0, 0}; + Vec3d pos = {0, 0, 0}; double r_back_mm = 1; double r_pin_mm = 0.5; @@ -120,17 +122,22 @@ struct Head { // the -1 z coordinate auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); - for(auto& p : mesh.points) p = quatern * p + tr; + for(auto& p : mesh.points) p = quatern * p + pos; } + inline double real_width() const + { + return 2 * r_pin_mm + width_mm + 2 * r_back_mm ; + } + inline double fullwidth() const { - return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; + return real_width() - penetration_mm; } inline Vec3d junction_point() const { - return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; + return pos + (fullwidth() - r_back_mm) * dir; } inline double request_pillar_radius(double radius) const @@ -211,20 +218,6 @@ struct Bridge { size_t steps = 45); }; -// A bridge that spans from model surface to model surface with small connecting -// edges on the endpoints. Used for headless support points. -struct CompactBridge { - Contour3D mesh; - long id = ID_UNSET; - - CompactBridge(const Vec3d& sp, - const Vec3d& ep, - const Vec3d& n, - double r, - bool endball = true, - size_t steps = 45); -}; - // A wrapper struct around the pad struct Pad { TriangleMesh tmesh; @@ -242,6 +235,67 @@ struct Pad { bool empty() const { return tmesh.facets_count() == 0; } }; +// Give points on a 3D ring with given center, radius and orientation +// method based on: +// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space +template +class PointRing { + std::array m_phis; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + // a and b vectors are perpendicular to the ring direction and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'm_phis' vector + Vec3d a = {0, 1, 0}, b; + double m_radius = 0.; + + static inline bool constexpr is_one(double val) + { + return std::abs(std::abs(val) - 1) < 1e-20; + } + +public: + + PointRing(const Vec3d &n) + { + m_phis = linspace_array(0., 2 * PI); + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { + a = {n(Z), n(X), n(Y)}; + b = {n(Y), n(Z), n(X)}; + } + else { + a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); + b = a.cross(n); + } + } + + Vec3d get(size_t idx, const Vec3d src, double r) const + { + double phi = m_phis[idx]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + double rpscos = r * cosphi; + double rpssin = r * sinphi; + + // Point on the sphere + return {src(X) + rpscos * a(X) + rpssin * b(X), + src(Y) + rpscos * a(Y) + rpssin * b(Y), + src(Z) + rpscos * a(Z) + rpssin * b(Z)}; + } +}; + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); + // This class will hold the support tree meshes with some additional // bookkeeping as well. Various parts of the support geometry are stored // separately and are merged when the caller queries the merged mesh. The @@ -264,7 +318,6 @@ class SupportTreeBuilder: public SupportTree { std::vector m_junctions; std::vector m_bridges; std::vector m_crossbridges; - std::vector m_compact_bridges; Pad m_pad; using Mutex = ccr::SpinningMutex; @@ -415,15 +468,6 @@ public: return _add_bridge(m_crossbridges, std::forward(args)...); } - template const CompactBridge& add_compact_bridge(Args&&...args) - { - std::lock_guard lk(m_mutex); - m_compact_bridges.emplace_back(std::forward(args)...); - m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); - m_meshcache_valid = false; - return m_compact_bridges.back(); - } - Head &head(unsigned id) { std::lock_guard lk(m_mutex); @@ -488,8 +532,6 @@ public: virtual const TriangleMesh &retrieve_mesh( MeshType meshtype = MeshType::Support) const override; - - bool build(const SupportableMesh &supportable_mesh); }; }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 29ad6057f1..df9de35555 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -42,6 +42,8 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, { if(sm.pts.empty()) return false; + builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; + SupportTreeBuildsteps alg(builder, sm); // Let's define the individual steps of the processing. We can experiment @@ -166,64 +168,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, return pc == ABORT; } -// Give points on a 3D ring with given center, radius and orientation -// method based on: -// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space -template -class PointRing { - std::array m_phis; - - // Two vectors that will be perpendicular to each other and to the - // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a - // placeholder. - // a and b vectors are perpendicular to the ring direction and to each other. - // Together they define the plane where we have to iterate with the - // given angles in the 'm_phis' vector - Vec3d a = {0, 1, 0}, b; - double m_radius = 0.; - - static inline bool constexpr is_one(double val) - { - return std::abs(std::abs(val) - 1) < 1e-20; - } - -public: - - PointRing(const Vec3d &n) - { - m_phis = linspace_array(0., 2 * PI); - - // We have to address the case when the direction vector v (same as - // dir) is coincident with one of the world axes. In this case two of - // its components will be completely zero and one is 1.0. Our method - // becomes dangerous here due to division with zero. Instead, vector - // 'a' can be an element-wise rotated version of 'v' - if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { - a = {n(Z), n(X), n(Y)}; - b = {n(Y), n(Z), n(X)}; - } - else { - a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); - b = a.cross(n); - } - } - - Vec3d get(size_t idx, const Vec3d src, double r) const - { - double phi = m_phis[idx]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - double rpscos = r * cosphi; - double rpssin = r * sinphi; - - // Point on the sphere - return {src(X) + rpscos * a(X) + rpssin * b(X), - src(Y) + rpscos * a(Y) + rpssin * b(Y), - src(Z) + rpscos * a(Z) + rpssin * b(Z)}; - } -}; - template static Hit min_hit(const C &hits) { @@ -312,7 +256,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( } EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( - const Vec3d &src, const Vec3d &dir, double r, bool ins_check) + const Vec3d &src, const Vec3d &dir, double r, double safety_d) { static const size_t SAMPLES = 8; PointRing ring{dir}; @@ -321,16 +265,19 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( // Hit results std::array hits; + + double sd = std::isnan(safety_d) ? m_cfg.safety_distance_mm : safety_d; + sd = sd * r / m_cfg.head_back_radius_mm; + + bool ins_check = sd < m_cfg.safety_distance_mm; ccr::enumerate(hits.begin(), hits.end(), - [this, r, src, ins_check, &ring, dir] (Hit &hit, size_t i) { - - const double sd = m_cfg.safety_distance_mm; - + [this, r, src, ins_check, &ring, dir, sd] (Hit &hit, size_t i) { + // Point on the circle on the pin sphere Vec3d p = ring.get(i, src, r + sd); - auto hr = m_mesh.query_ray_hit(p + sd * dir, dir); + auto hr = m_mesh.query_ray_hit(p + r * dir, dir); if(ins_check && hr.is_inside()) { if(hr.distance() > 2 * r + sd) hit = Hit(0.0); @@ -460,7 +407,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, Vec3d bridgestart = headjp; Vec3d bridgeend = nearjp_u; - double max_len = m_cfg.max_bridge_length_mm; + double max_len = r * m_cfg.max_bridge_length_mm / m_cfg.head_back_radius_mm; double max_slope = m_cfg.bridge_slope; double zdiff = 0.0; @@ -494,7 +441,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, // There will be a minimum distance from the ground where the // bridge is allowed to connect. This is an empiric value. - double minz = m_builder.ground_level + 2 * m_cfg.head_width_mm; + double minz = m_builder.ground_level + 4 * head.r_back_mm; if(bridgeend(Z) < minz) return false; double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r); @@ -509,7 +456,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, if(zdiff > 0) { m_builder.add_pillar(head.id, bridgestart, r); m_builder.add_junction(bridgestart, r); - m_builder.add_bridge(bridgestart, bridgeend, head.r_back_mm); + m_builder.add_bridge(bridgestart, bridgeend, r); } else { m_builder.add_bridge(head.id, bridgeend); } @@ -520,40 +467,6 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, return true; } -bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &head) -{ - PointIndex spindex = m_pillar_index.guarded_clone(); - - long nearest_id = ID_UNSET; - - Vec3d querypoint = head.junction_point(); - - while(nearest_id < 0 && !spindex.empty()) { m_thr(); - // loop until a suitable head is not found - // if there is a pillar closer than the cluster center - // (this may happen as the clustering is not perfect) - // than we will bridge to this closer pillar - - Vec3d qp(querypoint(X), querypoint(Y), m_builder.ground_level); - auto qres = spindex.nearest(qp, 1); - if(qres.empty()) break; - - auto ne = qres.front(); - nearest_id = ne.second; - - if(nearest_id >= 0) { - if(size_t(nearest_id) < m_builder.pillarcount()) { - if(!connect_to_nearpillar(head, nearest_id)) { - nearest_id = ID_UNSET; // continue searching - spindex.remove(ne); // without the current pillar - } - } - } - } - - return nearest_id >= 0; -} - void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, @@ -565,9 +478,10 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, Vec3d endp = {jp(X), jp(Y), gndlvl}; double sd = m_cfg.pillar_base_safety_distance_mm; long pillar_id = ID_UNSET; - double min_dist = sd + m_cfg.base_radius_mm + EPSILON; + bool can_add_base = radius >= m_cfg.head_back_radius_mm; + double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; + double min_dist = sd + base_r + EPSILON; double dist = 0; - bool can_add_base = true; bool normal_mode = true; // If in zero elevation mode and the pillar is too close to the model body, @@ -612,7 +526,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, endp = jp + std::get<0>(result.optimum) * dir; Vec3d pgnd = {endp(X), endp(Y), gndlvl}; - can_add_base = result.score > min_dist; + can_add_base = can_add_base && result.score > min_dist; double gnd_offs = m_mesh.ground_level_offset(); auto abort_in_shame = @@ -712,84 +626,85 @@ void SupportTreeBuildsteps::filter() auto [polar, azimuth] = dir_to_spheric(n); // skip if the tilt is not sane - if(polar >= PI - m_cfg.normal_cutoff_angle) { + if(polar < PI - m_cfg.normal_cutoff_angle) return; - // We saturate the polar angle to 3pi/4 - polar = std::max(polar, 3*PI / 4); - - // save the head (pinpoint) position - Vec3d hp = m_points.row(fidx); - - double w = m_cfg.head_width_mm + - m_cfg.head_back_radius_mm + - 2*m_cfg.head_front_radius_mm; - - double pin_r = double(m_support_pts[fidx].head_front_radius); - - // Reassemble the now corrected normal - auto nn = spheric_to_dir(polar, azimuth).normalized(); - - // check available distance - EigenMesh3D::hit_result t - = pinhead_mesh_intersect(hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); - - if(t.distance() <= w) { - - // Let's try to optimize this angle, there might be a - // viable normal that doesn't collide with the model - // geometry and its very close to the default. - - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = w; // space greater than w is enough - GeneticOptimizer solver(stc); - solver.seed(0); // we want deterministic behavior - - auto oresult = solver.optimize_max( - [this, pin_r, w, hp](double plr, double azm) - { - auto dir = spheric_to_dir(plr, azm).normalized(); - - double score = pinhead_mesh_distance( - hp, dir, pin_r, m_cfg.head_back_radius_mm, w); - - return score; - }, - initvals(polar, azimuth), // start with what we have - bound(3 * PI / 4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); - - if(oresult.score > w) { - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - nn = spheric_to_dir(polar, azimuth).normalized(); - t = EigenMesh3D::hit_result(oresult.score); - } - } - - // save the verified and corrected normal - m_support_nmls.row(fidx) = nn; - - if (t.distance() > w) { - // Check distance from ground, we might have zero elevation. - if (hp(Z) + w * nn(Z) < m_builder.ground_level) { - addfn(m_iheadless, fidx); - } else { - // mark the point for needing a head. - addfn(m_iheads, fidx); - } - } else if (polar >= 3 * PI / 4) { - // Headless supports do not tilt like the headed ones - // so the normal should point almost to the ground. - addfn(m_iheadless, fidx); + // We saturate the polar angle to 3pi/4 + polar = std::max(polar, 3*PI / 4); + + // save the head (pinpoint) position + Vec3d hp = m_points.row(fidx); + + // The distance needed for a pinhead to not collide with model. + double w = m_cfg.head_width_mm + + m_cfg.head_back_radius_mm + + 2*m_cfg.head_front_radius_mm; + + double pin_r = double(m_support_pts[fidx].head_front_radius); + + // Reassemble the now corrected normal + auto nn = spheric_to_dir(polar, azimuth).normalized(); + + // check available distance + EigenMesh3D::hit_result t + = pinhead_mesh_intersect(hp, // touching point + nn, // normal + pin_r, + m_cfg.head_back_radius_mm, + w); + + if(t.distance() <= w) { + + // Let's try to optimize this angle, there might be a + // viable normal that doesn't collide with the model + // geometry and its very close to the default. + + StopCriteria stc; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; + stc.stop_score = w; // space greater than w is enough + GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior + + auto oresult = solver.optimize_max( + [this, pin_r, w, hp](double plr, double azm) + { + auto dir = spheric_to_dir(plr, azm).normalized(); + + double score = pinhead_mesh_intersect( + hp, dir, pin_r, m_cfg.head_back_radius_mm, w).distance(); + + return score; + }, + initvals(polar, azimuth), // start with what we have + bound(3 * PI / 4, PI), // Must not exceed the tilt limit + bound(-PI, PI) // azimuth can be a full search + ); + + if(oresult.score > w) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + nn = spheric_to_dir(polar, azimuth).normalized(); + t = EigenMesh3D::hit_result(oresult.score); } } + + // save the verified and corrected normal + m_support_nmls.row(fidx) = nn; + + if (t.distance() > w) { + // Check distance from ground, we might have zero elevation. + if (hp(Z) + w * nn(Z) < m_builder.ground_level) { + addfn(m_iheadless, fidx); + } else { + // mark the point for needing a head. + addfn(m_iheads, fidx); + } + } else if (polar >= 3 * PI / 4) { + // Headless supports do not tilt like the headed ones + // so the normal should point almost to the ground. + addfn(m_iheadless, fidx); + } + }; ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), filterfn); @@ -811,6 +726,27 @@ void SupportTreeBuildsteps::add_pinheads() m_support_pts[i].pos.cast() // displacement ); } + + for (unsigned i : m_iheadless) { + const auto R = double(m_support_pts[i].head_front_radius); + + // The support point position on the mesh + Vec3d sph = m_support_pts[i].pos.cast(); + + // Get an initial normal from the filtering step + Vec3d n = m_support_nmls.row(i); + + // First we need to determine the available space for a mini pinhead. + // The goal is the move away from the model a little bit to make the + // contact point small as possible and avoid pearcing the model body. + double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); + + if (pin_space <= 0) continue; + + m_iheads.emplace_back(i); + m_builder.add_head(i, R, R, pin_space, + m_cfg.head_penetration_mm, n, sph); + } } void SupportTreeBuildsteps::classify() @@ -864,8 +800,6 @@ void SupportTreeBuildsteps::classify() void SupportTreeBuildsteps::routing_to_ground() { - const double pradius = m_cfg.head_back_radius_mm; - ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); @@ -931,7 +865,7 @@ void SupportTreeBuildsteps::routing_to_ground() Vec3d pstart = sidehead.junction_point(); // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - create_ground_pillar(pstart, sidehead.dir, pradius, sidehead.id); + create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); } } } @@ -943,7 +877,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) double r = head.r_back_mm; double t = bridge_mesh_distance(hjp, dir, head.r_back_mm); double d = 0, tdown = 0; - t = std::min(t, m_cfg.max_bridge_length_mm); + t = std::min(t, m_cfg.max_bridge_length_mm * r / m_cfg.head_back_radius_mm); while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r))) d += r; @@ -1041,6 +975,42 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) return true; } +bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) +{ + // Hope that a local copy takes less time than the whole search loop. + // We also need to remove elements progressively from the copied index. + PointIndex spindex = m_pillar_index.guarded_clone(); + + long nearest_id = ID_UNSET; + + Vec3d querypt = source.junction_point(); + + while(nearest_id < 0 && !spindex.empty()) { m_thr(); + // loop until a suitable head is not found + // if there is a pillar closer than the cluster center + // (this may happen as the clustering is not perfect) + // than we will bridge to this closer pillar + + Vec3d qp(querypt(X), querypt(Y), m_builder.ground_level); + auto qres = spindex.nearest(qp, 1); + if(qres.empty()) break; + + auto ne = qres.front(); + nearest_id = ne.second; + + if(nearest_id >= 0) { + if(size_t(nearest_id) < m_builder.pillarcount()) { + if(!connect_to_nearpillar(source, nearest_id)) { + nearest_id = ID_UNSET; // continue searching + spindex.remove(ne); // without the current pillar + } + } + } + } + + return nearest_id >= 0; +} + void SupportTreeBuildsteps::routing_to_model() { // We need to check if there is an easy way out to the bed surface. @@ -1054,18 +1024,18 @@ void SupportTreeBuildsteps::routing_to_model() auto& head = m_builder.head(idx); // Search nearby pillar - if(search_pillar_and_connect(head)) { head.transform(); return; } + if (search_pillar_and_connect(head)) { head.transform(); return; } // Cannot connect to nearby pillar. We will try to search for // a route to the ground. - if(connect_to_ground(head)) { head.transform(); return; } + if (connect_to_ground(head)) { head.transform(); return; } // No route to the ground, so connect to the model body as a last resort if (connect_to_model_body(head)) { return; } // We have failed to route this head. BOOST_LOG_TRIVIAL(warning) - << "Failed to route model facing support point. ID: " << idx; + << "Failed to route model facing support point. ID: " << idx; head.invalidate(); }); @@ -1107,9 +1077,10 @@ void SupportTreeBuildsteps::interconnect_pillars() // connections are already enough for the pillar if(pillar.links >= neighbors) return; + double max_d = d * pillar.r / m_cfg.head_back_radius_mm; // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ - return distance(e.first, qp) < d; + auto qres = m_pillar_index.query([qp, max_d](const PointIndexEl& e){ + return distance(e.first, qp) < max_d; }); // sort the result by distance (have to check if this is needed) @@ -1288,37 +1259,54 @@ void SupportTreeBuildsteps::routing_headless() // We will sink the pins into the model surface for a distance of 1/3 of // the pin radius - for(unsigned i : m_iheadless) { - m_thr(); - - const auto R = double(m_support_pts[i].head_front_radius); - const double HWIDTH_MM = std::min(R, m_cfg.head_penetration_mm); - - // Exact support position - Vec3d sph = m_support_pts[i].pos.cast(); - Vec3d n = m_support_nmls.row(i); // mesh outward normal - Vec3d sp = sph - n * HWIDTH_MM; // stick head start point - - Vec3d sj = sp + R * n; // stick start point - - // This is only for checking - double idist = bridge_mesh_distance(sph, DOWN, R, true); - double realdist = ray_mesh_intersect(sj, DOWN).distance(); - double dist = realdist; - - if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level; - - if(std::isnan(idist) || idist < 2*R || std::isnan(dist) || dist < 2*R) { - BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" - << " support stick at: " - << sj.transpose(); - continue; - } - - bool use_endball = !std::isinf(realdist); - Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ; - m_builder.add_compact_bridge(sp, ej, n, R, use_endball); - } +// for(unsigned i : m_iheadless) { +// m_thr(); + +// const auto R = double(m_support_pts[i].head_front_radius); + +// // The support point position on the mesh +// Vec3d sph = m_support_pts[i].pos.cast(); + +// // Get an initial normal from the filtering step +// Vec3d n = m_support_nmls.row(i); + +// // First we need to determine the available space for a mini pinhead. +// // The goal is the move away from the model a little bit to make the +// // contact point small as possible and avoid pearcing the model body. +// double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); + +// if (pin_space <= 0) continue; + +// auto &head = m_builder.add_head(i, R, R, pin_space, +// m_cfg.head_penetration_mm, n, sph); + +// // collision check + +// m_head_to_ground_scans[i] = +// bridge_mesh_intersect(head.junction_point(), DOWN, R); + +// // Here the steps will be similar as in route_to_model step: +// // 1. Search for a nearby pillar, include other mini pillars + +// // Search nearby pillar +// if (search_pillar_and_connect(head)) { head.transform(); continue; } + +// if (std::isinf(m_head_to_ground_scans[i].distance())) { +// create_ground_pillar(head.junction_point(), head.dir, m_cfg.head_back_radius_mm, head.id); +// } + +// // Cannot connect to nearby pillar. We will try to search for +// // a route to the ground. +// if (connect_to_ground(head)) { head.transform(); continue; } + +// // No route to the ground, so connect to the model body as a last resort +// if (connect_to_model_body(head)) { continue; } + +// BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" +// << " support stick at: " +// << sph.transpose(); +// head.invalidate(); +// } } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index cfe78fe97a..1962f802b2 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -229,11 +229,6 @@ class SupportTreeBuildsteps { double r_pin, double r_back, double width); - - template - inline double pinhead_mesh_distance(Args&&...args) { - return pinhead_mesh_intersect(std::forward(args)...).distance(); - } // Checking bridge (pillar and stick as well) intersection with the model. // If the function is used for headless sticks, the ins_check parameter @@ -247,7 +242,7 @@ class SupportTreeBuildsteps { const Vec3d& s, const Vec3d& dir, double r, - bool ins_check = false); + double safety_d = std::nan("")); template inline double bridge_mesh_distance(Args&&...args) { @@ -268,8 +263,8 @@ class SupportTreeBuildsteps { inline bool connect_to_ground(Head& head); bool connect_to_model_body(Head &head); - - bool search_pillar_and_connect(const Head& head); + + bool search_pillar_and_connect(const Head& source); // This is a proxy function for pillar creation which will mind the gap // between the pad and the model bottom in zero elevation mode. diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 9d47f3ae4d..f6b261fdaa 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -1,7 +1,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_print_tests.cpp - sla_test_utils.hpp sla_test_utils.cpp + sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp sla_raycast_tests.cpp) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1eaf796c00..5a3bd82a00 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -129,8 +129,7 @@ void test_supports(const std::string &obj_filename, // If there is no elevation, support points shall be removed from the // bottom of the object. if (std::abs(supportcfg.object_elevation_mm) < EPSILON) { - sla::remove_bottom_points(support_points, zmin, - supportcfg.base_height_mm); + sla::remove_bottom_points(support_points, zmin + supportcfg.base_height_mm); } else { // Should be support points at least on the bottom of the model REQUIRE_FALSE(support_points.empty()); @@ -141,7 +140,8 @@ void test_supports(const std::string &obj_filename, // Generate the actual support tree sla::SupportTreeBuilder treebuilder; - treebuilder.build(sla::SupportableMesh{emesh, support_points, supportcfg}); + sla::SupportableMesh sm{emesh, support_points, supportcfg}; + sla::SupportTreeBuildsteps::execute(treebuilder, sm); check_support_tree_integrity(treebuilder, supportcfg); diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp new file mode 100644 index 0000000000..c785e4ba5e --- /dev/null +++ b/tests/sla_print/sla_treebuilder_tests.cpp @@ -0,0 +1,96 @@ +#include +#include + +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/SLA/SupportTreeBuilder.hpp" + +TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { + using namespace Slic3r; + + TriangleMesh cube = make_cube(10., 10., 10.); + + sla::SupportConfig cfg = {}; // use default config + sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; + sla::SupportableMesh sm{cube, pts, cfg}; + + SECTION("Bridge is straight horizontal and pointing away from the cube") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + REQUIRE(std::isinf(hit.distance())); + + cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.require_shared_vertices(); + cube.WriteOBJFile("cube1.obj"); + } + + SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + REQUIRE(std::isinf(hit.distance())); + + cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.require_shared_vertices(); + cube.WriteOBJFile("cube2.obj"); + } +} + + +TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { + using namespace Slic3r; + + TriangleMesh sphere = make_sphere(1.); + + sla::SupportConfig cfg = {}; // use default config + cfg.head_back_radius_mm = cfg.head_front_radius_mm; + sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; + sla::SupportableMesh sm{sphere, pts, cfg}; + + SECTION("Bridge is straight horizontal and pointing away from the sphere") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.require_shared_vertices(); + sphere.WriteOBJFile("sphere1.obj"); + + REQUIRE(std::isinf(hit.distance())); + } + + SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.require_shared_vertices(); + sphere.WriteOBJFile("sphere2.obj"); + + REQUIRE(std::isinf(hit.distance())); + } + + SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { + + sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, + pts[0].head_front_radius); + + auto hit = sla::query_hit(sm, bridge); + + sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.require_shared_vertices(); + sphere.WriteOBJFile("sphere3.obj"); + + REQUIRE(std::isinf(hit.distance())); + } +} From 67b61c23f7cbc9061834b08d358ea9f53418ef46 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Jun 2020 17:42:29 +0200 Subject: [PATCH 236/826] Remove the discard region for bottom points removal. This was a workaround for small supports not to end up in the middle of the gap between the pad and the object. The issue needs to be solved at the support generation. --- src/libslic3r/SLAPrintSteps.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index ea016d5bb0..defc5246cc 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -374,12 +374,11 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (is_zero_elevation(po.config())) { - double discard = po.config().pad_enable.getBool() ? - po.m_config.pad_wall_height.getFloat() : - po.m_config.support_base_height.getFloat() ; +// double discard = pcfg.embed_object.object_gap_mm / +// std::cos(po.m_supportdata->cfg.bridge_slope) ; remove_bottom_points(po.m_supportdata->pts, - float(po.m_supportdata->emesh.ground_level() + discard)); + float(po.m_supportdata->emesh.ground_level() + EPSILON)); } po.m_supportdata->cfg = make_support_cfg(po.m_config); From 7b6565abeb0f9456b6bc17e0d9ef98f2a706c06c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 5 Jun 2020 20:19:19 +0200 Subject: [PATCH 237/826] Improvements on mini pillars --- src/libslic3r/Point.hpp | 11 +- src/libslic3r/SLA/EigenMesh3D.hpp | 2 + src/libslic3r/SLA/SupportTreeBuilder.cpp | 16 ++ src/libslic3r/SLA/SupportTreeBuilder.hpp | 6 + src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 178 ++++++++++---------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 2 +- 6 files changed, 124 insertions(+), 91 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index b818cd8bed..8c1c69fde3 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2( inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); } -inline Vec2i32 to_2d(const Vec2i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); } -inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } -inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } -inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } +template Eigen::Matrix +to_2d(const Eigen::Matrix &ptN) { return {ptN(0), ptN(1)}; } + +//inline Vec2i32 to_2d(const Vec3i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); } +//inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } +//inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } +//inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp index b932c0c18e..7b7562d477 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/EigenMesh3D.hpp @@ -125,6 +125,8 @@ public: } Vec3d normal_by_face_id(int face_id) const; + + const TriangleMesh * get_triangle_mesh() const { return m_tm; } }; // Calculate the normals for the selected points (from 'points' set) on the diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 8c9b54bb72..121a001453 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -314,6 +314,22 @@ Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): for(auto& p : mesh.points) p = quater * p + j1; } +Bridge::Bridge(const Vec3d &j1, + const Vec3d &j2, + double r1_mm, + double r2_mm, + size_t steps) +{ + Vec3d dir = (j2 - j1); + mesh = pinhead(r1_mm, r2_mm, dir.norm(), steps); + dir.normalize(); + + using Quaternion = Eigen::Quaternion; + auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); + + for(auto& p : mesh.points) p = quater * p + j1; +} + Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index aec2a7a585..66462ebbdf 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -216,6 +216,12 @@ struct Bridge { const Vec3d &j2, double r_mm = 0.8, size_t steps = 45); + + Bridge(const Vec3d &j1, + const Vec3d &j2, + double r1_mm, + double r2_mm, + size_t steps = 45); }; // A wrapper struct around the pad diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index df9de35555..e94e3c402e 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -467,107 +467,86 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, return true; } -void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, +bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, long head_id) { - const double SLOPE = 1. / std::cos(m_cfg.bridge_slope); - - double gndlvl = m_builder.ground_level; - Vec3d endp = {jp(X), jp(Y), gndlvl}; double sd = m_cfg.pillar_base_safety_distance_mm; long pillar_id = ID_UNSET; bool can_add_base = radius >= m_cfg.head_back_radius_mm; double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; + double gndlvl = m_builder.ground_level; + if (!can_add_base) gndlvl -= m_mesh.ground_level_offset(); + Vec3d endp = {jp(X), jp(Y), gndlvl}; double min_dist = sd + base_r + EPSILON; - double dist = 0; bool normal_mode = true; - - // If in zero elevation mode and the pillar is too close to the model body, - // the support pillar can not be placed in the gap between the model and - // the pad, and the pillar bases must not touch the model body either. - // To solve this, a corrector bridge is inserted between the starting point - // (jp) and the new pillar. - if (m_cfg.object_elevation_mm < EPSILON - && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { - // Get the distance from the mesh. This can be later optimized - // to get the distance in 2D plane because we are dealing with - // the ground level only. + Vec3d dir = sourcedir; - normal_mode = false; - - // The min distance needed to move away from the model in XY plane. - double current_d = min_dist - dist; - double current_bride_d = SLOPE * current_d; + auto to_floor = [gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + if (m_cfg.object_elevation_mm < EPSILON) + { // get a suitable direction for the corrector bridge. It is the // original sourcedir's azimuth but the polar angle is saturated to the // configured bridge slope. - auto [polar, azimuth] = dir_to_spheric(sourcedir); + auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - m_cfg.bridge_slope; - auto dir = spheric_to_dir(polar, azimuth).normalized(); - - StopCriteria scr; - scr.stop_score = min_dist; - SubplexOptimizer solver(scr); - - // Search for a distance along the corrector bridge to move the endpoint - // sufficiently away form the model body. The first few optimization - // cycles should succeed here. - auto result = solver.optimize_max( - [this, dir, jp, gndlvl](double mv) { - Vec3d endpt = jp + mv * dir; - endpt(Z) = gndlvl; - return std::sqrt(m_mesh.squared_distance(endpt)); - }, - initvals(current_bride_d), - bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d)); - - endp = jp + std::get<0>(result.optimum) * dir; - Vec3d pgnd = {endp(X), endp(Y), gndlvl}; - can_add_base = can_add_base && result.score > min_dist; - - double gnd_offs = m_mesh.ground_level_offset(); - auto abort_in_shame = - [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() - { - normal_mode = true; - can_add_base = false; // Nothing left to do, hope for the best - endp = {jp(X), jp(Y), gndlvl - gnd_offs }; - }; - - // We have to check if the bridge is feasible. - if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) - abort_in_shame(); - else { - // If the new endpoint is below ground, do not make a pillar - if (endp(Z) < gndlvl) - endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off - else { - - auto hit = bridge_mesh_intersect(endp, DOWN, radius); - if (!std::isinf(hit.distance())) abort_in_shame(); - - pillar_id = m_builder.add_pillar(endp, pgnd, radius); - - if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); - } - - m_builder.add_bridge(jp, endp, radius); - m_builder.add_junction(endp, radius); - - // Add a degenerated pillar and the bridge. - // The degenerate pillar will have zero length and it will - // prevent from queries of head_pillar() to have non-existing - // pillar when the head should have one. - if (head_id >= 0) + Vec3d dir = spheric_to_dir(polar, azimuth).normalized(); + + // Check the distance of the endpoint and the closest point on model + // body. It should be greater than the min_dist which is + // the safety distance from the model. It includes the pad gap if in + // zero elevation mode. + // + // Try to move along the established bridge direction to dodge the + // forbidden region for the endpoint. + double t = -radius; + while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || + !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { + t += radius; + endp = jp + t * dir; + normal_mode = false; + + if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { m_builder.add_pillar(head_id, jp, radius); + return false; + } + } + } + + // Check if the deduced route is sane and exit with error if not. + if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { + m_builder.add_pillar(head_id, jp, radius); + return false; + } + + // If this is a mini pillar, do not let it grow too long, but change the + // radius to the normal pillar as soon as it is possible. + if (radius < m_cfg.head_back_radius_mm) { + double t = 0.; + double new_radius = m_cfg.head_back_radius_mm; + Vec3d new_endp = endp; + double d = 0.; + while (!std::isinf(d = bridge_mesh_distance(new_endp, DOWN, new_radius)) + && new_endp.z() > gndlvl) + { + t += m_cfg.head_fullwidth(); + new_endp = endp + t * DOWN; + } + + if (std::isinf(d) && new_endp.z() > gndlvl) { + if (t > 0.) { + m_builder.add_bridge(endp, new_endp, radius, new_radius); + endp = new_endp; + } else { + m_builder.add_junction(endp, new_radius); + } + radius = new_radius; } } + // Straigh path down, no area to dodge if (normal_mode) { pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) : m_builder.add_pillar(jp, endp, radius); @@ -575,10 +554,31 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, if (can_add_base) m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, m_cfg.base_radius_mm); + } else { + + // Insert the bridge to get around the forbidden area + Vec3d pgnd{endp.x(), endp.y(), gndlvl}; + pillar_id = m_builder.add_pillar(endp, pgnd, radius); + + if (can_add_base) + m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, + m_cfg.base_radius_mm); + + m_builder.add_bridge(jp, endp, radius); + m_builder.add_junction(endp, radius); + + // Add a degenerated pillar and the bridge. + // The degenerate pillar will have zero length and it will + // prevent from queries of head_pillar() to have non-existing + // pillar when the head should have one. + if (head_id >= 0) + m_builder.add_pillar(head_id, jp, radius); } - + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.guarded_insert(endp, unsigned(pillar_id)); + + return true; } void SupportTreeBuildsteps::filter() @@ -835,7 +835,11 @@ void SupportTreeBuildsteps::routing_to_ground() Head &h = m_builder.head(hid); h.transform(); - create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); + if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { + BOOST_LOG_TRIVIAL(warning) + << "Pillar cannot be created for support point id: " << hid; + h.invalidate(); + } } // now we will go through the clusters ones again and connect the @@ -999,8 +1003,9 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) nearest_id = ne.second; if(nearest_id >= 0) { - if(size_t(nearest_id) < m_builder.pillarcount()) { - if(!connect_to_nearpillar(source, nearest_id)) { + if (size_t(nearest_id) < m_builder.pillarcount()) { + if(!connect_to_nearpillar(source, nearest_id) || + m_builder.pillar(nearest_id).r < source.r_back_mm) { nearest_id = ID_UNSET; // continue searching spindex.remove(ne); // without the current pillar } @@ -1104,7 +1109,8 @@ void SupportTreeBuildsteps::interconnect_pillars() const Pillar& neighborpillar = m_builder.pillar(re.second); // this neighbor is occupied, skip - if(neighborpillar.links >= neighbors) continue; + if (neighborpillar.links >= neighbors) continue; + if (neighborpillar.r < pillar.r) continue; if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index 1962f802b2..bd6a9613c0 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -271,7 +271,7 @@ class SupportTreeBuildsteps { // jp is the starting junction point which needs to be routed down. // sourcedir is the allowed direction of an optional bridge between the // jp junction and the final pillar. - void create_ground_pillar(const Vec3d &jp, + bool create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, long head_id = ID_UNSET); From ed460a3e7e14ccfbf8ee0345f03ce3fd40750dde Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 10 Jun 2020 15:34:06 +0200 Subject: [PATCH 238/826] Remove the `headless` step of support support tree gen --- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 97 +-------------------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 5 -- 2 files changed, 2 insertions(+), 100 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index e94e3c402e..334c88fb90 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -56,7 +56,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, ROUTING_GROUND, ROUTING_NONGROUND, CASCADE_PILLARS, - HEADLESS, MERGE_RESULT, DONE, ABORT, @@ -83,8 +82,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg), - std::bind(&SupportTreeBuildsteps::routing_headless, &alg), - std::bind(&SupportTreeBuildsteps::merge_result, &alg), [] () { @@ -103,10 +100,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, BOOST_LOG_TRIVIAL(info) << "Skipping model-facing supports as requested."; }; - program[HEADLESS] = []() { - BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" - " requested."; - }; } // Let's define a simple automaton that will run our program. @@ -119,7 +112,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, "Routing to ground", "Routing supports to model surface", "Interconnecting pillars", - "Processing small holes", "Merging support mesh", "Done", "Abort" @@ -133,7 +125,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, 60, 70, 80, - 85, 99, 100, 0 @@ -148,8 +139,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, case CLASSIFY: pc = ROUTING_GROUND; break; case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; - case CASCADE_PILLARS: pc = HEADLESS; break; - case HEADLESS: pc = MERGE_RESULT; break; + case CASCADE_PILLARS: pc = MERGE_RESULT; break; case MERGE_RESULT: pc = DONE; break; case DONE: case ABORT: break; @@ -521,31 +511,6 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, return false; } - // If this is a mini pillar, do not let it grow too long, but change the - // radius to the normal pillar as soon as it is possible. - if (radius < m_cfg.head_back_radius_mm) { - double t = 0.; - double new_radius = m_cfg.head_back_radius_mm; - Vec3d new_endp = endp; - double d = 0.; - while (!std::isinf(d = bridge_mesh_distance(new_endp, DOWN, new_radius)) - && new_endp.z() > gndlvl) - { - t += m_cfg.head_fullwidth(); - new_endp = endp + t * DOWN; - } - - if (std::isinf(d) && new_endp.z() > gndlvl) { - if (t > 0.) { - m_builder.add_bridge(endp, new_endp, radius, new_radius); - endp = new_endp; - } else { - m_builder.add_junction(endp, new_radius); - } - radius = new_radius; - } - } - // Straigh path down, no area to dodge if (normal_mode) { pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) : @@ -1258,62 +1223,4 @@ void SupportTreeBuildsteps::interconnect_pillars() } } -void SupportTreeBuildsteps::routing_headless() -{ - // For now we will just generate smaller headless sticks with a sharp - // ending point that connects to the mesh surface. - - // We will sink the pins into the model surface for a distance of 1/3 of - // the pin radius -// for(unsigned i : m_iheadless) { -// m_thr(); - -// const auto R = double(m_support_pts[i].head_front_radius); - -// // The support point position on the mesh -// Vec3d sph = m_support_pts[i].pos.cast(); - -// // Get an initial normal from the filtering step -// Vec3d n = m_support_nmls.row(i); - -// // First we need to determine the available space for a mini pinhead. -// // The goal is the move away from the model a little bit to make the -// // contact point small as possible and avoid pearcing the model body. -// double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); - -// if (pin_space <= 0) continue; - -// auto &head = m_builder.add_head(i, R, R, pin_space, -// m_cfg.head_penetration_mm, n, sph); - -// // collision check - -// m_head_to_ground_scans[i] = -// bridge_mesh_intersect(head.junction_point(), DOWN, R); - -// // Here the steps will be similar as in route_to_model step: -// // 1. Search for a nearby pillar, include other mini pillars - -// // Search nearby pillar -// if (search_pillar_and_connect(head)) { head.transform(); continue; } - -// if (std::isinf(m_head_to_ground_scans[i].distance())) { -// create_ground_pillar(head.junction_point(), head.dir, m_cfg.head_back_radius_mm, head.id); -// } - -// // Cannot connect to nearby pillar. We will try to search for -// // a route to the ground. -// if (connect_to_ground(head)) { head.transform(); continue; } - -// // No route to the ground, so connect to the model body as a last resort -// if (connect_to_model_body(head)) { continue; } - -// BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" -// << " support stick at: " -// << sph.transpose(); -// head.invalidate(); -// } -} - -} -} +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index bd6a9613c0..ae872f98b7 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -319,11 +319,6 @@ public: void interconnect_pillars(); - // Step: process the support points where there is not enough space for a - // full pinhead. In this case we will use a rounded sphere as a touching - // point and use a thinner bridge (let's call it a stick). - void routing_headless (); - inline void merge_result() { m_builder.merged_mesh(); } static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm); From 2ff04e6f682a3925d44e72a0179a139f58ecc9f3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 15 Jun 2020 16:02:47 +0200 Subject: [PATCH 239/826] Bugfixes for support generator * Fix support heads floating in air * Fix failing tests for the bridge mesh intersection * Fix failing assertions WIP refactoring support tree gen, as its a mess. --- src/libslic3r/SLA/SupportTreeBuilder.cpp | 277 ++++++++------------ src/libslic3r/SLA/SupportTreeBuilder.hpp | 144 +++++----- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 149 +++++++++-- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 72 ++++- 4 files changed, 371 insertions(+), 271 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 121a001453..ebeca78a72 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -214,6 +214,56 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) return mesh; } + +Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) +{ + if(baseheight <= 0) return {}; + + assert(steps >= 0); + auto last = int(steps - 1); + + Contour3D base; + + double a = 2*PI/steps; + double z = endpt(Z) + baseheight; + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius * std::cos(phi); + double y = endpt(Y) + radius * std::sin(phi); + base.points.emplace_back(x, y, z); + } + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius*std::cos(phi); + double y = endpt(Y) + radius*std::sin(phi); + base.points.emplace_back(x, y, z - baseheight); + } + + auto ep = endpt; ep(Z) += baseheight; + base.points.emplace_back(endpt); + base.points.emplace_back(ep); + + auto& indices = base.faces3; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for(int i = 0; i < last; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + indices.emplace_back(i, i + 1, hcenter); + indices.emplace_back(lcenter, offs + i + 1, offs + i); + } + + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + indices.emplace_back(hcenter, last, 0); + indices.emplace_back(offs, offs + last, lcenter); + + return base; +} + Head::Head(double r_big_mm, double r_small_mm, double length_mm, @@ -229,77 +279,76 @@ Head::Head(double r_big_mm, , width_mm(length_mm) , penetration_mm(penetration) { - mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); +// mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); // To simplify further processing, we translate the mesh so that the // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) - for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); +// for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } -Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st): - r(radius), steps(st), endpt(endp), starts_from_head(false) -{ - assert(steps > 0); - - height = jp(Z) - endp(Z); - if(height > EPSILON) { // Endpoint is below the starting point +//Pillar::Pillar(const Vec3d &endp, double h, double radius, size_t st): +// height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) +//{ +// assert(steps > 0); + +// if(height > EPSILON) { // Endpoint is below the starting point - // We just create a bridge geometry with the pillar parameters and - // move the data. - Contour3D body = cylinder(radius, height, st, endp); - mesh.points.swap(body.points); - mesh.faces3.swap(body.faces3); - } -} +// // We just create a bridge geometry with the pillar parameters and +// // move the data. +// Contour3D body = cylinder(radius, height, st, endp); +// mesh.points.swap(body.points); +// mesh.faces3.swap(body.faces3); +// } +//} -Pillar &Pillar::add_base(double baseheight, double radius) -{ - if(baseheight <= 0) return *this; - if(baseheight > height) baseheight = height; +//Pillar &Pillar::add_base(double baseheight, double radius) +//{ +// if(baseheight <= 0) return *this; +// if(baseheight > height) baseheight = height; - assert(steps >= 0); - auto last = int(steps - 1); +// assert(steps >= 0); +// auto last = int(steps - 1); - if(radius < r ) radius = r; +// if(radius < r ) radius = r; - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; +// double a = 2*PI/steps; +// double z = endpt(Z) + baseheight; - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + r*std::cos(phi); - double y = endpt(Y) + r*std::sin(phi); - base.points.emplace_back(x, y, z); - } +// for(size_t i = 0; i < steps; ++i) { +// double phi = i*a; +// double x = endpt(X) + r*std::cos(phi); +// double y = endpt(Y) + r*std::sin(phi); +// base.points.emplace_back(x, y, z); +// } - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); - } +// for(size_t i = 0; i < steps; ++i) { +// double phi = i*a; +// double x = endpt(X) + radius*std::cos(phi); +// double y = endpt(Y) + radius*std::sin(phi); +// base.points.emplace_back(x, y, z - baseheight); +// } - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); - base.points.emplace_back(ep); +// auto ep = endpt; ep(Z) += baseheight; +// base.points.emplace_back(endpt); +// base.points.emplace_back(ep); - auto& indices = base.faces3; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - indices.emplace_back(i, i + 1, hcenter); - indices.emplace_back(lcenter, offs + i + 1, offs + i); - } +// auto& indices = base.faces3; +// auto hcenter = int(base.points.size() - 1); +// auto lcenter = int(base.points.size() - 2); +// auto offs = int(steps); +// for(int i = 0; i < last; ++i) { +// indices.emplace_back(i, i + offs, offs + i + 1); +// indices.emplace_back(i, offs + i + 1, i + 1); +// indices.emplace_back(i, i + 1, hcenter); +// indices.emplace_back(lcenter, offs + i + 1, offs + i); +// } - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - indices.emplace_back(hcenter, last, 0); - indices.emplace_back(offs, offs + last, lcenter); - return *this; -} +// indices.emplace_back(0, last, offs); +// indices.emplace_back(last, offs + last, offs); +// indices.emplace_back(hcenter, last, 0); +// indices.emplace_back(offs, offs + last, lcenter); +// return *this; +//} Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): r(r_mm), startp(j1), endp(j2) @@ -423,7 +472,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const for (auto &head : m_heads) { if (ctl().stopcondition()) break; - if (head.is_valid()) merged.merge(head.mesh); + if (head.is_valid()) merged.merge(get_mesh(head)); } for (auto &stick : m_pillars) { @@ -512,119 +561,5 @@ static Hit min_hit(const C &hits) return *mit; } -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) -{ - static const size_t SAMPLES = 8; - - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. - - const double& sd = msh.cfg.safety_distance_mm; - - auto& m = msh.emesh; - using HitResult = EigenMesh3D::hit_result; - - // Hit results - std::array hits; - - Vec3d s1 = h.pos, s2 = h.junction_point(); - - struct Rings { - double rpin; - double rback; - Vec3d spin; - Vec3d sback; - PointRing ring; - - Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } - Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } - } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; - - // We will shoot multiple rays from the head pinpoint in the direction - // of the pinhead robe (side) surface. The result will be the smallest - // hit distance. - - auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d ps = rings.pinring(i); - // This is the point on the circle on the back sphere - Vec3d p = rings.backring(i); - - // Point ps is not on mesh but can be inside or - // outside as well. This would cause many problems - // with ray-casting. To detect the position we will - // use the ray-casting result (which has an is_inside - // predicate). - - Vec3d n = (p - ps).normalized(); - auto q = m.query_ray_hit(ps + sd * n, n); - - if (q.is_inside()) { // the hit is inside the model - if (q.distance() > rings.rpin) { - // If we are inside the model and the hit - // distance is bigger than our pin circle - // diameter, it probably indicates that the - // support point was already inside the - // model, or there is really no space - // around the point. We will assign a zero - // hit distance to these cases which will - // enforce the function return value to be - // an invalid ray with zero hit distance. - // (see min_element at the end) - hit = HitResult(0.0); - } else { - // re-cast the ray from the outside of the - // object. The starting point has an offset - // of 2*safety_distance because the - // original ray has also had an offset - auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); - hit = q2; - } - } else - hit = q; - }; - - ccr::enumerate(hits.begin(), hits.end(), hitfn); - - return min_hit(hits); -} - -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) -{ - static const size_t SAMPLES = 8; - - Vec3d dir = (br.endp - br.startp).normalized(); - PointRing ring{dir}; - - using Hit = EigenMesh3D::hit_result; - - // Hit results - std::array hits; - - const double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; - bool ins_check = sd < msh.cfg.safety_distance_mm; - - auto hitfn = [&br, &ring, &msh, dir, sd, ins_check](Hit & hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d p = ring.get(i, br.startp, br.r + sd); - - auto hr = msh.emesh.query_ray_hit(p + sd * dir, dir); - - if (ins_check && hr.is_inside()) { - if (hr.distance() > 2 * br.r + sd) - hit = Hit(0.0); - else { - // re-cast the ray from the outside of the object - hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, - dir); - } - } else - hit = hr; - }; - - ccr::enumerate(hits.begin(), hits.end(), hitfn); - - return min_hit(hits); -} }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 66462ebbdf..087173e556 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -74,10 +74,12 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), // h: Height // ssteps: how many edges will create the base circle // sp: starting point -Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0}); +Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); +Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); + const constexpr long ID_UNSET = -1; struct Head { @@ -114,15 +116,7 @@ struct Head { void transform() { - using Quaternion = Eigen::Quaternion; - - // We rotate the head to the specified direction The head's pointing - // side is facing upwards so this means that it would hold a support - // point with a normal pointing straight down. This is the reason of - // the -1 z coordinate - auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir); - - for(auto& p : mesh.points) p = quatern * p + pos; + // TODO: remove occurences } inline double real_width() const @@ -164,8 +158,8 @@ struct Junction { }; struct Pillar { - Contour3D mesh; - Contour3D base; +// Contour3D mesh; +// Contour3D base; double r = 1; size_t steps = 0; Vec3d endpt; @@ -182,27 +176,42 @@ struct Pillar { // How many pillars are cascaded with this one unsigned links = 0; - - Pillar(const Vec3d& jp, const Vec3d& endp, - double radius = 1, size_t st = 45); - - Pillar(const Junction &junc, const Vec3d &endp) - : Pillar(junc.pos, endp, junc.r, junc.steps) - {} - - Pillar(const Head &head, const Vec3d &endp, double radius = 1) - : Pillar(head.junction_point(), endp, - head.request_pillar_radius(radius), head.steps) - {} - + + Pillar(const Vec3d &endp, double h, double radius = 1, size_t st = 45): + height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) {} + + +// Pillar(const Junction &junc, const Vec3d &endp) +// : Pillar(junc.pos, endp, junc.r, junc.steps) +// {} + inline Vec3d startpoint() const { - return {endpt(X), endpt(Y), endpt(Z) + height}; + return {endpt.x(), endpt.y(), endpt.z() + height}; } inline const Vec3d& endpoint() const { return endpt; } - Pillar& add_base(double baseheight = 3, double radius = 2); +// Pillar& add_base(double baseheight = 3, double radius = 2); +}; + +struct Pedestal { + Vec3d pos; + double height, radius; + size_t steps = 45; + + Pedestal() = default; + Pedestal(const Vec3d &p, double h = 3., double r = 2., size_t stps = 45) + : pos{p}, height{h}, radius{r}, steps{stps} + {} + + Pedestal(const Pillar &p, double h = 3., double r = 2.) + : Pedestal{p.endpt, std::min(h, p.height), std::max(r, p.r), p.steps} + {} +}; + +struct PinJoin { + }; // A Bridge between two pillars (with junction endpoints) @@ -241,66 +250,39 @@ struct Pad { bool empty() const { return tmesh.facets_count() == 0; } }; -// Give points on a 3D ring with given center, radius and orientation -// method based on: -// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space -template -class PointRing { - std::array m_phis; +inline Contour3D get_mesh(const Head &h) +{ + Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, h.steps); - // Two vectors that will be perpendicular to each other and to the - // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a - // placeholder. - // a and b vectors are perpendicular to the ring direction and to each other. - // Together they define the plane where we have to iterate with the - // given angles in the 'm_phis' vector - Vec3d a = {0, 1, 0}, b; - double m_radius = 0.; + using Quaternion = Eigen::Quaternion; - static inline bool constexpr is_one(double val) - { - return std::abs(std::abs(val) - 1) < 1e-20; + // We rotate the head to the specified direction The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir); + + for(auto& p : mesh.points) p = quatern * p + h.pos; +} + +inline Contour3D get_mesh(const Pillar &p) +{ + assert(p.steps > 0); + + if(p.height > EPSILON) { // Endpoint is below the starting point + // We just create a bridge geometry with the pillar parameters and + // move the data. + return cylinder(p.r, p.height, p.steps, p.endpoint()); } -public: + return {}; +} - PointRing(const Vec3d &n) - { - m_phis = linspace_array(0., 2 * PI); +inline Contour3D get_mesh(const Pedestal &p, double h, double r) +{ + return pedestal(p.pos, p.height, p.radius, p.steps); +} - // We have to address the case when the direction vector v (same as - // dir) is coincident with one of the world axes. In this case two of - // its components will be completely zero and one is 1.0. Our method - // becomes dangerous here due to division with zero. Instead, vector - // 'a' can be an element-wise rotated version of 'v' - if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { - a = {n(Z), n(X), n(Y)}; - b = {n(Y), n(Z), n(X)}; - } - else { - a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); - b = a.cross(n); - } - } - - Vec3d get(size_t idx, const Vec3d src, double r) const - { - double phi = m_phis[idx]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - double rpscos = r * cosphi; - double rpssin = r * sinphi; - - // Point on the sphere - return {src(X) + rpscos * a(X) + rpssin * b(X), - src(Y) + rpscos * a(Y) + rpssin * b(Y), - src(Z) + rpscos * a(Z) + rpssin * b(Z)}; - } -}; - -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); // This class will hold the support tree meshes with some additional // bookkeeping as well. Various parts of the support geometry are stored diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 334c88fb90..a8e79dc179 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -15,6 +15,119 @@ using libnest2d::opt::StopCriteria; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::SubplexOptimizer; +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) +{ + static const size_t SAMPLES = 8; + + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + + const double& sd = msh.cfg.safety_distance_mm; + + auto& m = msh.emesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + Vec3d s1 = h.pos, s2 = h.junction_point(); + + struct Rings { + double rpin; + double rback; + Vec3d spin; + Vec3d sback; + PointRing ring; + + Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } + Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } + } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; + + // We will shoot multiple rays from the head pinpoint in the direction + // of the pinhead robe (side) surface. The result will be the smallest + // hit distance. + + auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { + // Point on the circle on the pin sphere + Vec3d ps = rings.pinring(i); + // This is the point on the circle on the back sphere + Vec3d p = rings.backring(i); + + // Point ps is not on mesh but can be inside or + // outside as well. This would cause many problems + // with ray-casting. To detect the position we will + // use the ray-casting result (which has an is_inside + // predicate). + + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd * n, n); + + if (q.is_inside()) { // the hit is inside the model + if (q.distance() > rings.rpin) { + // If we are inside the model and the hit + // distance is bigger than our pin circle + // diameter, it probably indicates that the + // support point was already inside the + // model, or there is really no space + // around the point. We will assign a zero + // hit distance to these cases which will + // enforce the function return value to be + // an invalid ray with zero hit distance. + // (see min_element at the end) + hit = HitResult(0.0); + } else { + // re-cast the ray from the outside of the + // object. The starting point has an offset + // of 2*safety_distance because the + // original ray has also had an offset + auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); + hit = q2; + } + } else + hit = q; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); +} + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) +{ + + static const size_t SAMPLES = 8; + + Vec3d dir = (br.endp - br.startp).normalized(); + PointRing ring{dir}; + + using Hit = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; + + auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { + + // Point on the circle on the pin sphere + Vec3d p = ring.get(i, br.startp, br.r + sd); + + auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); + + if(hr.is_inside()) { + if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); + else { + // re-cast the ray from the outside of the object + hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); + } + } else hit = hr; + }; + + ccr::enumerate(hits.begin(), hits.end(), hitfn); + + return min_hit(hits); +} + SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm) : m_cfg(sm.cfg) @@ -246,7 +359,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( } EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( - const Vec3d &src, const Vec3d &dir, double r, double safety_d) + const Vec3d &src, const Vec3d &dir, double r, double sd) { static const size_t SAMPLES = 8; PointRing ring{dir}; @@ -255,25 +368,20 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( // Hit results std::array hits; - - double sd = std::isnan(safety_d) ? m_cfg.safety_distance_mm : safety_d; - sd = sd * r / m_cfg.head_back_radius_mm; - - bool ins_check = sd < m_cfg.safety_distance_mm; ccr::enumerate(hits.begin(), hits.end(), - [this, r, src, ins_check, &ring, dir, sd] (Hit &hit, size_t i) { + [this, r, src, /*ins_check,*/ &ring, dir, sd] (Hit &hit, size_t i) { // Point on the circle on the pin sphere Vec3d p = ring.get(i, src, r + sd); auto hr = m_mesh.query_ray_hit(p + r * dir, dir); - if(ins_check && hr.is_inside()) { + if(/*ins_check && */hr.is_inside()) { if(hr.distance() > 2 * r + sd) hit = Hit(0.0); else { // re-cast the ray from the outside of the object - hit = m_mesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); + hit = m_mesh.query_ray_hit(p + (hr.distance() + EPSILON) * dir, dir); } } else hit = hr; }); @@ -499,7 +607,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, normal_mode = false; if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { - m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); return false; } } @@ -507,7 +615,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // Check if the deduced route is sane and exit with error if not. if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { - m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); return false; } @@ -798,13 +906,16 @@ void SupportTreeBuildsteps::routing_to_ground() cl_centroids.emplace_back(hid); Head &h = m_builder.head(hid); - h.transform(); if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; - h.invalidate(); + m_iheads_onmodel.emplace_back(h.id); +// h.invalidate(); + continue; } + + h.transform(); } // now we will go through the clusters ones again and connect the @@ -854,12 +965,14 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) if(!std::isinf(tdown)) return false; Vec3d endp = hjp + d * dir; - m_builder.add_bridge(head.id, endp); - m_builder.add_junction(endp, head.r_back_mm); + bool ret = false; + + if ((ret = create_ground_pillar(endp, dir, head.r_back_mm))) { + m_builder.add_bridge(head.id, endp); + m_builder.add_junction(endp, head.r_back_mm); + } - this->create_ground_pillar(endp, dir, head.r_back_mm); - - return true; + return ret; } bool SupportTreeBuildsteps::connect_to_ground(Head &head) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index ae872f98b7..bfa38505b0 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -46,6 +46,68 @@ inline Vec3d spheric_to_dir(const std::pair &v) return spheric_to_dir(v.first, v.second); } + +// Give points on a 3D ring with given center, radius and orientation +// method based on: +// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space +template +class PointRing { + std::array m_phis; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + // a and b vectors are perpendicular to the ring direction and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'm_phis' vector + Vec3d a = {0, 1, 0}, b; + double m_radius = 0.; + + static inline bool constexpr is_one(double val) + { + return std::abs(std::abs(val) - 1) < 1e-20; + } + +public: + + PointRing(const Vec3d &n) + { + m_phis = linspace_array(0., 2 * PI); + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) { + a = {n(Z), n(X), n(Y)}; + b = {n(Y), n(Z), n(X)}; + } + else { + a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize(); + b = a.cross(n); + } + } + + Vec3d get(size_t idx, const Vec3d src, double r) const + { + double phi = m_phis[idx]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + double rpscos = r * cosphi; + double rpssin = r * sinphi; + + // Point on the sphere + return {src(X) + rpscos * a(X) + rpssin * b(X), + src(Y) + rpscos * a(Y) + rpssin * b(Y), + src(Z) + rpscos * a(Z) + rpssin * b(Z)}; + } +}; + +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); +EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); + // This function returns the position of the centroid in the input 'clust' // vector of point indices. template @@ -242,7 +304,15 @@ class SupportTreeBuildsteps { const Vec3d& s, const Vec3d& dir, double r, - double safety_d = std::nan("")); + double safety_d); + + EigenMesh3D::hit_result bridge_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r) + { + return bridge_mesh_intersect(s, dir, r, m_cfg.safety_distance_mm); + } template inline double bridge_mesh_distance(Args&&...args) { From 184f64f8281229c0ffb36d273eda24fb12e14ebb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 16 Jun 2020 13:17:06 +0200 Subject: [PATCH 240/826] Separate support tree routing and meshing, remove Common.hpp/.cpp . * Remove Common.hpp and Common.cpp, move things into their respective modules in sla. --- src/libslic3r/CMakeLists.txt | 7 +- src/libslic3r/OpenVDBUtils.hpp | 1 - src/libslic3r/SLA/BoostAdapter.hpp | 4 +- src/libslic3r/SLA/Clustering.cpp | 152 +++++++ src/libslic3r/SLA/Clustering.hpp | 58 ++- src/libslic3r/SLA/Common.hpp | 27 -- src/libslic3r/SLA/Contour3D.hpp | 9 +- .../SLA/{Common.cpp => EigenMesh3D.cpp} | 372 ++---------------- src/libslic3r/SLA/EigenMesh3D.hpp | 6 +- src/libslic3r/SLA/Hollowing.cpp | 3 +- src/libslic3r/SLA/Hollowing.hpp | 1 - src/libslic3r/SLA/JobController.hpp | 1 + src/libslic3r/SLA/Pad.cpp | 1 - src/libslic3r/SLA/Rotfinder.cpp | 1 - src/libslic3r/SLA/SpatIndex.cpp | 161 ++++++++ src/libslic3r/SLA/SpatIndex.hpp | 2 +- src/libslic3r/SLA/SupportPoint.hpp | 1 - src/libslic3r/SLA/SupportPointGenerator.hpp | 1 - src/libslic3r/SLA/SupportTree.cpp | 1 - src/libslic3r/SLA/SupportTree.hpp | 1 - src/libslic3r/SLA/SupportTreeBuilder.cpp | 326 +-------------- src/libslic3r/SLA/SupportTreeBuilder.hpp | 184 +++------ src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 62 +-- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 50 +-- src/libslic3r/SLA/SupportTreeMesher.cpp | 268 +++++++++++++ src/libslic3r/SLA/SupportTreeMesher.hpp | 94 +++++ tests/sla_print/sla_treebuilder_tests.cpp | 15 +- 27 files changed, 897 insertions(+), 912 deletions(-) create mode 100644 src/libslic3r/SLA/Clustering.cpp delete mode 100644 src/libslic3r/SLA/Common.hpp rename src/libslic3r/SLA/{Common.cpp => EigenMesh3D.cpp} (58%) create mode 100644 src/libslic3r/SLA/SpatIndex.cpp create mode 100644 src/libslic3r/SLA/SupportTreeMesher.cpp create mode 100644 src/libslic3r/SLA/SupportTreeMesher.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9f566b4051..20f3c6b4ba 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -204,11 +204,11 @@ add_library(libslic3r STATIC SimplifyMesh.cpp MarchingSquares.hpp ${OpenVDBUtils_SOURCES} - SLA/Common.hpp - SLA/Common.cpp SLA/Pad.hpp SLA/Pad.cpp SLA/SupportTreeBuilder.hpp + SLA/SupportTreeMesher.hpp + SLA/SupportTreeMesher.cpp SLA/SupportTreeBuildsteps.hpp SLA/SupportTreeBuildsteps.cpp SLA/SupportTreeBuilder.cpp @@ -220,6 +220,7 @@ add_library(libslic3r STATIC SLA/Rotfinder.cpp SLA/BoostAdapter.hpp SLA/SpatIndex.hpp + SLA/SpatIndex.cpp SLA/RasterBase.hpp SLA/RasterBase.cpp SLA/AGGRaster.hpp @@ -236,7 +237,9 @@ add_library(libslic3r STATIC SLA/Contour3D.hpp SLA/Contour3D.cpp SLA/EigenMesh3D.hpp + SLA/EigenMesh3D.cpp SLA/Clustering.hpp + SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp ) diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index c493845a1c..e35231d35b 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -2,7 +2,6 @@ #define OPENVDBUTILS_HPP #include -#include #include #include diff --git a/src/libslic3r/SLA/BoostAdapter.hpp b/src/libslic3r/SLA/BoostAdapter.hpp index b7b3c63a6c..13e0465b14 100644 --- a/src/libslic3r/SLA/BoostAdapter.hpp +++ b/src/libslic3r/SLA/BoostAdapter.hpp @@ -1,7 +1,9 @@ #ifndef SLA_BOOSTADAPTER_HPP #define SLA_BOOSTADAPTER_HPP -#include +#include +#include + #include namespace boost { diff --git a/src/libslic3r/SLA/Clustering.cpp b/src/libslic3r/SLA/Clustering.cpp new file mode 100644 index 0000000000..41ff1d4f09 --- /dev/null +++ b/src/libslic3r/SLA/Clustering.cpp @@ -0,0 +1,152 @@ +#include "Clustering.hpp" +#include "boost/geometry/index/rtree.hpp" + +#include +#include + +namespace Slic3r { namespace sla { + +namespace bgi = boost::geometry::index; +using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; + +namespace { + +bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) +{ + return e1.second < e2.second; +}; + +ClusteredPoints cluster(Index3D &sindex, + unsigned max_points, + std::function( + const Index3D &, const PointIndexEl &)> qfn) +{ + using Elems = std::vector; + + // Recursive function for visiting all the points in a given distance to + // each other + std::function group = + [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) + { + for(auto& p : pts) { + std::vector tmp = qfn(sindex, p); + + std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); + + Elems newpts; + std::set_difference(tmp.begin(), tmp.end(), + cluster.begin(), cluster.end(), + std::back_inserter(newpts), cmp_ptidx_elements); + + int c = max_points && newpts.size() + cluster.size() > max_points? + int(max_points - cluster.size()) : int(newpts.size()); + + cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); + std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); + + if(!newpts.empty() && (!max_points || cluster.size() < max_points)) + group(newpts, cluster); + } + }; + + std::vector clusters; + for(auto it = sindex.begin(); it != sindex.end();) { + Elems cluster = {}; + Elems pts = {*it}; + group(pts, cluster); + + for(auto& c : cluster) sindex.remove(c); + it = sindex.begin(); + + clusters.emplace_back(cluster); + } + + ClusteredPoints result; + for(auto& cluster : clusters) { + result.emplace_back(); + for(auto c : cluster) result.back().emplace_back(c.second); + } + + return result; +} + +std::vector distance_queryfn(const Index3D& sindex, + const PointIndexEl& p, + double dist, + unsigned max_points) +{ + std::vector 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((p.first - it->first).norm() > dist) it = tmp.erase(it); + + return tmp; +} + +} // namespace + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const std::vector& indices, + std::function 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 PointIndexEl& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); +} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + std::function 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 PointIndexEl& p) + { + std::vector tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ + return predicate(p, e); + }), std::back_inserter(tmp)); + return tmp; + }); +} + +ClusteredPoints cluster(const Eigen::MatrixXd& 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 PointIndexEl& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Clustering.hpp b/src/libslic3r/SLA/Clustering.hpp index 1b0d47d953..269ec28822 100644 --- a/src/libslic3r/SLA/Clustering.hpp +++ b/src/libslic3r/SLA/Clustering.hpp @@ -2,7 +2,8 @@ #define SLA_CLUSTERING_HPP #include -#include + +#include #include namespace Slic3r { namespace sla { @@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector& indices, double dist, unsigned max_points); -ClusteredPoints cluster(const PointSet& points, +ClusteredPoints cluster(const Eigen::MatrixXd& points, double dist, unsigned max_points); @@ -26,5 +27,56 @@ ClusteredPoints cluster( std::function predicate, unsigned max_points); -}} +// This function returns the position of the centroid in the input 'clust' +// vector of point indices. +template +long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df) +{ + switch(clust.size()) { + case 0: /* empty cluster */ return -1; + case 1: /* only one element */ return 0; + case 2: /* if two elements, there is no center */ return 0; + default: ; + } + + // The function works by calculating for each point the average distance + // from all the other points in the cluster. We create a selector bitmask of + // the same size as the cluster. The bitmask will have two true bits and + // false bits for the rest of items and we will loop through all the + // permutations of the bitmask (combinations of two points). Get the + // distance for the two points and add the distance to the averages. + // The point with the smallest average than wins. + + // The complexity should be O(n^2) but we will mostly apply this function + // for small clusters only (cca 3 elements) + + std::vector sel(clust.size(), false); // create full zero bitmask + std::fill(sel.end() - 2, sel.end(), true); // insert the two ones + std::vector avgs(clust.size(), 0.0); // store the average distances + + do { + std::array idx; + for(size_t i = 0, j = 0; i < clust.size(); i++) + if(sel[i]) idx[j++] = i; + + double d = df(pointfn(clust[idx[0]]), + pointfn(clust[idx[1]])); + + // add the distance to the sums for both associated points + for(auto i : idx) avgs[i] += d; + + // now continue with the next permutation of the bitmask with two 1s + } while(std::next_permutation(sel.begin(), sel.end())); + + // Divide by point size in the cluster to get the average (may be redundant) + for(auto& a : avgs) a /= clust.size(); + + // get the lowest average distance and return the index + auto minit = std::min_element(avgs.begin(), avgs.end()); + return long(minit - avgs.begin()); +} + + +}} // namespace Slic3r::sla + #endif // CLUSTERING_HPP diff --git a/src/libslic3r/SLA/Common.hpp b/src/libslic3r/SLA/Common.hpp deleted file mode 100644 index ca616cabce..0000000000 --- a/src/libslic3r/SLA/Common.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SLA_COMMON_HPP -#define SLA_COMMON_HPP - -#include -#include -#include -#include -#include - - -namespace Slic3r { - -// Typedefs from Point.hpp -typedef Eigen::Matrix Vec3f; -typedef Eigen::Matrix Vec3d; -typedef Eigen::Matrix Vec3i; -typedef Eigen::Matrix Vec4i; - -namespace sla { - -using PointSet = Eigen::MatrixXd; - -} // namespace sla -} // namespace Slic3r - - -#endif // SLASUPPORTTREE_HPP diff --git a/src/libslic3r/SLA/Contour3D.hpp b/src/libslic3r/SLA/Contour3D.hpp index 295612f19b..1a4fa9a29c 100644 --- a/src/libslic3r/SLA/Contour3D.hpp +++ b/src/libslic3r/SLA/Contour3D.hpp @@ -1,11 +1,14 @@ #ifndef SLA_CONTOUR3D_HPP #define SLA_CONTOUR3D_HPP -#include - #include -namespace Slic3r { namespace sla { +namespace Slic3r { + +// Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils) +using Vec4i = Eigen::Matrix; + +namespace sla { class EigenMesh3D; diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/EigenMesh3D.cpp similarity index 58% rename from src/libslic3r/SLA/Common.cpp rename to src/libslic3r/SLA/EigenMesh3D.cpp index a7420a7fb8..be44e324c6 100644 --- a/src/libslic3r/SLA/Common.cpp +++ b/src/libslic3r/SLA/EigenMesh3D.cpp @@ -1,185 +1,16 @@ -#include -#include -#include -#include -#include -#include -#include +#include "EigenMesh3D.hpp" +#include "Concurrency.hpp" + #include +#include -// for concave hull merging decisions -#include -#include "boost/geometry/index/rtree.hpp" - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4244) -#pragma warning(disable: 4267) -#endif - - -#include +#include #ifdef SLIC3R_HOLE_RAYCASTER - #include +#include #endif - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - -namespace Slic3r { -namespace sla { - - -/* ************************************************************************** - * PointIndex implementation - * ************************************************************************** */ - -class PointIndex::Impl { -public: - using BoostIndex = boost::geometry::index::rtree< PointIndexEl, - boost::geometry::index::rstar<16, 4> /* ? */ >; - - BoostIndex m_store; -}; - -PointIndex::PointIndex(): m_impl(new Impl()) {} -PointIndex::~PointIndex() {} - -PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} - -PointIndex& PointIndex::operator=(const PointIndex &cpy) -{ - m_impl.reset(new Impl(*cpy.m_impl)); - return *this; -} - -PointIndex& PointIndex::operator=(PointIndex &&cpy) -{ - m_impl.swap(cpy.m_impl); - return *this; -} - -void PointIndex::insert(const PointIndexEl &el) -{ - m_impl->m_store.insert(el); -} - -bool PointIndex::remove(const PointIndexEl& el) -{ - return m_impl->m_store.remove(el) == 1; -} - -std::vector -PointIndex::query(std::function fn) const -{ - namespace bgi = boost::geometry::index; - - std::vector ret; - m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); - return ret; -} - -std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) const -{ - namespace bgi = boost::geometry::index; - std::vector ret; ret.reserve(k); - m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); - return ret; -} - -size_t PointIndex::size() const -{ - return m_impl->m_store.size(); -} - -void PointIndex::foreach(std::function fn) -{ - for(auto& el : m_impl->m_store) fn(el); -} - -void PointIndex::foreach(std::function fn) const -{ - for(const auto &el : m_impl->m_store) fn(el); -} - -/* ************************************************************************** - * BoxIndex implementation - * ************************************************************************** */ - -class BoxIndex::Impl { -public: - using BoostIndex = boost::geometry::index:: - rtree /* ? */>; - - BoostIndex m_store; -}; - -BoxIndex::BoxIndex(): m_impl(new Impl()) {} -BoxIndex::~BoxIndex() {} - -BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} - -BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) -{ - m_impl.reset(new Impl(*cpy.m_impl)); - return *this; -} - -BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) -{ - m_impl.swap(cpy.m_impl); - return *this; -} - -void BoxIndex::insert(const BoxIndexEl &el) -{ - m_impl->m_store.insert(el); -} - -bool BoxIndex::remove(const BoxIndexEl& el) -{ - return m_impl->m_store.remove(el) == 1; -} - -std::vector BoxIndex::query(const BoundingBox &qrbb, - BoxIndex::QueryType qt) -{ - namespace bgi = boost::geometry::index; - - std::vector ret; ret.reserve(m_impl->m_store.size()); - - switch (qt) { - case qtIntersects: - m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); - break; - case qtWithin: - m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); - } - - return ret; -} - -size_t BoxIndex::size() const -{ - return m_impl->m_store.size(); -} - -void BoxIndex::foreach(std::function fn) -{ - for(auto& el : m_impl->m_store) fn(el); -} - - -/* **************************************************************************** - * EigenMesh3D implementation - * ****************************************************************************/ - +namespace Slic3r { namespace sla { class EigenMesh3D::AABBImpl { private: @@ -189,7 +20,7 @@ public: void init(const TriangleMesh& tm) { m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - tm.its.vertices, tm.its.indices); + tm.its.vertices, tm.its.indices); } void intersect_ray(const TriangleMesh& tm, @@ -215,9 +46,9 @@ public: size_t idx_unsigned = 0; Vec3d closest_vec3d(closest); double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( - tm.its.vertices, - tm.its.indices, - m_tree, point, idx_unsigned, closest_vec3d); + tm.its.vertices, + tm.its.indices, + m_tree, point, idx_unsigned, closest_vec3d); i = int(idx_unsigned); closest = closest_vec3d; return dist; @@ -231,7 +62,7 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) { auto&& bb = tmesh.bounding_box(); m_ground_level += bb.min(Z); - + // Build the AABB accelaration tree m_aabb->init(tmesh); } @@ -289,7 +120,6 @@ Vec3d EigenMesh3D::normal_by_face_id(int face_id) const { } - EigenMesh3D::hit_result EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const { @@ -325,7 +155,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const std::vector outs; std::vector hits; m_aabb->intersect_ray(*m_tm, s, dir, hits); - + // The sort is necessary, the hits are not always sorted. std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); @@ -334,7 +164,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const // along an axis of a cube due to floating-point approximations in igl (?) hits.erase(std::unique(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) - { return a.t == b.t; }), + { return a.t == b.t; }), hits.end()); // Convert the igl::Hit into hit_result @@ -356,7 +186,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const #ifdef SLIC3R_HOLE_RAYCASTER EigenMesh3D::hit_result EigenMesh3D::filter_hits( - const std::vector& object_hits) const + const std::vector& object_hits) const { assert(! m_holes.empty()); hit_result out(*this); @@ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits( }; std::vector hole_isects; hole_isects.reserve(m_holes.size()); - + auto sf = s.cast(); auto dirf = dir.cast(); @@ -461,29 +291,17 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { return sqdst; } -/* **************************************************************************** - * Misc functions - * ****************************************************************************/ -namespace { - -bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, - double eps = 0.05) +static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, + double eps = 0.05) { using Line3D = Eigen::ParametrizedLine; - + auto line = Line3D::Through(e1, e2); double d = line.distance(p); return std::abs(d) < eps; } -template double distance(const Vec& pp1, const Vec& pp2) { - auto p = pp2 - pp1; - return std::sqrt(p.transpose() * p); -} - -} - PointSet normals(const PointSet& points, const EigenMesh3D& mesh, double eps, @@ -531,11 +349,11 @@ PointSet normals(const PointSet& points, // ic will mark a single vertex. int ia = -1, ib = -1, ic = -1; - if (std::abs(distance(p, p1)) < eps) { + if (std::abs((p - p1).norm()) < eps) { ic = trindex(0); - } else if (std::abs(distance(p, p2)) < eps) { + } else if (std::abs((p - p2).norm()) < eps) { ic = trindex(1); - } else if (std::abs(distance(p, p3)) < eps) { + } else if (std::abs((p - p3).norm()) < eps) { ic = trindex(2); } else if (point_on_edge(p, p1, p2, eps)) { ia = trindex(0); @@ -612,148 +430,4 @@ PointSet normals(const PointSet& points, return ret; } -namespace bgi = boost::geometry::index; -using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; - -namespace { - -bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) -{ - return e1.second < e2.second; -}; - -ClusteredPoints cluster(Index3D &sindex, - unsigned max_points, - std::function( - const Index3D &, const PointIndexEl &)> qfn) -{ - using Elems = std::vector; - - // Recursive function for visiting all the points in a given distance to - // each other - std::function group = - [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) - { - for(auto& p : pts) { - std::vector tmp = qfn(sindex, p); - - std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); - - Elems newpts; - std::set_difference(tmp.begin(), tmp.end(), - cluster.begin(), cluster.end(), - std::back_inserter(newpts), cmp_ptidx_elements); - - int c = max_points && newpts.size() + cluster.size() > max_points? - int(max_points - cluster.size()) : int(newpts.size()); - - cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); - std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); - - if(!newpts.empty() && (!max_points || cluster.size() < max_points)) - group(newpts, cluster); - } - }; - - std::vector clusters; - for(auto it = sindex.begin(); it != sindex.end();) { - Elems cluster = {}; - Elems pts = {*it}; - group(pts, cluster); - - for(auto& c : cluster) sindex.remove(c); - it = sindex.begin(); - - clusters.emplace_back(cluster); - } - - ClusteredPoints result; - for(auto& cluster : clusters) { - result.emplace_back(); - for(auto c : cluster) result.back().emplace_back(c.second); - } - - return result; -} - -std::vector distance_queryfn(const Index3D& sindex, - const PointIndexEl& p, - double dist, - unsigned max_points) -{ - std::vector 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; -} - -} // namespace - -// Clustering a set of points by the given criteria -ClusteredPoints cluster( - const std::vector& indices, - std::function 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 PointIndexEl& p) - { - return distance_queryfn(sidx, p, dist, max_points); - }); -} - -// Clustering a set of points by the given criteria -ClusteredPoints cluster( - const std::vector& indices, - std::function pointfn, - std::function 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 PointIndexEl& p) - { - std::vector tmp; tmp.reserve(max_points); - sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& 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 PointIndexEl& p) - { - return distance_queryfn(sidx, p, dist, max_points); - }); -} - -} // namespace sla -} // namespace Slic3r +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp index 7b7562d477..c9196bb432 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/EigenMesh3D.hpp @@ -1,8 +1,10 @@ #ifndef SLA_EIGENMESH3D_H #define SLA_EIGENMESH3D_H -#include +#include +#include +#include // There is an implementation of a hole-aware raycaster that was eventually // not used in production version. It is now hidden under following define @@ -19,6 +21,8 @@ class TriangleMesh; namespace sla { +using PointSet = Eigen::MatrixXd; + /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree. // Implemented in libslic3r/SLA/Common.cpp diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 0dd9436a1d..44e4dd8390 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -3,11 +3,10 @@ #include #include #include -#include #include -#include #include #include +#include #include diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index cc7d310eae..1f65fa8b70 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -2,7 +2,6 @@ #define SLA_HOLLOWING_HPP #include -#include #include #include diff --git a/src/libslic3r/SLA/JobController.hpp b/src/libslic3r/SLA/JobController.hpp index 3baa3d12d1..b815e4d6fc 100644 --- a/src/libslic3r/SLA/JobController.hpp +++ b/src/libslic3r/SLA/JobController.hpp @@ -2,6 +2,7 @@ #define SLA_JOBCONTROLLER_HPP #include +#include namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index d933ef5ed7..f2b189cd11 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index fda8383b11..81ef00e6b3 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include "Model.hpp" diff --git a/src/libslic3r/SLA/SpatIndex.cpp b/src/libslic3r/SLA/SpatIndex.cpp new file mode 100644 index 0000000000..d95ba55bee --- /dev/null +++ b/src/libslic3r/SLA/SpatIndex.cpp @@ -0,0 +1,161 @@ +#include "SpatIndex.hpp" + +// for concave hull merging decisions +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif + +#include "boost/geometry/index/rtree.hpp" + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace Slic3r { namespace sla { + +/* ************************************************************************** + * PointIndex implementation + * ************************************************************************** */ + +class PointIndex::Impl { +public: + using BoostIndex = boost::geometry::index::rtree< PointIndexEl, + boost::geometry::index::rstar<16, 4> /* ? */ >; + + BoostIndex m_store; +}; + +PointIndex::PointIndex(): m_impl(new Impl()) {} +PointIndex::~PointIndex() {} + +PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +PointIndex& PointIndex::operator=(const PointIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +PointIndex& PointIndex::operator=(PointIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void PointIndex::insert(const PointIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool PointIndex::remove(const PointIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector +PointIndex::query(std::function fn) const +{ + namespace bgi = boost::geometry::index; + + std::vector ret; + m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); + return ret; +} + +std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) const +{ + namespace bgi = boost::geometry::index; + std::vector ret; ret.reserve(k); + m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); + return ret; +} + +size_t PointIndex::size() const +{ + return m_impl->m_store.size(); +} + +void PointIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +void PointIndex::foreach(std::function fn) const +{ + for(const auto &el : m_impl->m_store) fn(el); +} + +/* ************************************************************************** + * BoxIndex implementation + * ************************************************************************** */ + +class BoxIndex::Impl { +public: + using BoostIndex = boost::geometry::index:: + rtree /* ? */>; + + BoostIndex m_store; +}; + +BoxIndex::BoxIndex(): m_impl(new Impl()) {} +BoxIndex::~BoxIndex() {} + +BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void BoxIndex::insert(const BoxIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool BoxIndex::remove(const BoxIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector BoxIndex::query(const BoundingBox &qrbb, + BoxIndex::QueryType qt) +{ + namespace bgi = boost::geometry::index; + + std::vector ret; ret.reserve(m_impl->m_store.size()); + + switch (qt) { + case qtIntersects: + m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); + break; + case qtWithin: + m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); + } + + return ret; +} + +size_t BoxIndex::size() const +{ + return m_impl->m_store.size(); +} + +void BoxIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SpatIndex.hpp b/src/libslic3r/SLA/SpatIndex.hpp index 2955cdcdf6..ef059d3ae6 100644 --- a/src/libslic3r/SLA/SpatIndex.hpp +++ b/src/libslic3r/SLA/SpatIndex.hpp @@ -73,7 +73,7 @@ public: BoxIndex& operator=(BoxIndex&&); void insert(const BoxIndexEl&); - inline void insert(const BoundingBox& bb, unsigned idx) + void insert(const BoundingBox& bb, unsigned idx) { insert(std::make_pair(bb, unsigned(idx))); } diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 455962cc40..2b973697bb 100644 --- a/src/libslic3r/SLA/SupportPoint.hpp +++ b/src/libslic3r/SLA/SupportPoint.hpp @@ -2,7 +2,6 @@ #define SLA_SUPPORTPOINT_HPP #include -#include #include namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 1729230561..3f07e96746 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -3,7 +3,6 @@ #include -#include #include #include diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 2edc4d21b1..eec819e225 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index c6255aa2f2..3b9f603fd2 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index ebeca78a72..d4a9d00c99 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -1,278 +1,18 @@ #include #include +#include #include namespace Slic3r { namespace sla { -Contour3D sphere(double rho, Portion portion, double fa) { - - Contour3D ret; - - // prohibit close to zero radius - if(rho <= 1e-6 && rho >= -1e-6) return ret; - - auto& vertices = ret.points; - auto& facets = ret.faces3; - - // Algorithm: - // Add points one-by-one to the sphere grid and form facets using relative - // coordinates. Sphere is composed effectively of a mesh of stacked circles. - - // adjust via rounding to get an even multiple for any provided angle. - double angle = (2*PI / floor(2*PI / fa)); - - // Ring to be scaled to generate the steps of the sphere - std::vector ring; - - for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); - - const auto sbegin = size_t(2*std::get<0>(portion)/angle); - const auto send = size_t(2*std::get<1>(portion)/angle); - - const size_t steps = ring.size(); - const double increment = 1.0 / double(steps); - - // special case: first ring connects to 0,0,0 - // insert and form facets. - if(sbegin == 0) - vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); - - auto id = coord_t(vertices.size()); - for (size_t i = 0; i < ring.size(); i++) { - // Fixed scaling - const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); - // radius of the circle for this step. - const double r = std::sqrt(std::abs(rho*rho - z*z)); - Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(b(0), b(1), z)); - - if (sbegin == 0) - (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) : - facets.emplace_back(id - 1, 0, id); - ++id; - } - - // General case: insert and form facets for each step, - // joining it to the ring below it. - for (size_t s = sbegin + 2; s < send - 1; s++) { - const double z = -rho + increment*double(s*2.0*rho); - const double r = std::sqrt(std::abs(rho*rho - z*z)); - - for (size_t i = 0; i < ring.size(); i++) { - Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(b(0), b(1), z)); - auto id_ringsize = coord_t(id - int(ring.size())); - if (i == 0) { - // wrap around - facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) ); - facets.emplace_back(id - 1, id_ringsize, id); - } else { - facets.emplace_back(id_ringsize - 1, id_ringsize, id); - facets.emplace_back(id - 1, id_ringsize - 1, id); - } - id++; - } - } - - // special case: last ring connects to 0,0,rho*2.0 - // only form facets. - if(send >= size_t(2*PI / angle)) { - vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); - for (size_t i = 0; i < ring.size(); i++) { - auto id_ringsize = coord_t(id - int(ring.size())); - if (i == 0) { - // third vertex is on the other side of the ring. - facets.emplace_back(id - 1, id_ringsize, id); - } else { - auto ci = coord_t(id_ringsize + coord_t(i)); - facets.emplace_back(ci - 1, ci, id); - } - } - } - id++; - - return ret; -} - -Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) -{ - Contour3D ret; - - auto steps = int(ssteps); - auto& points = ret.points; - auto& indices = ret.faces3; - points.reserve(2*ssteps); - double a = 2*PI/steps; - - Vec3d jp = sp; - Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; - - // Upper circle points - for(int i = 0; i < steps; ++i) { - double phi = i*a; - double ex = endp(X) + r*std::cos(phi); - double ey = endp(Y) + r*std::sin(phi); - points.emplace_back(ex, ey, endp(Z)); - } - - // Lower circle points - for(int i = 0; i < steps; ++i) { - double phi = i*a; - double x = jp(X) + r*std::cos(phi); - double y = jp(Y) + r*std::sin(phi); - points.emplace_back(x, y, jp(Z)); - } - - // Now create long triangles connecting upper and lower circles - indices.reserve(2*ssteps); - auto offs = steps; - for(int i = 0; i < steps - 1; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - } - - // Last triangle connecting the first and last vertices - auto last = steps - 1; - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - - // According to the slicing algorithms, we need to aid them with generating - // a watertight body. So we create a triangle fan for the upper and lower - // ending of the cylinder to close the geometry. - points.emplace_back(jp); int ci = int(points.size() - 1); - for(int i = 0; i < steps - 1; ++i) - indices.emplace_back(i + offs + 1, i + offs, ci); - - indices.emplace_back(offs, steps + offs - 1, ci); - - points.emplace_back(endp); ci = int(points.size() - 1); - for(int i = 0; i < steps - 1; ++i) - indices.emplace_back(ci, i, i + 1); - - indices.emplace_back(steps - 1, 0, ci); - - return ret; -} - -Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) -{ - assert(length > 0.); - assert(r_back > 0.); - assert(r_pin > 0.); - - Contour3D mesh; - - // We create two spheres which will be connected with a robe that fits - // both circles perfectly. - - // Set up the model detail level - const double detail = 2*PI/steps; - - // We don't generate whole circles. Instead, we generate only the - // portions which are visible (not covered by the robe) To know the - // exact portion of the bottom and top circles we need to use some - // rules of tangent circles from which we can derive (using simple - // triangles the following relations: - - // The height of the whole mesh - const double h = r_back + r_pin + length; - double phi = PI / 2. - std::acos((r_back - r_pin) / h); - - // To generate a whole circle we would pass a portion of (0, Pi) - // To generate only a half horizontal circle we can pass (0, Pi/2) - // The calculated phi is an offset to the half circles needed to smooth - // the transition from the circle to the robe geometry - - auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); - - for(auto& p : s2.points) p.z() += h; - - mesh.merge(s1); - mesh.merge(s2); - - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { - coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); - coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; - - mesh.faces3.emplace_back(i1s1, i2s1, i2s2); - mesh.faces3.emplace_back(i1s1, i2s2, i1s2); - } - - auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); - auto i2s1 = coord_t(s1.points.size()) - 1; - auto i1s2 = coord_t(s1.points.size()); - auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; - - mesh.faces3.emplace_back(i2s2, i2s1, i1s1); - mesh.faces3.emplace_back(i1s2, i2s2, i1s1); - - return mesh; -} - - -Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) -{ - if(baseheight <= 0) return {}; - - assert(steps >= 0); - auto last = int(steps - 1); - - Contour3D base; - - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius * std::cos(phi); - double y = endpt(Y) + radius * std::sin(phi); - base.points.emplace_back(x, y, z); - } - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); - } - - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); - base.points.emplace_back(ep); - - auto& indices = base.faces3; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { - indices.emplace_back(i, i + offs, offs + i + 1); - indices.emplace_back(i, offs + i + 1, i + 1); - indices.emplace_back(i, i + 1, hcenter); - indices.emplace_back(lcenter, offs + i + 1, offs + i); - } - - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); - indices.emplace_back(hcenter, last, 0); - indices.emplace_back(offs, offs + last, lcenter); - - return base; -} - Head::Head(double r_big_mm, double r_small_mm, double length_mm, double penetration, const Vec3d &direction, - const Vec3d &offset, - const size_t circlesteps) - : steps(circlesteps) - , dir(direction) + const Vec3d &offset) + : dir(direction) , pos(offset) , r_back_mm(r_big_mm) , r_pin_mm(r_small_mm) @@ -350,35 +90,6 @@ Head::Head(double r_big_mm, // return *this; //} -Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): - r(r_mm), startp(j1), endp(j2) -{ - using Quaternion = Eigen::Quaternion; - Vec3d dir = (j2 - j1).normalized(); - double d = distance(j2, j1); - - mesh = cylinder(r, d, steps); - - auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); - for(auto& p : mesh.points) p = quater * p + j1; -} - -Bridge::Bridge(const Vec3d &j1, - const Vec3d &j2, - double r1_mm, - double r2_mm, - size_t steps) -{ - Vec3d dir = (j2 - j1); - mesh = pinhead(r1_mm, r2_mm, dir.norm(), steps); - dir.normalize(); - - using Quaternion = Eigen::Quaternion; - auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); - - for(auto& p : mesh.points) p = quater * p + j1; -} - Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, @@ -464,7 +175,7 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) return *this; } -const TriangleMesh &SupportTreeBuilder::merged_mesh() const +const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const { if (m_meshcache_valid) return m_meshcache; @@ -472,28 +183,31 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const for (auto &head : m_heads) { if (ctl().stopcondition()) break; - if (head.is_valid()) merged.merge(get_mesh(head)); + if (head.is_valid()) merged.merge(get_mesh(head, steps)); } - for (auto &stick : m_pillars) { + for (auto &pill : m_pillars) { if (ctl().stopcondition()) break; - merged.merge(stick.mesh); - merged.merge(stick.base); + merged.merge(get_mesh(pill, steps)); + } + + for (auto &pedest : m_pedestals) { + merged.merge(get_mesh(pedest, steps)); } for (auto &j : m_junctions) { if (ctl().stopcondition()) break; - merged.merge(j.mesh); + merged.merge(get_mesh(j, steps)); } for (auto &bs : m_bridges) { if (ctl().stopcondition()) break; - merged.merge(bs.mesh); + merged.merge(get_mesh(bs, steps)); } for (auto &bs : m_crossbridges) { if (ctl().stopcondition()) break; - merged.merge(bs.mesh); + merged.merge(get_mesh(bs, steps)); } if (ctl().stopcondition()) { @@ -550,16 +264,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const return m_meshcache; } -template -static Hit min_hit(const C &hits) -{ - auto mit = std::min_element(hits.begin(), hits.end(), - [](const Hit &h1, const Hit &h2) { - return h1.distance() < h2.distance(); - }); - - return *mit; -} - - }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 087173e556..cc039de6fc 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -2,7 +2,6 @@ #define SLA_SUPPORTTREEBUILDER_HPP #include -#include #include #include #include @@ -50,13 +49,6 @@ namespace sla { * nearby pillar. */ -using Coordf = double; -using Portion = std::tuple; - -inline Portion make_portion(double a, double b) { - return std::make_tuple(a, b); -} - template double distance(const Vec& p) { return std::sqrt(p.transpose() * p); } @@ -66,27 +58,13 @@ template double distance(const Vec& pp1, const Vec& pp2) { return distance(p); } -Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), - double fa=(2*PI/360)); - -// Down facing cylinder in Z direction with arguments: -// r: radius -// h: Height -// ssteps: how many edges will create the base circle -// sp: starting point -Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); - -Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); - -Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); - const constexpr long ID_UNSET = -1; +const Vec3d DOWN = {0.0, 0.0, -1.0}; + +// A pinhead originating from a support point struct Head { - Contour3D mesh; - - size_t steps = 45; - Vec3d dir = {0, 0, -1}; + Vec3d dir = DOWN; Vec3d pos = {0, 0, 0}; double r_back_mm = 1; @@ -110,9 +88,9 @@ struct Head { double r_small_mm, double length_mm, double penetration, - const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end) - const Vec3d &offset = {0, 0, 0}, // displacement - const size_t circlesteps = 45); + const Vec3d &direction = DOWN, // direction (normal to the dull end) + const Vec3d &offset = {0, 0, 0} // displacement + ); void transform() { @@ -141,29 +119,27 @@ struct Head { } }; +struct Join { + enum Types { + jtPillarBrigde, jtHeadPillar, jtPillarPedestal, jtBridgePedestal, + jtPillarAnchor, jtBridgeAnchor + }; +}; + +// A junction connecting bridges and pillars struct Junction { - Contour3D mesh; double r = 1; - size_t steps = 45; Vec3d pos; long id = ID_UNSET; - - Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): - r(r_mm), steps(stepnum), pos(tr) - { - mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps); - for(auto& p : mesh.points) p += tr; - } + + Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} }; + struct Pillar { -// Contour3D mesh; -// Contour3D base; - double r = 1; - size_t steps = 0; + double height, r; Vec3d endpt; - double height = 0; long id = ID_UNSET; @@ -177,60 +153,47 @@ struct Pillar { // How many pillars are cascaded with this one unsigned links = 0; - Pillar(const Vec3d &endp, double h, double radius = 1, size_t st = 45): - height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) {} + Pillar(const Vec3d &endp, double h, double radius = 1.): + height{h}, r(radius), endpt(endp), starts_from_head(false) {} - -// Pillar(const Junction &junc, const Vec3d &endp) -// : Pillar(junc.pos, endp, junc.r, junc.steps) -// {} - - inline Vec3d startpoint() const + Vec3d startpoint() const { return {endpt.x(), endpt.y(), endpt.z() + height}; } - inline const Vec3d& endpoint() const { return endpt; } + const Vec3d& endpoint() const { return endpt; } // Pillar& add_base(double baseheight = 3, double radius = 2); }; +// A base for pillars or bridges that end on the ground struct Pedestal { Vec3d pos; double height, radius; - size_t steps = 45; + long id = ID_UNSET; - Pedestal() = default; - Pedestal(const Vec3d &p, double h = 3., double r = 2., size_t stps = 45) - : pos{p}, height{h}, radius{r}, steps{stps} - {} - - Pedestal(const Pillar &p, double h = 3., double r = 2.) - : Pedestal{p.endpt, std::min(h, p.height), std::max(r, p.r), p.steps} + Pedestal(const Vec3d &p, double h = 3., double r = 2.) + : pos{p}, height{h}, radius{r} {} }; -struct PinJoin { - -}; +// This is the thing that anchors a pillar or bridge to the model body. +// It is actually a reverse pinhead. +struct Anchor: public Head { using Head::Head; }; // A Bridge between two pillars (with junction endpoints) struct Bridge { - Contour3D mesh; double r = 0.8; long id = ID_UNSET; Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); Bridge(const Vec3d &j1, const Vec3d &j2, - double r_mm = 0.8, - size_t steps = 45); + double r_mm = 0.8): r{r_mm}, startp{j1}, endp{j2} + {} - Bridge(const Vec3d &j1, - const Vec3d &j2, - double r1_mm, - double r2_mm, - size_t steps = 45); + double get_length() const { return (endp - startp).norm(); } + Vec3d get_dir() const { return (endp - startp).normalized(); } }; // A wrapper struct around the pad @@ -250,40 +213,6 @@ struct Pad { bool empty() const { return tmesh.facets_count() == 0; } }; -inline Contour3D get_mesh(const Head &h) -{ - Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, h.steps); - - using Quaternion = Eigen::Quaternion; - - // We rotate the head to the specified direction The head's pointing - // side is facing upwards so this means that it would hold a support - // point with a normal pointing straight down. This is the reason of - // the -1 z coordinate - auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir); - - for(auto& p : mesh.points) p = quatern * p + h.pos; -} - -inline Contour3D get_mesh(const Pillar &p) -{ - assert(p.steps > 0); - - if(p.height > EPSILON) { // Endpoint is below the starting point - // We just create a bridge geometry with the pillar parameters and - // move the data. - return cylinder(p.r, p.height, p.steps, p.endpoint()); - } - - return {}; -} - -inline Contour3D get_mesh(const Pedestal &p, double h, double r) -{ - return pedestal(p.pos, p.height, p.radius, p.steps); -} - - // This class will hold the support tree meshes with some additional // bookkeeping as well. Various parts of the support geometry are stored // separately and are merged when the caller queries the merged mesh. The @@ -300,12 +229,15 @@ inline Contour3D get_mesh(const Pedestal &p, double h, double r) // merged mesh. It can be retrieved using a dedicated method (pad()) class SupportTreeBuilder: public SupportTree { // For heads it is beneficial to use the same IDs as for the support points. - std::vector m_heads; - std::vector m_head_indices; - std::vector m_pillars; + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; std::vector m_junctions; - std::vector m_bridges; - std::vector m_crossbridges; + std::vector m_bridges; + std::vector m_crossbridges; + std::vector m_pedestals; + std::vector m_anchors; + Pad m_pad; using Mutex = ccr::SpinningMutex; @@ -347,7 +279,7 @@ public: return m_heads.back(); } - template long add_pillar(long headid, Args&&... args) + template long add_pillar(long headid, double length) { std::lock_guard lk(m_mutex); if (m_pillars.capacity() < m_heads.size()) @@ -356,7 +288,9 @@ public: assert(headid >= 0 && size_t(headid) < m_head_indices.size()); Head &head = m_heads[m_head_indices[size_t(headid)]]; - m_pillars.emplace_back(head, std::forward(args)...); + Vec3d hjp = head.junction_point() - Vec3d{0, 0, length}; + m_pillars.emplace_back(hjp, length, head.r_back_mm); + Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); head.pillar_id = pillar.id; @@ -371,7 +305,19 @@ public: { std::lock_guard lk(m_mutex); assert(pid >= 0 && size_t(pid) < m_pillars.size()); - m_pillars[size_t(pid)].add_base(baseheight, radius); + m_pedestals.emplace_back(m_pillars[size_t(pid)].endpt, baseheight, radius); + m_pedestals.back().id = m_pedestals.size() - 1; + m_meshcache_valid = false; +// m_pillars[size_t(pid)].add_base(baseheight, radius); + } + + template const Anchor& add_anchor(Args&&...args) + { + std::lock_guard lk(m_mutex); + m_anchors.emplace_back(std::forward(args)...); + m_anchors.back().id = long(m_junctions.size() - 1); + m_meshcache_valid = false; + return m_anchors.back(); } void increment_bridges(const Pillar& pillar) @@ -432,18 +378,18 @@ public: return m_junctions.back(); } - const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45) + const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r) { - return _add_bridge(m_bridges, s, e, r, n); + return _add_bridge(m_bridges, s, e, r); } - const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45) + const Bridge& add_bridge(long headid, const Vec3d &endp) { std::lock_guard lk(m_mutex); assert(headid >= 0 && size_t(headid) < m_head_indices.size()); Head &h = m_heads[m_head_indices[size_t(headid)]]; - m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s); + m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm); m_bridges.back().id = long(m_bridges.size() - 1); h.bridge_id = m_bridges.back().id; @@ -471,7 +417,7 @@ public: } inline const std::vector &pillars() const { return m_pillars; } - inline const std::vector &heads() const { return m_heads; } + inline const std::vector &heads() const { return m_heads; } inline const std::vector &bridges() const { return m_bridges; } inline const std::vector &crossbridges() const { return m_crossbridges; } @@ -496,7 +442,7 @@ public: const Pad& pad() const { return m_pad; } // WITHOUT THE PAD!!! - const TriangleMesh &merged_mesh() const; + const TriangleMesh &merged_mesh(size_t steps = 45) const; // WITH THE PAD double full_height() const; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index a8e79dc179..4b8366ee44 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -7,14 +8,23 @@ namespace Slic3r { namespace sla { -static const Vec3d DOWN = {0.0, 0.0, -1.0}; - using libnest2d::opt::initvals; using libnest2d::opt::bound; using libnest2d::opt::StopCriteria; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::SubplexOptimizer; +template +static Hit min_hit(const C &hits) +{ + auto mit = std::min_element(hits.begin(), hits.end(), + [](const Hit &h1, const Hit &h2) { + return h1.distance() < h2.distance(); + }); + + return *mit; +} + EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) { static const size_t SAMPLES = 8; @@ -158,7 +168,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; SupportTreeBuildsteps alg(builder, sm); - + // Let's define the individual steps of the processing. We can experiment // later with the ordering and the dependencies between them. enum Steps { @@ -271,17 +281,6 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, return pc == ABORT; } -template -static Hit min_hit(const C &hits) -{ - auto mit = std::min_element(hits.begin(), hits.end(), - [](const Hit &h1, const Hit &h2) { - return h1.distance() < h2.distance(); - }); - - return *mit; -} - EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) { @@ -552,7 +551,7 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) { // A partial pillar is needed under the starting head. if(zdiff > 0) { - m_builder.add_pillar(head.id, bridgestart, r); + m_builder.add_pillar(head.id, headjp.z() - bridgestart.z()); m_builder.add_junction(bridgestart, r); m_builder.add_bridge(bridgestart, bridgeend, r); } else { @@ -607,7 +606,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, normal_mode = false; if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { - if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, 0.); return false; } } @@ -615,14 +614,15 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // Check if the deduced route is sane and exit with error if not. if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { - if (head_id >= 0) m_builder.add_pillar(head_id, jp, radius); + if (head_id >= 0) m_builder.add_pillar(head_id, 0.); return false; } // Straigh path down, no area to dodge if (normal_mode) { - pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, endp, radius) : - m_builder.add_pillar(jp, endp, radius); + double h = jp.z() - endp.z(); + pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) : + m_builder.add_pillar(jp, h, radius); if (can_add_base) m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, @@ -630,8 +630,9 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, } else { // Insert the bridge to get around the forbidden area - Vec3d pgnd{endp.x(), endp.y(), gndlvl}; - pillar_id = m_builder.add_pillar(endp, pgnd, radius); +// Vec3d pgnd{endp.x(), endp.y(), gndlvl}; + double h = endp.z() - gndlvl; + pillar_id = m_builder.add_pillar(endp, h, radius); if (can_add_base) m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, @@ -645,7 +646,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // prevent from queries of head_pillar() to have non-existing // pillar when the head should have one. if (head_id >= 0) - m_builder.add_pillar(head_id, jp, radius); + m_builder.add_pillar(head_id, 0.); } if(pillar_id >= 0) // Save the pillar endpoint in the spatial index @@ -1034,7 +1035,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) head.transform(); - long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm); + long pillar_id = m_builder.add_pillar(head.id, hit.distance() + h); Pillar &pill = m_builder.pillar(pillar_id); Vec3d taildir = endp - hitp; @@ -1046,11 +1047,14 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) w = 0.; } - Head tailhead(head.r_back_mm, head.r_pin_mm, w, - m_cfg.head_penetration_mm, taildir, hitp); + m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w, + m_cfg.head_penetration_mm, taildir, hitp); - tailhead.transform(); - pill.base = tailhead.mesh; +// Head tailhead(head.r_back_mm, head.r_pin_mm, w, +// m_cfg.head_penetration_mm, taildir, hitp); + +// tailhead.transform(); +// pill.base = tailhead.mesh; m_pillar_index.guarded_insert(pill.endpoint(), pill.id); @@ -1297,8 +1301,8 @@ void SupportTreeBuildsteps::interconnect_pillars() if (found) for (unsigned n = 0; n < needpillars; n++) { Vec3d s = spts[n]; - Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); - p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + Pillar p(s, s.z() - gnd, pillar().r); +// p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); if (interconnect(pillar(), p)) { Pillar &pp = m_builder.pillar(m_builder.add_pillar(p)); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index bfa38505b0..fc5670b163 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -5,6 +5,7 @@ #include #include +#include namespace Slic3r { namespace sla { @@ -108,55 +109,6 @@ public: EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); -// This function returns the position of the centroid in the input 'clust' -// vector of point indices. -template -long cluster_centroid(const ClusterEl& clust, - const std::function &pointfn, - DistFn df) -{ - switch(clust.size()) { - case 0: /* empty cluster */ return ID_UNSET; - case 1: /* only one element */ return 0; - case 2: /* if two elements, there is no center */ return 0; - default: ; - } - - // The function works by calculating for each point the average distance - // from all the other points in the cluster. We create a selector bitmask of - // the same size as the cluster. The bitmask will have two true bits and - // false bits for the rest of items and we will loop through all the - // permutations of the bitmask (combinations of two points). Get the - // distance for the two points and add the distance to the averages. - // The point with the smallest average than wins. - - // The complexity should be O(n^2) but we will mostly apply this function - // for small clusters only (cca 3 elements) - - std::vector sel(clust.size(), false); // create full zero bitmask - std::fill(sel.end() - 2, sel.end(), true); // insert the two ones - std::vector avgs(clust.size(), 0.0); // store the average distances - - do { - std::array idx; - for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i; - - double d = df(pointfn(clust[idx[0]]), - pointfn(clust[idx[1]])); - - // add the distance to the sums for both associated points - for(auto i : idx) avgs[i] += d; - - // now continue with the next permutation of the bitmask with two 1s - } while(std::next_permutation(sel.begin(), sel.end())); - - // Divide by point size in the cluster to get the average (may be redundant) - for(auto& a : avgs) a /= clust.size(); - - // get the lowest average distance and return the index - auto minit = std::min_element(avgs.begin(), avgs.end()); - return long(minit - avgs.begin()); -} inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp new file mode 100644 index 0000000000..1d9be6c348 --- /dev/null +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -0,0 +1,268 @@ +#include "SupportTreeMesher.hpp" + +namespace Slic3r { namespace sla { + +Contour3D sphere(double rho, Portion portion, double fa) { + + Contour3D ret; + + // prohibit close to zero radius + if(rho <= 1e-6 && rho >= -1e-6) return ret; + + auto& vertices = ret.points; + auto& facets = ret.faces3; + + // Algorithm: + // Add points one-by-one to the sphere grid and form facets using relative + // coordinates. Sphere is composed effectively of a mesh of stacked circles. + + // adjust via rounding to get an even multiple for any provided angle. + double angle = (2*PI / floor(2*PI / fa)); + + // Ring to be scaled to generate the steps of the sphere + std::vector ring; + + for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); + + const auto sbegin = size_t(2*std::get<0>(portion)/angle); + const auto send = size_t(2*std::get<1>(portion)/angle); + + const size_t steps = ring.size(); + const double increment = 1.0 / double(steps); + + // special case: first ring connects to 0,0,0 + // insert and form facets. + if(sbegin == 0) + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); + + auto id = coord_t(vertices.size()); + for (size_t i = 0; i < ring.size(); i++) { + // Fixed scaling + const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); + // radius of the circle for this step. + const double r = std::sqrt(std::abs(rho*rho - z*z)); + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + + if (sbegin == 0) + (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) : + facets.emplace_back(id - 1, 0, id); + ++id; + } + + // General case: insert and form facets for each step, + // joining it to the ring below it. + for (size_t s = sbegin + 2; s < send - 1; s++) { + const double z = -rho + increment*double(s*2.0*rho); + const double r = std::sqrt(std::abs(rho*rho - z*z)); + + for (size_t i = 0; i < ring.size(); i++) { + Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(b(0), b(1), z)); + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // wrap around + facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) ); + facets.emplace_back(id - 1, id_ringsize, id); + } else { + facets.emplace_back(id_ringsize - 1, id_ringsize, id); + facets.emplace_back(id - 1, id_ringsize - 1, id); + } + id++; + } + } + + // special case: last ring connects to 0,0,rho*2.0 + // only form facets. + if(send >= size_t(2*PI / angle)) { + vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); + for (size_t i = 0; i < ring.size(); i++) { + auto id_ringsize = coord_t(id - int(ring.size())); + if (i == 0) { + // third vertex is on the other side of the ring. + facets.emplace_back(id - 1, id_ringsize, id); + } else { + auto ci = coord_t(id_ringsize + coord_t(i)); + facets.emplace_back(ci - 1, ci, id); + } + } + } + id++; + + return ret; +} + +Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) +{ + assert(steps > 0); + + Contour3D ret; + + auto steps = int(ssteps); + auto& points = ret.points; + auto& indices = ret.faces3; + points.reserve(2*ssteps); + double a = 2*PI/steps; + + Vec3d jp = sp; + Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; + + // Upper circle points + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double ex = endp(X) + r*std::cos(phi); + double ey = endp(Y) + r*std::sin(phi); + points.emplace_back(ex, ey, endp(Z)); + } + + // Lower circle points + for(int i = 0; i < steps; ++i) { + double phi = i*a; + double x = jp(X) + r*std::cos(phi); + double y = jp(Y) + r*std::sin(phi); + points.emplace_back(x, y, jp(Z)); + } + + // Now create long triangles connecting upper and lower circles + indices.reserve(2*ssteps); + auto offs = steps; + for(int i = 0; i < steps - 1; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + } + + // Last triangle connecting the first and last vertices + auto last = steps - 1; + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + + // According to the slicing algorithms, we need to aid them with generating + // a watertight body. So we create a triangle fan for the upper and lower + // ending of the cylinder to close the geometry. + points.emplace_back(jp); int ci = int(points.size() - 1); + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(i + offs + 1, i + offs, ci); + + indices.emplace_back(offs, steps + offs - 1, ci); + + points.emplace_back(endp); ci = int(points.size() - 1); + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(ci, i, i + 1); + + indices.emplace_back(steps - 1, 0, ci); + + return ret; +} + +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) +{ + assert(steps > 0); + assert(length > 0.); + assert(r_back > 0.); + assert(r_pin > 0.); + + Contour3D mesh; + + // We create two spheres which will be connected with a robe that fits + // both circles perfectly. + + // Set up the model detail level + const double detail = 2*PI/steps; + + // We don't generate whole circles. Instead, we generate only the + // portions which are visible (not covered by the robe) To know the + // exact portion of the bottom and top circles we need to use some + // rules of tangent circles from which we can derive (using simple + // triangles the following relations: + + // The height of the whole mesh + const double h = r_back + r_pin + length; + double phi = PI / 2. - std::acos((r_back - r_pin) / h); + + // To generate a whole circle we would pass a portion of (0, Pi) + // To generate only a half horizontal circle we can pass (0, Pi/2) + // The calculated phi is an offset to the half circles needed to smooth + // the transition from the circle to the robe geometry + + auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); + auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); + + for(auto& p : s2.points) p.z() += h; + + mesh.merge(s1); + mesh.merge(s2); + + for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; + idx1++, idx2++) + { + coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); + coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; + + mesh.faces3.emplace_back(i1s1, i2s1, i2s2); + mesh.faces3.emplace_back(i1s1, i2s2, i1s2); + } + + auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); + auto i2s1 = coord_t(s1.points.size()) - 1; + auto i1s2 = coord_t(s1.points.size()); + auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; + + mesh.faces3.emplace_back(i2s2, i2s1, i1s1); + mesh.faces3.emplace_back(i1s2, i2s2, i1s1); + + return mesh; +} + +Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) +{ + assert(steps > 0); + + if(baseheight <= 0) return {}; + + assert(steps >= 0); + auto last = int(steps - 1); + + Contour3D base; + + double a = 2*PI/steps; + double z = endpt(Z) + baseheight; + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius * std::cos(phi); + double y = endpt(Y) + radius * std::sin(phi); + base.points.emplace_back(x, y, z); + } + + for(size_t i = 0; i < steps; ++i) { + double phi = i*a; + double x = endpt(X) + radius*std::cos(phi); + double y = endpt(Y) + radius*std::sin(phi); + base.points.emplace_back(x, y, z - baseheight); + } + + auto ep = endpt; ep(Z) += baseheight; + base.points.emplace_back(endpt); + base.points.emplace_back(ep); + + auto& indices = base.faces3; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for(int i = 0; i < last; ++i) { + indices.emplace_back(i, i + offs, offs + i + 1); + indices.emplace_back(i, offs + i + 1, i + 1); + indices.emplace_back(i, i + 1, hcenter); + indices.emplace_back(lcenter, offs + i + 1, offs + i); + } + + indices.emplace_back(0, last, offs); + indices.emplace_back(last, offs + last, offs); + indices.emplace_back(hcenter, last, 0); + indices.emplace_back(offs, offs + last, lcenter); + + return base; +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp new file mode 100644 index 0000000000..677cab3b81 --- /dev/null +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -0,0 +1,94 @@ +#ifndef SUPPORTTREEMESHER_HPP +#define SUPPORTTREEMESHER_HPP + +#include "libslic3r/Point.hpp" + +#include "libslic3r/SLA/SupportTreeBuilder.hpp" +#include "libslic3r/SLA/Contour3D.hpp" + +namespace Slic3r { namespace sla { + +using Portion = std::tuple; + +inline Portion make_portion(double a, double b) +{ + return std::make_tuple(a, b); +} + +Contour3D sphere(double rho, + Portion portion = make_portion(0., 2. * PI), + double fa = (2. * PI / 360.)); + +// Down facing cylinder in Z direction with arguments: +// r: radius +// h: Height +// ssteps: how many edges will create the base circle +// sp: starting point +Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); + +Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); + +Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); + +inline Contour3D get_mesh(const Head &h, size_t steps) +{ + Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps); + + // To simplify further processing, we translate the mesh so that the + // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) + for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm); + + using Quaternion = Eigen::Quaternion; + + // We rotate the head to the specified direction The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir); + + for(auto& p : mesh.points) p = quatern * p + h.pos; + + return mesh; +} + +inline Contour3D get_mesh(const Pillar &p, size_t steps) +{ + if(p.height > EPSILON) { // Endpoint is below the starting point + // We just create a bridge geometry with the pillar parameters and + // move the data. + return cylinder(p.r, p.height, steps, p.endpoint()); + } + + return {}; +} + +inline Contour3D get_mesh(const Pedestal &p, size_t steps) +{ + return pedestal(p.pos, p.height, p.radius, steps); +} + +inline Contour3D get_mesh(const Junction &j, size_t steps) +{ + Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps); + for(auto& p : mesh.points) p += j.pos; + return mesh; +} + +inline Contour3D get_mesh(const Bridge &br, size_t steps) +{ + using Quaternion = Eigen::Quaternion; + Vec3d v = (br.endp - br.startp); + Vec3d dir = v.normalized(); + double d = v.norm(); + + Contour3D mesh = cylinder(br.r, d, steps); + + auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); + for(auto& p : mesh.points) p = quater * p + br.startp; + + return mesh; +} + +}} + +#endif // SUPPORTTREEMESHER_HPP diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp index c785e4ba5e..05aca963ea 100644 --- a/tests/sla_print/sla_treebuilder_tests.cpp +++ b/tests/sla_print/sla_treebuilder_tests.cpp @@ -2,7 +2,8 @@ #include #include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/SLA/SupportTreeBuilder.hpp" +#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" +#include "libslic3r/SLA/SupportTreeMesher.hpp" TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { using namespace Slic3r; @@ -13,6 +14,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]" sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; sla::SupportableMesh sm{cube, pts, cfg}; + size_t steps = 45; SECTION("Bridge is straight horizontal and pointing away from the cube") { sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, @@ -22,7 +24,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]" REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); cube.require_shared_vertices(); cube.WriteOBJFile("cube1.obj"); } @@ -35,7 +37,7 @@ TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]" REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(bridge.mesh)); + cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); cube.require_shared_vertices(); cube.WriteOBJFile("cube2.obj"); } @@ -52,6 +54,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; sla::SupportableMesh sm{sphere, pts, cfg}; + size_t steps = 45; SECTION("Bridge is straight horizontal and pointing away from the sphere") { sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, @@ -59,7 +62,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); sphere.require_shared_vertices(); sphere.WriteOBJFile("sphere1.obj"); @@ -73,7 +76,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); sphere.require_shared_vertices(); sphere.WriteOBJFile("sphere2.obj"); @@ -87,7 +90,7 @@ TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(bridge.mesh)); + sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); sphere.require_shared_vertices(); sphere.WriteOBJFile("sphere3.obj"); From 301a168b8998d6ffd8389af9729357cd771fc9e4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jun 2020 13:49:35 +0200 Subject: [PATCH 241/826] Fix bugs and non working tests Fix failing tests Try to fix build on windows Try to fix failng tests on Mac --- src/libslic3r/SLA/SupportTreeBuilder.cpp | 91 +++++---------------- src/libslic3r/SLA/SupportTreeBuilder.hpp | 33 ++------ src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 81 +++++++++++------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 13 +-- src/libslic3r/SLA/SupportTreeMesher.cpp | 72 ++++++++-------- src/libslic3r/SLA/SupportTreeMesher.hpp | 19 +++-- tests/sla_print/sla_test_utils.cpp | 4 +- 7 files changed, 132 insertions(+), 181 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index d4a9d00c99..9590936231 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include #include #include @@ -19,77 +21,8 @@ Head::Head(double r_big_mm, , width_mm(length_mm) , penetration_mm(penetration) { -// mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); - - // To simplify further processing, we translate the mesh so that the - // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) -// for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } -//Pillar::Pillar(const Vec3d &endp, double h, double radius, size_t st): -// height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) -//{ -// assert(steps > 0); - -// if(height > EPSILON) { // Endpoint is below the starting point - -// // We just create a bridge geometry with the pillar parameters and -// // move the data. -// Contour3D body = cylinder(radius, height, st, endp); -// mesh.points.swap(body.points); -// mesh.faces3.swap(body.faces3); -// } -//} - -//Pillar &Pillar::add_base(double baseheight, double radius) -//{ -// if(baseheight <= 0) return *this; -// if(baseheight > height) baseheight = height; - -// assert(steps >= 0); -// auto last = int(steps - 1); - -// if(radius < r ) radius = r; - -// double a = 2*PI/steps; -// double z = endpt(Z) + baseheight; - -// for(size_t i = 0; i < steps; ++i) { -// double phi = i*a; -// double x = endpt(X) + r*std::cos(phi); -// double y = endpt(Y) + r*std::sin(phi); -// base.points.emplace_back(x, y, z); -// } - -// for(size_t i = 0; i < steps; ++i) { -// double phi = i*a; -// double x = endpt(X) + radius*std::cos(phi); -// double y = endpt(Y) + radius*std::sin(phi); -// base.points.emplace_back(x, y, z - baseheight); -// } - -// auto ep = endpt; ep(Z) += baseheight; -// base.points.emplace_back(endpt); -// base.points.emplace_back(ep); - -// auto& indices = base.faces3; -// auto hcenter = int(base.points.size() - 1); -// auto lcenter = int(base.points.size() - 2); -// auto offs = int(steps); -// for(int i = 0; i < last; ++i) { -// indices.emplace_back(i, i + offs, offs + i + 1); -// indices.emplace_back(i, offs + i + 1, i + 1); -// indices.emplace_back(i, i + 1, hcenter); -// indices.emplace_back(lcenter, offs + i + 1, offs + i); -// } - -// indices.emplace_back(0, last, offs); -// indices.emplace_back(last, offs + last, offs); -// indices.emplace_back(hcenter, last, 0); -// indices.emplace_back(offs, offs + last, lcenter); -// return *this; -//} - Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, @@ -175,6 +108,18 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) return *this; } +void SupportTreeBuilder::add_pillar_base(long pid, double baseheight, double radius) +{ + std::lock_guard lk(m_mutex); + assert(pid >= 0 && size_t(pid) < m_pillars.size()); + Pillar& pll = m_pillars[size_t(pid)]; + m_pedestals.emplace_back(pll.endpt, std::min(baseheight, pll.height), + std::max(radius, pll.r), pll.r); + + m_pedestals.back().id = m_pedestals.size() - 1; + m_meshcache_valid = false; +} + const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const { if (m_meshcache_valid) return m_meshcache; @@ -192,6 +137,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const } for (auto &pedest : m_pedestals) { + if (ctl().stopcondition()) break; merged.merge(get_mesh(pedest, steps)); } @@ -209,7 +155,12 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const if (ctl().stopcondition()) break; merged.merge(get_mesh(bs, steps)); } - + + for (auto &anch : m_anchors) { + if (ctl().stopcondition()) break; + merged.merge(get_mesh(anch, steps)); + } + if (ctl().stopcondition()) { // In case of failure we have to return an empty mesh m_meshcache = TriangleMesh(); diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index cc039de6fc..2b3ff91a06 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -91,12 +91,7 @@ struct Head { const Vec3d &direction = DOWN, // direction (normal to the dull end) const Vec3d &offset = {0, 0, 0} // displacement ); - - void transform() - { - // TODO: remove occurences - } - + inline double real_width() const { return 2 * r_pin_mm + width_mm + 2 * r_back_mm ; @@ -119,13 +114,6 @@ struct Head { } }; -struct Join { - enum Types { - jtPillarBrigde, jtHeadPillar, jtPillarPedestal, jtBridgePedestal, - jtPillarAnchor, jtBridgeAnchor - }; -}; - // A junction connecting bridges and pillars struct Junction { double r = 1; @@ -136,7 +124,6 @@ struct Junction { Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} }; - struct Pillar { double height, r; Vec3d endpt; @@ -162,18 +149,16 @@ struct Pillar { } const Vec3d& endpoint() const { return endpt; } - -// Pillar& add_base(double baseheight = 3, double radius = 2); }; // A base for pillars or bridges that end on the ground struct Pedestal { Vec3d pos; - double height, radius; + double height, r_bottom, r_top; long id = ID_UNSET; - Pedestal(const Vec3d &p, double h = 3., double r = 2.) - : pos{p}, height{h}, radius{r} + Pedestal(const Vec3d &p, double h, double rbottom, double rtop) + : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop} {} }; @@ -301,15 +286,7 @@ public: return pillar.id; } - void add_pillar_base(long pid, double baseheight = 3, double radius = 2) - { - std::lock_guard lk(m_mutex); - assert(pid >= 0 && size_t(pid) < m_pillars.size()); - m_pedestals.emplace_back(m_pillars[size_t(pid)].endpt, baseheight, radius); - m_pedestals.back().id = m_pedestals.size() - 1; - m_meshcache_valid = false; -// m_pillars[size_t(pid)].add_base(baseheight, radius); - } + void add_pillar_base(long pid, double baseheight = 3, double radius = 2); template const Anchor& add_anchor(Args&&...args) { diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 4b8366ee44..c6b2884d26 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -580,7 +580,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, bool normal_mode = true; Vec3d dir = sourcedir; - auto to_floor = [gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; if (m_cfg.object_elevation_mm < EPSILON) { @@ -599,6 +599,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // Try to move along the established bridge direction to dodge the // forbidden region for the endpoint. double t = -radius; + bool succ = true; while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { t += radius; @@ -607,36 +608,58 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - return false; + succ = false; + break; } } + + if (!succ) { + if (can_add_base) { + can_add_base = false; + base_r = 0.; + gndlvl -= m_mesh.ground_level_offset(); + min_dist = sd + base_r + EPSILON; + endp = {jp(X), jp(Y), gndlvl + radius}; + + t = -radius; + while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || + !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { + t += radius; + endp = jp + t * dir; + normal_mode = false; + + if (t > m_cfg.max_bridge_length_mm || endp(Z) < (gndlvl + radius)) { + if (head_id >= 0) m_builder.add_pillar(head_id, 0.); + return false; + } + } + } else return false; + } } + double h = (jp - endp).norm(); + // Check if the deduced route is sane and exit with error if not. - if (bridge_mesh_distance(jp, dir, radius) < (endp - jp).norm()) { + if (bridge_mesh_distance(jp, dir, radius) < h) { if (head_id >= 0) m_builder.add_pillar(head_id, 0.); return false; } // Straigh path down, no area to dodge if (normal_mode) { - double h = jp.z() - endp.z(); pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) : - m_builder.add_pillar(jp, h, radius); + m_builder.add_pillar(endp, h, radius); if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); + add_pillar_base(pillar_id); } else { // Insert the bridge to get around the forbidden area -// Vec3d pgnd{endp.x(), endp.y(), gndlvl}; - double h = endp.z() - gndlvl; - pillar_id = m_builder.add_pillar(endp, h, radius); + Vec3d pgnd{endp.x(), endp.y(), gndlvl}; + pillar_id = m_builder.add_pillar(pgnd, endp.z() - gndlvl, radius); if (can_add_base) - m_builder.add_pillar_base(pillar_id, m_cfg.base_height_mm, - m_cfg.base_radius_mm); + add_pillar_base(pillar_id); m_builder.add_bridge(jp, endp, radius); m_builder.add_junction(endp, radius); @@ -912,11 +935,8 @@ void SupportTreeBuildsteps::routing_to_ground() BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; m_iheads_onmodel.emplace_back(h.id); -// h.invalidate(); continue; } - - h.transform(); } // now we will go through the clusters ones again and connect the @@ -939,7 +959,6 @@ void SupportTreeBuildsteps::routing_to_ground() if (c == cidx) continue; auto &sidehead = m_builder.head(c); - sidehead.transform(); if (!connect_to_nearpillar(sidehead, centerpillarID) && !search_pillar_and_connect(sidehead)) { @@ -1016,6 +1035,12 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) if (it == m_head_to_ground_scans.end()) return false; auto &hit = it->second; + + if (!hit.is_hit()) { + // TODO scan for potential anchor points on model surface + return false; + } + Vec3d hjp = head.junction_point(); double zangle = std::asin(hit.direction()(Z)); zangle = std::max(zangle, PI/4); @@ -1033,13 +1058,11 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? center_hit.position() : hit.position(); - head.transform(); - - long pillar_id = m_builder.add_pillar(head.id, hit.distance() + h); + long pillar_id = m_builder.add_pillar(head.id, hjp.z() - endp.z()); Pillar &pill = m_builder.pillar(pillar_id); Vec3d taildir = endp - hitp; - double dist = distance(endp, hitp) + m_cfg.head_penetration_mm; + double dist = (hitp - endp).norm() + m_cfg.head_penetration_mm; double w = dist - 2 * head.r_pin_mm - head.r_back_mm; if (w < 0.) { @@ -1050,12 +1073,6 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w, m_cfg.head_penetration_mm, taildir, hitp); -// Head tailhead(head.r_back_mm, head.r_pin_mm, w, -// m_cfg.head_penetration_mm, taildir, hitp); - -// tailhead.transform(); -// pill.base = tailhead.mesh; - m_pillar_index.guarded_insert(pill.endpoint(), pill.id); return true; @@ -1111,11 +1128,11 @@ void SupportTreeBuildsteps::routing_to_model() auto& head = m_builder.head(idx); // Search nearby pillar - if (search_pillar_and_connect(head)) { head.transform(); return; } + if (search_pillar_and_connect(head)) { return; } // Cannot connect to nearby pillar. We will try to search for // a route to the ground. - if (connect_to_ground(head)) { head.transform(); return; } + if (connect_to_ground(head)) { return; } // No route to the ground, so connect to the model body as a last resort if (connect_to_model_body(head)) { return; } @@ -1300,12 +1317,14 @@ void SupportTreeBuildsteps::interconnect_pillars() if (found) for (unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; - Pillar p(s, s.z() - gnd, pillar().r); -// p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + Vec3d s = spts[n]; + Pillar p(Vec3d{s.x(), s.y(), gnd}, s.z() - gnd, pillar().r); if (interconnect(pillar(), p)) { Pillar &pp = m_builder.pillar(m_builder.add_pillar(p)); + + add_pillar_base(pp.id); + m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); m_builder.add_junction(s, pillar().r); diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index fc5670b163..51d8344480 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -17,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -inline Vec2d to_vec2(const Vec3d& v3) { - return {v3(X), v3(Y)}; -} +inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; } inline std::pair dir_to_spheric(const Vec3d &n, double norm = 1.) { @@ -47,7 +45,6 @@ inline Vec3d spheric_to_dir(const std::pair &v) return spheric_to_dir(v.first, v.second); } - // Give points on a 3D ring with given center, radius and orientation // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -297,8 +294,12 @@ class SupportTreeBuildsteps { const Vec3d &sourcedir, double radius, long head_id = ID_UNSET); - - + + void add_pillar_base(long pid) + { + m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm); + } + public: SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp index 1d9be6c348..15491775b4 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.cpp +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -94,7 +94,7 @@ Contour3D sphere(double rho, Portion portion, double fa) { Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) { - assert(steps > 0); + assert(ssteps > 0); Contour3D ret; @@ -157,7 +157,7 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) { assert(steps > 0); - assert(length > 0.); + assert(length >= 0.); assert(r_back > 0.); assert(r_pin > 0.); @@ -167,7 +167,7 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) // both circles perfectly. // Set up the model detail level - const double detail = 2*PI/steps; + const double detail = 2 * PI / steps; // We don't generate whole circles. Instead, we generate only the // portions which are visible (not covered by the robe) To know the @@ -176,26 +176,24 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) // triangles the following relations: // The height of the whole mesh - const double h = r_back + r_pin + length; - double phi = PI / 2. - std::acos((r_back - r_pin) / h); + const double h = r_back + r_pin + length; + double phi = PI / 2. - std::acos((r_back - r_pin) / h); // To generate a whole circle we would pass a portion of (0, Pi) // To generate only a half horizontal circle we can pass (0, Pi/2) // The calculated phi is an offset to the half circles needed to smooth // the transition from the circle to the robe geometry - auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); - auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); + auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail); + auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail); - for(auto& p : s2.points) p.z() += h; + for (auto &p : s2.points) p.z() += h; mesh.merge(s1); mesh.merge(s2); - for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); - idx1 < s1.points.size() - 1; - idx1++, idx2++) - { + for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); + idx1 < s1.points.size() - 1; idx1++, idx2++) { coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; @@ -214,43 +212,43 @@ Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) return mesh; } -Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) +Contour3D halfcone(double baseheight, + double r_bottom, + double r_top, + const Vec3d &pos, + size_t steps) { assert(steps > 0); - if(baseheight <= 0) return {}; - - assert(steps >= 0); - auto last = int(steps - 1); + if (baseheight <= 0 || steps <= 0) return {}; Contour3D base; - double a = 2*PI/steps; - double z = endpt(Z) + baseheight; - - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius * std::cos(phi); - double y = endpt(Y) + radius * std::sin(phi); - base.points.emplace_back(x, y, z); + double a = 2 * PI / steps; + auto last = int(steps - 1); + Vec3d ep{pos.x(), pos.y(), pos.z() + baseheight}; + for (size_t i = 0; i < steps; ++i) { + double phi = i * a; + double x = pos.x() + r_top * std::cos(phi); + double y = pos.y() + r_top * std::sin(phi); + base.points.emplace_back(x, y, ep.z()); } - for(size_t i = 0; i < steps; ++i) { - double phi = i*a; - double x = endpt(X) + radius*std::cos(phi); - double y = endpt(Y) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - baseheight); + for (size_t i = 0; i < steps; ++i) { + double phi = i * a; + double x = pos.x() + r_bottom * std::cos(phi); + double y = pos.y() + r_bottom * std::sin(phi); + base.points.emplace_back(x, y, pos.z()); } - auto ep = endpt; ep(Z) += baseheight; - base.points.emplace_back(endpt); + base.points.emplace_back(pos); base.points.emplace_back(ep); - auto& indices = base.faces3; - auto hcenter = int(base.points.size() - 1); - auto lcenter = int(base.points.size() - 2); - auto offs = int(steps); - for(int i = 0; i < last; ++i) { + auto &indices = base.faces3; + auto hcenter = int(base.points.size() - 1); + auto lcenter = int(base.points.size() - 2); + auto offs = int(steps); + for (int i = 0; i < last; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); indices.emplace_back(i, offs + i + 1, i + 1); indices.emplace_back(i, i + 1, hcenter); diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp index 677cab3b81..a086680c34 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.hpp +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -24,23 +24,28 @@ Contour3D sphere(double rho, // h: Height // ssteps: how many edges will create the base circle // sp: starting point -Contour3D cylinder(double r, double h, size_t steps = 45, const Vec3d &sp = Vec3d::Zero()); +Contour3D cylinder(double r, + double h, + size_t steps = 45, + const Vec3d &sp = Vec3d::Zero()); Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45); -Contour3D pedestal(const Vec3d &pt, double baseheight, double radius, size_t steps = 45); +Contour3D halfcone(double baseheight, + double r_bottom, + double r_top, + const Vec3d &pt = Vec3d::Zero(), + size_t steps = 45); inline Contour3D get_mesh(const Head &h, size_t steps) { Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps); - // To simplify further processing, we translate the mesh so that the - // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm); using Quaternion = Eigen::Quaternion; - // We rotate the head to the specified direction The head's pointing + // We rotate the head to the specified direction. The head's pointing // side is facing upwards so this means that it would hold a support // point with a normal pointing straight down. This is the reason of // the -1 z coordinate @@ -64,7 +69,7 @@ inline Contour3D get_mesh(const Pillar &p, size_t steps) inline Contour3D get_mesh(const Pedestal &p, size_t steps) { - return pedestal(p.pos, p.height, p.radius, steps); + return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps); } inline Contour3D get_mesh(const Junction &j, size_t steps) @@ -89,6 +94,6 @@ inline Contour3D get_mesh(const Bridge &br, size_t steps) return mesh; } -}} +}} // namespace Slic3r::sla #endif // SUPPORTTREEMESHER_HPP diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 5a3bd82a00..4cd94b7ed3 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -157,8 +157,8 @@ void test_supports(const std::string &obj_filename, if (std::abs(supportcfg.object_elevation_mm) < EPSILON) allowed_zmin = zmin - 2 * supportcfg.head_back_radius_mm; - REQUIRE(obb.min.z() >= allowed_zmin); - REQUIRE(obb.max.z() <= zmax); + REQUIRE(obb.min.z() >= Approx(allowed_zmin)); + REQUIRE(obb.max.z() <= Approx(zmax)); // Move out the support tree into the byproducts, we can examine it further // in various tests. From f19b3a2344cb499d962b9665a97028b053d98cbc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Jun 2020 09:25:17 +0200 Subject: [PATCH 242/826] Id-s put in a base class for support tree primitives --- src/libslic3r/SLA/SupportTreeBuilder.hpp | 30 +++++++++------------ src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 8 +++--- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 2 +- tests/sla_print/sla_test_utils.cpp | 4 +-- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 2b3ff91a06..aa8a4ea83b 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -58,12 +58,17 @@ template double distance(const Vec& pp1, const Vec& pp2) { return distance(p); } -const constexpr long ID_UNSET = -1; - const Vec3d DOWN = {0.0, 0.0, -1.0}; +struct SupportTreeNode +{ + static const constexpr long ID_UNSET = -1; + + long id = ID_UNSET; // For identification withing a tree. +}; + // A pinhead originating from a support point -struct Head { +struct Head: public SupportTreeNode { Vec3d dir = DOWN; Vec3d pos = {0, 0, 0}; @@ -71,10 +76,7 @@ struct Head { double r_pin_mm = 0.5; double width_mm = 2; double penetration_mm = 0.5; - - // For identification purposes. This will be used as the index into the - // container holding the head structures. See SLASupportTree::Impl - long id = ID_UNSET; + // If there is a pillar connecting to this head, then the id will be set. long pillar_id = ID_UNSET; @@ -115,21 +117,17 @@ struct Head { }; // A junction connecting bridges and pillars -struct Junction { +struct Junction: public SupportTreeNode { double r = 1; Vec3d pos; - - long id = ID_UNSET; Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {} }; -struct Pillar { +struct Pillar: public SupportTreeNode { double height, r; Vec3d endpt; - long id = ID_UNSET; - // If the pillar connects to a head, this is the id of that head bool starts_from_head = true; // Could start from a junction as well long start_junction_id = ID_UNSET; @@ -152,10 +150,9 @@ struct Pillar { }; // A base for pillars or bridges that end on the ground -struct Pedestal { +struct Pedestal: public SupportTreeNode { Vec3d pos; double height, r_bottom, r_top; - long id = ID_UNSET; Pedestal(const Vec3d &p, double h, double rbottom, double rtop) : pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop} @@ -167,9 +164,8 @@ struct Pedestal { struct Anchor: public Head { using Head::Head; }; // A Bridge between two pillars (with junction endpoints) -struct Bridge { +struct Bridge: public SupportTreeNode { double r = 0.8; - long id = ID_UNSET; Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero(); Bridge(const Vec3d &j1, diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index c6b2884d26..00f09b8121 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -570,7 +570,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, long head_id) { double sd = m_cfg.pillar_base_safety_distance_mm; - long pillar_id = ID_UNSET; + long pillar_id = SupportTreeNode::ID_UNSET; bool can_add_base = radius >= m_cfg.head_back_radius_mm; double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; double gndlvl = m_builder.ground_level; @@ -1029,7 +1029,7 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) bool SupportTreeBuildsteps::connect_to_model_body(Head &head) { - if (head.id <= ID_UNSET) return false; + if (head.id <= SupportTreeNode::ID_UNSET) return false; auto it = m_head_to_ground_scans.find(unsigned(head.id)); if (it == m_head_to_ground_scans.end()) return false; @@ -1084,7 +1084,7 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) // We also need to remove elements progressively from the copied index. PointIndex spindex = m_pillar_index.guarded_clone(); - long nearest_id = ID_UNSET; + long nearest_id = SupportTreeNode::ID_UNSET; Vec3d querypt = source.junction_point(); @@ -1105,7 +1105,7 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) if (size_t(nearest_id) < m_builder.pillarcount()) { if(!connect_to_nearpillar(source, nearest_id) || m_builder.pillar(nearest_id).r < source.r_back_mm) { - nearest_id = ID_UNSET; // continue searching + nearest_id = SupportTreeNode::ID_UNSET; // continue searching spindex.remove(ne); // without the current pillar } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index 51d8344480..e8f73149e3 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -293,7 +293,7 @@ class SupportTreeBuildsteps { bool create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, - long head_id = ID_UNSET); + long head_id = SupportTreeNode::ID_UNSET); void add_pillar_base(long pid) { diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 4cd94b7ed3..bc0cfb0cd1 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -175,8 +175,8 @@ void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, double H2 = cfg.max_dual_pillar_height_mm; for (const sla::Head &head : stree.heads()) { - REQUIRE((!head.is_valid() || head.pillar_id != sla::ID_UNSET || - head.bridge_id != sla::ID_UNSET)); + REQUIRE((!head.is_valid() || head.pillar_id != sla::SupportTreeNode::ID_UNSET || + head.bridge_id != sla::SupportTreeNode::ID_UNSET)); } for (const sla::Pillar &pillar : stree.pillars()) { From 645fbed88bb94d3addf32691e08f0e9453978120 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Jun 2020 09:49:50 +0200 Subject: [PATCH 243/826] Make compile time support tree conf params constexpr --- src/libslic3r/SLA/SupportTree.cpp | 14 -------------- src/libslic3r/SLA/SupportTree.hpp | 14 +++++++------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index eec819e225..1bb4cfab76 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -28,20 +28,6 @@ namespace Slic3r { namespace sla { -// Compile time configuration value definitions: - -// The max Z angle for a normal at which it will get completely ignored. -const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; - -// The shortest distance of any support structure from the model surface -const double SupportConfig::safety_distance_mm = 0.5; - -const double SupportConfig::max_solo_pillar_height_mm = 15.0; -const double SupportConfig::max_dual_pillar_height_mm = 35.0; -const double SupportConfig::optimizer_rel_score_diff = 1e-6; -const unsigned SupportConfig::optimizer_max_iterations = 1000; -const unsigned SupportConfig::pillar_cascade_neighbors = 3; - void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const { outmesh.merge(retrieve_mesh(MeshType::Support)); outmesh.merge(retrieve_mesh(MeshType::Pad)); diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 3b9f603fd2..1415ab8fe9 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -94,16 +94,16 @@ struct SupportConfig // ///////////////////////////////////////////////////////////////////////// // The max Z angle for a normal at which it will get completely ignored. - static const double normal_cutoff_angle; + static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0; // The shortest distance of any support structure from the model surface - static const double safety_distance_mm; + static const double constexpr safety_distance_mm = 0.5; - 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 double constexpr max_solo_pillar_height_mm = 15.0; + static const double constexpr max_dual_pillar_height_mm = 35.0; + static const double constexpr optimizer_rel_score_diff = 1e-6; + static const unsigned constexpr optimizer_max_iterations = 1000; + static const unsigned constexpr pillar_cascade_neighbors = 3; }; From 1eec6c473c660196dbe7ca421d0abefbf4ea8739 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 25 Jun 2020 13:58:51 +0200 Subject: [PATCH 244/826] Rename EigenMesh3D to IndexedMesh and SupportConfig to SupportTreeConfig --- src/libslic3r/CMakeLists.txt | 4 +- src/libslic3r/SLA/Contour3D.cpp | 4 +- src/libslic3r/SLA/Contour3D.hpp | 4 +- src/libslic3r/SLA/Hollowing.cpp | 4 +- .../SLA/{EigenMesh3D.cpp => IndexedMesh.cpp} | 46 ++--- .../SLA/{EigenMesh3D.hpp => IndexedMesh.hpp} | 32 +-- src/libslic3r/SLA/ReprojectPointsOnMesh.hpp | 6 +- src/libslic3r/SLA/SupportPointGenerator.cpp | 10 +- src/libslic3r/SLA/SupportPointGenerator.hpp | 8 +- src/libslic3r/SLA/SupportTree.hpp | 21 +- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 186 +++++++++--------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 19 +- src/libslic3r/SLAPrint.cpp | 6 +- src/libslic3r/SLAPrint.hpp | 2 +- src/slic3r/GUI/MeshUtils.cpp | 4 +- src/slic3r/GUI/MeshUtils.hpp | 4 +- tests/sla_print/sla_print_tests.cpp | 12 +- tests/sla_print/sla_raycast_tests.cpp | 4 +- tests/sla_print/sla_test_utils.cpp | 10 +- tests/sla_print/sla_test_utils.hpp | 12 +- tests/sla_print/sla_treebuilder_tests.cpp | 134 ++++++------- 21 files changed, 269 insertions(+), 263 deletions(-) rename src/libslic3r/SLA/{EigenMesh3D.cpp => IndexedMesh.cpp} (92%) rename src/libslic3r/SLA/{EigenMesh3D.hpp => IndexedMesh.hpp} (86%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 20f3c6b4ba..91da5df5d6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -236,8 +236,8 @@ add_library(libslic3r STATIC SLA/SupportPointGenerator.cpp SLA/Contour3D.hpp SLA/Contour3D.cpp - SLA/EigenMesh3D.hpp - SLA/EigenMesh3D.cpp + SLA/IndexedMesh.hpp + SLA/IndexedMesh.cpp SLA/Clustering.hpp SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp diff --git a/src/libslic3r/SLA/Contour3D.cpp b/src/libslic3r/SLA/Contour3D.cpp index 408465d43e..96d10af208 100644 --- a/src/libslic3r/SLA/Contour3D.cpp +++ b/src/libslic3r/SLA/Contour3D.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include @@ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh) faces3.swap(trmesh.its.indices); } -Contour3D::Contour3D(const EigenMesh3D &emesh) { +Contour3D::Contour3D(const IndexedMesh &emesh) { points.reserve(emesh.vertices().size()); faces3.reserve(emesh.indices().size()); diff --git a/src/libslic3r/SLA/Contour3D.hpp b/src/libslic3r/SLA/Contour3D.hpp index 1a4fa9a29c..3380cd6ab0 100644 --- a/src/libslic3r/SLA/Contour3D.hpp +++ b/src/libslic3r/SLA/Contour3D.hpp @@ -10,7 +10,7 @@ using Vec4i = Eigen::Matrix; namespace sla { -class EigenMesh3D; +class IndexedMesh; /// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with /// other meshes of this type and converting to and from other mesh formats. @@ -22,7 +22,7 @@ struct Contour3D { Contour3D() = default; Contour3D(const TriangleMesh &trmesh); Contour3D(TriangleMesh &&trmesh); - Contour3D(const EigenMesh3D &emesh); + Contour3D(const IndexedMesh &emesh); Contour3D& merge(const Contour3D& ctr); Contour3D& merge(const Pointf3s& triangles); diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 44e4dd8390..5334054a05 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -159,7 +159,7 @@ bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir, const Eigen::ParametrizedLine ray(s, dir.normalized()); for (size_t i=0; i<2; ++i) - out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero()); + out[i] = std::make_pair(sla::IndexedMesh::hit_result::infty(), Vec3d::Zero()); const float sqr_radius = pow(radius, 2.f); diff --git a/src/libslic3r/SLA/EigenMesh3D.cpp b/src/libslic3r/SLA/IndexedMesh.cpp similarity index 92% rename from src/libslic3r/SLA/EigenMesh3D.cpp rename to src/libslic3r/SLA/IndexedMesh.cpp index be44e324c6..573b62b6db 100644 --- a/src/libslic3r/SLA/EigenMesh3D.cpp +++ b/src/libslic3r/SLA/IndexedMesh.cpp @@ -1,4 +1,4 @@ -#include "EigenMesh3D.hpp" +#include "IndexedMesh.hpp" #include "Concurrency.hpp" #include @@ -12,7 +12,7 @@ namespace Slic3r { namespace sla { -class EigenMesh3D::AABBImpl { +class IndexedMesh::AABBImpl { private: AABBTreeIndirect::Tree3f m_tree; @@ -57,7 +57,7 @@ public: static const constexpr double MESH_EPS = 1e-6; -EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) +IndexedMesh::IndexedMesh(const TriangleMesh& tmesh) : m_aabb(new AABBImpl()), m_tm(&tmesh) { auto&& bb = tmesh.bounding_box(); @@ -67,61 +67,61 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) m_aabb->init(tmesh); } -EigenMesh3D::~EigenMesh3D() {} +IndexedMesh::~IndexedMesh() {} -EigenMesh3D::EigenMesh3D(const EigenMesh3D &other): +IndexedMesh::IndexedMesh(const IndexedMesh &other): m_tm(other.m_tm), m_ground_level(other.m_ground_level), m_aabb( new AABBImpl(*other.m_aabb) ) {} -EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) +IndexedMesh &IndexedMesh::operator=(const IndexedMesh &other) { m_tm = other.m_tm; m_ground_level = other.m_ground_level; m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; } -EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default; +IndexedMesh &IndexedMesh::operator=(IndexedMesh &&other) = default; -EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default; +IndexedMesh::IndexedMesh(IndexedMesh &&other) = default; -const std::vector& EigenMesh3D::vertices() const +const std::vector& IndexedMesh::vertices() const { return m_tm->its.vertices; } -const std::vector& EigenMesh3D::indices() const +const std::vector& IndexedMesh::indices() const { return m_tm->its.indices; } -const Vec3f& EigenMesh3D::vertices(size_t idx) const +const Vec3f& IndexedMesh::vertices(size_t idx) const { return m_tm->its.vertices[idx]; } -const Vec3i& EigenMesh3D::indices(size_t idx) const +const Vec3i& IndexedMesh::indices(size_t idx) const { return m_tm->its.indices[idx]; } -Vec3d EigenMesh3D::normal_by_face_id(int face_id) const { +Vec3d IndexedMesh::normal_by_face_id(int face_id) const { return m_tm->stl.facet_start[face_id].normal.cast(); } -EigenMesh3D::hit_result -EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const +IndexedMesh::hit_result +IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const { assert(is_approx(dir.norm(), 1.)); igl::Hit hit; @@ -149,10 +149,10 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const return ret; } -std::vector -EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const +std::vector +IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const { - std::vector outs; + std::vector outs; std::vector hits; m_aabb->intersect_ray(*m_tm, s, dir, hits); @@ -170,7 +170,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const // Convert the igl::Hit into hit_result outs.reserve(hits.size()); for (const igl::Hit& hit : hits) { - outs.emplace_back(EigenMesh3D::hit_result(*this)); + outs.emplace_back(IndexedMesh::hit_result(*this)); outs.back().m_t = double(hit.t); outs.back().m_dir = dir; outs.back().m_source = s; @@ -185,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const #ifdef SLIC3R_HOLE_RAYCASTER -EigenMesh3D::hit_result EigenMesh3D::filter_hits( - const std::vector& object_hits) const +IndexedMesh::hit_result IndexedMesh::filter_hits( + const std::vector& object_hits) const { assert(! m_holes.empty()); hit_result out(*this); @@ -282,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits( #endif -double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { +double IndexedMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { double sqdst = 0; Eigen::Matrix pp = p; Eigen::Matrix cc; @@ -303,7 +303,7 @@ static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, } PointSet normals(const PointSet& points, - const EigenMesh3D& mesh, + const IndexedMesh& mesh, double eps, std::function thr, // throw on cancel const std::vector& pt_indices) diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/IndexedMesh.hpp similarity index 86% rename from src/libslic3r/SLA/EigenMesh3D.hpp rename to src/libslic3r/SLA/IndexedMesh.hpp index c9196bb432..b0970608e2 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/IndexedMesh.hpp @@ -1,5 +1,5 @@ -#ifndef SLA_EIGENMESH3D_H -#define SLA_EIGENMESH3D_H +#ifndef SLA_INDEXEDMESH_H +#define SLA_INDEXEDMESH_H #include #include @@ -26,7 +26,7 @@ using PointSet = Eigen::MatrixXd; /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree. // Implemented in libslic3r/SLA/Common.cpp -class EigenMesh3D { +class IndexedMesh { class AABBImpl; const TriangleMesh* m_tm; @@ -42,15 +42,15 @@ class EigenMesh3D { public: - explicit EigenMesh3D(const TriangleMesh&); + explicit IndexedMesh(const TriangleMesh&); - EigenMesh3D(const EigenMesh3D& other); - EigenMesh3D& operator=(const EigenMesh3D&); + IndexedMesh(const IndexedMesh& other); + IndexedMesh& operator=(const IndexedMesh&); - EigenMesh3D(EigenMesh3D &&other); - EigenMesh3D& operator=(EigenMesh3D &&other); + IndexedMesh(IndexedMesh &&other); + IndexedMesh& operator=(IndexedMesh &&other); - ~EigenMesh3D(); + ~IndexedMesh(); inline double ground_level() const { return m_ground_level + m_gnd_offset; } inline void ground_level_offset(double o) { m_gnd_offset = o; } @@ -66,15 +66,15 @@ public: // m_t holds a distance from m_source to the intersection. double m_t = infty(); int m_face_id = -1; - const EigenMesh3D *m_mesh = nullptr; + const IndexedMesh *m_mesh = nullptr; Vec3d m_dir; Vec3d m_source; Vec3d m_normal; - friend class EigenMesh3D; + friend class IndexedMesh; // 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) {} + // IndexedMesh::query_ray_hit method. + explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {} public: // This denotes no hit on the mesh. static inline constexpr double infty() { return std::numeric_limits::infinity(); } @@ -111,7 +111,7 @@ public: // This function is currently not used anywhere, it was written when the // holes were subtracted on slices, that is, before we started using CGAL // to actually cut the holes into the mesh. - hit_result filter_hits(const std::vector& obj_hits) const; + hit_result filter_hits(const std::vector& obj_hits) const; #endif // Casting a ray on the mesh, returns the distance where the hit occures. @@ -136,11 +136,11 @@ public: // Calculate the normals for the selected points (from 'points' set) on the // mesh. This will call squared distance for each point. PointSet normals(const PointSet& points, - const EigenMesh3D& convert_mesh, + const IndexedMesh& convert_mesh, double eps = 0.05, // min distance from edges std::function throw_on_cancel = [](){}, const std::vector& selected_points = {}); }} // namespace Slic3r::sla -#endif // EIGENMESH3D_H +#endif // INDEXEDMESH_H diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp index 702d1bce18..4737a6c212 100644 --- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp +++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp @@ -4,7 +4,7 @@ #include "libslic3r/Point.hpp" #include "SupportPoint.hpp" #include "Hollowing.hpp" -#include "EigenMesh3D.hpp" +#include "IndexedMesh.hpp" #include "libslic3r/Model.hpp" #include @@ -15,7 +15,7 @@ template Vec3d pos(const Pt &p) { return p.pos.template cast() template void pos(Pt &p, const Vec3d &pp) { p.pos = pp.cast(); } template -void reproject_support_points(const EigenMesh3D &mesh, std::vector &pts) +void reproject_support_points(const IndexedMesh &mesh, std::vector &pts) { tbb::parallel_for(size_t(0), pts.size(), [&mesh, &pts](size_t idx) { int junk; @@ -40,7 +40,7 @@ inline void reproject_points_and_holes(ModelObject *object) TriangleMesh rmsh = object->raw_mesh(); rmsh.require_shared_vertices(); - EigenMesh3D emesh{rmsh}; + IndexedMesh emesh{rmsh}; if (has_sppoints) reproject_support_points(emesh, object->sla_support_points); diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index b598439cae..3cd075ae69 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -50,7 +50,7 @@ float SupportPointGenerator::distance_limit(float angle) const }*/ SupportPointGenerator::SupportPointGenerator( - const sla::EigenMesh3D &emesh, + const sla::IndexedMesh &emesh, const std::vector &slices, const std::vector & heights, const Config & config, @@ -64,7 +64,7 @@ SupportPointGenerator::SupportPointGenerator( } SupportPointGenerator::SupportPointGenerator( - const EigenMesh3D &emesh, + const IndexedMesh &emesh, const SupportPointGenerator::Config &config, std::function throw_on_cancel, std::function statusfn) @@ -95,8 +95,8 @@ void SupportPointGenerator::project_onto_mesh(std::vector& po m_throw_on_cancel(); Vec3f& p = points[point_id].pos; // Project the point upward and downward and choose the closer intersection with the mesh. - sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); - sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); + sla::IndexedMesh::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); + sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); bool up = hit_up.is_hit(); bool down = hit_down.is_hit(); @@ -104,7 +104,7 @@ void SupportPointGenerator::project_onto_mesh(std::vector& po if (!up && !down) continue; - sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; + sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; p = p + (hit.distance() * hit.direction()).cast(); } }); diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 3f07e96746..f1b3770254 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -27,10 +27,10 @@ public: inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) }; - SupportPointGenerator(const EigenMesh3D& emesh, const std::vector& slices, + SupportPointGenerator(const IndexedMesh& emesh, const std::vector& slices, const std::vector& heights, const Config& config, std::function throw_on_cancel, std::function statusfn); - SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function throw_on_cancel, std::function statusfn); + SupportPointGenerator(const IndexedMesh& emesh, const Config& config, std::function throw_on_cancel, std::function statusfn); const std::vector& output() const { return m_output; } std::vector& output() { return m_output; } @@ -206,7 +206,7 @@ private: static void output_structures(const std::vector &structures); #endif // SLA_SUPPORTPOINTGEN_DEBUG - const EigenMesh3D& m_emesh; + const IndexedMesh& m_emesh; std::function m_throw_on_cancel; std::function m_statusfn; diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 1415ab8fe9..7d54b76a40 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -31,7 +31,7 @@ enum class PillarConnectionMode dynamic }; -struct SupportConfig +struct SupportTreeConfig { bool enabled = true; @@ -107,23 +107,30 @@ struct SupportConfig }; +// TODO: Part of future refactor +//class SupportConfig { +// std::optional tree_cfg {std::in_place_t{}}; // fill up +// std::optional pad_cfg; +//}; + enum class MeshType { Support, Pad }; struct SupportableMesh { - EigenMesh3D emesh; + IndexedMesh emesh; SupportPoints pts; - SupportConfig cfg; + SupportTreeConfig cfg; + PadConfig pad_cfg; explicit SupportableMesh(const TriangleMesh & trmsh, const SupportPoints &sp, - const SupportConfig &c) + const SupportTreeConfig &c) : emesh{trmsh}, pts{sp}, cfg{c} {} - explicit SupportableMesh(const EigenMesh3D &em, + explicit SupportableMesh(const IndexedMesh &em, const SupportPoints &sp, - const SupportConfig &c) + const SupportTreeConfig &c) : emesh{em}, pts{sp}, cfg{c} {} }; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 00f09b8121..b29ad0b9c1 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -14,7 +14,7 @@ using libnest2d::opt::StopCriteria; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::SubplexOptimizer; -template +template static Hit min_hit(const C &hits) { auto mit = std::min_element(hits.begin(), hits.end(), @@ -25,118 +25,118 @@ static Hit min_hit(const C &hits) return *mit; } -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &h) -{ - static const size_t SAMPLES = 8; +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &h) +//{ +// static const size_t SAMPLES = 8; - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. +// // Move away slightly from the touching point to avoid raycasting on the +// // inner surface of the mesh. - const double& sd = msh.cfg.safety_distance_mm; +// const double& sd = msh.cfg.safety_distance_mm; - auto& m = msh.emesh; - using HitResult = EigenMesh3D::hit_result; +// auto& m = msh.emesh; +// using HitResult = IndexedMesh::hit_result; - // Hit results - std::array hits; +// // Hit results +// std::array hits; - Vec3d s1 = h.pos, s2 = h.junction_point(); +// Vec3d s1 = h.pos, s2 = h.junction_point(); - struct Rings { - double rpin; - double rback; - Vec3d spin; - Vec3d sback; - PointRing ring; +// struct Rings { +// double rpin; +// double rback; +// Vec3d spin; +// Vec3d sback; +// PointRing ring; - Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } - Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } - } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; +// Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } +// Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } +// } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; - // We will shoot multiple rays from the head pinpoint in the direction - // of the pinhead robe (side) surface. The result will be the smallest - // hit distance. +// // We will shoot multiple rays from the head pinpoint in the direction +// // of the pinhead robe (side) surface. The result will be the smallest +// // hit distance. - auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d ps = rings.pinring(i); - // This is the point on the circle on the back sphere - Vec3d p = rings.backring(i); +// auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { +// // Point on the circle on the pin sphere +// Vec3d ps = rings.pinring(i); +// // This is the point on the circle on the back sphere +// Vec3d p = rings.backring(i); - // Point ps is not on mesh but can be inside or - // outside as well. This would cause many problems - // with ray-casting. To detect the position we will - // use the ray-casting result (which has an is_inside - // predicate). +// // Point ps is not on mesh but can be inside or +// // outside as well. This would cause many problems +// // with ray-casting. To detect the position we will +// // use the ray-casting result (which has an is_inside +// // predicate). - Vec3d n = (p - ps).normalized(); - auto q = m.query_ray_hit(ps + sd * n, n); +// Vec3d n = (p - ps).normalized(); +// auto q = m.query_ray_hit(ps + sd * n, n); - if (q.is_inside()) { // the hit is inside the model - if (q.distance() > rings.rpin) { - // If we are inside the model and the hit - // distance is bigger than our pin circle - // diameter, it probably indicates that the - // support point was already inside the - // model, or there is really no space - // around the point. We will assign a zero - // hit distance to these cases which will - // enforce the function return value to be - // an invalid ray with zero hit distance. - // (see min_element at the end) - hit = HitResult(0.0); - } else { - // re-cast the ray from the outside of the - // object. The starting point has an offset - // of 2*safety_distance because the - // original ray has also had an offset - auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); - hit = q2; - } - } else - hit = q; - }; +// if (q.is_inside()) { // the hit is inside the model +// if (q.distance() > rings.rpin) { +// // If we are inside the model and the hit +// // distance is bigger than our pin circle +// // diameter, it probably indicates that the +// // support point was already inside the +// // model, or there is really no space +// // around the point. We will assign a zero +// // hit distance to these cases which will +// // enforce the function return value to be +// // an invalid ray with zero hit distance. +// // (see min_element at the end) +// hit = HitResult(0.0); +// } else { +// // re-cast the ray from the outside of the +// // object. The starting point has an offset +// // of 2*safety_distance because the +// // original ray has also had an offset +// auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); +// hit = q2; +// } +// } else +// hit = q; +// }; - ccr::enumerate(hits.begin(), hits.end(), hitfn); +// ccr::enumerate(hits.begin(), hits.end(), hitfn); - return min_hit(hits); -} +// return min_hit(hits); +//} -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) -{ +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) +//{ - static const size_t SAMPLES = 8; +// static const size_t SAMPLES = 8; - Vec3d dir = (br.endp - br.startp).normalized(); - PointRing ring{dir}; +// Vec3d dir = (br.endp - br.startp).normalized(); +// PointRing ring{dir}; - using Hit = EigenMesh3D::hit_result; +// using Hit = IndexedMesh::hit_result; - // Hit results - std::array hits; +// // Hit results +// std::array hits; - double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; +// double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; - auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { +// auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { - // Point on the circle on the pin sphere - Vec3d p = ring.get(i, br.startp, br.r + sd); +// // Point on the circle on the pin sphere +// Vec3d p = ring.get(i, br.startp, br.r + sd); - auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); +// auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); - if(hr.is_inside()) { - if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); - else { - // re-cast the ray from the outside of the object - hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); - } - } else hit = hr; - }; +// if(hr.is_inside()) { +// if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); +// else { +// // re-cast the ray from the outside of the object +// hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); +// } +// } else hit = hr; +// }; - ccr::enumerate(hits.begin(), hits.end(), hitfn); +// ccr::enumerate(hits.begin(), hits.end(), hitfn); - return min_hit(hits); -} +// return min_hit(hits); +//} SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm) @@ -281,7 +281,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, return pc == ABORT; } -EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( +IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) { static const size_t SAMPLES = 8; @@ -292,7 +292,7 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( const double& sd = m_cfg.safety_distance_mm; auto& m = m_mesh; - using HitResult = EigenMesh3D::hit_result; + using HitResult = IndexedMesh::hit_result; // Hit results std::array hits; @@ -357,13 +357,13 @@ EigenMesh3D::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( return min_hit(hits); } -EigenMesh3D::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( +IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( const Vec3d &src, const Vec3d &dir, double r, double sd) { static const size_t SAMPLES = 8; PointRing ring{dir}; - using Hit = EigenMesh3D::hit_result; + using Hit = IndexedMesh::hit_result; // Hit results std::array hits; @@ -742,7 +742,7 @@ void SupportTreeBuildsteps::filter() auto nn = spheric_to_dir(polar, azimuth).normalized(); // check available distance - EigenMesh3D::hit_result t + IndexedMesh::hit_result t = pinhead_mesh_intersect(hp, // touching point nn, // normal pin_r, @@ -781,7 +781,7 @@ void SupportTreeBuildsteps::filter() polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); nn = spheric_to_dir(polar, azimuth).normalized(); - t = EigenMesh3D::hit_result(oresult.score); + t = IndexedMesh::hit_result(oresult.score); } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index e8f73149e3..a985867890 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -103,9 +103,8 @@ public: } }; -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); -EigenMesh3D::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); - +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan("")); +//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan("")); inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); @@ -181,8 +180,8 @@ IntegerOnly pairhash(I a, I b) } class SupportTreeBuildsteps { - const SupportConfig& m_cfg; - const EigenMesh3D& m_mesh; + const SupportTreeConfig& m_cfg; + const IndexedMesh& m_mesh; const std::vector& m_support_pts; using PtIndices = std::vector; @@ -191,7 +190,7 @@ class SupportTreeBuildsteps { PtIndices m_iheads_onmodel; PtIndices m_iheadless; // headless support points - std::map m_head_to_ground_scans; + std::map m_head_to_ground_scans; // normals for support points from model faces. PointSet m_support_nmls; @@ -217,7 +216,7 @@ class SupportTreeBuildsteps { // When bridging heads to pillars... TODO: find a cleaner solution ccr::BlockingMutex m_bridge_mutex; - inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s, + inline IndexedMesh::hit_result ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) { return m_mesh.query_ray_hit(s, dir); @@ -234,7 +233,7 @@ class SupportTreeBuildsteps { // point was inside the model, an "invalid" hit_result will be returned // with a zero distance value instead of a NAN. This way the result can // be used safely for comparison with other distances. - EigenMesh3D::hit_result pinhead_mesh_intersect( + IndexedMesh::hit_result pinhead_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r_pin, @@ -249,13 +248,13 @@ class SupportTreeBuildsteps { // point was inside the model, an "invalid" hit_result will be returned // with a zero distance value instead of a NAN. This way the result can // be used safely for comparison with other distances. - EigenMesh3D::hit_result bridge_mesh_intersect( + IndexedMesh::hit_result bridge_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r, double safety_d); - EigenMesh3D::hit_result bridge_mesh_intersect( + IndexedMesh::hit_result bridge_mesh_intersect( const Vec3d& s, const Vec3d& dir, double r) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 2402207a8a..eee3bbc9fa 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -35,9 +35,9 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c) } // Compile the argument for support creation from the static print config. -sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) +sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) { - sla::SupportConfig scfg; + sla::SupportTreeConfig scfg; scfg.enabled = c.supports_enable.getBool(); scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); @@ -616,7 +616,7 @@ std::string SLAPrint::validate() const return L("Cannot proceed without support points! " "Add support points or disable support generation."); - sla::SupportConfig cfg = make_support_cfg(po->config()); + sla::SupportTreeConfig cfg = make_support_cfg(po->config()); double elv = cfg.object_elevation_mm; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 9d41586ee6..f4b220c58c 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -544,7 +544,7 @@ private: bool is_zero_elevation(const SLAPrintObjectConfig &c); -sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c); +sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c); sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 581f50a882..ee0abe76f9 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -134,7 +134,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& Vec3d direction; line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); - std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector hits = m_emesh.query_ray_hits(point, direction); if (hits.empty()) return false; // no intersection found @@ -184,7 +184,7 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; + std::vector hits; // Offset the start of the ray by EPSILON to account for numerical inaccuracies. hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast(), direction_to_camera.cast()); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 2758577a25..60dcb30c81 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -3,7 +3,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/Geometry.hpp" -#include "libslic3r/SLA/EigenMesh3D.hpp" +#include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" #include "slic3r/GUI/3DScene.hpp" @@ -147,7 +147,7 @@ public: Vec3f get_triangle_normal(size_t facet_idx) const; private: - sla::EigenMesh3D m_emesh; + sla::IndexedMesh m_emesh; std::vector m_normals; }; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 82df2c1a6f..9a9c762e3d 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -37,9 +37,9 @@ TEST_CASE("Support point generator should be deterministic if seeded", "[SLASupportGeneration], [SLAPointGen]") { TriangleMesh mesh = load_model("A_upsidedown.obj"); - sla::EigenMesh3D emesh{mesh}; + sla::IndexedMesh emesh{mesh}; - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; sla::SupportPointGenerator::Config autogencfg; autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; @@ -124,14 +124,14 @@ TEST_CASE("WingedPadAroundObjectIsValid", "[SLASupportGeneration]") { } TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; supportcfg.object_elevation_mm = 5.; for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname); } TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; supportcfg.object_elevation_mm = 0; for (auto &fname: SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); @@ -139,7 +139,7 @@ TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") { TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; for (auto fname : SUPPORT_TEST_MODELS) test_support_model_collision(fname, supportcfg); @@ -147,7 +147,7 @@ TEST_CASE("ElevatedSupportsDoNotPierceModel", "[SLASupportGeneration]") { TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { - sla::SupportConfig supportcfg; + sla::SupportTreeConfig supportcfg; supportcfg.object_elevation_mm = 0; for (auto fname : SUPPORT_TEST_MODELS) diff --git a/tests/sla_print/sla_raycast_tests.cpp b/tests/sla_print/sla_raycast_tests.cpp index c82e4569a8..b56909280b 100644 --- a/tests/sla_print/sla_raycast_tests.cpp +++ b/tests/sla_print/sla_raycast_tests.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include "sla_test_utils.hpp" @@ -65,7 +65,7 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]") cube.merge(*cube_inside); cube.require_shared_vertices(); - sla::EigenMesh3D emesh{cube}; + sla::IndexedMesh emesh{cube}; emesh.load_holes(holes); Vec3d s = center.cast(); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index bc0cfb0cd1..c46cf675c6 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -2,13 +2,13 @@ #include "libslic3r/SLA/AGGRaster.hpp" void test_support_model_collision(const std::string &obj_filename, - const sla::SupportConfig &input_supportcfg, + const sla::SupportTreeConfig &input_supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes) { SupportByproducts byproducts; - sla::SupportConfig supportcfg = input_supportcfg; + sla::SupportTreeConfig supportcfg = input_supportcfg; // Set head penetration to a small negative value which should ensure that // the supports will not touch the model body. @@ -73,7 +73,7 @@ void export_failed_case(const std::vector &support_slices, const Sup } void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg, + const sla::SupportTreeConfig &supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes, SupportByproducts &out) @@ -104,7 +104,7 @@ void test_supports(const std::string &obj_filename, // Create the special index-triangle mesh with spatial indexing which // is the input of the support point and support mesh generators - sla::EigenMesh3D emesh{mesh}; + sla::IndexedMesh emesh{mesh}; #ifdef SLIC3R_HOLE_RAYCASTER if (hollowingcfg.enabled) @@ -168,7 +168,7 @@ void test_supports(const std::string &obj_filename, } void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, - const sla::SupportConfig &cfg) + const sla::SupportTreeConfig &cfg) { double gnd = stree.ground_level; double H1 = cfg.max_solo_pillar_height_mm; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 3652b1f81c..fdd883ed84 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -67,16 +67,16 @@ struct SupportByproducts const constexpr float CLOSING_RADIUS = 0.005f; void check_support_tree_integrity(const sla::SupportTreeBuilder &stree, - const sla::SupportConfig &cfg); + const sla::SupportTreeConfig &cfg); void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg, + const sla::SupportTreeConfig &supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes, SupportByproducts &out); inline void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg, + const sla::SupportTreeConfig &supportcfg, SupportByproducts &out) { sla::HollowingConfig hcfg; @@ -85,7 +85,7 @@ inline void test_supports(const std::string &obj_filename, } inline void test_supports(const std::string &obj_filename, - const sla::SupportConfig &supportcfg = {}) + const sla::SupportTreeConfig &supportcfg = {}) { SupportByproducts byproducts; test_supports(obj_filename, supportcfg, byproducts); @@ -97,13 +97,13 @@ void export_failed_case(const std::vector &support_slices, void test_support_model_collision( const std::string &obj_filename, - const sla::SupportConfig &input_supportcfg, + const sla::SupportTreeConfig &input_supportcfg, const sla::HollowingConfig &hollowingcfg, const sla::DrainHoles &drainholes); inline void test_support_model_collision( const std::string &obj_filename, - const sla::SupportConfig &input_supportcfg = {}) + const sla::SupportTreeConfig &input_supportcfg = {}) { sla::HollowingConfig hcfg; hcfg.enabled = false; diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp index 05aca963ea..91c2ea6f8e 100644 --- a/tests/sla_print/sla_treebuilder_tests.cpp +++ b/tests/sla_print/sla_treebuilder_tests.cpp @@ -1,99 +1,99 @@ -#include -#include +//#include +//#include -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" -#include "libslic3r/SLA/SupportTreeMesher.hpp" +//#include "libslic3r/TriangleMesh.hpp" +//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" +//#include "libslic3r/SLA/SupportTreeMesher.hpp" -TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { - using namespace Slic3r; +//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { +// using namespace Slic3r; - TriangleMesh cube = make_cube(10., 10., 10.); +// TriangleMesh cube = make_cube(10., 10., 10.); - sla::SupportConfig cfg = {}; // use default config - sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; - sla::SupportableMesh sm{cube, pts, cfg}; +// sla::SupportConfig cfg = {}; // use default config +// sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; +// sla::SupportableMesh sm{cube, pts, cfg}; - size_t steps = 45; - SECTION("Bridge is straight horizontal and pointing away from the cube") { +// size_t steps = 45; +// SECTION("Bridge is straight horizontal and pointing away from the cube") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - REQUIRE(std::isinf(hit.distance())); +// REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - cube.require_shared_vertices(); - cube.WriteOBJFile("cube1.obj"); - } +// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// cube.require_shared_vertices(); +// cube.WriteOBJFile("cube1.obj"); +// } - SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, - pts[0].head_front_radius); +// SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - REQUIRE(std::isinf(hit.distance())); +// REQUIRE(std::isinf(hit.distance())); - cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - cube.require_shared_vertices(); - cube.WriteOBJFile("cube2.obj"); - } -} +// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// cube.require_shared_vertices(); +// cube.WriteOBJFile("cube2.obj"); +// } +//} -TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { - using namespace Slic3r; +//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { +// using namespace Slic3r; - TriangleMesh sphere = make_sphere(1.); +// TriangleMesh sphere = make_sphere(1.); - sla::SupportConfig cfg = {}; // use default config - cfg.head_back_radius_mm = cfg.head_front_radius_mm; - sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; - sla::SupportableMesh sm{sphere, pts, cfg}; +// sla::SupportConfig cfg = {}; // use default config +// cfg.head_back_radius_mm = cfg.head_front_radius_mm; +// sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; +// sla::SupportableMesh sm{sphere, pts, cfg}; - size_t steps = 45; - SECTION("Bridge is straight horizontal and pointing away from the sphere") { +// size_t steps = 45; +// SECTION("Bridge is straight horizontal and pointing away from the sphere") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - sphere.require_shared_vertices(); - sphere.WriteOBJFile("sphere1.obj"); +// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// sphere.require_shared_vertices(); +// sphere.WriteOBJFile("sphere1.obj"); - REQUIRE(std::isinf(hit.distance())); - } +// REQUIRE(std::isinf(hit.distance())); +// } - SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { +// SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - sphere.require_shared_vertices(); - sphere.WriteOBJFile("sphere2.obj"); +// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// sphere.require_shared_vertices(); +// sphere.WriteOBJFile("sphere2.obj"); - REQUIRE(std::isinf(hit.distance())); - } +// REQUIRE(std::isinf(hit.distance())); +// } - SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { +// SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { - sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, - pts[0].head_front_radius); +// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, +// pts[0].head_front_radius); - auto hit = sla::query_hit(sm, bridge); +// auto hit = sla::query_hit(sm, bridge); - sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); - sphere.require_shared_vertices(); - sphere.WriteOBJFile("sphere3.obj"); +// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); +// sphere.require_shared_vertices(); +// sphere.WriteOBJFile("sphere3.obj"); - REQUIRE(std::isinf(hit.distance())); - } -} +// REQUIRE(std::isinf(hit.distance())); +// } +//} From a68564e2d01994b13c1d675ec3d08ec0434d1b84 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Jun 2020 15:12:37 +0200 Subject: [PATCH 245/826] Include test name with output obj files for sla_print_tests --- tests/sla_print/sla_test_utils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index c46cf675c6..8978281d8c 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -69,7 +69,8 @@ void export_failed_case(const std::vector &support_slices, const Sup m.merge(byproducts.input_mesh); m.repair(); m.require_shared_vertices(); - m.WriteOBJFile(byproducts.obj_fname.c_str()); + m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" + + byproducts.obj_fname).c_str()); } void test_supports(const std::string &obj_filename, From 7c655b5d7e1732448b8a37fd335b8e9535372a38 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 29 Jun 2020 20:09:37 +0200 Subject: [PATCH 246/826] Fix junction made below ground level. --- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index b29ad0b9c1..2116568e8c 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1333,9 +1333,8 @@ void SupportTreeBuildsteps::interconnect_pillars() if (distance(pillarsp, s) < t) m_builder.add_bridge(pillarsp, s, pillar().r); - if (pillar().endpoint()(Z) > m_builder.ground_level) - m_builder.add_junction(pillar().endpoint(), - pillar().r); + if (pillar().endpoint()(Z) > m_builder.ground_level + pillar().r) + m_builder.add_junction(pillar().endpoint(), pillar().r); newpills.emplace_back(pp.id); m_builder.increment_links(pillar()); From 8cb115a03543f0d09c9e113ab8e42cf9eb39a694 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 8 Jul 2020 11:31:01 +0200 Subject: [PATCH 247/826] Add possible manipulation of small support diameter. --- src/libslic3r/PrintConfig.cpp | 21 +++- src/libslic3r/PrintConfig.hpp | 5 + src/libslic3r/SLA/SupportTree.hpp | 2 + src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 131 ++------------------ src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 14 ++- src/libslic3r/SLAPrint.cpp | 6 +- src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 9 files changed, 57 insertions(+), 125 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a25292298c..5c1ce4b7f8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2715,7 +2715,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionBool(true)); def = this->add("support_head_front_diameter", coFloat); - def->label = L("Support head front diameter"); + def->label = L("Pinhead front diameter"); def->category = L("Supports"); def->tooltip = L("Diameter of the pointing side of the head"); def->sidetext = L("mm"); @@ -2724,7 +2724,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(0.4)); def = this->add("support_head_penetration", coFloat); - def->label = L("Support head penetration"); + def->label = L("Head penetration"); def->category = L("Supports"); def->tooltip = L("How much the pinhead has to penetrate the model surface"); def->sidetext = L("mm"); @@ -2733,7 +2733,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(0.2)); def = this->add("support_head_width", coFloat); - def->label = L("Support head width"); + def->label = L("Pinhead width"); def->category = L("Supports"); def->tooltip = L("Width from the back sphere center to the front sphere center"); def->sidetext = L("mm"); @@ -2743,7 +2743,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(1.0)); def = this->add("support_pillar_diameter", coFloat); - def->label = L("Support pillar diameter"); + def->label = L("Pillar diameter"); def->category = L("Supports"); def->tooltip = L("Diameter in mm of the support pillars"); def->sidetext = L("mm"); @@ -2751,6 +2751,17 @@ void PrintConfigDef::init_sla_params() def->max = 15; def->mode = comSimple; def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_small_pillar_diameter_percent", coPercent); + def->label = L("Small pillar diameter percent"); + def->category = L("Supports"); + def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter " + "which are used in problematic areas where a normal pilla cannot fit."); + def->sidetext = L("%"); + def->min = 1; + def->max = 100; + def->mode = comExpert; + def->set_default_value(new ConfigOptionPercent(50)); def = this->add("support_max_bridges_on_pillar", coInt); def->label = L("Max bridges on a pillar"); @@ -2763,7 +2774,7 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionInt(3)); def = this->add("support_pillar_connection_mode", coEnum); - def->label = L("Support pillar connection mode"); + def->label = L("Pillar connection mode"); def->tooltip = L("Controls the bridge type between two neighboring pillars." " Can be zig-zag, cross (double zig-zag) or dynamic which" " will automatically switch between the first two depending" diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f28ef2a228..0213a6d6bd 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1018,6 +1018,10 @@ public: // Radius in mm of the support pillars. ConfigOptionFloat support_pillar_diameter /*= 0.8*/; + + // The percentage of smaller pillars compared to the normal pillar diameter + // which are used in problematic areas where a normal pilla cannot fit. + ConfigOptionPercent support_small_pillar_diameter_percent; // How much bridge (supporting another pinhead) can be placed on a pillar. ConfigOptionInt support_max_bridges_on_pillar; @@ -1142,6 +1146,7 @@ protected: OPT_PTR(support_head_penetration); OPT_PTR(support_head_width); OPT_PTR(support_pillar_diameter); + OPT_PTR(support_small_pillar_diameter_percent); OPT_PTR(support_max_bridges_on_pillar); OPT_PTR(support_pillar_connection_mode); OPT_PTR(support_buildplate_only); diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 7d54b76a40..4be90161d5 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -44,6 +44,8 @@ struct SupportTreeConfig // Radius of the back side of the 3d arrow. double head_back_radius_mm = 0.5; + double head_fallback_radius_mm = 0.25; + // Width in mm from the back sphere center to the front sphere center. double head_width_mm = 1.0; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 2116568e8c..7f6c034ddd 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -25,119 +25,6 @@ static Hit min_hit(const C &hits) return *mit; } -//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &h) -//{ -// static const size_t SAMPLES = 8; - -// // Move away slightly from the touching point to avoid raycasting on the -// // inner surface of the mesh. - -// const double& sd = msh.cfg.safety_distance_mm; - -// auto& m = msh.emesh; -// using HitResult = IndexedMesh::hit_result; - -// // Hit results -// std::array hits; - -// Vec3d s1 = h.pos, s2 = h.junction_point(); - -// struct Rings { -// double rpin; -// double rback; -// Vec3d spin; -// Vec3d sback; -// PointRing ring; - -// Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } -// Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } -// } rings {h.r_pin_mm + sd, h.r_back_mm + sd, s1, s2, h.dir}; - -// // We will shoot multiple rays from the head pinpoint in the direction -// // of the pinhead robe (side) surface. The result will be the smallest -// // hit distance. - -// auto hitfn = [&m, &rings, sd](HitResult &hit, size_t i) { -// // Point on the circle on the pin sphere -// Vec3d ps = rings.pinring(i); -// // This is the point on the circle on the back sphere -// Vec3d p = rings.backring(i); - -// // Point ps is not on mesh but can be inside or -// // outside as well. This would cause many problems -// // with ray-casting. To detect the position we will -// // use the ray-casting result (which has an is_inside -// // predicate). - -// Vec3d n = (p - ps).normalized(); -// auto q = m.query_ray_hit(ps + sd * n, n); - -// if (q.is_inside()) { // the hit is inside the model -// if (q.distance() > rings.rpin) { -// // If we are inside the model and the hit -// // distance is bigger than our pin circle -// // diameter, it probably indicates that the -// // support point was already inside the -// // model, or there is really no space -// // around the point. We will assign a zero -// // hit distance to these cases which will -// // enforce the function return value to be -// // an invalid ray with zero hit distance. -// // (see min_element at the end) -// hit = HitResult(0.0); -// } else { -// // re-cast the ray from the outside of the -// // object. The starting point has an offset -// // of 2*safety_distance because the -// // original ray has also had an offset -// auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n); -// hit = q2; -// } -// } else -// hit = q; -// }; - -// ccr::enumerate(hits.begin(), hits.end(), hitfn); - -// return min_hit(hits); -//} - -//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d) -//{ - -// static const size_t SAMPLES = 8; - -// Vec3d dir = (br.endp - br.startp).normalized(); -// PointRing ring{dir}; - -// using Hit = IndexedMesh::hit_result; - -// // Hit results -// std::array hits; - -// double sd = std::isnan(safety_d) ? msh.cfg.safety_distance_mm : safety_d; - -// auto hitfn = [&msh, &br, &ring, dir, sd] (Hit &hit, size_t i) { - -// // Point on the circle on the pin sphere -// Vec3d p = ring.get(i, br.startp, br.r + sd); - -// auto hr = msh.emesh.query_ray_hit(p + br.r * dir, dir); - -// if(hr.is_inside()) { -// if(hr.distance() > 2 * br.r + sd) hit = Hit(0.0); -// else { -// // re-cast the ray from the outside of the object -// hit = msh.emesh.query_ray_hit(p + (hr.distance() + 2 * sd) * dir, dir); -// } -// } else hit = hr; -// }; - -// ccr::enumerate(hits.begin(), hits.end(), hitfn); - -// return min_hit(hits); -//} - SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm) : m_cfg(sm.cfg) @@ -282,15 +169,18 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, } IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( - const Vec3d &s, const Vec3d &dir, double r_pin, double r_back, double width) + const Vec3d &s, + const Vec3d &dir, + double r_pin, + double r_back, + double width, + double sd) { static const size_t SAMPLES = 8; // Move away slightly from the touching point to avoid raycasting on the // inner surface of the mesh. - const double& sd = m_cfg.safety_distance_mm; - auto& m = m_mesh; using HitResult = IndexedMesh::hit_result; @@ -836,12 +726,17 @@ void SupportTreeBuildsteps::add_pinheads() // First we need to determine the available space for a mini pinhead. // The goal is the move away from the model a little bit to make the // contact point small as possible and avoid pearcing the model body. - double pin_space = std::min(2 * R, bridge_mesh_distance(sph, n, R, 0.)); + double back_r = m_cfg.head_fallback_radius_mm; + double max_w = 2 * R; + double pin_space = std::min(max_w, + pinhead_mesh_intersect(sph, n, R, back_r, + max_w, 0.) + .distance()); if (pin_space <= 0) continue; m_iheads.emplace_back(i); - m_builder.add_head(i, R, R, pin_space, + m_builder.add_head(i, back_r, R, pin_space, m_cfg.head_penetration_mm, n, sph); } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index a985867890..d19194a87d 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -238,7 +238,19 @@ class SupportTreeBuildsteps { const Vec3d& dir, double r_pin, double r_back, - double width); + double width, + double safety_d); + + IndexedMesh::hit_result pinhead_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r_pin, + double r_back, + double width) + { + return pinhead_mesh_intersect(s, dir, r_pin, r_back, width, + m_cfg.safety_distance_mm); + } // Checking bridge (pillar and stick as well) intersection with the model. // If the function is used for headless sticks, the ins_check parameter diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index eee3bbc9fa..4395bea461 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -41,7 +41,10 @@ sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c) scfg.enabled = c.supports_enable.getBool(); scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); - scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); + double pillar_r = 0.5 * c.support_pillar_diameter.getFloat(); + scfg.head_back_radius_mm = pillar_r; + scfg.head_fallback_radius_mm = + 0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r; scfg.head_penetration_mm = c.support_head_penetration.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat(); scfg.object_elevation_mm = is_zero_elevation(c) ? @@ -925,6 +928,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector& Preset::sla_print_options() "support_head_penetration", "support_head_width", "support_pillar_diameter", + "support_small_pillar_diameter_percent", "support_max_bridges_on_pillar", "support_pillar_connection_mode", "support_buildplate_only", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 84bc5a5726..86b483a8df 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3919,6 +3919,7 @@ void TabSLAPrint::build() optgroup = page->new_optgroup(L("Support pillar")); optgroup->append_single_option_line("support_pillar_diameter"); + optgroup->append_single_option_line("support_small_pillar_diameter_percent"); optgroup->append_single_option_line("support_max_bridges_on_pillar"); optgroup->append_single_option_line("support_pillar_connection_mode"); From 927b81ea9710266e2effb050ca27cb8762239870 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 16 Jul 2020 11:49:30 +0200 Subject: [PATCH 248/826] Working small-to-normal support merging Fixed fatal bug with anchors for mini supports Make the optimization cleaner in support generatior Much better widening behaviour Add an optimizer interface and the NLopt implementation into libslic3r New optimizer based only on nlopt C interfase Fix build and tests --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/Optimizer.hpp | 369 ++++++++++ src/libslic3r/SLA/IndexedMesh.hpp | 2 +- src/libslic3r/SLA/SupportTreeBuilder.cpp | 5 + src/libslic3r/SLA/SupportTreeBuilder.hpp | 45 +- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 758 ++++++++++---------- src/libslic3r/SLA/SupportTreeBuildsteps.hpp | 17 +- src/libslic3r/SLA/SupportTreeMesher.hpp | 18 + tests/sla_print/sla_print_tests.cpp | 11 + 9 files changed, 831 insertions(+), 395 deletions(-) create mode 100644 src/libslic3r/Optimizer.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 91da5df5d6..58b74402e1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -203,6 +203,7 @@ add_library(libslic3r STATIC SimplifyMeshImpl.hpp SimplifyMesh.cpp MarchingSquares.hpp + Optimizer.hpp ${OpenVDBUtils_SOURCES} SLA/Pad.hpp SLA/Pad.cpp diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimizer.hpp new file mode 100644 index 0000000000..dc70abe332 --- /dev/null +++ b/src/libslic3r/Optimizer.hpp @@ -0,0 +1,369 @@ +#ifndef NLOPTOPTIMIZER_HPP +#define NLOPTOPTIMIZER_HPP + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { namespace opt { + +// A type to hold the complete result of the optimization. +template struct Result { + int resultcode; + std::array optimum; + double score; +}; + +// An interval of possible input values for optimization +class Bound { + double m_min, m_max; + +public: + Bound(double min = std::numeric_limits::min(), + double max = std::numeric_limits::max()) + : m_min(min), m_max(max) + {} + + double min() const noexcept { return m_min; } + double max() const noexcept { return m_max; } +}; + +// Helper types for optimization function input and bounds +template using Input = std::array; +template using Bounds = std::array; + +// A type for specifying the stop criteria. Setter methods can be concatenated +class StopCriteria { + + // If the absolute value difference between two scores. + double m_abs_score_diff = std::nan(""); + + // If the relative value difference between two scores. + double m_rel_score_diff = std::nan(""); + + // Stop if this value or better is found. + double m_stop_score = std::nan(""); + + // A predicate that if evaluates to true, the optimization should terminate + // and the best result found prior to termination should be returned. + std::function m_stop_condition = [] { return false; }; + + // The max allowed number of iterations. + unsigned m_max_iterations = 0; + +public: + + StopCriteria & abs_score_diff(double val) + { + m_abs_score_diff = val; return *this; + } + + double abs_score_diff() const { return m_abs_score_diff; } + + StopCriteria & rel_score_diff(double val) + { + m_rel_score_diff = val; return *this; + } + + double rel_score_diff() const { return m_rel_score_diff; } + + StopCriteria & stop_score(double val) + { + m_stop_score = val; return *this; + } + + double stop_score() const { return m_stop_score; } + + StopCriteria & max_iterations(double val) + { + m_max_iterations = val; return *this; + } + + double max_iterations() const { return m_max_iterations; } + + template StopCriteria & stop_condition(Fn &&cond) + { + m_stop_condition = cond; return *this; + } + + bool stop_condition() { return m_stop_condition(); } +}; + +// Helper to be used in static_assert. +template struct always_false { enum { value = false }; }; + +// Basic interface to optimizer object +template class Optimizer { +public: + + Optimizer(const StopCriteria &) + { + static_assert(always_false::value, + "Optimizer unimplemented for given method!"); + } + + Optimizer &to_min() { return *this; } + Optimizer &to_max() { return *this; } + Optimizer &set_criteria(const StopCriteria &) { return *this; } + StopCriteria get_criteria() const { return {}; }; + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) { return {}; } + + // optional for randomized methods: + void seed(long /*s*/) {} +}; + +namespace detail { + +// Helper types for NLopt algorithm selection in template contexts +template struct NLoptAlg {}; + +// NLopt can combine multiple algorithms if one is global an other is a local +// method. This is how template specializations can be informed about this fact. +template +struct NLoptAlgComb {}; + +template struct IsNLoptAlg { + static const constexpr bool value = false; +}; + +template struct IsNLoptAlg> { + static const constexpr bool value = true; +}; + +template +struct IsNLoptAlg> { + static const constexpr bool value = true; +}; + +template +using NLoptOnly = std::enable_if_t::value, T>; + +// Convert any collection to tuple. This is useful for object functions taking +// an argument list of doubles. Make things cleaner on the call site of +// optimize(). +template struct to_tuple_ { + static auto call(const C &c) + { + return std::tuple_cat(std::tuple(c[N-I]), + to_tuple_::call(c)); + } +}; + +template struct to_tuple_<0, N, T, C> { + static auto call(const C &c) { return std::tuple<>(); } +}; + +// C array to tuple +template auto carray_tuple(const T *v) +{ + return to_tuple_::call(v); +} + +// Helper to convert C style array to std::array +template auto to_arr(const T (&a) [N]) +{ + std::array r; + std::copy(std::begin(a), std::end(a), std::begin(r)); + return r; +} + +enum class OptDir { MIN, MAX }; // Where to optimize + +struct NLopt { // Helper RAII class for nlopt_opt + nlopt_opt ptr = nullptr; + + template explicit NLopt(A&&...a) + { + ptr = nlopt_create(std::forward(a)...); + } + + NLopt(const NLopt&) = delete; + NLopt(NLopt&&) = delete; + NLopt& operator=(const NLopt&) = delete; + NLopt& operator=(NLopt&&) = delete; + + ~NLopt() { nlopt_destroy(ptr); } +}; + +template class NLoptOpt {}; + +// Optimizers based on NLopt. +template class NLoptOpt> { +protected: + StopCriteria m_stopcr; + OptDir m_dir; + + template using TOptData = + std::tuple*, NLoptOpt*, nlopt_opt>; + + template + static double optfunc(unsigned n, const double *params, + double *gradient, + void *data) + { + assert(n >= N); + + auto tdata = static_cast*>(data); + + if (std::get<1>(*tdata)->m_stopcr.stop_condition()) + nlopt_force_stop(std::get<2>(*tdata)); + + auto fnptr = std::get<0>(*tdata); + auto funval = carray_tuple(params); + + return std::apply(*fnptr, funval); + } + + template + void set_up(NLopt &nl, const Bounds& bounds) + { + std::array lb, ub; + + for (size_t i = 0; i < N; ++i) { + lb[i] = bounds[i].min(); + ub[i] = bounds[i].max(); + } + + nlopt_set_lower_bounds(nl.ptr, lb.data()); + nlopt_set_upper_bounds(nl.ptr, ub.data()); + + double abs_diff = m_stopcr.abs_score_diff(); + double rel_diff = m_stopcr.rel_score_diff(); + double stopval = m_stopcr.stop_score(); + if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff); + if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff); + if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval); + + if(this->m_stopcr.max_iterations() > 0) + nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations()); + } + + template + Result optimize(NLopt &nl, Fn &&fn, const Input &initvals) + { + Result r; + + TOptData data = std::make_tuple(&fn, this, nl.ptr); + + switch(m_dir) { + case OptDir::MIN: + nlopt_set_min_objective(nl.ptr, optfunc, &data); break; + case OptDir::MAX: + nlopt_set_max_objective(nl.ptr, optfunc, &data); break; + } + + r.optimum = initvals; + r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score); + + return r; + } + +public: + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) + { + NLopt nl{alg, N}; + set_up(nl, bounds); + + return optimize(nl, std::forward(func), initvals); + } + + explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {} + + void set_criteria(const StopCriteria &cr) { m_stopcr = cr; } + const StopCriteria &get_criteria() const noexcept { return m_stopcr; } + void set_dir(OptDir dir) noexcept { m_dir = dir; } + + void seed(long s) { nlopt_srand(s); } +}; + +template +class NLoptOpt>: public NLoptOpt> +{ + using Base = NLoptOpt>; +public: + + template + Result optimize(Fn&& f, + const Input &initvals, + const Bounds& bounds) + { + NLopt nl_glob{glob, N}, nl_loc{loc, N}; + + Base::set_up(nl_glob, bounds); + Base::set_up(nl_loc, bounds); + nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr); + + return Base::optimize(nl_glob, std::forward(f), initvals); + } + + explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {} +}; + +} // namespace detail; + +// Optimizers based on NLopt. +template class Optimizer> { + detail::NLoptOpt m_opt; + +public: + + Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; } + Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; } + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) + { + return m_opt.optimize(std::forward(func), initvals, bounds); + } + + explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {} + + Optimizer &set_criteria(const StopCriteria &cr) + { + m_opt.set_criteria(cr); return *this; + } + + const StopCriteria &get_criteria() const { return m_opt.get_criteria(); } + + void seed(long s) { m_opt.seed(s); } +}; + +template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } +template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } + +// Predefinded NLopt algorithms that are used in the codebase +using AlgNLoptGenetic = detail::NLoptAlgComb; +using AlgNLoptSubplex = detail::NLoptAlg; +using AlgNLoptSimplex = detail::NLoptAlg; + +// Helper defs for pre-crafted global and local optimizers that work well. +using DefaultGlobalOptimizer = Optimizer; +using DefaultLocalOptimizer = Optimizer; + +}} // namespace Slic3r::opt + +#endif // NLOPTOPTIMIZER_HPP diff --git a/src/libslic3r/SLA/IndexedMesh.hpp b/src/libslic3r/SLA/IndexedMesh.hpp index b0970608e2..a72492b344 100644 --- a/src/libslic3r/SLA/IndexedMesh.hpp +++ b/src/libslic3r/SLA/IndexedMesh.hpp @@ -87,7 +87,7 @@ public: 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; } - inline bool is_hit() const { return !std::isinf(m_t); } + inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); } inline const Vec3d& normal() const { assert(is_valid()); diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 9590936231..daa01ef24d 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -156,6 +156,11 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const merged.merge(get_mesh(bs, steps)); } + for (auto &bs : m_diffbridges) { + if (ctl().stopcondition()) break; + merged.merge(get_mesh(bs, steps)); + } + for (auto &anch : m_anchors) { if (ctl().stopcondition()) break; merged.merge(get_mesh(anch, steps)); diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index aa8a4ea83b..f29263ca3f 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -177,6 +177,14 @@ struct Bridge: public SupportTreeNode { Vec3d get_dir() const { return (endp - startp).normalized(); } }; +struct DiffBridge: public Bridge { + double end_r; + + DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e) + : Bridge{p_s, p_e, r_s}, end_r{r_e} + {} +}; + // A wrapper struct around the pad struct Pad { TriangleMesh tmesh; @@ -210,14 +218,15 @@ struct Pad { // merged mesh. It can be retrieved using a dedicated method (pad()) class SupportTreeBuilder: public SupportTree { // For heads it is beneficial to use the same IDs as for the support points. - std::vector m_heads; - std::vector m_head_indices; - std::vector m_pillars; - std::vector m_junctions; - std::vector m_bridges; - std::vector m_crossbridges; - std::vector m_pedestals; - std::vector m_anchors; + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; + std::vector m_junctions; + std::vector m_bridges; + std::vector m_crossbridges; + std::vector m_diffbridges; + std::vector m_pedestals; + std::vector m_anchors; Pad m_pad; @@ -228,8 +237,8 @@ class SupportTreeBuilder: public SupportTree { mutable bool m_meshcache_valid = false; mutable double m_model_height = 0; // the full height of the model - template - const Bridge& _add_bridge(std::vector &br, Args&&... args) + template + const BridgeT& _add_bridge(std::vector &br, Args&&... args) { std::lock_guard lk(m_mutex); br.emplace_back(std::forward(args)...); @@ -331,17 +340,6 @@ public: return pillar.id; } - const Pillar& head_pillar(unsigned headid) const - { - std::lock_guard lk(m_mutex); - assert(headid < m_head_indices.size()); - - const Head& h = m_heads[m_head_indices[headid]]; - assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); - - return m_pillars[size_t(h.pillar_id)]; - } - template const Junction& add_junction(Args&&... args) { std::lock_guard lk(m_mutex); @@ -374,6 +372,11 @@ public: { return _add_bridge(m_crossbridges, std::forward(args)...); } + + template const DiffBridge& add_diffbridge(Args&&... args) + { + return _add_bridge(m_diffbridges, std::forward(args)...); + } Head &head(unsigned id) { diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 7f6c034ddd..7ed4108023 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1,18 +1,25 @@ #include #include -#include -#include +#include #include namespace Slic3r { namespace sla { -using libnest2d::opt::initvals; -using libnest2d::opt::bound; -using libnest2d::opt::StopCriteria; -using libnest2d::opt::GeneticOptimizer; -using libnest2d::opt::SubplexOptimizer; +using Slic3r::opt::initvals; +using Slic3r::opt::bounds; +using Slic3r::opt::StopCriteria; +using Slic3r::opt::Optimizer; +using Slic3r::opt::AlgNLoptSubplex; +using Slic3r::opt::AlgNLoptGenetic; + +StopCriteria get_criteria(const SupportTreeConfig &cfg) +{ + return StopCriteria{} + .rel_score_diff(cfg.optimizer_rel_score_diff) + .max_iterations(cfg.optimizer_max_iterations); +} template static Hit min_hit(const C &hits) @@ -37,7 +44,7 @@ SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder, { // Prepare the support points in Eigen/IGL format as well, we will use // it mostly in this form. - + long i = 0; for (const SupportPoint &sp : m_support_pts) { m_points.row(i)(X) = double(sp.pos(X)); @@ -51,7 +58,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, const SupportableMesh &sm) { if(sm.pts.empty()) return false; - + builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm; SupportTreeBuildsteps alg(builder, sm); @@ -72,46 +79,46 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, NUM_STEPS //... }; - + // Collect the algorithm steps into a nice sequence std::array, NUM_STEPS> program = { [] () { // Begin... // Potentially clear up the shared data (not needed for now) }, - + std::bind(&SupportTreeBuildsteps::filter, &alg), - + std::bind(&SupportTreeBuildsteps::add_pinheads, &alg), - + std::bind(&SupportTreeBuildsteps::classify, &alg), - + std::bind(&SupportTreeBuildsteps::routing_to_ground, &alg), - + std::bind(&SupportTreeBuildsteps::routing_to_model, &alg), - + std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg), - + std::bind(&SupportTreeBuildsteps::merge_result, &alg), - + [] () { // Done }, - + [] () { // Abort } }; - + Steps pc = BEGIN; - + if(sm.cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { BOOST_LOG_TRIVIAL(info) << "Skipping model-facing supports as requested."; }; } - + // Let's define a simple automaton that will run our program. auto progress = [&builder, &pc] () { static const std::array stepstr { @@ -126,7 +133,7 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, "Done", "Abort" }; - + static const std::array stepstate { 0, 10, @@ -139,9 +146,9 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, 100, 0 }; - + if(builder.ctl().stopcondition()) pc = ABORT; - + switch(pc) { case BEGIN: pc = FILTER; break; case FILTER: pc = PINHEADS; break; @@ -155,16 +162,16 @@ bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder, case ABORT: break; default: ; } - + builder.ctl().statuscb(stepstate[pc], stepstr[pc]); }; - + // Just here we run the computation... while(pc < DONE) { progress(); program[pc](); } - + return pc == ABORT; } @@ -177,48 +184,48 @@ IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( double sd) { static const size_t SAMPLES = 8; - + // Move away slightly from the touching point to avoid raycasting on the // inner surface of the mesh. - + auto& m = m_mesh; using HitResult = IndexedMesh::hit_result; - + // Hit results std::array hits; - + struct Rings { double rpin; double rback; Vec3d spin; Vec3d sback; PointRing ring; - + Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); } Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); } } rings {r_pin + sd, r_back + sd, s, s + width * dir, dir}; - + // We will shoot multiple rays from the head pinpoint in the direction // of the pinhead robe (side) surface. The result will be the smallest // hit distance. - - ccr::enumerate(hits.begin(), hits.end(), + + ccr::enumerate(hits.begin(), hits.end(), [&m, &rings, sd](HitResult &hit, size_t i) { - + // Point on the circle on the pin sphere Vec3d ps = rings.pinring(i); // This is the point on the circle on the back sphere Vec3d p = rings.backring(i); - + // Point ps is not on mesh but can be inside or // outside as well. This would cause many problems // with ray-casting. To detect the position we will // use the ray-casting result (which has an is_inside - // predicate). - + // predicate). + Vec3d n = (p - ps).normalized(); auto q = m.query_ray_hit(ps + sd * n, n); - + if (q.is_inside()) { // the hit is inside the model if (q.distance() > rings.rpin) { // If we are inside the model and the hit @@ -243,7 +250,7 @@ IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( } else hit = q; }); - + return min_hit(hits); } @@ -252,20 +259,20 @@ IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( { static const size_t SAMPLES = 8; PointRing ring{dir}; - + using Hit = IndexedMesh::hit_result; - + // Hit results std::array hits; - - ccr::enumerate(hits.begin(), hits.end(), + + ccr::enumerate(hits.begin(), hits.end(), [this, r, src, /*ins_check,*/ &ring, dir, sd] (Hit &hit, size_t i) { // Point on the circle on the pin sphere Vec3d p = ring.get(i, src, r + sd); - + auto hr = m_mesh.query_ray_hit(p + r * dir, dir); - + if(/*ins_check && */hr.is_inside()) { if(hr.distance() > 2 * r + sd) hit = Hit(0.0); else { @@ -274,7 +281,7 @@ IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( } } else hit = hr; }); - + return min_hit(hits); } @@ -288,61 +295,61 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, // shorter pillar is too short to start a new bridge but the taller // pillar could still be bridged with the shorter one. bool was_connected = false; - + Vec3d supper = pillar.startpoint(); Vec3d slower = nextpillar.startpoint(); Vec3d eupper = pillar.endpoint(); Vec3d elower = nextpillar.endpoint(); - + double zmin = m_builder.ground_level + m_cfg.base_height_mm; eupper(Z) = std::max(eupper(Z), zmin); elower(Z) = std::max(elower(Z), zmin); - + // The usable length of both pillars should be positive if(slower(Z) - elower(Z) < 0) return false; if(supper(Z) - eupper(Z) < 0) return false; - + double pillar_dist = distance(Vec2d{slower(X), slower(Y)}, Vec2d{supper(X), supper(Y)}); double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); - + if(pillar_dist < 2 * m_cfg.head_back_radius_mm || pillar_dist > m_cfg.max_pillar_link_distance_mm) return false; - + if(supper(Z) < slower(Z)) supper.swap(slower); if(eupper(Z) < elower(Z)) eupper.swap(elower); - + double startz = 0, endz = 0; - + startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z); endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z); - + if(slower(Z) - eupper(Z) < std::abs(zstep)) { // no space for even one cross - + // Get max available space startz = std::min(supper(Z), slower(Z) - zstep); endz = std::max(eupper(Z) + zstep, elower(Z)); - + // Align to center double available_dist = (startz - endz); double rounds = std::floor(available_dist / std::abs(zstep)); startz -= 0.5 * (available_dist - rounds * std::abs(zstep)); } - + auto pcm = m_cfg.pillar_connection_mode; bool docrosses = pcm == PillarConnectionMode::cross || (pcm == PillarConnectionMode::dynamic && pillar_dist > 2*m_cfg.base_radius_mm); - + // 'sj' means starting junction, 'ej' is the end junction of a bridge. // They will be swapped in every iteration thus the zig-zag pattern. // According to a config parameter, a second bridge may be added which // results in a cross connection between the pillars. Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep; - + // TODO: This is a workaround to not have a faulty last bridge while(ej(Z) >= eupper(Z) /*endz*/) { if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance) @@ -350,7 +357,7 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, m_builder.add_crossbridge(sj, ej, pillar.r); was_connected = true; } - + // double bridging: (crosses) if(docrosses) { Vec3d sjback(ej(X), ej(Y), sj(Z)); @@ -363,11 +370,11 @@ bool SupportTreeBuildsteps::interconnect(const Pillar &pillar, was_connected = true; } } - + sj.swap(ej); ej(Z) = sj(Z) + zstep; } - + return was_connected; } @@ -377,67 +384,67 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, auto nearpillar = [this, nearpillar_id]() -> const Pillar& { return m_builder.pillar(nearpillar_id); }; - - if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar) + + if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar) return false; - + Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar().startpoint(); Vec3d nearjp_l = nearpillar().endpoint(); - + double r = head.r_back_mm; double d2d = distance(to_2d(headjp), to_2d(nearjp_u)); double d3d = distance(headjp, nearjp_u); - + double hdiff = nearjp_u(Z) - headjp(Z); double slope = std::atan2(hdiff, d2d); - + Vec3d bridgestart = headjp; Vec3d bridgeend = nearjp_u; double max_len = r * m_cfg.max_bridge_length_mm / m_cfg.head_back_radius_mm; double max_slope = m_cfg.bridge_slope; double zdiff = 0.0; - + // check the default situation if feasible for a bridge if(d3d > max_len || slope > -max_slope) { // not feasible to connect the two head junctions. We have to search // for a suitable touch point. - + double Zdown = headjp(Z) + d2d * std::tan(-max_slope); Vec3d touchjp = bridgeend; touchjp(Z) = Zdown; double D = distance(headjp, touchjp); zdiff = Zdown - nearjp_u(Z); - + if(zdiff > 0) { Zdown -= zdiff; bridgestart(Z) -= zdiff; touchjp(Z) = Zdown; - + double t = bridge_mesh_distance(headjp, DOWN, r); - + // We can't insert a pillar under the source head to connect // with the nearby pillar's starting junction if(t < zdiff) return false; } - + if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len) bridgeend(Z) = Zdown; else return false; } - + // There will be a minimum distance from the ground where the // bridge is allowed to connect. This is an empiric value. double minz = m_builder.ground_level + 4 * head.r_back_mm; if(bridgeend(Z) < minz) return false; - + double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r); - + // Cannot insert the bridge. (further search might not worth the hassle) if(t < distance(bridgestart, bridgeend)) return false; - + std::lock_guard lk(m_bridge_mutex); - + if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) { // A partial pillar is needed under the starting head. if(zdiff > 0) { @@ -447,31 +454,59 @@ bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head, } else { m_builder.add_bridge(head.id, bridgeend); } - + m_builder.increment_bridges(nearpillar()); } else return false; - + return true; } -bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, +bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp, const Vec3d &sourcedir, double radius, long head_id) { - double sd = m_cfg.pillar_base_safety_distance_mm; + Vec3d jp = hjp, endp = jp, dir = sourcedir; long pillar_id = SupportTreeNode::ID_UNSET; - bool can_add_base = radius >= m_cfg.head_back_radius_mm; - double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; - double gndlvl = m_builder.ground_level; - if (!can_add_base) gndlvl -= m_mesh.ground_level_offset(); - Vec3d endp = {jp(X), jp(Y), gndlvl}; - double min_dist = sd + base_r + EPSILON; - bool normal_mode = true; - Vec3d dir = sourcedir; + bool can_add_base = false, non_head = false; + + double gndlvl = 0.; // The Z level where pedestals should be + double jp_gnd = 0.; // The lowest Z where a junction center can be + double gap_dist = 0.; // The gap distance between the model and the pad auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; }; + auto eval_limits = [this, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd] + (bool base_en = true) + { + can_add_base = base_en && radius >= m_cfg.head_back_radius_mm; + double base_r = can_add_base ? m_cfg.base_radius_mm : 0.; + gndlvl = m_builder.ground_level; + if (!can_add_base) gndlvl -= m_mesh.ground_level_offset(); + jp_gnd = gndlvl + (can_add_base ? 0. : m_cfg.head_back_radius_mm); + gap_dist = m_cfg.pillar_base_safety_distance_mm + base_r + EPSILON; + }; + + eval_limits(); + + // We are dealing with a mini pillar that's potentially too long + if (radius < m_cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius) + { + std::optional diffbr = + search_widening_path(jp, dir, radius, m_cfg.head_back_radius_mm); + + if (diffbr && diffbr->endp.z() > jp_gnd) { + auto &br = m_builder.add_diffbridge(diffbr.value()); + if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id; + endp = diffbr->endp; + radius = diffbr->end_r; + m_builder.add_junction(endp, radius); + non_head = true; + dir = diffbr->get_dir(); + eval_limits(); + } else return false; + } + if (m_cfg.object_elevation_mm < EPSILON) { // get a suitable direction for the corrector bridge. It is the @@ -479,101 +514,118 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp, // configured bridge slope. auto [polar, azimuth] = dir_to_spheric(dir); polar = PI - m_cfg.bridge_slope; - Vec3d dir = spheric_to_dir(polar, azimuth).normalized(); + Vec3d d = spheric_to_dir(polar, azimuth).normalized(); + double t = bridge_mesh_distance(endp, dir, radius); + double tmax = std::min(m_cfg.max_bridge_length_mm, t); + t = 0.; - // Check the distance of the endpoint and the closest point on model - // body. It should be greater than the min_dist which is - // the safety distance from the model. It includes the pad gap if in - // zero elevation mode. - // - // Try to move along the established bridge direction to dodge the - // forbidden region for the endpoint. - double t = -radius; - bool succ = true; - while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || - !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { + double zd = endp.z() - jp_gnd; + double tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + Vec3d nexp = endp; + double dlast = 0.; + while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) { t += radius; - endp = jp + t * dir; - normal_mode = false; + nexp = endp + t * d; + } - if (t > m_cfg.max_bridge_length_mm || endp(Z) < gndlvl) { - if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - succ = false; - break; + if (dlast < gap_dist && can_add_base) { + nexp = endp; + t = 0.; + can_add_base = false; + eval_limits(can_add_base); + + zd = endp.z() - jp_gnd; + tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope); + tmax = std::min(tmax, tmax2); + + while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist || + !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) { + t += radius; + nexp = endp + t * d; } } - if (!succ) { - if (can_add_base) { - can_add_base = false; - base_r = 0.; - gndlvl -= m_mesh.ground_level_offset(); - min_dist = sd + base_r + EPSILON; - endp = {jp(X), jp(Y), gndlvl + radius}; + // Could not find a path to avoid the pad gap + if (dlast < gap_dist) return false; - t = -radius; - while (std::sqrt(m_mesh.squared_distance(to_floor(endp))) < min_dist || - !std::isinf(bridge_mesh_distance(endp, DOWN, radius))) { - t += radius; - endp = jp + t * dir; - normal_mode = false; + if (t > 0.) { // Need to make additional bridge + const Bridge& br = m_builder.add_bridge(endp, nexp, radius); + if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id; - if (t > m_cfg.max_bridge_length_mm || endp(Z) < (gndlvl + radius)) { - if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - return false; - } - } - } else return false; + m_builder.add_junction(nexp, radius); + endp = nexp; + non_head = true; } } - double h = (jp - endp).norm(); + Vec3d gp = to_floor(endp); + double h = endp.z() - gp.z(); - // Check if the deduced route is sane and exit with error if not. - if (bridge_mesh_distance(jp, dir, radius) < h) { - if (head_id >= 0) m_builder.add_pillar(head_id, 0.); - return false; - } + pillar_id = head_id >= 0 && !non_head ? m_builder.add_pillar(head_id, h) : + m_builder.add_pillar(gp, h, radius); - // Straigh path down, no area to dodge - if (normal_mode) { - pillar_id = head_id >= 0 ? m_builder.add_pillar(head_id, h) : - m_builder.add_pillar(endp, h, radius); - - if (can_add_base) - add_pillar_base(pillar_id); - } else { - - // Insert the bridge to get around the forbidden area - Vec3d pgnd{endp.x(), endp.y(), gndlvl}; - pillar_id = m_builder.add_pillar(pgnd, endp.z() - gndlvl, radius); - - if (can_add_base) - add_pillar_base(pillar_id); - - m_builder.add_bridge(jp, endp, radius); - m_builder.add_junction(endp, radius); - - // Add a degenerated pillar and the bridge. - // The degenerate pillar will have zero length and it will - // prevent from queries of head_pillar() to have non-existing - // pillar when the head should have one. - if (head_id >= 0) - m_builder.add_pillar(head_id, 0.); - } + if (can_add_base) + add_pillar_base(pillar_id); if(pillar_id >= 0) // Save the pillar endpoint in the spatial index - m_pillar_index.guarded_insert(endp, unsigned(pillar_id)); + m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt, + unsigned(pillar_id)); return true; } +std::optional SupportTreeBuildsteps::search_widening_path( + const Vec3d &jp, const Vec3d &dir, double radius, double new_radius) +{ + double w = radius + 2 * m_cfg.head_back_radius_mm; + double stopval = w + jp.z() - m_builder.ground_level; + Optimizer solver(get_criteria(m_cfg).stop_score(stopval)); + + auto [polar, azimuth] = dir_to_spheric(dir); + + double fallback_ratio = radius / m_cfg.head_back_radius_mm; + + auto oresult = solver.to_max().optimize( + [this, jp, radius, new_radius](double plr, double azm, double t) { + auto d = spheric_to_dir(plr, azm).normalized(); + double ret = pinhead_mesh_intersect(jp, d, radius, new_radius, t) + .distance(); + double down = bridge_mesh_distance(jp + t * d, d, new_radius); + + if (ret > t && std::isinf(down)) + ret += jp.z() - m_builder.ground_level; + + return ret; + }, + initvals({polar, azimuth, w}), // start with what we have + bounds({ + {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit + {-PI, PI}, // azimuth can be a full search + {radius + m_cfg.head_back_radius_mm, + fallback_ratio * m_cfg.max_bridge_length_mm} + })); + + if (oresult.score >= stopval) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + double t = std::get<2>(oresult.optimum); + Vec3d endp = jp + t * spheric_to_dir(polar, azimuth); + + return DiffBridge(jp, endp, radius, m_cfg.head_back_radius_mm); + } + + return {}; +} + void SupportTreeBuildsteps::filter() { // Get the points that are too close to each other and keep only the // first one auto aliases = cluster(m_points, D_SP, 2); - + PtIndices filtered_indices; filtered_indices.reserve(aliases.size()); m_iheads.reserve(aliases.size()); @@ -582,49 +634,62 @@ void SupportTreeBuildsteps::filter() // Here we keep only the front point of the cluster. filtered_indices.emplace_back(a.front()); } - + // calculate the normals to the triangles for filtered points auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm, m_thr, filtered_indices); - + // Not all of the support points have to be a valid position for // support creation. The angle may be inappropriate or there may // not be enough space for the pinhead. Filtering is applied for // these reasons. - - ccr::SpinningMutex mutex; - auto addfn = [&mutex](PtIndices &container, unsigned val) { - std::lock_guard lk(mutex); - container.emplace_back(val); - }; - - auto filterfn = [this, &nmls, addfn](unsigned fidx, size_t i) { + + std::vector heads; heads.reserve(m_support_pts.size()); + for (const SupportPoint &sp : m_support_pts) { m_thr(); - + heads.emplace_back( + std::nan(""), + sp.head_front_radius, + 0., + m_cfg.head_penetration_mm, + Vec3d::Zero(), // dir + sp.pos.cast() // displacement + ); + } + + std::function filterfn; + filterfn = [this, &nmls, &heads, &filterfn](unsigned fidx, size_t i, double back_r) { + m_thr(); + auto n = nmls.row(Eigen::Index(i)); - + // for all normals we generate the spherical coordinates and // saturate the polar angle to 45 degrees from the bottom then // convert back to standard coordinates to get the new normal. // Then we just create a quaternion from the two normals // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - + auto [polar, azimuth] = dir_to_spheric(n); - + // skip if the tilt is not sane - if(polar < PI - m_cfg.normal_cutoff_angle) return; - + if (polar < PI - m_cfg.normal_cutoff_angle) return; + // We saturate the polar angle to 3pi/4 - polar = std::max(polar, 3*PI / 4); + polar = std::max(polar, PI - m_cfg.bridge_slope); // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); + double lmin = m_cfg.head_width_mm, lmax = lmin; + + if (back_r < m_cfg.head_back_radius_mm) { + lmin = 0., lmax = m_cfg.head_penetration_mm; + } + // The distance needed for a pinhead to not collide with model. - double w = m_cfg.head_width_mm + - m_cfg.head_back_radius_mm + - 2*m_cfg.head_front_radius_mm; + double w = lmin + 2 * back_r + 2 * m_cfg.head_front_radius_mm - + m_cfg.head_penetration_mm; double pin_r = double(m_support_pts[fidx].head_front_radius); @@ -632,113 +697,69 @@ void SupportTreeBuildsteps::filter() auto nn = spheric_to_dir(polar, azimuth).normalized(); // check available distance - IndexedMesh::hit_result t - = pinhead_mesh_intersect(hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); - - if(t.distance() <= w) { + IndexedMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r, + back_r, w); + if (t.distance() < w) { // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model // geometry and its very close to the default. - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = w; // space greater than w is enough - GeneticOptimizer solver(stc); - solver.seed(0); // we want deterministic behavior + // stc.stop_score = w; // space greater than w is enough + Optimizer solver(get_criteria(m_cfg)); + solver.seed(0); + //solver.seed(0); // we want deterministic behavior - auto oresult = solver.optimize_max( - [this, pin_r, w, hp](double plr, double azm) + auto oresult = solver.to_max().optimize( + [this, pin_r, back_r, hp](double plr, double azm, double l) { auto dir = spheric_to_dir(plr, azm).normalized(); double score = pinhead_mesh_intersect( - hp, dir, pin_r, m_cfg.head_back_radius_mm, w).distance(); + hp, dir, pin_r, back_r, l).distance(); return score; }, - initvals(polar, azimuth), // start with what we have - bound(3 * PI / 4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); + initvals({polar, azimuth, (lmin + lmax) / 2.}), // start with what we have + bounds({ + {PI - m_cfg.bridge_slope, PI}, // Must not exceed the tilt limit + {-PI, PI}, // azimuth can be a full search + {lmin, lmax} + })); if(oresult.score > w) { polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); nn = spheric_to_dir(polar, azimuth).normalized(); + lmin = std::get<2>(oresult.optimum); t = IndexedMesh::hit_result(oresult.score); } } - // save the verified and corrected normal - m_support_nmls.row(fidx) = nn; + if (t.distance() > w && hp(Z) + w * nn(Z) >= m_builder.ground_level) { + Head &h = heads[fidx]; + h.id = fidx; h.dir = nn; h.width_mm = lmin; h.r_back_mm = back_r; + } else if (back_r > m_cfg.head_fallback_radius_mm) { + filterfn(fidx, i, m_cfg.head_fallback_radius_mm); + } + }; - if (t.distance() > w) { - // Check distance from ground, we might have zero elevation. - if (hp(Z) + w * nn(Z) < m_builder.ground_level) { - addfn(m_iheadless, fidx); - } else { - // mark the point for needing a head. - addfn(m_iheads, fidx); - } - } else if (polar >= 3 * PI / 4) { - // Headless supports do not tilt like the headed ones - // so the normal should point almost to the ground. - addfn(m_iheadless, fidx); + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), + [this, &filterfn](unsigned fidx, size_t i) { + filterfn(fidx, i, m_cfg.head_back_radius_mm); + }); + + for (size_t i = 0; i < heads.size(); ++i) + if (heads[i].is_valid()) { + m_builder.add_head(i, heads[i]); + m_iheads.emplace_back(i); } - }; - - ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), filterfn); - m_thr(); } void SupportTreeBuildsteps::add_pinheads() { - for (unsigned i : m_iheads) { - m_thr(); - m_builder.add_head( - i, - m_cfg.head_back_radius_mm, - m_support_pts[i].head_front_radius, - m_cfg.head_width_mm, - m_cfg.head_penetration_mm, - m_support_nmls.row(i), // dir - m_support_pts[i].pos.cast() // displacement - ); - } - - for (unsigned i : m_iheadless) { - const auto R = double(m_support_pts[i].head_front_radius); - - // The support point position on the mesh - Vec3d sph = m_support_pts[i].pos.cast(); - - // Get an initial normal from the filtering step - Vec3d n = m_support_nmls.row(i); - - // First we need to determine the available space for a mini pinhead. - // The goal is the move away from the model a little bit to make the - // contact point small as possible and avoid pearcing the model body. - double back_r = m_cfg.head_fallback_radius_mm; - double max_w = 2 * R; - double pin_space = std::min(max_w, - pinhead_mesh_intersect(sph, n, R, back_r, - max_w, 0.) - .distance()); - - if (pin_space <= 0) continue; - - m_iheads.emplace_back(i); - m_builder.add_head(i, back_r, R, pin_space, - m_cfg.head_penetration_mm, n, sph); - } } void SupportTreeBuildsteps::classify() @@ -747,37 +768,37 @@ void SupportTreeBuildsteps::classify() PtIndices ground_head_indices; ground_head_indices.reserve(m_iheads.size()); m_iheads_onmodel.reserve(m_iheads.size()); - + // First we decide which heads reach the ground and can be full // pillars and which shall be connected to the model surface (or // search a suitable path around the surface that leads to the // ground -- TODO) for(unsigned i : m_iheads) { m_thr(); - - auto& head = m_builder.head(i); + + Head &head = m_builder.head(i); double r = head.r_back_mm; Vec3d headjp = head.junction_point(); - + // collision check auto hit = bridge_mesh_intersect(headjp, DOWN, r); - + if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); else if(m_cfg.ground_facing_only) head.invalidate(); else m_iheads_onmodel.emplace_back(i); - + m_head_to_ground_scans[i] = hit; } - + // We want to search for clusters of points that are far enough // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. - + auto pointfn = [this](unsigned i) { return m_builder.head(i).junction_point(); }; - + auto predicate = [this](const PointIndexEl &e1, const PointIndexEl &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); @@ -794,10 +815,10 @@ void SupportTreeBuildsteps::routing_to_ground() { ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); - + for (auto &cl : m_pillar_clusters) { m_thr(); - + // place all the centroid head positions into the index. We // will query for alternative pillar positions. If a sidehead // cannot connect to the cluster centroid, we have to search @@ -805,9 +826,9 @@ void SupportTreeBuildsteps::routing_to_ground() // elements in the cluster, the centroid is arbitrary and the // sidehead is allowed to connect to a nearby pillar to // increase structural stability. - + if (cl.empty()) continue; - + // get the current cluster centroid auto & thr = m_thr; const auto &points = m_points; @@ -821,11 +842,11 @@ void SupportTreeBuildsteps::routing_to_ground() assert(lcid >= 0); unsigned hid = cl[size_t(lcid)]; // Head ID - + cl_centroids.emplace_back(hid); - + Head &h = m_builder.head(hid); - + if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) { BOOST_LOG_TRIVIAL(warning) << "Pillar cannot be created for support point id: " << hid; @@ -833,34 +854,32 @@ void SupportTreeBuildsteps::routing_to_ground() continue; } } - + // now we will go through the clusters ones again and connect the // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; for (auto cl : m_pillar_clusters) { m_thr(); - + auto cidx = cl_centroids[ci++]; - - // TODO: don't consider the cluster centroid but calculate a - // central position where the pillar can be placed. this way - // the weight is distributed more effectively on the pillar. - - auto centerpillarID = m_builder.head_pillar(cidx).id; - - for (auto c : cl) { - m_thr(); - if (c == cidx) continue; - - auto &sidehead = m_builder.head(c); - - if (!connect_to_nearpillar(sidehead, centerpillarID) && - !search_pillar_and_connect(sidehead)) { - Vec3d pstart = sidehead.junction_point(); - // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; - // Could not find a pillar, create one - create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); + + auto q = m_pillar_index.query(m_builder.head(cidx).junction_point(), 1); + if (!q.empty()) { + long centerpillarID = q.front().second; + for (auto c : cl) { + m_thr(); + if (c == cidx) continue; + + auto &sidehead = m_builder.head(c); + + if (!connect_to_nearpillar(sidehead, centerpillarID) && + !search_pillar_and_connect(sidehead)) { + Vec3d pstart = sidehead.junction_point(); + // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; + // Could not find a pillar, create one + create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id); + } } } } @@ -876,9 +895,9 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r))) d += r; - + if(!std::isinf(tdown)) return false; - + Vec3d endp = hjp + d * dir; bool ret = false; @@ -886,38 +905,33 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir) m_builder.add_bridge(head.id, endp); m_builder.add_junction(endp, head.r_back_mm); } - + return ret; } bool SupportTreeBuildsteps::connect_to_ground(Head &head) { if (connect_to_ground(head, head.dir)) return true; - + // Optimize bridge direction: // Straight path failed so we will try to search for a suitable // direction out of the cavity. auto [polar, azimuth] = dir_to_spheric(head.dir); - - StopCriteria stc; - stc.max_iterations = m_cfg.optimizer_max_iterations; - stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; - stc.stop_score = 1e6; - GeneticOptimizer solver(stc); + + Optimizer solver(get_criteria(m_cfg).stop_score(1e6)); solver.seed(0); // we want deterministic behavior - + double r_back = head.r_back_mm; - Vec3d hjp = head.junction_point(); - auto oresult = solver.optimize_max( + Vec3d hjp = head.junction_point(); + auto oresult = solver.to_max().optimize( [this, hjp, r_back](double plr, double azm) { Vec3d n = spheric_to_dir(plr, azm).normalized(); return bridge_mesh_distance(hjp, n, r_back); }, - initvals(polar, azimuth), // let's start with what we have - bound(3*PI/4, PI), // Must not exceed the slope limit - bound(-PI, PI) // azimuth can be a full range search - ); - + initvals({polar, azimuth}), // let's start with what we have + bounds({ {PI - m_cfg.bridge_slope, PI}, {-PI, PI} }) + ); + Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized(); return connect_to_ground(head, bridgedir); } @@ -925,10 +939,10 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) bool SupportTreeBuildsteps::connect_to_model_body(Head &head) { if (head.id <= SupportTreeNode::ID_UNSET) return false; - + auto it = m_head_to_ground_scans.find(unsigned(head.id)); if (it == m_head_to_ground_scans.end()) return false; - + auto &hit = it->second; if (!hit.is_hit()) { @@ -943,9 +957,11 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) // The width of the tail head that we would like to have... h = std::min(hit.distance() - head.r_back_mm, h); - - if(h <= 0.) return false; - + + // If this is a mini pillar dont bother with the tail width, can be 0. + if (head.r_back_mm < m_cfg.head_back_radius_mm) h = std::max(h, 0.); + else if (h <= 0.) return false; + Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h}; auto center_hit = m_mesh.query_ray_hit(hjp, DOWN); @@ -969,7 +985,7 @@ bool SupportTreeBuildsteps::connect_to_model_body(Head &head) m_cfg.head_penetration_mm, taildir, hitp); m_pillar_index.guarded_insert(pill.endpoint(), pill.id); - + return true; } @@ -1011,7 +1027,7 @@ bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source) } void SupportTreeBuildsteps::routing_to_model() -{ +{ // We need to check if there is an easy way out to the bed surface. // If it can be routed there with a bridge shorter than // min_bridge_distance. @@ -1019,23 +1035,23 @@ void SupportTreeBuildsteps::routing_to_model() ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), [this] (const unsigned idx, size_t) { m_thr(); - + auto& head = m_builder.head(idx); - + // Search nearby pillar if (search_pillar_and_connect(head)) { return; } - + // Cannot connect to nearby pillar. We will try to search for // a route to the ground. if (connect_to_ground(head)) { return; } - + // No route to the ground, so connect to the model body as a last resort if (connect_to_model_body(head)) { return; } - + // We have failed to route this head. BOOST_LOG_TRIVIAL(warning) << "Failed to route model facing support point. ID: " << idx; - + head.invalidate(); }); } @@ -1045,19 +1061,19 @@ void SupportTreeBuildsteps::interconnect_pillars() // Now comes the algorithm that connects pillars with each other. // Ideally every pillar should be connected with at least one of its // neighbors if that neighbor is within max_pillar_link_distance - + // Pillars with height exceeding H1 will require at least one neighbor // to connect with. Height exceeding H2 require two neighbors. double H1 = m_cfg.max_solo_pillar_height_mm; double H2 = m_cfg.max_dual_pillar_height_mm; double d = m_cfg.max_pillar_link_distance_mm; - + //A connection between two pillars only counts if the height ratio is // bigger than 50% double min_height_ratio = 0.5; - + std::set pairs; - + // A function to connect one pillar with its neighbors. THe number of // neighbors is given in the configuration. This function if called // for every pillar in the pillar index. A pair of pillar will not @@ -1067,68 +1083,68 @@ void SupportTreeBuildsteps::interconnect_pillars() [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { Vec3d qp = el.first; // endpoint of the pillar - + const Pillar& pillar = m_builder.pillar(el.second); // actual pillar - + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; - + // connections are already enough for the pillar if(pillar.links >= neighbors) return; - + double max_d = d * pillar.r / m_cfg.head_back_radius_mm; // Query all remaining points within reach auto qres = m_pillar_index.query([qp, max_d](const PointIndexEl& e){ return distance(e.first, qp) < max_d; }); - + // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); }); - + for(auto& re : qres) { // process the queried neighbors - + if(re.second == el.second) continue; // Skip self - + auto a = el.second, b = re.second; - + // Get unique hash for the given pair (order doesn't matter) auto hashval = pairhash(a, b); - + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; - + const Pillar& neighborpillar = m_builder.pillar(re.second); - + // this neighbor is occupied, skip if (neighborpillar.links >= neighbors) continue; if (neighborpillar.r < pillar.r) continue; - + if(interconnect(pillar, neighborpillar)) { pairs.insert(hashval); - + // If the interconnection length between the two pillars is // less than 50% of the longer pillar's height, don't count if(pillar.height < H1 || neighborpillar.height / pillar.height > min_height_ratio) m_builder.increment_links(pillar); - + if(neighborpillar.height < H1 || pillar.height / neighborpillar.height > min_height_ratio) m_builder.increment_links(neighborpillar); - + } - + // connections are enough for one pillar if(pillar.links >= neighbors) break; } }; - + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - + // We would be done here if we could allow some pillars to not be // connected with any neighbors. But this might leave the support tree // unprintable. @@ -1136,16 +1152,16 @@ void SupportTreeBuildsteps::interconnect_pillars() // The current solution is to insert additional pillars next to these // lonely pillars. One or even two additional pillar might get inserted // depending on the length of the lonely pillar. - + size_t pillarcount = m_builder.pillarcount(); - + // Again, go through all pillars, this time in the whole support tree // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_builder.pillar(pid); }; - + // Decide how many additional pillars will be needed: - + unsigned needpillars = 0; if (pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; @@ -1156,28 +1172,28 @@ void SupportTreeBuildsteps::interconnect_pillars() // No neighbors could be found and the pillar is too long. needpillars = 1; } - + needpillars = std::max(pillar().links, needpillars) - pillar().links; if (needpillars == 0) continue; - + // Search for new pillar locations: - + bool found = false; double alpha = 0; // goes to 2Pi double r = 2 * m_cfg.base_radius_mm; Vec3d pillarsp = pillar().startpoint(); - + // temp value for starting point detection Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); - + // A vector of bool for placement feasbility std::vector canplace(needpillars, false); std::vector spts(needpillars); // vector of starting points - + double gnd = m_builder.ground_level; double min_dist = m_cfg.pillar_base_safety_distance_mm + m_cfg.base_radius_mm + EPSILON; - + while(!found && alpha < 2*PI) { for (unsigned n = 0; n < needpillars && (!n || canplace[n - 1]); @@ -1188,25 +1204,25 @@ void SupportTreeBuildsteps::interconnect_pillars() s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; - + // Check the path vertically down Vec3d check_from = s + Vec3d{0., 0., pillar().r}; auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r); Vec3d gndsp{s(X), s(Y), gnd}; - + // If the path is clear, check for pillar base collisions canplace[n] = std::isinf(hr.distance()) && std::sqrt(m_mesh.squared_distance(gndsp)) > min_dist; } - + found = std::all_of(canplace.begin(), canplace.end(), [](bool v) { return v; }); - + // 20 angles will be tried... alpha += 0.1 * PI; } - + std::vector newpills; newpills.reserve(needpillars); @@ -1247,7 +1263,7 @@ void SupportTreeBuildsteps::interconnect_pillars() m_builder.increment_links(nxpll); } } - + m_pillar_index.foreach(cascadefn); } } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp index d19194a87d..013666f074 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.hpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.hpp @@ -45,6 +45,11 @@ inline Vec3d spheric_to_dir(const std::pair &v) return spheric_to_dir(v.first, v.second); } +inline Vec3d spheric_to_dir(const std::array &v) +{ + return spheric_to_dir(v[0], v[1]); +} + // Give points on a 3D ring with given center, radius and orientation // method based on: // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space @@ -249,7 +254,8 @@ class SupportTreeBuildsteps { double width) { return pinhead_mesh_intersect(s, dir, r_pin, r_back, width, - m_cfg.safety_distance_mm); + r_back * m_cfg.safety_distance_mm / + m_cfg.head_back_radius_mm); } // Checking bridge (pillar and stick as well) intersection with the model. @@ -271,7 +277,9 @@ class SupportTreeBuildsteps { const Vec3d& dir, double r) { - return bridge_mesh_intersect(s, dir, r, m_cfg.safety_distance_mm); + return bridge_mesh_intersect(s, dir, r, + r * m_cfg.safety_distance_mm / + m_cfg.head_back_radius_mm); } template @@ -311,6 +319,11 @@ class SupportTreeBuildsteps { m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm); } + std::optional search_widening_path(const Vec3d &jp, + const Vec3d &dir, + double radius, + double new_radius); + public: SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp index a086680c34..63182745da 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.hpp +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -94,6 +94,24 @@ inline Contour3D get_mesh(const Bridge &br, size_t steps) return mesh; } +inline Contour3D get_mesh(const DiffBridge &br, size_t steps) +{ + double h = br.get_length(); + Contour3D mesh = halfcone(h, br.r, br.end_r, Vec3d::Zero(), steps); + + using Quaternion = Eigen::Quaternion; + + // We rotate the head to the specified direction. The head's pointing + // side is facing upwards so this means that it would hold a support + // point with a normal pointing straight down. This is the reason of + // the -1 z coordinate + auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, 1}, br.get_dir()); + + for(auto& p : mesh.points) p = quatern * p + br.startp; + + return mesh; +} + }} // namespace Slic3r::sla #endif // SUPPORTTREEMESHER_HPP diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 9a9c762e3d..dad2b90971 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -4,6 +4,8 @@ #include "sla_test_utils.hpp" +#include + namespace { const char *const BELOW_PAD_TEST_OBJECTS[] = { @@ -228,3 +230,12 @@ TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]") cntr.from_obj(infile); } } + +TEST_CASE("halfcone test", "[halfcone]") { + sla::DiffBridge br{Vec3d{1., 1., 1.}, Vec3d{10., 10., 10.}, 0.25, 0.5}; + + TriangleMesh m = sla::to_triangle_mesh(sla::get_mesh(br, 45)); + + m.require_shared_vertices(); + m.WriteOBJFile("Halfcone.obj"); +} From f3c0bf46d4573bc8592a8dab70695eb1fe8aaef1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 17 Jul 2020 14:09:28 +0200 Subject: [PATCH 249/826] finish optimizer interface and remove commented code --- src/libslic3r/Optimizer.hpp | 73 ++++++++++++--------- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 23 +++---- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimizer.hpp index dc70abe332..6495ae7ff4 100644 --- a/src/libslic3r/Optimizer.hpp +++ b/src/libslic3r/Optimizer.hpp @@ -103,6 +103,16 @@ public: bool stop_condition() { return m_stop_condition(); } }; +// Helper class to use optimization methods involving gradient. +template struct ScoreGradient { + double score; + std::optional> gradient; + + ScoreGradient(double s, const std::array &grad) + : score{s}, gradient{grad} + {} +}; + // Helper to be used in static_assert. template struct always_false { enum { value = false }; }; @@ -112,13 +122,13 @@ public: Optimizer(const StopCriteria &) { - static_assert(always_false::value, - "Optimizer unimplemented for given method!"); + static_assert (always_false::value, + "Optimizer unimplemented for given method!"); } - Optimizer &to_min() { return *this; } - Optimizer &to_max() { return *this; } - Optimizer &set_criteria(const StopCriteria &) { return *this; } + Optimizer &to_min() { return *this; } + Optimizer &to_max() { return *this; } + Optimizer &set_criteria(const StopCriteria &) { return *this; } StopCriteria get_criteria() const { return {}; }; template @@ -156,35 +166,20 @@ struct IsNLoptAlg> { template using NLoptOnly = std::enable_if_t::value, T>; -// Convert any collection to tuple. This is useful for object functions taking -// an argument list of doubles. Make things cleaner on the call site of -// optimize(). -template struct to_tuple_ { - static auto call(const C &c) - { - return std::tuple_cat(std::tuple(c[N-I]), - to_tuple_::call(c)); - } -}; - -template struct to_tuple_<0, N, T, C> { - static auto call(const C &c) { return std::tuple<>(); } -}; - -// C array to tuple -template auto carray_tuple(const T *v) -{ - return to_tuple_::call(v); -} - -// Helper to convert C style array to std::array -template auto to_arr(const T (&a) [N]) +// Helper to convert C style array to std::array. The copy should be optimized +// away with modern compilers. +template auto to_arr(const T *a) { std::array r; - std::copy(std::begin(a), std::end(a), std::begin(r)); + std::copy(a, a + N, std::begin(r)); return r; } +template auto to_arr(const T (&a) [N]) +{ + return to_arr(static_cast(a)); +} + enum class OptDir { MIN, MAX }; // Where to optimize struct NLopt { // Helper RAII class for nlopt_opt @@ -227,9 +222,19 @@ protected: nlopt_force_stop(std::get<2>(*tdata)); auto fnptr = std::get<0>(*tdata); - auto funval = carray_tuple(params); + auto funval = to_arr(params); - return std::apply(*fnptr, funval); + double scoreval = 0.; + using RetT = decltype((*fnptr)(funval)); + if constexpr (std::is_convertible_v>) { + ScoreGradient score = (*fnptr)(funval); + for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i]; + scoreval = score.score; + } else { + scoreval = (*fnptr)(funval); + } + + return scoreval; } template @@ -354,12 +359,18 @@ public: template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } +template auto score_gradient(double s, const double (&grad)[N]) +{ + return ScoreGradient(s, detail::to_arr(grad)); +} // Predefinded NLopt algorithms that are used in the codebase using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; +// TODO: define others if needed... + // Helper defs for pre-crafted global and local optimizers that work well. using DefaultGlobalOptimizer = Optimizer; using DefaultLocalOptimizer = Optimizer; diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 7ed4108023..2b40f00828 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -496,7 +496,7 @@ bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp, search_widening_path(jp, dir, radius, m_cfg.head_back_radius_mm); if (diffbr && diffbr->endp.z() > jp_gnd) { - auto &br = m_builder.add_diffbridge(diffbr.value()); + auto &br = m_builder.add_diffbridge(*diffbr); if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id; endp = diffbr->endp; radius = diffbr->end_r; @@ -589,7 +589,9 @@ std::optional SupportTreeBuildsteps::search_widening_path( double fallback_ratio = radius / m_cfg.head_back_radius_mm; auto oresult = solver.to_max().optimize( - [this, jp, radius, new_radius](double plr, double azm, double t) { + [this, jp, radius, new_radius](const opt::Input<3> &input) { + auto &[plr, azm, t] = input; + auto d = spheric_to_dir(plr, azm).normalized(); double ret = pinhead_mesh_intersect(jp, d, radius, new_radius, t) .distance(); @@ -705,24 +707,22 @@ void SupportTreeBuildsteps::filter() // viable normal that doesn't collide with the model // geometry and its very close to the default. - // stc.stop_score = w; // space greater than w is enough Optimizer solver(get_criteria(m_cfg)); - solver.seed(0); - //solver.seed(0); // we want deterministic behavior + solver.seed(0); // we want deterministic behavior auto oresult = solver.to_max().optimize( - [this, pin_r, back_r, hp](double plr, double azm, double l) + [this, pin_r, back_r, hp](const opt::Input<3> &input) { + auto &[plr, azm, l] = input; + auto dir = spheric_to_dir(plr, azm).normalized(); - double score = pinhead_mesh_intersect( + return pinhead_mesh_intersect( hp, dir, pin_r, back_r, l).distance(); - - return score; }, initvals({polar, azimuth, (lmin + lmax) / 2.}), // start with what we have bounds({ - {PI - m_cfg.bridge_slope, PI}, // Must not exceed the tilt limit + {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit {-PI, PI}, // azimuth can be a full search {lmin, lmax} })); @@ -924,7 +924,8 @@ bool SupportTreeBuildsteps::connect_to_ground(Head &head) double r_back = head.r_back_mm; Vec3d hjp = head.junction_point(); auto oresult = solver.to_max().optimize( - [this, hjp, r_back](double plr, double azm) { + [this, hjp, r_back](const opt::Input<2> &input) { + auto &[plr, azm] = input; Vec3d n = spheric_to_dir(plr, azm).normalized(); return bridge_mesh_distance(hjp, n, r_back); }, From 0614d6d4a8ce959085488b8f6690c48f13442c99 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 3 Aug 2020 19:07:30 +0200 Subject: [PATCH 250/826] Remove leftover junk comments --- src/libslic3r/SLAPrintSteps.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index defc5246cc..76bbf498d0 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -374,9 +374,6 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (is_zero_elevation(po.config())) { -// double discard = pcfg.embed_object.object_gap_mm / -// std::cos(po.m_supportdata->cfg.bridge_slope) ; - remove_bottom_points(po.m_supportdata->pts, float(po.m_supportdata->emesh.ground_level() + EPSILON)); } From 510e787bc7d6c5ceb27340e18457b7a4428fb095 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 4 Aug 2020 10:26:08 +0200 Subject: [PATCH 251/826] Fixed Print.xsp --- xs/xsp/Print.xsp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 0952513ca3..e4957c042f 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -76,10 +76,10 @@ _constant() %code%{ RETVAL = const_cast(&THIS->skirt()); %}; Ref brim() %code%{ RETVAL = const_cast(&THIS->brim()); %}; - std::string estimated_normal_print_time() - %code%{ RETVAL = THIS->print_statistics().estimated_normal_print_time; %}; - std::string estimated_silent_print_time() - %code%{ RETVAL = THIS->print_statistics().estimated_silent_print_time; %}; +// std::string estimated_normal_print_time() +// %code%{ RETVAL = THIS->print_statistics().estimated_normal_print_time; %}; +// std::string estimated_silent_print_time() +// %code%{ RETVAL = THIS->print_statistics().estimated_silent_print_time; %}; double total_used_filament() %code%{ RETVAL = THIS->print_statistics().total_used_filament; %}; double total_extruded_volume() From 8fc5be7e4f10bc397931269beecf1e5ab1fc8fef Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 5 Aug 2020 15:43:46 +0200 Subject: [PATCH 252/826] Refactoring to allow to quickly build the various options to show the estimated printing time in gcode viewer scene --- src/libslic3r/GCode.cpp | 76 +++++++++++++++------------- src/libslic3r/GCode.hpp | 6 ++- src/libslic3r/GCodeTimeEstimator.cpp | 23 ++------- src/libslic3r/GCodeTimeEstimator.hpp | 10 ++-- src/libslic3r/Print.cpp | 14 +---- src/libslic3r/Print.hpp | 22 ++------ src/libslic3r/Technologies.hpp | 8 ++- src/slic3r/GUI/GCodeViewer.cpp | 42 +++++++++------ src/slic3r/GUI/GCodeViewer.hpp | 10 +++- src/slic3r/GUI/GLCanvas3D.cpp | 11 ++-- src/slic3r/GUI/GLCanvas3D.hpp | 2 + src/slic3r/GUI/GUI_Preview.cpp | 10 ++-- src/slic3r/GUI/GUI_Preview.hpp | 2 + src/slic3r/GUI/KBShortcutsDialog.cpp | 10 ++-- src/slic3r/GUI/Plater.cpp | 53 ++----------------- 15 files changed, 125 insertions(+), 174 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d7db315a7d..915694d12f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -779,8 +779,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ m_processor.process_file(path_tmp); if (result != nullptr) *result = std::move(m_processor.extract_result()); -#endif // ENABLE_GCODE_VIEWER - +#else GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data(); @@ -789,21 +788,19 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ BOOST_LOG_TRIVIAL(debug) << "Time estimator post processing" << log_memory_info(); GCodeTimeEstimator::post_process(path_tmp, 60.0f, remaining_times_enabled ? &normal_data : nullptr, (remaining_times_enabled && m_silent_time_estimator_enabled) ? &silent_data : nullptr); - if (remaining_times_enabled) - { + if (remaining_times_enabled) { m_normal_time_estimator.reset(); if (m_silent_time_estimator_enabled) m_silent_time_estimator.reset(); } -#if !ENABLE_GCODE_VIEWER // starts analyzer calculations if (m_enable_analyzer) { BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data" << log_memory_info(); m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); m_analyzer.reset(); } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER if (rename_file(path_tmp, path)) throw std::runtime_error( @@ -820,7 +817,8 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ // free functions called by GCode::_do_export() namespace DoExport { - static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled) +#if !ENABLE_GCODE_VIEWER + static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled) { // resets time estimators normal_time_estimator.reset(); @@ -892,10 +890,12 @@ namespace DoExport { normal_time_estimator.set_filament_unload_times(config.filament_unload_time.values); } } +#endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool silent_time_estimator_enabled) + static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) { + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode; processor.reset(); processor.apply_config(config); processor.enable_stealth_time_estimator(silent_time_estimator_enabled); @@ -1049,10 +1049,12 @@ namespace DoExport { // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. static std::string update_print_stats_and_format_filament_stats( - const GCodeTimeEstimator &normal_time_estimator, +#if !ENABLE_GCODE_VIEWER + const GCodeTimeEstimator &normal_time_estimator, const GCodeTimeEstimator &silent_time_estimator, const bool silent_time_estimator_enabled, - const bool has_wipe_tower, +#endif // !ENABLE_GCODE_VIEWER + const bool has_wipe_tower, const WipeTowerData &wipe_tower_data, const std::vector &extruders, PrintStatistics &print_statistics) @@ -1060,21 +1062,13 @@ namespace DoExport { std::string filament_stats_string_out; print_statistics.clear(); -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - print_statistics.estimated_normal_print_time_str = normal_time_estimator.get_time_dhm/*s*/(); - print_statistics.estimated_silent_print_time_str = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; - print_statistics.estimated_normal_custom_gcode_print_times_str = normal_time_estimator.get_custom_gcode_times_dhm(true); - if (silent_time_estimator_enabled) - print_statistics.estimated_silent_custom_gcode_print_times_str = silent_time_estimator.get_custom_gcode_times_dhm(true); -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else +#if !ENABLE_GCODE_VIEWER print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhm/*s*/(); print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhm/*s*/() : "N/A"; print_statistics.estimated_normal_custom_gcode_print_times = normal_time_estimator.get_custom_gcode_times_dhm(true); if (silent_time_estimator_enabled) print_statistics.estimated_silent_custom_gcode_print_times = silent_time_estimator.get_custom_gcode_times_dhm(true); -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); if (! extruders.empty()) { std::pair out_filament_used_mm ("; filament used [mm] = ", 0); @@ -1165,12 +1159,13 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu { PROFILE_FUNC(); - DoExport::init_time_estimators(print.config(), - // modifies the following: - m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled); #if ENABLE_GCODE_VIEWER + // modifies m_silent_time_estimator_enabled DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled); #else + DoExport::init_time_estimators(print.config(), + // modifies the following: + m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled); DoExport::init_gcode_analyzer(print.config(), m_analyzer); #endif // ENABLE_GCODE_VIEWER @@ -1274,12 +1269,13 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu print.throw_if_canceled(); // adds tags for time estimators - if (print.config().remaining_times.value) - { +#if !ENABLE_GCODE_VIEWER + if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag); if (m_silent_time_estimator_enabled) _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); } +#endif // !ENABLE_GCODE_VIEWER // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); @@ -1574,25 +1570,31 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write(file, m_writer.postamble()); // adds tags for time estimators +#if !ENABLE_GCODE_VIEWER if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag); if (m_silent_time_estimator_enabled) _writeln(file, GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag); } +#endif // !ENABLE_GCODE_VIEWER print.throw_if_canceled(); // calculates estimated printing time +#if !ENABLE_GCODE_VIEWER m_normal_time_estimator.calculate_time(false); if (m_silent_time_estimator_enabled) m_silent_time_estimator.calculate_time(false); +#endif // !ENABLE_GCODE_VIEWER // Get filament stats. _write(file, DoExport::update_print_stats_and_format_filament_stats( // Const inputs - m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled, - has_wipe_tower, print.wipe_tower_data(), +#if !ENABLE_GCODE_VIEWER + m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled, +#endif // !ENABLE_GCODE_VIEWER + has_wipe_tower, print.wipe_tower_data(), m_writer.extruders(), // Modifies print.m_print_statistics)); @@ -1601,9 +1603,11 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost); if (print.m_print_statistics.total_toolchanges > 0) _write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); +#if !ENABLE_GCODE_VIEWER _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); if (m_silent_time_estimator_enabled) _write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str()); +#endif // !ENABLE_GCODE_VIEWER // Append full config. _write(file, "\n"); @@ -1879,9 +1883,9 @@ namespace ProcessLayer #else // add tag for analyzer gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n"; -#endif // ENABLE_GCODE_VIEWER // add tag for time estimator - gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; + gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; +#endif // ENABLE_GCODE_VIEWER if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer // && !MMU1 @@ -1910,10 +1914,12 @@ namespace ProcessLayer //! FIXME_in_fw show message during print pause if (!pause_print_msg.empty()) gcode += "M117 " + pause_print_msg + "\n"; - // add tag for time estimator +#if !ENABLE_GCODE_VIEWER + // add tag for time estimator gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n"; gcode += config.pause_print_gcode; - } +#endif // !ENABLE_GCODE_VIEWER + } else { #if ENABLE_GCODE_VIEWER @@ -2422,14 +2428,14 @@ void GCode::process_layer( #endif /* HAS_PRESSURE_EQUALIZER */ _write(file, gcode); - BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << +#if !ENABLE_GCODE_VIEWER + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << ", time estimator memory: " << format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << -#if !ENABLE_GCODE_VIEWER ", analyzer memory: " << format_memsize_MB(m_analyzer.memory_used()) << -#endif // !ENABLE_GCODE_VIEWER log_memory_info(); +#endif // !ENABLE_GCODE_VIEWER } void GCode::apply_print_config(const PrintConfig &print_config) @@ -3078,10 +3084,12 @@ void GCode::_write(FILE* file, const char *what) // writes string to file fwrite(gcode, 1, ::strlen(gcode), file); +#if !ENABLE_GCODE_VIEWER // updates time estimator and gcode lines vector m_normal_time_estimator.add_gcode_block(gcode); if (m_silent_time_estimator_enabled) m_silent_time_estimator.add_gcode_block(gcode); +#endif // !ENABLE_GCODE_VIEWER } } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 69f98bfa52..6f97d5dbd3 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -17,8 +17,8 @@ #include "GCode/GCodeProcessor.hpp" #else #include "GCode/Analyzer.hpp" -#endif // ENABLE_GCODE_VIEWER #include "GCodeTimeEstimator.hpp" +#endif // ENABLE_GCODE_VIEWER #include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" @@ -179,8 +179,10 @@ public: #endif // !ENABLE_GCODE_VIEWER m_brim_done(false), m_second_layer_things_done(false), +#if !ENABLE_GCODE_VIEWER m_normal_time_estimator(GCodeTimeEstimator::Normal), m_silent_time_estimator(GCodeTimeEstimator::Silent), +#endif // !ENABLE_GCODE_VIEWER m_silent_time_estimator_enabled(false), m_last_obj_copy(nullptr, Point(std::numeric_limits::max(), std::numeric_limits::max())) {} @@ -405,9 +407,11 @@ private: // Index of a last object copy extruded. std::pair m_last_obj_copy; +#if !ENABLE_GCODE_VIEWER // Time estimators GCodeTimeEstimator m_normal_time_estimator; GCodeTimeEstimator m_silent_time_estimator; +#endif // !ENABLE_GCODE_VIEWER bool m_silent_time_estimator_enabled; #if ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 4bfe322ce2..aa9ee2f643 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -9,6 +9,8 @@ #include #include +#if !ENABLE_GCODE_VIEWER + static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float MILLISEC_TO_SEC = 0.001f; static const float INCHES_TO_MM = 25.4f; @@ -722,24 +724,6 @@ namespace Slic3r { return ret; } -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - std::vector>> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const - { - std::vector>> ret; - - float total_time = 0.0f; - for (const auto& [type, time] : m_custom_gcode_times) { - std::string duration = _get_time_dhm(time); - std::string remaining = include_remaining ? _get_time_dhm(m_time - total_time) : ""; - ret.push_back({ type, { duration, remaining } }); - total_time += time; - } - - return ret; - } -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else std::vector> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const { std::vector> ret; @@ -760,7 +744,6 @@ namespace Slic3r { return ret; } -#endif // ENABLE_GCODE_VIEWER // Return an estimate of the memory consumed by the time estimator. size_t GCodeTimeEstimator::memory_used() const @@ -1690,3 +1673,5 @@ namespace Slic3r { } #endif // ENABLE_MOVE_STATS } + +#endif // !ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 3b92f02692..0dd3407cb0 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -6,6 +6,8 @@ #include "GCodeReader.hpp" #include "CustomGCode.hpp" +#if !ENABLE_GCODE_VIEWER + #define ENABLE_MOVE_STATS 0 namespace Slic3r { @@ -370,13 +372,7 @@ namespace Slic3r { // Returns the estimated time, in format DDd HHh MMm, for each custom_gcode // If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)" -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - std::vector>> get_custom_gcode_times_dhm(bool include_remaining) const; -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else std::vector> get_custom_gcode_times_dhm(bool include_remaining) const; -#endif // ENABLE_GCODE_VIEWER // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; @@ -487,4 +483,6 @@ namespace Slic3r { } /* namespace Slic3r */ +#endif // !ENABLE_GCODE_VIEWER + #endif /* slic3r_GCodeTimeEstimator_hpp_ */ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a48d6f602f..1b1aad2578 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2190,23 +2190,13 @@ std::string Print::output_filename(const std::string &filename_base) const DynamicConfig PrintStatistics::config() const { DynamicConfig config; -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - config.set_key_value("print_time", new ConfigOptionString(this->estimated_normal_print_time_str)); - config.set_key_value("normal_print_time", new ConfigOptionString(this->estimated_normal_print_time_str)); - config.set_key_value("silent_print_time", new ConfigOptionString(this->estimated_silent_print_time_str)); -#else - config.set_key_value("print_time", new ConfigOptionString(short_time(get_time_dhms(this->estimated_normal_print_time)))); - config.set_key_value("normal_print_time", new ConfigOptionString(short_time(get_time_dhms(this->estimated_normal_print_time)))); - config.set_key_value("silent_print_time", new ConfigOptionString(short_time(get_time_dhms(this->estimated_silent_print_time)))); -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else +#if !ENABLE_GCODE_VIEWER std::string normal_print_time = short_time(this->estimated_normal_print_time); std::string silent_print_time = short_time(this->estimated_silent_print_time); config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.)); config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume)); config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost)); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 62c00aa88d..73efbf49a1 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -303,19 +303,12 @@ private: struct PrintStatistics { PrintStatistics() { clear(); } -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - std::string estimated_normal_print_time_str; - std::string estimated_silent_print_time_str; - std::vector>> estimated_normal_custom_gcode_print_times_str; - std::vector>> estimated_silent_custom_gcode_print_times_str; -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else +#if !ENABLE_GCODE_VIEWER std::string estimated_normal_print_time; std::string estimated_silent_print_time; std::vector> estimated_normal_custom_gcode_print_times; std::vector> estimated_silent_custom_gcode_print_times; -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER double total_used_filament; double total_extruded_volume; double total_cost; @@ -333,19 +326,12 @@ struct PrintStatistics std::string finalize_output_path(const std::string &path_in) const; void clear() { -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - estimated_normal_print_time_str.clear(); - estimated_silent_print_time_str.clear(); - estimated_normal_custom_gcode_print_times_str.clear(); - estimated_silent_custom_gcode_print_times_str.clear(); -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else +#if !ENABLE_GCODE_VIEWER estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); estimated_normal_custom_gcode_print_times.clear(); estimated_silent_custom_gcode_print_times.clear(); -#endif //ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER total_used_filament = 0.; total_extruded_volume = 0.; total_cost = 0.; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c14e2df52f..75849b689a 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,7 +58,11 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR (1 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG (1 && ENABLE_GCODE_VIEWER) + +#define TIME_ESTIMATE_NONE 0 +#define TIME_ESTIMATE_DEFAULT 1 +#define TIME_ESTIMATE_MODAL 2 +#define TIME_ESTIMATE_LEGEND 3 +#define GCODE_VIEWER_TIME_ESTIMATE TIME_ESTIMATE_MODAL #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e746635ee2..7c5252070d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -332,7 +332,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& wxGetApp().plater()->set_bed_shape(bed_shape, "", "", true); } +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE m_time_statistics = gcode_result.time_statistics; +#endif // GCODE_VIEWER_TIME_ESTIMATE } void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) @@ -406,7 +408,9 @@ void GCodeViewer::reset() m_layers_zs = std::vector(); m_layers_z_range = { 0.0, 0.0 }; m_roles = std::vector(); +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE m_time_statistics.reset(); +#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); @@ -419,7 +423,7 @@ void GCodeViewer::render() const m_statistics.reset_opengl(); #endif // ENABLE_GCODE_VIEWER_STATISTICS -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL if (m_roles.empty()) { m_time_estimate_frames_count = 0; return; @@ -427,7 +431,7 @@ void GCodeViewer::render() const #else if (m_roles.empty()) return; -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); @@ -435,7 +439,9 @@ void GCodeViewer::render() const m_sequential_view.marker.render(); render_shells(); render_legend(); +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE render_time_estimate(); +#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -474,9 +480,9 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); -#if !ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT flags = set_flag(flags, static_cast(Preview::OptionType::TimeEstimate), is_time_estimate_enabled()); -#endif // !ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE return flags; } @@ -496,9 +502,9 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); -#if !ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT enable_time_estimate(is_flag_set(static_cast(Preview::OptionType::TimeEstimate))); -#endif // !ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) @@ -510,6 +516,7 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range wxGetApp().plater()->update_preview_moves_slider(); } +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE void GCodeViewer::enable_time_estimate(bool enable) { m_time_estimate_enabled = enable; @@ -517,6 +524,7 @@ void GCodeViewer::enable_time_estimate(bool enable) wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } +#endif // GCODE_VIEWER_TIME_ESTIMATE void GCodeViewer::export_toolpaths_to_obj(const char* filename) const { @@ -1682,24 +1690,25 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE void GCodeViewer::render_time_estimate() const { if (!m_time_estimate_enabled) { -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL m_time_estimate_frames_count = 0; -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE return; } ImGuiWrapper& imgui = *wxGetApp().imgui(); -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL // esc if (ImGui::GetIO().KeysDown[27]) { m_time_estimate_enabled = false; return; } -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE using Times = std::pair; using TimesList = std::vector>; @@ -1833,7 +1842,7 @@ void GCodeViewer::render_time_estimate() const imgui.text(short_time(get_time_dhms(time))); ImGui::SameLine(offsets[1]); char buf[64]; - ::sprintf(buf, "%.2f%%", 100.0f * percentage); + ::sprintf(buf, "%.1f%%", 100.0f * percentage); ImGuiWindow* window = ImGui::GetCurrentWindow(); ImRect frame_bb; frame_bb.Min = { ImGui::GetCursorScreenPos().x, window->DC.CursorPos.y }; @@ -1978,7 +1987,7 @@ void GCodeViewer::render_time_estimate() const }; Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL std::string title = _u8L("Estimated printing time"); ImGui::OpenPopup(title.c_str()); @@ -1996,14 +2005,14 @@ void GCodeViewer::render_time_estimate() const } #else imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, 0.5f * static_cast(cnv_size.get_height() })); + ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, 0.5f * static_cast(cnv_size.get_height()) }); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); imgui.begin(std::string("Time_estimate"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); // title imgui.title(_u8L("Estimated printing time")); -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE // mode tabs ImGui::BeginTabBar("mode_tabs"); @@ -2029,7 +2038,7 @@ void GCodeViewer::render_time_estimate() const } ImGui::EndTabBar(); -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL // this is ugly, but it is the only way to ensure that the dialog is large // enough to show enterely the title // see: https://github.com/ocornut/imgui/issues/3239 @@ -2044,9 +2053,10 @@ void GCodeViewer::render_time_estimate() const } #else imgui.end(); -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE ImGui::PopStyleVar(); } +#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 0a32a97e67..06cd1326fb 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -341,13 +341,15 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE PrintEstimatedTimeStatistics m_time_statistics; -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL mutable bool m_time_estimate_enabled{ false }; mutable unsigned int m_time_estimate_frames_count{ 0 }; #else bool m_time_estimate_enabled{ false }; -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#endif // GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL +#endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE #if ENABLE_GCODE_VIEWER_STATISTICS mutable Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -403,8 +405,10 @@ public: bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE bool is_time_estimate_enabled() const { return m_time_estimate_enabled; } void enable_time_estimate(bool enable); +#endif // GCODE_VIEWER_TIME_ESTIMATE void export_toolpaths_to_obj(const char* filename) const; @@ -416,7 +420,9 @@ private: void render_toolpaths() const; void render_shells() const; void render_legend() const; +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE void render_time_estimate() const; +#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5a1381a779..35be455591 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3109,17 +3109,18 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) break; } #endif // ENABLE_RENDER_PICKING_PASS -#if ENABLE_GCODE_VIEWER +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT || GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL case 'T': - case 't': { + case 't': + { if (!m_main_toolbar.is_enabled()) { m_gcode_viewer.enable_time_estimate(!m_gcode_viewer.is_time_estimate_enabled()); m_dirty = true; wxGetApp().plater()->update_preview_bottom_toolbar(); - } + } break; - } -#endif // ENABLE_GCODE_VIEWER + } +#endif // GCODE_VIEWER_TIME_ESTIMATE case 'Z': #if ENABLE_GCODE_VIEWER case 'z': diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 15bb3e6f06..ee1d32abb8 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -558,7 +558,9 @@ public: void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); } +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE bool is_time_estimate_enabled() const { return m_gcode_viewer.is_time_estimate_enabled(); } +#endif // GCODE_VIEWER_TIME_ESTIMATE #endif // ENABLE_GCODE_VIEWER void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 98c02322ee..38657a460a 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -323,12 +323,12 @@ bool Preview::init(wxWindow* parent, Model* model) get_option_type_string(OptionType::CustomGCodes) + "|0|" + get_option_type_string(OptionType::Shells) + "|0|" + get_option_type_string(OptionType::ToolMarker) + "|0|" + -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG - get_option_type_string(OptionType::Legend) + "|1" -#else +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT get_option_type_string(OptionType::Legend) + "|1|" + get_option_type_string(OptionType::TimeEstimate) + "|1" -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG +#else + get_option_type_string(OptionType::Legend) + "|1" +#endif // GCODE_VIEWER_TIME_ESTIMATE ); Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); #else @@ -1459,7 +1459,9 @@ wxString Preview::get_option_type_string(OptionType type) const case OptionType::Shells: { return _L("Shells"); } case OptionType::ToolMarker: { return _L("Tool marker"); } case OptionType::Legend: { return _L("Legend"); } +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE case OptionType::TimeEstimate: { return _L("Estimated printing time"); } +#endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE default: { return ""; } } } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index ff3bf41371..c74dccc5c1 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -150,7 +150,9 @@ public: Shells, ToolMarker, Legend, +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE TimeEstimate +#endif // GCODE_VIEWER_TIME_ESTIMATE }; Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index fc6bc98914..2c65673cd0 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -204,13 +204,11 @@ void KBShortcutsDialog::fill_shortcuts() { "U", L("Upper Layer") }, { "D", L("Lower Layer") }, { "L", L("Show/Hide Legend") }, -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG - { "T", L("Show Estimated printing time") } -#else +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT { "T", L("Show/Hide Estimated printing time") } -#endif // ENABLE_GCODE_VIEWER_MODAL_TIME_ESTIMATE_DIALOG -#endif // ENABLE_GCODE_VIEWER +#elif GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL + { "T", L("Show Estimated printing time") } +#endif // GCODE_VIEWER_TIME_ESTIMATE }; m_full_shortcuts.push_back(std::make_pair(_L("Preview"), preview_shortcuts)); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e2c141e4be..a03d31bffb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1324,15 +1324,10 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siCost, info_text, new_label); #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - if (p->plater->get_current_canvas3D()->is_time_estimate_enabled() || (ps.estimated_normal_print_time_str == "N/A" && ps.estimated_silent_print_time_str == "N/A")) + // hide the estimate time + p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); #else - if (p->plater->get_current_canvas3D()->is_time_estimate_enabled() || (ps.estimated_normal_print_time <= 0.0f && ps.estimated_silent_print_time <= 0.0f)) -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else - if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") -#endif // ENABLE_GCODE_VIEWER p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); else { new_label = _L("Estimated printing time") + ":"; @@ -1340,15 +1335,7 @@ void Sidebar::update_sliced_info_sizer() wxString str_color = _L("Color"); wxString str_pause = _L("Pause"); -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - auto fill_labels = [str_color, str_pause](const std::vector>>& times, -#else - auto fill_labels = [str_color, str_pause](const std::vector>>& times, -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else auto fill_labels = [str_color, str_pause](const std::vector>& times, -#endif // ENABLE_GCODE_VIEWER wxString& new_label, wxString& info_text) { int color_change_count = 0; @@ -1366,41 +1353,10 @@ void Sidebar::update_sliced_info_sizer() if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint) new_label += format_wxstr(" -> %1%", str_pause); -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - info_text += format_wxstr("\n%1% (%2%)", times[i].second.first, times[i].second.second); -#else - info_text += format_wxstr("\n%1% (%2%)", short_time(get_time_dhms(times[i].second.first)), short_time(get_time_dhms(times[i].second.second))); -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else info_text += format_wxstr("\n%1%", times[i].second); -#endif // ENABLE_GCODE_VIEWER } }; -#if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR - if (ps.estimated_normal_print_time_str != "N/A") { - new_label += format_wxstr("\n - %1%", _L("normal mode")); - info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time_str); - fill_labels(ps.estimated_normal_custom_gcode_print_times_str, new_label, info_text); - } - if (ps.estimated_silent_print_time_str != "N/A") { - new_label += format_wxstr("\n - %1%", _L("stealth mode")); - info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time_str); - fill_labels(ps.estimated_silent_custom_gcode_print_times_str, new_label, info_text); -#else - if (ps.estimated_normal_print_time > 0.0f) { - new_label += format_wxstr("\n - %1%", _L("normal mode")); - info_text += format_wxstr("\n%1%", short_time(get_time_dhms(ps.estimated_normal_print_time))); - fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text); - } - if (ps.estimated_silent_print_time > 0.0f) { - new_label += format_wxstr("\n - %1%", _L("stealth mode")); - info_text += format_wxstr("\n%1%", short_time(get_time_dhms(ps.estimated_silent_print_time))); - fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text); -#endif // ENABLE_GCODE_VIEWER_USE_OLD_TIME_ESTIMATOR -#else if (ps.estimated_normal_print_time != "N/A") { new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time); @@ -1416,10 +1372,10 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr("\n - %1%", _L("stealth mode")); info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time); fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text); -#endif // ENABLE_GCODE_VIEWER } p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); } +#endif // !ENABLE_GCODE_VIEWER // if there is a wipe tower, insert number of toolchanges info into the array: p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A"); @@ -1428,9 +1384,8 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); } } -#if ENABLE_GCODE_VIEWER + Layout(); -#endif // ENABLE_GCODE_VIEWER } void Sidebar::show_sliced_info_sizer(const bool show) From 171acf094c3b3a7320ab665a041a4bf2ba5b62fb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 5 Aug 2020 16:34:01 +0200 Subject: [PATCH 253/826] Change license of libnest2d to LGPLv3 --- src/libnest2d/LICENSE.txt | 816 ++++++++------------------------------ 1 file changed, 160 insertions(+), 656 deletions(-) diff --git a/src/libnest2d/LICENSE.txt b/src/libnest2d/LICENSE.txt index dba13ed2dd..07b1d92c0e 100644 --- a/src/libnest2d/LICENSE.txt +++ b/src/libnest2d/LICENSE.txt @@ -1,661 +1,165 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file From 1674d2af291b1933fd28258e5415501079f23ce9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Aug 2020 19:25:04 +0200 Subject: [PATCH 254/826] UnsavedChangesDialog improvements: * support markup text and colored icons for cells + Extended BitmapTextRenderer for using of markup text --- src/slic3r/GUI/ObjectDataViewModel.cpp | 76 +++- src/slic3r/GUI/ObjectDataViewModel.hpp | 19 +- src/slic3r/GUI/Search.cpp | 8 + src/slic3r/GUI/Search.hpp | 10 +- src/slic3r/GUI/Tab.cpp | 12 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 533 ++++++++++++++++++++---- src/slic3r/GUI/UnsavedChangesDialog.hpp | 108 +++-- 7 files changed, 664 insertions(+), 102 deletions(-) diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 336475d2e0..badaa7a041 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -9,6 +9,12 @@ #include #include +#include "wx/generic/private/markuptext.h" +#include "wx/generic/private/rowheightcache.h" +#include "wx/generic/private/widthcalc.h" +#if wxUSE_ACCESSIBILITY +#include "wx/private/markupparser.h" +#endif // wxUSE_ACCESSIBILITY namespace Slic3r { @@ -1575,9 +1581,44 @@ wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) } #endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +BitmapTextRenderer::~BitmapTextRenderer() +{ +#ifdef SUPPORTS_MARKUP + if (m_markupText) + delete m_markupText; +#endif // SUPPORTS_MARKUP +} + +#ifdef SUPPORTS_MARKUP +void BitmapTextRenderer::EnableMarkup(bool enable) +{ + if (enable) + { + if (!m_markupText) + { + m_markupText = new wxItemMarkupText(wxString()); + } + } + else + { + if (m_markupText) + { + delete m_markupText; + m_markupText = nullptr; + } + } +} +#endif // SUPPORTS_MARKUP + bool BitmapTextRenderer::SetValue(const wxVariant &value) { m_value << value; + +#ifdef SUPPORTS_MARKUP + if (m_markupText) + m_markupText->SetMarkup(m_value.GetText()); +#endif // SUPPORTS_MARKUP + return true; } @@ -1589,6 +1630,11 @@ bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY wxString BitmapTextRenderer::GetAccessibleDescription() const { +#ifdef SUPPORTS_MARKUP + if (m_markupText) + return wxMarkupParser::Strip(m_text); +#endif // SUPPORTS_MARKUP + return m_value.GetText(); } #endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING @@ -1609,7 +1655,17 @@ bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) xoffset = icon_sz.x + 4; } - RenderText(m_value.GetText(), xoffset, rect, dc, state); +#ifdef SUPPORTS_MARKUP + if (m_markupText) + { + int flags = 0; + + rect.x += xoffset; + m_markupText->Render(GetView(), *dc, rect, flags, GetEllipsizeMode()); + } + else +#endif // SUPPORTS_MARKUP + RenderText(m_value.GetText(), xoffset, rect, dc, state); return true; } @@ -1618,7 +1674,23 @@ wxSize BitmapTextRenderer::GetSize() const { if (!m_value.GetText().empty()) { - wxSize size = GetTextExtent(m_value.GetText()); + wxSize size; +#ifdef SUPPORTS_MARKUP + if (m_markupText) + { + wxDataViewCtrl* const view = GetView(); + wxClientDC dc(view); + if (GetAttr().HasFont()) + dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); + + size = m_markupText->Measure(dc); + } + else +#endif // SUPPORTS_MARKUP + size = GetTextExtent(m_value.GetText()); + + int lines = m_value.GetText().Freq('\n') + 1; + size.SetHeight(size.GetHeight() * lines); if (m_value.GetBitmap().IsOk()) size.x += m_value.GetBitmap().GetWidth() + 4; diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index c184842664..c8545e4a4f 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -2,9 +2,10 @@ #define slic3r_GUI_ObjectDataViewModel_hpp_ #include - #include +#include "GUI_App.hpp" + namespace Slic3r { enum class ModelVolumeType : int; @@ -83,9 +84,19 @@ public: ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), m_parent(parent) - {} + { +#ifdef SUPPORTS_MARKUP + m_markupText = nullptr; +#endif // SUPPORTS_MARKUP + } #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + ~BitmapTextRenderer(); + +#ifdef SUPPORTS_MARKUP + void EnableMarkup(bool enable = true); +#endif // SUPPORTS_MARKUP + bool SetValue(const wxVariant& value); bool GetValue(wxVariant& value) const; #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY @@ -114,6 +125,10 @@ private: DataViewBitmapText m_value; bool m_was_unusable_symbol{ false }; wxWindow* m_parent{ nullptr }; + +#ifdef SUPPORTS_MARKUP + class wxItemMarkupText* m_markupText; +#endif // SUPPORTS_MARKUP }; diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 6eab6b72ac..d13ef469f1 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -321,6 +321,14 @@ const Option& OptionsSearcher::get_option(size_t pos_in_filter) const return options[found[pos_in_filter].option_idx]; } +const Option& OptionsSearcher::get_option(const std::string& opt_key) const +{ + auto it = std::upper_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key) })); + assert(it != options.end()); + + return options[it - options.begin()]; +} + void OptionsSearcher::add_key(const std::string& opt_key, const wxString& group, const wxString& category) { groups_and_categories[opt_key] = GroupAndCategory{group, category}; diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 8202222e9d..a57e0d015d 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -37,8 +37,8 @@ struct GroupAndCategory { }; struct Option { - bool operator<(const Option& other) const { return other.label > this->label; } - bool operator>(const Option& other) const { return other.label < this->label; } +// bool operator<(const Option& other) const { return other.label > this->label; } + bool operator<(const Option& other) const { return other.opt_key > this->opt_key; } // Fuzzy matching works at a character level. Thus matching with wide characters is a safer bet than with short characters, // though for some languages (Chinese?) it may not work correctly. @@ -116,12 +116,18 @@ public: const FoundOption& operator[](const size_t pos) const noexcept { return found[pos]; } const Option& get_option(size_t pos_in_filter) const; + const Option& get_option(const std::string& opt_key) const; const std::vector& found_options() { return found; } const GroupAndCategory& get_group_and_category (const std::string& opt_key) { return groups_and_categories[opt_key]; } std::string& search_string() { return search_line; } void set_printer_technology(PrinterTechnology pt) { printer_technology = pt; } + + void sort_options_by_opt_key() { + std::sort(options.begin(), options.end(), [](const Option& o1, const Option& o2) { + return o1.opt_key < o2.opt_key; }); + } }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 49317f802f..dbb8761d3a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3133,8 +3133,16 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { UnsavedChangesDialog dlg(m_type); - dlg.ShowModal(); - + if (dlg.ShowModal() == wxID_CANCEL) + return false; + if (dlg.just_continue()) + return true; + if (dlg.save_preset()) + // save selected changes + return false; + if (dlg.move_preset()) + // move selected changes + return false; if (presets == nullptr) presets = m_presets; // Display a dialog showing the dirty options in a human readable form. diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 21da295d40..46f086765c 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -13,11 +13,12 @@ #include "GUI_App.hpp" #include "Plater.hpp" #include "Tab.hpp" +#include "ObjectDataViewModel.hpp" #define FTS_FUZZY_MATCH_IMPLEMENTATION #include "fts_fuzzy_match.h" -#include "imgui/imconfig.h" +#include "BitmapCache.hpp" using boost::optional; @@ -29,12 +30,39 @@ namespace GUI { // ModelNode: a node inside UnsavedChangesModel // ---------------------------------------------------------------------------- +static const std::map type_icon_names = { + {Preset::TYPE_PRINT, "cog" }, + {Preset::TYPE_SLA_PRINT, "cog" }, + {Preset::TYPE_FILAMENT, "spool" }, + {Preset::TYPE_SLA_MATERIAL, "resin" }, + {Preset::TYPE_PRINTER, "sla_printer" }, +}; + +static std::string black = "#000000"; +static std::string grey = "#808080"; +static std::string orange = "#ed6b21"; + +static void color_string(wxString& str, const std::string& color) +{ +#ifdef SUPPORTS_MARKUP + str = from_u8((boost::format("%2%") % color % into_u8(str)).str()); +#endif +} + +static void make_string_bold(wxString& str) +{ +#ifdef SUPPORTS_MARKUP + str = from_u8((boost::format("%1%") % into_u8(str)).str()); +#endif +} + // preset(root) node -ModelNode::ModelNode(const wxString& text, Preset::Type preset_type) : +ModelNode::ModelNode(Preset::Type preset_type, const wxString& text) : m_parent(nullptr), m_preset_type(preset_type), m_text(text) { + m_icon = create_scaled_bitmap(type_icon_names.at(preset_type)); } // group node @@ -42,38 +70,230 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& m_parent(parent), m_text(text) { + m_icon = create_scaled_bitmap(icon_name); } -// group node -ModelNode::ModelNode(ModelNode* parent, const wxString& text, bool is_option) : +// category node +ModelNode::ModelNode(ModelNode* parent, const wxString& text) : m_parent(parent), - m_text(text), - m_container(!is_option) + m_text(text) { } +wxBitmap ModelNode::get_bitmap(const wxString& color) +{ + /* It's supposed that standard size of an icon is 48px*16px for 100% scaled display. + * So set sizes for solid_colored icons used for filament preset + * and scale them in respect to em_unit value + */ + const double em = em_unit(m_parent_win); + const int icon_width = lround(6.4 * em); + const int icon_height = lround(1.6 * em); + + BitmapCache bmp_cache; + unsigned char rgb[3]; + BitmapCache::parse_color(into_u8(color), rgb); + // there is no need to scale created solid bitmap + return bmp_cache.mksolid(icon_width, icon_height, rgb, true); +} + +// option node +ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value) : + m_parent(parent), + m_old_color(old_value.StartsWith("#") ? old_value : ""), + m_new_color(new_value.StartsWith("#") ? new_value : ""), + m_container(false), + m_text(text), + m_old_value(old_value), + m_new_value(new_value) +{ + // check if old/new_value is color + if (m_old_color.IsEmpty()) { + if (!m_new_color.IsEmpty()) + m_old_value = _L("Undef"); + } + else { + m_old_color_bmp = get_bitmap(m_old_color); + m_old_value.Clear(); + } + + if (m_new_color.IsEmpty()) { + if (!m_old_color.IsEmpty()) + m_new_value = _L("Undef"); + } + else { + m_new_color_bmp = get_bitmap(m_new_color); + m_new_value.Clear(); + } + + // "color" strings + color_string(m_old_value, black); + color_string(m_new_value, orange); +} + +void ModelNode::UpdateEnabling() +{ +#ifdef SUPPORTS_MARKUP + auto change_text_color = [](wxString& str, const std::string& clr_from, const std::string& clr_to) + { + std::string old_val = into_u8(str); + boost::replace_all(old_val, clr_from, clr_to); + str = from_u8(old_val); + }; + + if (!m_toggle) { + change_text_color(m_text, black, grey); + change_text_color(m_old_value, black, grey); + change_text_color(m_new_value, orange,grey); + } + else { + change_text_color(m_text, grey, black); + change_text_color(m_old_value, grey, black); + change_text_color(m_new_value, grey, orange); + } +#endif + // update icons for the colors + if (!m_old_color.IsEmpty()) + m_old_color_bmp = get_bitmap(m_toggle? m_old_color : grey); + if (!m_new_color.IsEmpty()) + m_new_color_bmp = get_bitmap(m_toggle? m_new_color : grey); +} + // ---------------------------------------------------------------------------- // UnsavedChangesModel // ---------------------------------------------------------------------------- -UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) +UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) : + m_parent_win(parent) { - int icon_id = 0; - for (const std::string& icon : { "cog", "printer", "sla_printer", "spool", "resin" }) - m_icon[icon_id++] = ScalableBitmap(parent, icon); - - m_root = new ModelNode("Preset", Preset::TYPE_INVALID); } UnsavedChangesModel::~UnsavedChangesModel() { - delete m_root; + for (ModelNode* preset_node : m_preset_nodes) + delete preset_node; +} + +wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset_name) +{ + // "color" strings + color_string(preset_name, black); + make_string_bold(preset_name); + + auto preset = new ModelNode(type, preset_name); + m_preset_nodes.emplace_back(preset); + + wxDataViewItem child((void*)preset); + wxDataViewItem parent(nullptr); + + ItemAdded(parent, child); + return child; +} + +ModelNode* UnsavedChangesModel::AddOption(ModelNode* group_node, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* option = new ModelNode(group_node, option_name, old_value, new_value); + group_node->Append(option); + ItemAdded(wxDataViewItem((void*)group_node), wxDataViewItem((void*)option)); + + return option; +} + +ModelNode* UnsavedChangesModel::AddOptionWithGroup(ModelNode* category_node, wxString group_name, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* group_node = new ModelNode(category_node, group_name); + category_node->Append(group_node); + wxDataViewItem group_item = wxDataViewItem((void*)group_node); + ItemAdded(wxDataViewItem((void*)category_node), group_item); + m_ctrl->Expand(group_item); + + return AddOption(group_node, option_name, old_value, new_value); +} + +ModelNode* UnsavedChangesModel::AddOptionWithGroupAndCategory(ModelNode* preset_node, wxString category_name, wxString group_name, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* category_node = new ModelNode(preset_node, category_name, "cog"); + preset_node->Append(category_node); + ItemAdded(wxDataViewItem((void*)preset_node), wxDataViewItem((void*)category_node)); + + return AddOptionWithGroup(category_node, group_name, option_name, old_value, new_value); +} + +wxDataViewItem UnsavedChangesModel::AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name, + wxString old_value, wxString new_value) +{ + // "color" strings + color_string(category_name, black); + color_string(group_name, black); + color_string(option_name, black); + + // "make" strings bold + make_string_bold(category_name); + make_string_bold(group_name); + + // add items + for (ModelNode* preset : m_preset_nodes) + if (preset->type() == type) + { + for (ModelNode* category : preset->GetChildren()) + if (category->text() == category_name) + { + for (ModelNode* group : category->GetChildren()) + if (group->text() == group_name) + return wxDataViewItem((void*)AddOption(group, option_name, old_value, new_value)); + + return wxDataViewItem((void*)AddOptionWithGroup(category, group_name, option_name, old_value, new_value)); + } + + return wxDataViewItem((void*)AddOptionWithGroupAndCategory(preset, category_name, group_name, option_name, old_value, new_value)); + } + + return wxDataViewItem(nullptr); +} + +static void update_children(ModelNode* parent) +{ + if (parent->IsContainer()) { + bool toggle = parent->IsToggled(); + for (ModelNode* child : parent->GetChildren()) { + child->Toggle(toggle); + child->UpdateEnabling(); + update_children(child); + } + } +} + +static void update_parents(ModelNode* node) +{ + ModelNode* parent = node->GetParent(); + if (parent) { + bool toggle = false; + for (ModelNode* child : parent->GetChildren()) { + if (child->IsToggled()) { + toggle = true; + break; + } + } + parent->Toggle(toggle); + parent->UpdateEnabling(); + update_parents(parent); + } +} + +void UnsavedChangesModel::UpdateItemEnabling(wxDataViewItem item) +{ + assert(item.IsOk()); + ModelNode* node = (ModelNode*)item.GetID(); + node->UpdateEnabling(); + + update_children(node); + update_parents(node); } void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const { - wxASSERT(item.IsOk()); + assert(item.IsOk()); ModelNode* node = (ModelNode*)item.GetID(); switch (col) @@ -81,20 +301,14 @@ void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& ite case colToggle: variant = node->m_toggle; break; - case colTypeIcon: - variant << node->m_type_icon; - break; - case colGroupIcon: - variant << node->m_group_icon; - break; - case colMarkedText: - variant =node->m_text; + case colIconText: + variant << DataViewBitmapText(node->m_text, node->m_icon); break; case colOldValue: - variant =node->m_text; + variant << DataViewBitmapText(node->m_old_value, node->m_old_color_bmp); break; case colNewValue: - variant =node->m_text; + variant << DataViewBitmapText(node->m_new_value, node->m_new_color_bmp); break; default: @@ -109,24 +323,27 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte ModelNode* node = (ModelNode*)item.GetID(); switch (col) { + case colIconText: { + DataViewBitmapText data; + data << variant; + node->m_icon = data.GetBitmap(); + node->m_text = data.GetText(); + return true; } case colToggle: node->m_toggle = variant.GetBool(); return true; - case colTypeIcon: - node->m_type_icon << variant; - return true; - case colGroupIcon: - node->m_group_icon << variant; - return true; - case colMarkedText: - node->m_text = variant.GetString(); - return true; - case colOldValue: - node->m_text = variant.GetString(); - return true; - case colNewValue: - node->m_text = variant.GetString(); - return true; + case colOldValue: { + DataViewBitmapText data; + data << variant; + node->m_old_color_bmp = data.GetBitmap(); + node->m_old_value = data.GetText(); + return true; } + case colNewValue: { + DataViewBitmapText data; + data << variant; + node->m_new_color_bmp = data.GetBitmap(); + node->m_new_value = data.GetText(); + return true; } default: wxLogError("UnsavedChangesModel::SetValue: wrong column"); } @@ -136,11 +353,11 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte bool UnsavedChangesModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const { assert(item.IsOk()); - - ModelNode* node = (ModelNode*)item.GetID(); + if (col == colToggle) + return true; // disable unchecked nodes - return !node->IsToggle(); + return ((ModelNode*)item.GetID())->IsToggled(); } wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const @@ -152,7 +369,7 @@ wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const ModelNode* node = (ModelNode*)item.GetID(); // "MyMusic" also has no parent - if (node == m_root) + if (node->IsRoot()) return wxDataViewItem(nullptr); return wxDataViewItem((void*)node->GetParent()); @@ -172,8 +389,9 @@ unsigned int UnsavedChangesModel::GetChildren(const wxDataViewItem& parent, wxDa { ModelNode* node = (ModelNode*)parent.GetID(); if (!node) { - array.Add(wxDataViewItem((void*)m_root)); - return 1; + for (auto preset_node : m_preset_nodes) + array.Add(wxDataViewItem((void*)preset_node)); + return m_preset_nodes.size(); } if (node->GetChildCount() == 0) @@ -191,13 +409,16 @@ unsigned int UnsavedChangesModel::GetChildren(const wxDataViewItem& parent, wxDa wxString UnsavedChangesModel::GetColumnType(unsigned int col) const { - if (col == colToggle) + switch (col) + { + case colToggle: return "bool"; - - if (col < colMarkedText) - return "wxBitmap"; - - return "string"; + case colIconText: + case colOldValue: + case colNewValue: + default: + return "DataViewBitmapText";//"string"; + } } @@ -214,43 +435,215 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) int border = 10; int em = em_unit(); - changes_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 60), wxBORDER_SIMPLE); - changes_tree_model = new UnsavedChangesModel(this); - changes_tree->AssociateModel(changes_tree_model); + m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 30), wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT); + m_tree_model = new UnsavedChangesModel(this); + m_tree->AssociateModel(m_tree_model); + m_tree_model->SetAssociatedControl(m_tree); - changes_tree->AppendToggleColumn(L"\u2610", UnsavedChangesModel::colToggle);//2610,11,12 //2714 - changes_tree->AppendBitmapColumn("", UnsavedChangesModel::colTypeIcon); - changes_tree->AppendBitmapColumn("", UnsavedChangesModel::colGroupIcon); - - wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer(); + m_tree->AppendToggleColumn(/*L"\u2714"*/"", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em, wxALIGN_NOT);//2610,11,12 //2714 + BitmapTextRenderer* renderer = new BitmapTextRenderer(m_tree); #ifdef SUPPORTS_MARKUP - markupRenderer->EnableMarkup(); + renderer->EnableMarkup(); #endif + m_tree->AppendColumn(new wxDataViewColumn("", renderer, UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE)); + m_tree->AppendColumn(new wxDataViewColumn("Old value", renderer, UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); + m_tree->AppendColumn(new wxDataViewColumn("New value", renderer, UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); - changes_tree->AppendColumn(new wxDataViewColumn("", markupRenderer, UnsavedChangesModel::colMarkedText, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); - changes_tree->AppendColumn(new wxDataViewColumn("Old value", markupRenderer, UnsavedChangesModel::colOldValue, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); - changes_tree->AppendColumn(new wxDataViewColumn("New value", markupRenderer, UnsavedChangesModel::colNewValue, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); + m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); - wxStdDialogButtonSizer* cancel_btn = this->CreateStdDialogButtonSizer(wxCANCEL); + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); + + Tab* tab = wxGetApp().get_tab(type); + assert(tab); + + PresetCollection* presets = tab->get_presets(); + + wxString label= from_u8((boost::format(_u8L("Save selected to preset:%1%"))% ("\"" + presets->get_selected_preset().name + "\"")).str()); + auto save_btn = new wxButton(this, m_save_btn_id = NewControlId(), label); + save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); + buttons->Insert(0, save_btn, 0, wxLEFT, 5); + + label = from_u8((boost::format(_u8L("Move selected to preset:%1%"))% ("\"NewSelectedPreset\"")).str()); + auto move_btn = new wxButton(this, m_move_btn_id = NewControlId(), label); + move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); + buttons->Insert(1, move_btn, 0, wxLEFT, 5); + + auto continue_btn = new wxButton(this, m_continue_btn_id = NewControlId(), _L("Continue without changes")); + continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); + buttons->Insert(2, continue_btn, 0, wxLEFT, 5); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There is unsaved changes for the current preset") + ":"), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(changes_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(cancel_btn, 0, wxEXPAND | wxALL, border); + topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There is unsaved changes for") + (": \"" + tab->title() + "\"")), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); + + update(type); SetSizer(topSizer); topSizer->SetSizeHints(this); } +void UnsavedChangesDialog::item_value_changed(wxDataViewEvent& event) +{ + if (event.GetColumn() != UnsavedChangesModel::colToggle) + return; + + wxDataViewItem item = event.GetItem(); + + m_tree_model->UpdateItemEnabling(item); + m_tree->Refresh(); +} + +void UnsavedChangesDialog::close(Action action) +{ + m_action = action; + this->EndModal(wxID_CLOSE); +} + +template +wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config) +{ + const std::vector& names = config.def()->options.at(opt_key).enum_labels;//ConfigOptionEnum::get_enum_names(); + T val = config.option>(opt_key)->value; + return from_u8(_u8L(names[static_cast(val)])); +} + +static wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config) +{ + wxString out; + + // FIXME controll, if opt_key has index + int opt_idx = 0; + + ConfigOptionType type = config.def()->options.at(opt_key).type; + + switch (type) { + case coInt: + return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str()); + case coInts: { + const ConfigOptionInts* opt = config.opt(opt_key); + if (opt) + return from_u8((boost::format("%1%") % opt->get_at(opt_idx)).str()); + break; + } + case coBool: + return config.opt_bool(opt_key) ? "true" : "false"; + case coBools: { + const ConfigOptionBools* opt = config.opt(opt_key); + if (opt) + return opt->get_at(opt_idx) ? "true" : "false"; + break; + } + case coPercent: + return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str()); + case coPercents: { + const ConfigOptionPercents* opt = config.opt(opt_key); + if (opt) + return from_u8((boost::format("%1%%%") % int(opt->get_at(opt_idx))).str()); + break; + } + case coFloat: + return double_to_string(config.opt_float(opt_key)); + case coFloats: { + const ConfigOptionFloats* opt = config.opt(opt_key); + if (opt) + return double_to_string(opt->get_at(opt_idx)); + break; + } + case coString: + return from_u8(config.opt_string(opt_key)); + case coStrings: { + const ConfigOptionStrings* strings = config.opt(opt_key); + if (strings) { + if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { + if (strings->empty()) + return _L("All"); + for (size_t id = 0; id < strings->size(); id++) + out += from_u8(strings->get_at(id)) + "\n"; + out.RemoveLast(1); + return out; + } + if (!strings->empty()) + return from_u8(strings->get_at(opt_idx)); + } + break; + } + case coFloatOrPercent: { + const ConfigOptionFloatOrPercent* opt = config.opt(opt_key); + if (opt) + out = double_to_string(opt->value) + (opt->percent ? "%" : ""); + return out; + } + case coEnum: { + if (opt_key == "top_fill_pattern" || + opt_key == "bottom_fill_pattern" || + opt_key == "fill_pattern") + return get_string_from_enum(opt_key, config); + if (opt_key == "gcode_flavor") + return get_string_from_enum(opt_key, config); + if (opt_key == "ironing_type") + return get_string_from_enum(opt_key, config); + if (opt_key == "support_material_pattern") + return get_string_from_enum(opt_key, config); + if (opt_key == "seam_position") + return get_string_from_enum(opt_key, config); + if (opt_key == "display_orientation") + return get_string_from_enum(opt_key, config); + if (opt_key == "support_pillar_connection_mode") + return get_string_from_enum(opt_key, config); + break; + } + case coPoints: { + /* + if (opt_key == "bed_shape") { + config.option(opt_key)->values = boost::any_cast>(value); + break; + } + ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) }; + config.option(opt_key)->set_at(vec_new, opt_index, 0); + */ + return "Points"; + } + default: + break; + } + return out; +} + +void UnsavedChangesDialog::update(Preset::Type type) +{ + Tab* tab = wxGetApp().get_tab(type); + assert(tab); + + PresetCollection* presets = tab->get_presets(); + // Display a dialog showing the dirty options in a human readable form. + const DynamicPrintConfig& old_config = presets->get_selected_preset().config; + const DynamicPrintConfig& new_config = presets->get_edited_preset().config; + + m_tree_model->AddPreset(type, from_u8(presets->get_edited_preset().name)); + + Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); + searcher.sort_options_by_opt_key(); + + // Collect dirty options. + for (const std::string& opt_key : presets->current_dirty_options()) { + const Search::Option& option = searcher.get_option(opt_key); + + m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, + get_string_value(opt_key, old_config), get_string_value(opt_key, new_config)); + } +} + + void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); - msw_buttons_rescale(this, em, { wxID_CANCEL }); + msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id }); - const wxSize& size = wxSize(80 * em, 60 * em); + const wxSize& size = wxSize(80 * em, 30 * em); SetMinSize(size); Fit(); @@ -260,7 +653,7 @@ void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) void UnsavedChangesDialog::on_sys_color_changed() { // msw_rescale updates just icons, so use it -// changes_tree_model->msw_rescale(); +// m_tree_model->msw_rescale(); Refresh(); } diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index a3ee7d984f..c4a02d7bcc 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -31,11 +31,17 @@ WX_DEFINE_ARRAY_PTR(ModelNode*, ModelNodePtrArray); class ModelNode { + wxWindow* m_parent_win{ nullptr }; + ModelNode* m_parent; ModelNodePtrArray m_children; wxBitmap m_empty_bmp; Preset::Type m_preset_type {Preset::TYPE_INVALID}; + // saved values for colors if they exist + wxString m_old_color; + wxString m_new_color; + // TODO/FIXME: // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) // needs to know in advance if a node is or _will be_ a container. @@ -47,23 +53,29 @@ class ModelNode // would be added to the control) bool m_container {true}; + wxBitmap get_bitmap(const wxString& color); + public: bool m_toggle {true}; - wxBitmap m_type_icon; - wxBitmap m_group_icon; + wxBitmap m_icon; + wxBitmap m_old_color_bmp; + wxBitmap m_new_color_bmp; wxString m_text; wxString m_old_value; wxString m_new_value; // preset(root) node - ModelNode(const wxString& text, Preset::Type preset_type); + ModelNode(Preset::Type preset_type, const wxString& text); - // group node + // category node ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name); // group node - ModelNode(ModelNode* parent, const wxString& text, bool is_option); + ModelNode(ModelNode* parent, const wxString& text); + + // option node + ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value); ~ModelNode() { // free all our children nodes @@ -75,15 +87,21 @@ public: } bool IsContainer() const { return m_container; } - bool IsToggle() const { return m_toggle; } + bool IsToggled() const { return m_toggle; } + void Toggle(bool toggle = true) { m_toggle = toggle; } + bool IsRoot() const { return m_parent == nullptr; } + Preset::Type type() const { return m_preset_type; } + const wxString& text() const { return m_text; } ModelNode* GetParent() { return m_parent; } ModelNodePtrArray& GetChildren() { return m_children; } ModelNode* GetNthChild(unsigned int n) { return m_children.Item(n); } unsigned int GetChildCount() const { return m_children.GetCount(); } - void Insert(ModelNode* child, unsigned int n) { m_children.Insert(child, n); } - void Append(ModelNode* child) { m_children.Add(child); } + void Insert(ModelNode* child, unsigned int n) { m_children.Insert(child, n); } + void Append(ModelNode* child) { m_children.Add(child); } + + void UpdateEnabling(); }; @@ -93,15 +111,31 @@ public: class UnsavedChangesModel : public wxDataViewModel { - ModelNode* m_root; - ScalableBitmap m_icon[5]; + wxWindow* m_parent_win {nullptr}; + std::vector m_preset_nodes; + + wxDataViewCtrl* m_ctrl{ nullptr }; + + ModelNode *AddOption(ModelNode *group_node, + wxString option_name, + wxString old_value, + wxString new_value); + ModelNode *AddOptionWithGroup(ModelNode *category_node, + wxString group_name, + wxString option_name, + wxString old_value, + wxString new_value); + ModelNode *AddOptionWithGroupAndCategory(ModelNode *preset_node, + wxString category_name, + wxString group_name, + wxString option_name, + wxString old_value, + wxString new_value); public: enum { colToggle, - colTypeIcon, - colGroupIcon, - colMarkedText, + colIconText, colOldValue, colNewValue, colMax @@ -110,18 +144,28 @@ public: UnsavedChangesModel(wxWindow* parent); ~UnsavedChangesModel(); - virtual unsigned int GetColumnCount() const override { return colMax; } - virtual wxString GetColumnType(unsigned int col) const override; + void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } - virtual wxDataViewItem GetParent(const wxDataViewItem& item) const override; - virtual unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + wxDataViewItem AddPreset(Preset::Type type, wxString preset_name); + wxDataViewItem AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name, + wxString old_value, wxString new_value); - virtual void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; - virtual bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + void UpdateItemEnabling(wxDataViewItem item); - virtual bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; - virtual bool IsContainer(const wxDataViewItem& item) const override; + unsigned int GetColumnCount() const override { return colMax; } + wxString GetColumnType(unsigned int col) const override; + wxDataViewItem GetParent(const wxDataViewItem& item) const override; + unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + + void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; + bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + + bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; + bool IsContainer(const wxDataViewItem& item) const override; + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } }; @@ -130,14 +174,30 @@ public: //------------------------------------------ class UnsavedChangesDialog : public DPIDialog { - wxDataViewCtrl* changes_tree{ nullptr }; - UnsavedChangesModel* changes_tree_model{ nullptr }; + wxDataViewCtrl* m_tree { nullptr }; + UnsavedChangesModel* m_tree_model { nullptr }; + + int m_save_btn_id { wxID_ANY }; + int m_move_btn_id { wxID_ANY }; + int m_continue_btn_id { wxID_ANY }; + + enum class Action { + Save, + Move, + Continue + } m_action; public: UnsavedChangesDialog(Preset::Type type); ~UnsavedChangesDialog() {} - void ProcessSelection(wxDataViewItem selection); + void update(Preset::Type type); + void item_value_changed(wxDataViewEvent &event); + void close(Action action); + + bool save_preset() const { return m_action == Action::Save; } + bool move_preset() const { return m_action == Action::Move; } + bool just_continue() const { return m_action == Action::Continue; } protected: void on_dpi_changed(const wxRect& suggested_rect) override; From 93c1671e09b65905ac7da7bba60dcea15d9f5e4e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Aug 2020 20:26:40 +0200 Subject: [PATCH 255/826] Custom renderers extracted from the ObjectDataViewModel --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/ExtraRenderers.cpp | 314 +++++++++++++++++++++++++ src/slic3r/GUI/ExtraRenderers.hpp | 162 +++++++++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 14 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 305 ------------------------ src/slic3r/GUI/ObjectDataViewModel.hpp | 153 +----------- 6 files changed, 490 insertions(+), 460 deletions(-) create mode 100644 src/slic3r/GUI/ExtraRenderers.cpp create mode 100644 src/slic3r/GUI/ExtraRenderers.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index cd28d6eb20..b22a3cb1fb 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -165,6 +165,8 @@ set(SLIC3R_GUI_SOURCES GUI/Search.hpp GUI/UnsavedChangesDialog.cpp GUI/UnsavedChangesDialog.hpp + GUI/ExtraRenderers.cpp + GUI/ExtraRenderers.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp new file mode 100644 index 0000000000..494bfee6a2 --- /dev/null +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -0,0 +1,314 @@ +#include "ExtraRenderers.hpp" +#include "wxExtensions.hpp" +#include "GUI.hpp" +#include "I18N.hpp" + +#include +#include "wx/generic/private/markuptext.h" +#include "wx/generic/private/rowheightcache.h" +#include "wx/generic/private/widthcalc.h" +#if wxUSE_ACCESSIBILITY +#include "wx/private/markupparser.h" +#endif // wxUSE_ACCESSIBILITY + +using Slic3r::GUI::from_u8; +using Slic3r::GUI::into_u8; + + +//----------------------------------------------------------------------------- +// DataViewBitmapText +//----------------------------------------------------------------------------- + +wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) + +IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) + +// --------------------------------------------------------- +// BitmapTextRenderer +// --------------------------------------------------------- + +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, + int align /*= wxDVR_DEFAULT_ALIGNMENT*/): +wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) +{ + SetMode(mode); + SetAlignment(align); +} +#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + +BitmapTextRenderer::~BitmapTextRenderer() +{ +#ifdef SUPPORTS_MARKUP + if (m_markupText) + delete m_markupText; +#endif // SUPPORTS_MARKUP +} + +#ifdef SUPPORTS_MARKUP +void BitmapTextRenderer::EnableMarkup(bool enable) +{ + if (enable) + { + if (!m_markupText) + { + m_markupText = new wxItemMarkupText(wxString()); + } + } + else + { + if (m_markupText) + { + delete m_markupText; + m_markupText = nullptr; + } + } +} +#endif // SUPPORTS_MARKUP + +bool BitmapTextRenderer::SetValue(const wxVariant &value) +{ + m_value << value; + +#ifdef SUPPORTS_MARKUP + if (m_markupText) + m_markupText->SetMarkup(m_value.GetText()); +#endif // SUPPORTS_MARKUP + + return true; +} + +bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const +{ + return false; +} + +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY +wxString BitmapTextRenderer::GetAccessibleDescription() const +{ +#ifdef SUPPORTS_MARKUP + if (m_markupText) + return wxMarkupParser::Strip(m_text); +#endif // SUPPORTS_MARKUP + + return m_value.GetText(); +} +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + +bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { +#ifdef __APPLE__ + wxSize icon_sz = icon.GetScaledSize(); +#else + wxSize icon_sz = icon.GetSize(); +#endif + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2); + xoffset = icon_sz.x + 4; + } + +#ifdef SUPPORTS_MARKUP + if (m_markupText) + { + int flags = 0; + + rect.x += xoffset; + m_markupText->Render(GetView(), *dc, rect, flags, GetEllipsizeMode()); + } + else +#endif // SUPPORTS_MARKUP + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize BitmapTextRenderer::GetSize() const +{ + if (!m_value.GetText().empty()) + { + wxSize size; +#ifdef SUPPORTS_MARKUP + if (m_markupText) + { + wxDataViewCtrl* const view = GetView(); + wxClientDC dc(view); + if (GetAttr().HasFont()) + dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); + + size = m_markupText->Measure(dc); + } + else +#endif // SUPPORTS_MARKUP + size = GetTextExtent(m_value.GetText()); + + int lines = m_value.GetText().Freq('\n') + 1; + size.SetHeight(size.GetHeight() * lines); + + if (m_value.GetBitmap().IsOk()) + size.x += m_value.GetBitmap().GetWidth() + 4; + return size; + } + return wxSize(80, 20); +} + + +wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + if (!can_create_editor_ctrl()) + return nullptr; + + DataViewBitmapText data; + data << value; + + m_was_unusable_symbol = false; + + wxPoint position = labelRect.GetPosition(); + if (data.GetBitmap().IsOk()) { + const int bmp_width = data.GetBitmap().GetWidth(); + position.x += bmp_width; + labelRect.SetWidth(labelRect.GetWidth() - bmp_width); + } + + wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), + position, labelRect.GetSize(), wxTE_PROCESS_ENTER); + text_editor->SetInsertionPointEnd(); + text_editor->SelectAll(); + + return text_editor; +} + +bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); + if (!text_editor || text_editor->GetValue().IsEmpty()) + return false; + + std::string chosen_name = into_u8(text_editor->GetValue()); + const char* unusable_symbols = "<>:/\\|?*\""; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + m_was_unusable_symbol = true; + return false; + } + } + + // The icon can't be edited so get its old value and reuse it. + wxVariant valueOld; + GetView()->GetModel()->GetValue(valueOld, m_item, /*colName*/0); + + DataViewBitmapText bmpText; + bmpText << valueOld; + + // But replace the text with the value entered by user. + bmpText.SetText(text_editor->GetValue()); + + value << bmpText; + return true; +} + +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +bool BitmapChoiceRenderer::SetValue(const wxVariant& value) +{ + m_value << value; + return true; +} + +bool BitmapChoiceRenderer::GetValue(wxVariant& value) const +{ + value << m_value; + return true; +} + +bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); + xoffset = icon.GetWidth() + 4; + + if (rect.height==0) + rect.height= icon.GetHeight(); + } + + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize BitmapChoiceRenderer::GetSize() const +{ + wxSize sz = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + sz.x += m_value.GetBitmap().GetWidth() + 4; + + return sz; +} + + +wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + if (!can_create_editor_ctrl()) + return nullptr; + + std::vector icons = get_extruder_color_icons(); + if (icons.empty()) + return nullptr; + + DataViewBitmapText data; + data << value; + + auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, + labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), + 0, nullptr , wxCB_READONLY); + + int i=0; + for (wxBitmap* bmp : icons) { + if (i==0) { + c_editor->Append(_L("default"), *bmp); + ++i; + } + + c_editor->Append(wxString::Format("%d", i), *bmp); + ++i; + } + c_editor->SetSelection(atoi(data.GetText().c_str())); + + // to avoid event propagation to other sidebar items + c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + evt.StopPropagation(); + // FinishEditing grabs new selection and triggers config update. We better call + // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. + this->FinishEditing(); + }); + + return c_editor; +} + +bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl; + int selection = c->GetSelection(); + if (selection < 0) + return false; + + DataViewBitmapText bmpText; + + bmpText.SetText(c->GetString(selection)); + bmpText.SetBitmap(c->GetItemBitmap(selection)); + + value << bmpText; + return true; +} + + diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp new file mode 100644 index 0000000000..96cf349453 --- /dev/null +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -0,0 +1,162 @@ +#ifndef slic3r_GUI_ExtraRenderers_hpp_ +#define slic3r_GUI_ExtraRenderers_hpp_ + +#include + +#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) + #define SUPPORTS_MARKUP +#endif + +// ---------------------------------------------------------------------------- +// DataViewBitmapText: helper class used by BitmapTextRenderer +// ---------------------------------------------------------------------------- + +class DataViewBitmapText : public wxObject +{ +public: + DataViewBitmapText( const wxString &text = wxEmptyString, + const wxBitmap& bmp = wxNullBitmap) : + m_text(text), + m_bmp(bmp) + { } + + DataViewBitmapText(const DataViewBitmapText &other) + : wxObject(), + m_text(other.m_text), + m_bmp(other.m_bmp) + { } + + void SetText(const wxString &text) { m_text = text; } + wxString GetText() const { return m_text; } + void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } + const wxBitmap &GetBitmap() const { return m_bmp; } + + bool IsSameAs(const DataViewBitmapText& other) const { + return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); + } + + bool operator==(const DataViewBitmapText& other) const { + return IsSameAs(other); + } + + bool operator!=(const DataViewBitmapText& other) const { + return !IsSameAs(other); + } + +private: + wxString m_text; + wxBitmap m_bmp; + + wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); +}; +DECLARE_VARIANT_OBJECT(DataViewBitmapText) + +// ---------------------------------------------------------------------------- +// BitmapTextRenderer +// ---------------------------------------------------------------------------- +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +class BitmapTextRenderer : public wxDataViewRenderer +#else +class BitmapTextRenderer : public wxDataViewCustomRenderer +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +{ +public: + BitmapTextRenderer(wxWindow* parent, + wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + + , int align = wxDVR_DEFAULT_ALIGNMENT +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + ); +#else + ) : + wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), + m_parent(parent) + { +#ifdef SUPPORTS_MARKUP + m_markupText = nullptr; +#endif // SUPPORTS_MARKUP + } +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + + ~BitmapTextRenderer(); + +#ifdef SUPPORTS_MARKUP + void EnableMarkup(bool enable = true); +#endif // SUPPORTS_MARKUP + + bool SetValue(const wxVariant& value); + bool GetValue(wxVariant& value) const; +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY + virtual wxString GetAccessibleDescription() const override; +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override + { +#ifdef __WXOSX__ + return false; +#else + return true; +#endif + } + wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override; + bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override; + bool WasCanceled() const { return m_was_unusable_symbol; } + + void set_can_create_editor_ctrl_function(std::function can_create_fn) { can_create_editor_ctrl = can_create_fn; } + +private: + DataViewBitmapText m_value; + bool m_was_unusable_symbol{ false }; + wxWindow* m_parent{ nullptr }; + + std::function can_create_editor_ctrl { nullptr }; + +#ifdef SUPPORTS_MARKUP + class wxItemMarkupText* m_markupText; +#endif // SUPPORTS_MARKUP +}; + + +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +class BitmapChoiceRenderer : public wxDataViewCustomRenderer +{ +public: + BitmapChoiceRenderer(wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL + ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} + + bool SetValue(const wxVariant& value); + bool GetValue(wxVariant& value) const; + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override { return true; } + wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override; + bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override; + + void set_can_create_editor_ctrl_function(std::function can_create_fn) { can_create_editor_ctrl = can_create_fn; } + +private: + DataViewBitmapText m_value; + std::function can_create_editor_ctrl { nullptr }; +}; + + +#endif // slic3r_GUI_ExtraRenderers_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9d6b2b9cb8..a434e39fda 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -277,7 +277,11 @@ void ObjectList::create_objects_ctrl() // column ItemName(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps - AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(this), + BitmapTextRenderer* bmp_text_renderer = new BitmapTextRenderer(this); + bmp_text_renderer->set_can_create_editor_ctrl_function([this]() { + return m_objects_model->GetItemType(GetSelection()) & (itVolume | itObject); + }); + AppendColumn(new wxDataViewColumn(_L("Name"), bmp_text_renderer, colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column PrintableProperty (Icon) of the view control: @@ -285,11 +289,15 @@ void ObjectList::create_objects_ctrl() wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // column Extruder of the view control: - AppendColumn(new wxDataViewColumn(_(L("Extruder")), new BitmapChoiceRenderer(), + BitmapChoiceRenderer* bmp_choice_renderer = new BitmapChoiceRenderer(); + bmp_choice_renderer->set_can_create_editor_ctrl_function([this]() { + return m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer | itObject); + }); + AppendColumn(new wxDataViewColumn(_L("Extruder"), bmp_choice_renderer, colExtruder, 8*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE)); // column ItemEditing of the view control: - AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em, + AppendBitmapColumn(_L("Editing"), colEditing, wxDATAVIEW_CELL_INERT, 3*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // For some reason under OSX on 4K(5K) monitors in wxDataViewColumn constructor doesn't set width of column. diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index badaa7a041..a42073dd0a 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -1555,311 +1555,6 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo DeleteWarningIcon(child); } } -/* -} -} -*/ -//----------------------------------------------------------------------------- -// DataViewBitmapText -//----------------------------------------------------------------------------- - -wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) - -IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) - -// --------------------------------------------------------- -// BitmapTextRenderer -// --------------------------------------------------------- - -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, - int align /*= wxDVR_DEFAULT_ALIGNMENT*/): -wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) -{ - SetMode(mode); - SetAlignment(align); -} -#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - -BitmapTextRenderer::~BitmapTextRenderer() -{ -#ifdef SUPPORTS_MARKUP - if (m_markupText) - delete m_markupText; -#endif // SUPPORTS_MARKUP -} - -#ifdef SUPPORTS_MARKUP -void BitmapTextRenderer::EnableMarkup(bool enable) -{ - if (enable) - { - if (!m_markupText) - { - m_markupText = new wxItemMarkupText(wxString()); - } - } - else - { - if (m_markupText) - { - delete m_markupText; - m_markupText = nullptr; - } - } -} -#endif // SUPPORTS_MARKUP - -bool BitmapTextRenderer::SetValue(const wxVariant &value) -{ - m_value << value; - -#ifdef SUPPORTS_MARKUP - if (m_markupText) - m_markupText->SetMarkup(m_value.GetText()); -#endif // SUPPORTS_MARKUP - - return true; -} - -bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const -{ - return false; -} - -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY -wxString BitmapTextRenderer::GetAccessibleDescription() const -{ -#ifdef SUPPORTS_MARKUP - if (m_markupText) - return wxMarkupParser::Strip(m_text); -#endif // SUPPORTS_MARKUP - - return m_value.GetText(); -} -#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - -bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) -{ - int xoffset = 0; - - const wxBitmap& icon = m_value.GetBitmap(); - if (icon.IsOk()) - { -#ifdef __APPLE__ - wxSize icon_sz = icon.GetScaledSize(); -#else - wxSize icon_sz = icon.GetSize(); -#endif - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2); - xoffset = icon_sz.x + 4; - } - -#ifdef SUPPORTS_MARKUP - if (m_markupText) - { - int flags = 0; - - rect.x += xoffset; - m_markupText->Render(GetView(), *dc, rect, flags, GetEllipsizeMode()); - } - else -#endif // SUPPORTS_MARKUP - RenderText(m_value.GetText(), xoffset, rect, dc, state); - - return true; -} - -wxSize BitmapTextRenderer::GetSize() const -{ - if (!m_value.GetText().empty()) - { - wxSize size; -#ifdef SUPPORTS_MARKUP - if (m_markupText) - { - wxDataViewCtrl* const view = GetView(); - wxClientDC dc(view); - if (GetAttr().HasFont()) - dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); - - size = m_markupText->Measure(dc); - } - else -#endif // SUPPORTS_MARKUP - size = GetTextExtent(m_value.GetText()); - - int lines = m_value.GetText().Freq('\n') + 1; - size.SetHeight(size.GetHeight() * lines); - - if (m_value.GetBitmap().IsOk()) - size.x += m_value.GetBitmap().GetWidth() + 4; - return size; - } - return wxSize(80, 20); -} - - -wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) -{ - wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); - ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); - - if ( !(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itObject)) ) - return nullptr; - - DataViewBitmapText data; - data << value; - - m_was_unusable_symbol = false; - - wxPoint position = labelRect.GetPosition(); - if (data.GetBitmap().IsOk()) { - const int bmp_width = data.GetBitmap().GetWidth(); - position.x += bmp_width; - labelRect.SetWidth(labelRect.GetWidth() - bmp_width); - } - - wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), - position, labelRect.GetSize(), wxTE_PROCESS_ENTER); - text_editor->SetInsertionPointEnd(); - text_editor->SelectAll(); - - return text_editor; -} - -bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) -{ - wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); - if (!text_editor || text_editor->GetValue().IsEmpty()) - return false; - - std::string chosen_name = Slic3r::normalize_utf8_nfc(text_editor->GetValue().ToUTF8()); - const char* unusable_symbols = "<>:/\\|?*\""; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - m_was_unusable_symbol = true; - return false; - } - } - - // The icon can't be edited so get its old value and reuse it. - wxVariant valueOld; - GetView()->GetModel()->GetValue(valueOld, m_item, colName); - - DataViewBitmapText bmpText; - bmpText << valueOld; - - // But replace the text with the value entered by user. - bmpText.SetText(text_editor->GetValue()); - - value << bmpText; - return true; -} - -// ---------------------------------------------------------------------------- -// BitmapChoiceRenderer -// ---------------------------------------------------------------------------- - -bool BitmapChoiceRenderer::SetValue(const wxVariant& value) -{ - m_value << value; - return true; -} - -bool BitmapChoiceRenderer::GetValue(wxVariant& value) const -{ - value << m_value; - return true; -} - -bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) -{ - int xoffset = 0; - - const wxBitmap& icon = m_value.GetBitmap(); - if (icon.IsOk()) - { - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); - xoffset = icon.GetWidth() + 4; - - if (rect.height==0) - rect.height= icon.GetHeight(); - } - - RenderText(m_value.GetText(), xoffset, rect, dc, state); - - return true; -} - -wxSize BitmapChoiceRenderer::GetSize() const -{ - wxSize sz = GetTextExtent(m_value.GetText()); - - if (m_value.GetBitmap().IsOk()) - sz.x += m_value.GetBitmap().GetWidth() + 4; - - return sz; -} - - -wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) -{ - wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); - ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); - - if (!(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itLayer | itObject))) - return nullptr; - - std::vector icons = get_extruder_color_icons(); - if (icons.empty()) - return nullptr; - - DataViewBitmapText data; - data << value; - - auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, - labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), - 0, nullptr , wxCB_READONLY); - - int i=0; - for (wxBitmap* bmp : icons) { - if (i==0) { - c_editor->Append(_(L("default")), *bmp); - ++i; - } - - c_editor->Append(wxString::Format("%d", i), *bmp); - ++i; - } - c_editor->SetSelection(atoi(data.GetText().c_str())); - - // to avoid event propagation to other sidebar items - c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { - evt.StopPropagation(); - // FinishEditing grabs new selection and triggers config update. We better call - // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. - this->FinishEditing(); - }); - - return c_editor; -} - -bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) -{ - wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl; - int selection = c->GetSelection(); - if (selection < 0) - return false; - - DataViewBitmapText bmpText; - - bmpText.SetText(c->GetString(selection)); - bmpText.SetBitmap(c->GetItemBitmap(selection)); - - value << bmpText; - return true; -} } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index c8545e4a4f..12480139d2 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -4,7 +4,7 @@ #include #include -#include "GUI_App.hpp" +#include "ExtraRenderers.hpp" namespace Slic3r { @@ -15,157 +15,6 @@ namespace GUI { typedef double coordf_t; typedef std::pair t_layer_height_range; -// ---------------------------------------------------------------------------- -// DataViewBitmapText: helper class used by BitmapTextRenderer -// ---------------------------------------------------------------------------- - -class DataViewBitmapText : public wxObject -{ -public: - DataViewBitmapText( const wxString &text = wxEmptyString, - const wxBitmap& bmp = wxNullBitmap) : - m_text(text), - m_bmp(bmp) - { } - - DataViewBitmapText(const DataViewBitmapText &other) - : wxObject(), - m_text(other.m_text), - m_bmp(other.m_bmp) - { } - - void SetText(const wxString &text) { m_text = text; } - wxString GetText() const { return m_text; } - void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } - const wxBitmap &GetBitmap() const { return m_bmp; } - - bool IsSameAs(const DataViewBitmapText& other) const { - return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); - } - - bool operator==(const DataViewBitmapText& other) const { - return IsSameAs(other); - } - - bool operator!=(const DataViewBitmapText& other) const { - return !IsSameAs(other); - } - -private: - wxString m_text; - wxBitmap m_bmp; - - wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); -}; -DECLARE_VARIANT_OBJECT(DataViewBitmapText) - -// ---------------------------------------------------------------------------- -// BitmapTextRenderer -// ---------------------------------------------------------------------------- -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -class BitmapTextRenderer : public wxDataViewRenderer -#else -class BitmapTextRenderer : public wxDataViewCustomRenderer -#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -{ -public: - BitmapTextRenderer(wxWindow* parent, - wxDataViewCellMode mode = -#ifdef __WXOSX__ - wxDATAVIEW_CELL_INERT -#else - wxDATAVIEW_CELL_EDITABLE -#endif - - , int align = wxDVR_DEFAULT_ALIGNMENT -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - ); -#else - ) : - wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), - m_parent(parent) - { -#ifdef SUPPORTS_MARKUP - m_markupText = nullptr; -#endif // SUPPORTS_MARKUP - } -#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - - ~BitmapTextRenderer(); - -#ifdef SUPPORTS_MARKUP - void EnableMarkup(bool enable = true); -#endif // SUPPORTS_MARKUP - - bool SetValue(const wxVariant& value); - bool GetValue(wxVariant& value) const; -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY - virtual wxString GetAccessibleDescription() const override; -#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - - virtual bool Render(wxRect cell, wxDC* dc, int state) override; - virtual wxSize GetSize() const override; - - bool HasEditorCtrl() const override - { -#ifdef __WXOSX__ - return false; -#else - return true; -#endif - } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override; - bool GetValueFromEditorCtrl(wxWindow* ctrl, - wxVariant& value) override; - bool WasCanceled() const { return m_was_unusable_symbol; } - -private: - DataViewBitmapText m_value; - bool m_was_unusable_symbol{ false }; - wxWindow* m_parent{ nullptr }; - -#ifdef SUPPORTS_MARKUP - class wxItemMarkupText* m_markupText; -#endif // SUPPORTS_MARKUP -}; - - -// ---------------------------------------------------------------------------- -// BitmapChoiceRenderer -// ---------------------------------------------------------------------------- - -class BitmapChoiceRenderer : public wxDataViewCustomRenderer -{ -public: - BitmapChoiceRenderer(wxDataViewCellMode mode = -#ifdef __WXOSX__ - wxDATAVIEW_CELL_INERT -#else - wxDATAVIEW_CELL_EDITABLE -#endif - , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL - ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} - - bool SetValue(const wxVariant& value); - bool GetValue(wxVariant& value) const; - - virtual bool Render(wxRect cell, wxDC* dc, int state) override; - virtual wxSize GetSize() const override; - - bool HasEditorCtrl() const override { return true; } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override; - bool GetValueFromEditorCtrl(wxWindow* ctrl, - wxVariant& value) override; - -private: - DataViewBitmapText m_value; -}; - - // ---------------------------------------------------------------------------- // ObjectDataViewModelNode: a node inside ObjectDataViewModel // ---------------------------------------------------------------------------- From 2aa1c2776c5c6b96b0e60ace24fef8720c73a502 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 6 Aug 2020 10:15:34 +0200 Subject: [PATCH 256/826] GCodeViewer -> Estimated printing times shown in the legend --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 317 ++++++++++++++++++++++++++- src/slic3r/GUI/GCodeViewer.hpp | 3 + src/slic3r/GUI/GUI_Preview.cpp | 4 + src/slic3r/GUI/KBShortcutsDialog.cpp | 4 + 5 files changed, 320 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 75849b689a..609aecf635 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -63,6 +63,6 @@ #define TIME_ESTIMATE_DEFAULT 1 #define TIME_ESTIMATE_MODAL 2 #define TIME_ESTIMATE_LEGEND 3 -#define GCODE_VIEWER_TIME_ESTIMATE TIME_ESTIMATE_MODAL +#define GCODE_VIEWER_TIME_ESTIMATE TIME_ESTIMATE_LEGEND #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 7c5252070d..2b25a11bb5 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -411,6 +411,9 @@ void GCodeViewer::reset() #if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE m_time_statistics.reset(); #endif // GCODE_VIEWER_TIME_ESTIMATE +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal; +#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); @@ -1375,8 +1378,21 @@ void GCodeViewer::render_legend() const Line }; +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast(m_time_estimate_mode)]; + + float icon_size = ImGui::GetTextLineHeight(); + float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); + + auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, + bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f }, + std::function callback = nullptr) { + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); +#else auto append_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { float icon_size = ImGui::GetTextLineHeight(); +#endif // GCODE_VIEWER_TIME_ESTIMATE ImVec2 pos = ImGui::GetCursorScreenPos(); switch (type) { @@ -1438,9 +1454,49 @@ void GCodeViewer::render_legend() const if (callback != nullptr) { if (ImGui::MenuItem(label.c_str())) callback(); +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + + // to avoid the tooltip to change size when moving the mouse + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + float width = percent_bar_size * percent / max_percent; + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + } +#endif // GCODE_VIEWER_TIME_ESTIMATE } else imgui.text(label); + +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + if (!visible) + ImGui::PopStyleVar(); +#endif // GCODE_VIEWER_TIME_ESTIMATE }; auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -1461,6 +1517,34 @@ void GCodeViewer::render_legend() const } }; +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[0]); + ImGui::SameLine(offsets[0]); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[1]); + ImGui::SameLine(offsets[1]); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[2]); + ImGui::Separator(); + }; + + auto max_width = [](const std::vector& items, const std::string& title, float extra_size = 0.0f) { + float ret = ImGui::CalcTextSize(title.c_str()).x; + for (const std::string& item : items) { + ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x); + } + return ret; + }; + + auto calculate_offsets = [max_width](const std::vector& labels, const std::vector& times, + const std::array& titles, float extra_size = 0.0f) { + const ImGuiStyle& style = ImGui::GetStyle(); + std::array ret = { 0.0f, 0.0f }; + ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x; + ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x; + return ret; + }; +#endif // GCODE_VIEWER_TIME_ESTIMATE + auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { std::vector>> ret; ret.reserve(custom_gcode_per_print_z.size()); @@ -1508,10 +1592,83 @@ void GCodeViewer::render_legend() const return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; - // extrusion paths -> title +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + auto role_time_and_percent = [this, time_mode](ExtrusionRole role) { + auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); + return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); + }; + + // data used to properly align items in columns when showing time + std::array offsets = { 0.0f, 0.0f }; + std::vector labels; + std::vector times; + std::vector percents; + float max_percent = 0.0f; + + if (m_view_type == EViewType::FeatureType) { + // calculate offsets to align time/percentage data + for (size_t i = 0; i < m_roles.size(); ++i) { + ExtrusionRole role = m_roles[i]; + if (role < erCount) { + labels.push_back(_u8L(ExtrusionEntity::role_to_string(role))); + auto [time, percent] = role_time_and_percent(role); + times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); + percents.push_back(percent); + max_percent = std::max(max_percent, percent); + } + } + + offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size); + } + + // total estimated printing time section + if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { + ImGui::AlignTextToFramePadding(); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Estimated printing time") + ":"); + ImGui::SameLine(); + imgui.text(short_time(get_time_dhms(time_mode.time))); + + auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { + if (m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + ImGui::SameLine(0.0f, 10.0f); + if (imgui.button(label)) { + m_time_estimate_mode = mode; + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + }; + + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + break; + } + } + ImGui::Spacing(); + } +#endif // GCODE_VIEWER_TIME_ESTIMATE + + // extrusion paths section -> title switch (m_view_type) { +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + case EViewType::FeatureType: + { + append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets); + break; + } +#else case EViewType::FeatureType: { imgui.title(_u8L("Feature type")); break; } +#endif // GCODE_VIEWER_TIME_ESTIMATE case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } @@ -1522,28 +1679,38 @@ void GCodeViewer::render_legend() const default: { break; } } - // extrusion paths -> items + // extrusion paths section -> items switch (m_view_type) { case EViewType::FeatureType: { - for (ExtrusionRole role : m_roles) { + for (size_t i = 0; i < m_roles.size(); ++i) { + ExtrusionRole role = m_roles[i]; + if (role >= erCount) + continue; bool visible = is_visible(role); +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], labels[i], + visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() { +#else if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role]() { - if (role < erCount) { - m_extrusions.role_visibility_flags = is_visible(role) ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); + append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), + [this, role, visible]() { +#endif // GCODE_VIEWER_TIME_ESTIMATE + m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->update_preview_bottom_toolbar(); } - }); + ); +#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_LEGEND if (!visible) ImGui::PopStyleVar(); +#endif // GCODE_VIEWER_TIME_ESTIMATE } break; } @@ -1621,7 +1788,139 @@ void GCodeViewer::render_legend() const default: { break; } } - // travel paths +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + // partial estimated printing time section + if (m_view_type == EViewType::ColorPrint) { + using Times = std::pair; + using TimesList = std::vector>; + + // helper structure containig the data needed to render the time items + struct PartialTime + { + enum class EType : unsigned char + { + Print, + ColorChange, + Pause + }; + EType type; + int extruder_id; + Color color1; + Color color2; + Times times; + }; + using PartialTimes = std::vector; + + auto generate_partial_times = [this](const TimesList& times) { + PartialTimes items; + + std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; + int extruders_count = wxGetApp().extruders_edited_cnt(); + std::vector last_color(extruders_count); + for (int i = 0; i < extruders_count; ++i) { + last_color[i] = m_tool_colors[i]; + } + int last_extruder_id = 1; + for (const auto& time_rec : times) { + switch (time_rec.first) + { + case CustomGCode::PausePrint: + { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); + custom_gcode_per_print_z.erase(it); + } + break; + } + case CustomGCode::ColorChange: + { + auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); + if (it != custom_gcode_per_print_z.end()) { + items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second }); + items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); + last_color[it->extruder - 1] = decode_color(it->color); + last_extruder_id = it->extruder; + custom_gcode_per_print_z.erase(it); + } + else + items.push_back({ PartialTime::EType::Print, last_extruder_id, Color(), Color(), time_rec.second }); + + break; + } + default: { break; } + } + } + + return items; + }; + + auto append_color = [this, &imgui](const Color& color1, const Color& color2, std::array& offsets, const Times& times) { + imgui.text(_u8L("Color change")); + ImGui::SameLine(); + + float icon_size = ImGui::GetTextLineHeight(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6); + center.x += icon_size; + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(times.second - times.first))); + }; + + PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times); + if (!partial_times.empty()) { + labels.clear(); + times.clear(); + + for (const PartialTime& item : partial_times) { + switch (item.type) + { + case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; } + case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; } + case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; } + } + times.push_back(short_time(get_time_dhms(item.times.second))); + } + offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size); + + ImGui::Spacing(); + append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); + for (const PartialTime& item : partial_times) { + switch (item.type) + { + case PartialTime::EType::Print: + { + imgui.text(_u8L("Print")); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(item.times.second))); + ImGui::SameLine(offsets[1]); + imgui.text(short_time(get_time_dhms(item.times.first))); + break; + } + case PartialTime::EType::Pause: + { + imgui.text(_u8L("Pause")); + ImGui::SameLine(offsets[0]); + imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); + break; + } + case PartialTime::EType::ColorChange: + { + append_color(item.color1, item.color2, offsets, item.times); + break; + } + } + } + } + } +#endif // GCODE_VIEWER_TIME_ESTIMATE + + // travel paths section if (m_buffers[buffer_id(EMoveType::Travel)].visible) { switch (m_view_type) { @@ -1671,7 +1970,7 @@ void GCodeViewer::render_legend() const #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR }; - // options + // options section if (any_option_available()) { // title ImGui::Spacing(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 06cd1326fb..038b837b2b 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -350,6 +350,9 @@ private: bool m_time_estimate_enabled{ false }; #endif // GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL #endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + mutable PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal }; +#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS mutable Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 38657a460a..03e062d65d 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1458,7 +1458,11 @@ wxString Preview::get_option_type_string(OptionType type) const case OptionType::CustomGCodes: { return _L("Custom GCodes"); } case OptionType::Shells: { return _L("Shells"); } case OptionType::ToolMarker: { return _L("Tool marker"); } +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + case OptionType::Legend: { return _L("Legend/Estimated printing time"); } +#else case OptionType::Legend: { return _L("Legend"); } +#endif // GCODE_VIEWER_TIME_ESTIMATE #if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE case OptionType::TimeEstimate: { return _L("Estimated printing time"); } #endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 2c65673cd0..2d4b65a987 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -203,7 +203,11 @@ void KBShortcutsDialog::fill_shortcuts() { L("Arrow Down"), L("Lower Layer") }, { "U", L("Upper Layer") }, { "D", L("Lower Layer") }, +#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND + { "L", L("Show/Hide Legend/Estimated printing time") }, +#else { "L", L("Show/Hide Legend") }, +#endif // GCODE_VIEWER_TIME_ESTIMATE #if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT { "T", L("Show/Hide Estimated printing time") } #elif GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL From 1079d4644c04d075e783d3fb3ad59f13077fae32 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 6 Aug 2020 10:40:04 +0200 Subject: [PATCH 257/826] PhysicalPrinterDialog improvements : Printer device default name is changed to force the user to change it SavePresetDialog : Fixed OSX bug, when wxEVT_TEXT wasn't invoked after change selection in ComboBox --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 12 +++++++++--- src/slic3r/GUI/PresetComboBoxes.cpp | 5 +++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index f14f498010..12d1cd2871 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -161,13 +161,15 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - m_default_name = _L("My Printer Device"); + m_default_name = _L("Type here the name of your printer device"); + bool new_printer = true; if (printer_name.IsEmpty()) printer_name = m_default_name; else { std::string full_name = into_u8(printer_name); printer_name = from_u8(PhysicalPrinter::get_short_name(full_name)); + new_printer = false; } wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":"); @@ -206,7 +208,6 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config); build_printhost_settings(m_optgroup); - //m_optgroup->reload_config(); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btnOK = static_cast(this->FindWindowById(wxID_OK, this)); @@ -230,6 +231,11 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) SetSizer(topSizer); topSizer->SetSizeHints(this); + + if (new_printer) { + m_printer_name->SetFocus(); + m_printer_name->SelectAll(); + } } PhysicalPrinterDialog::~PhysicalPrinterDialog() @@ -494,7 +500,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) std::string renamed_from; // temporary save previous printer name if it was edited - if (m_printer.name != _u8L("My Printer Device") && + if (m_printer.name != into_u8(m_default_name) && m_printer.name != into_u8(printer_name)) renamed_from = m_printer.name; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 77bdb38122..da33ee51af 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1054,6 +1054,11 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_combo->Append(from_u8(value)); m_combo->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update(); }); +#ifdef __WXOSX__ + // Under OSX wxEVT_TEXT wasn't invoked after change selection in combobox, + // So process wxEVT_COMBOBOX too + m_combo->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { update(); }); +#endif //__WXOSX__ m_valid_label = new wxStaticText(m_parent, wxID_ANY, ""); m_valid_label->SetFont(wxGetApp().bold_font()); From 42f3bfb0f6a8df7f84a26c247fd1cd6620017200 Mon Sep 17 00:00:00 2001 From: Slic3rPE Date: Thu, 6 Aug 2020 10:56:14 +0200 Subject: [PATCH 258/826] Fixed a build under OSX --- src/slic3r/GUI/ExtraRenderers.cpp | 31 ++++++++++++++----------- src/slic3r/GUI/ExtraRenderers.hpp | 8 +++++-- src/slic3r/GUI/ObjectDataViewModel.cpp | 6 ----- src/slic3r/GUI/PresetComboBoxes.hpp | 1 + src/slic3r/GUI/UnsavedChangesDialog.cpp | 6 ++--- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 494bfee6a2..b49a3eb60e 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -4,9 +4,11 @@ #include "I18N.hpp" #include +#ifdef wxHAS_GENERIC_DATAVIEWCTRL #include "wx/generic/private/markuptext.h" #include "wx/generic/private/rowheightcache.h" #include "wx/generic/private/widthcalc.h" +#endif #if wxUSE_ACCESSIBILITY #include "wx/private/markupparser.h" #endif // wxUSE_ACCESSIBILITY @@ -40,29 +42,30 @@ wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) BitmapTextRenderer::~BitmapTextRenderer() { #ifdef SUPPORTS_MARKUP + #ifdef wxHAS_GENERIC_DATAVIEWCTRL if (m_markupText) delete m_markupText; + #endif //wxHAS_GENERIC_DATAVIEWCTRL #endif // SUPPORTS_MARKUP } #ifdef SUPPORTS_MARKUP void BitmapTextRenderer::EnableMarkup(bool enable) { - if (enable) - { +#ifdef wxHAS_GENERIC_DATAVIEWCTRL + if (enable) { if (!m_markupText) - { m_markupText = new wxItemMarkupText(wxString()); - } } - else - { - if (m_markupText) - { + else { + if (m_markupText) { delete m_markupText; m_markupText = nullptr; } } +#elseif + is_markupText = enable +#endif //wxHAS_GENERIC_DATAVIEWCTRL } #endif // SUPPORTS_MARKUP @@ -70,10 +73,10 @@ bool BitmapTextRenderer::SetValue(const wxVariant &value) { m_value << value; -#ifdef SUPPORTS_MARKUP +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) if (m_markupText) m_markupText->SetMarkup(m_value.GetText()); -#endif // SUPPORTS_MARKUP +#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL return true; } @@ -111,7 +114,7 @@ bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) xoffset = icon_sz.x + 4; } -#ifdef SUPPORTS_MARKUP +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) if (m_markupText) { int flags = 0; @@ -120,7 +123,7 @@ bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) m_markupText->Render(GetView(), *dc, rect, flags, GetEllipsizeMode()); } else -#endif // SUPPORTS_MARKUP +#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL RenderText(m_value.GetText(), xoffset, rect, dc, state); return true; @@ -131,7 +134,7 @@ wxSize BitmapTextRenderer::GetSize() const if (!m_value.GetText().empty()) { wxSize size; -#ifdef SUPPORTS_MARKUP +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) if (m_markupText) { wxDataViewCtrl* const view = GetView(); @@ -142,7 +145,7 @@ wxSize BitmapTextRenderer::GetSize() const size = m_markupText->Measure(dc); } else -#endif // SUPPORTS_MARKUP +#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL size = GetTextExtent(m_value.GetText()); int lines = m_value.GetText().Freq('\n') + 1; diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index 96cf349453..41f0d7d32e 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -77,9 +77,9 @@ public: wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), m_parent(parent) { -#ifdef SUPPORTS_MARKUP +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) m_markupText = nullptr; -#endif // SUPPORTS_MARKUP +#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL } #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING @@ -120,7 +120,11 @@ private: std::function can_create_editor_ctrl { nullptr }; #ifdef SUPPORTS_MARKUP + #ifdef wxHAS_GENERIC_DATAVIEWCTRL class wxItemMarkupText* m_markupText; + #elseif + bool is_markupText; + #endif #endif // SUPPORTS_MARKUP }; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index a42073dd0a..79fedfa527 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -9,12 +9,6 @@ #include #include -#include "wx/generic/private/markuptext.h" -#include "wx/generic/private/rowheightcache.h" -#include "wx/generic/private/widthcalc.h" -#if wxUSE_ACCESSIBILITY -#include "wx/private/markupparser.h" -#endif // wxUSE_ACCESSIBILITY namespace Slic3r { diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index f31b67fbe6..7f51f775ee 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -15,6 +15,7 @@ class ScalableButton; class wxBoxSizer; class wxComboBox; class wxStaticBitmap; +class wxRadioBox; namespace Slic3r { diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 46f086765c..4db41ffaa3 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -44,14 +44,14 @@ static std::string orange = "#ed6b21"; static void color_string(wxString& str, const std::string& color) { -#ifdef SUPPORTS_MARKUP +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) str = from_u8((boost::format("%2%") % color % into_u8(str)).str()); #endif } static void make_string_bold(wxString& str) { -#ifdef SUPPORTS_MARKUP +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) str = from_u8((boost::format("%1%") % into_u8(str)).str()); #endif } @@ -133,7 +133,7 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& ol void ModelNode::UpdateEnabling() { -#ifdef SUPPORTS_MARKUP +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) auto change_text_color = [](wxString& str, const std::string& clr_from, const std::string& clr_to) { std::string old_val = into_u8(str); From 0c6f66eca668a2fea9aaa0f4794c08dca2e1a17b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 6 Aug 2020 13:36:21 +0200 Subject: [PATCH 259/826] GCodeViewer -> Tweaks in legend rendering --- src/slic3r/GUI/GCodeViewer.cpp | 46 +++++++++++++++++++-------------- src/slic3r/GUI/ImGuiWrapper.cpp | 21 ++++++++------- src/slic3r/GUI/ImGuiWrapper.hpp | 5 +++- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2b25a11bb5..db40e96158 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1460,7 +1460,7 @@ void GCodeViewer::render_legend() const if (ImGui::IsItemHovered()) { if (!visible) ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROND); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); ImGui::BeginTooltip(); imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); ImGui::EndTooltip(); @@ -1503,7 +1503,7 @@ void GCodeViewer::render_legend() const auto append_range_item = [this, draw_list, &imgui, append_item](int i, float value, unsigned int decimals) { char buf[1024]; ::sprintf(buf, "%.*f", decimals, value); - append_item(EItemType::Hexagon, Range_Colors[i], buf); + append_item(EItemType::Rect, Range_Colors[i], buf); }; float step_size = range.step_size(); @@ -1644,12 +1644,12 @@ void GCodeViewer::render_legend() const { case PrintEstimatedTimeStatistics::ETimeMode::Normal: { - show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + show_mode_button(_u8L("Stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); break; } case PrintEstimatedTimeStatistics::ETimeMode::Stealth: { - show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + show_mode_button(_u8L("Normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); break; } } @@ -1690,13 +1690,13 @@ void GCodeViewer::render_legend() const continue; bool visible = is_visible(role); #if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND - append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], labels[i], + append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() { #else if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - append_item(EItemType::Hexagon, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), + append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), [this, role, visible]() { #endif // GCODE_VIEWER_TIME_ESTIMATE m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); @@ -1723,7 +1723,7 @@ void GCodeViewer::render_legend() const { // shows only extruders actually used for (unsigned char i : m_extruder_ids) { - append_item(EItemType::Hexagon, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); } break; } @@ -1735,20 +1735,20 @@ void GCodeViewer::render_legend() const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Hexagon, m_tool_colors.front(), _u8L("Default color")); + append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); } else { for (int i = items_cnt; i >= 0; --i) { // create label for color change item if (i == 0) { - append_item(EItemType::Hexagon, m_tool_colors[0], upto_label(cp_values.front().second.first)); + append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first)); break; } else if (i == items_cnt) { - append_item(EItemType::Hexagon, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); + append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second)); continue; } - append_item(EItemType::Hexagon, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); + append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first)); } } } @@ -1759,7 +1759,7 @@ void GCodeViewer::render_legend() const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode - append_item(EItemType::Hexagon, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); + append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); } else { for (int j = items_cnt; j >= 0; --j) { @@ -1767,17 +1767,17 @@ void GCodeViewer::render_legend() const std::string label = _u8L("Extruder") + " " + std::to_string(i + 1); if (j == 0) { label += " " + upto_label(cp_values.front().second.first); - append_item(EItemType::Hexagon, m_tool_colors[i], label); + append_item(EItemType::Rect, m_tool_colors[i], label); break; } else if (j == items_cnt) { label += " " + above_label(cp_values[j - 1].second.second); - append_item(EItemType::Hexagon, cp_values[j - 1].first, label); + append_item(EItemType::Rect, cp_values[j - 1].first, label); continue; } label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first); - append_item(EItemType::Hexagon, cp_values[j - 1].first, label); + append_item(EItemType::Rect, cp_values[j - 1].first, label); } } } @@ -1864,10 +1864,18 @@ void GCodeViewer::render_legend() const ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 pos = ImGui::GetCursorScreenPos(); pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6); - center.x += icon_size; - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6); + + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f })); + pos.x += icon_size; + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); + +// ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); +// draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6); +// center.x += icon_size; +// draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6); + ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second - times.first))); }; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 0ecfdaf389..00353e4937 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -50,11 +50,14 @@ static const std::map font_icons = { {ImGui::ErrorMarker , "flag_red" } }; -const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROND = { 0.133f, 0.133f, 0.133f, 0.8f }; -const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.133f, 0.133f, 0.133f, 0.8f }; +const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = { 0.233f, 0.233f, 0.233f, 1.0f }; +const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = { 0.433f, 0.433f, 0.433f, 1.8f }; +const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED; ImGuiWrapper::ImGuiWrapper() : m_glyph_ranges(nullptr) @@ -1031,7 +1034,7 @@ void ImGuiWrapper::init_style() // Window style.WindowRounding = 4.0f; - set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROND); + set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROUND); set_color(ImGuiCol_TitleBgActive, COL_ORANGE_DARK); // Generics @@ -1043,9 +1046,9 @@ void ImGuiWrapper::init_style() set_color(ImGuiCol_TextSelectedBg, COL_ORANGE_DARK); // Buttons - set_color(ImGuiCol_Button, COL_ORANGE_DARK); - set_color(ImGuiCol_ButtonHovered, COL_ORANGE_LIGHT); - set_color(ImGuiCol_ButtonActive, COL_ORANGE_LIGHT); + set_color(ImGuiCol_Button, COL_BUTTON_BACKGROUND); + set_color(ImGuiCol_ButtonHovered, COL_BUTTON_HOVERED); + set_color(ImGuiCol_ButtonActive, COL_BUTTON_ACTIVE); // Checkbox set_color(ImGuiCol_CheckMark, COL_ORANGE_LIGHT); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index f1387ec194..5484e46c6f 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -96,11 +96,14 @@ public: bool want_text_input() const; bool want_any_input() const; - static const ImVec4 COL_WINDOW_BACKGROND; static const ImVec4 COL_GREY_DARK; static const ImVec4 COL_GREY_LIGHT; static const ImVec4 COL_ORANGE_DARK; static const ImVec4 COL_ORANGE_LIGHT; + static const ImVec4 COL_WINDOW_BACKGROUND; + static const ImVec4 COL_BUTTON_BACKGROUND; + static const ImVec4 COL_BUTTON_HOVERED; + static const ImVec4 COL_BUTTON_ACTIVE; private: void init_font(bool compress); From 41b1dc3d80d43f4a2df141edd3d4b5bd1ee24ce1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 6 Aug 2020 14:05:42 +0200 Subject: [PATCH 260/826] Fix of custom supports 3MF loading Multiple-part objects were not handled correctly --- src/libslic3r/Format/3mf.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 3612e6898c..59dc85a0ae 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1878,10 +1878,11 @@ namespace Slic3r { volume->calculate_convex_hull(); // recreate custom supports from previously loaded attribute - assert(geometry.custom_supports.size() == triangles_count); for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]); + size_t index = src_start_id/3 + i; + assert(index < geometry.custom_supports.size()); + if (! geometry.custom_supports[index].empty()) + volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); } // apply the remaining volume's metadata From bcd998f9f1169f8e41e29f7615a0f4fe5d887014 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 6 Aug 2020 14:25:00 +0200 Subject: [PATCH 261/826] GCodeViewer -> New set of colors for toolpaths --- src/slic3r/GUI/GCodeViewer.cpp | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index db40e96158..ad84184015 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -218,23 +218,42 @@ void GCodeViewer::SequentialView::Marker::render() const const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.75f, 0.75f, 0.75f }, // erNone - { 1.00f, 1.00f, 0.40f }, // erPerimeter - { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter - { 0.00f, 0.00f, 1.00f }, // erOverhangPerimeter + { 1.00f, 0.90f, 0.43f }, // erPerimeter + { 1.00f, 0.49f, 0.22f }, // erExternalPerimeter + { 0.12f, 0.12f, 1.00f }, // erOverhangPerimeter { 0.69f, 0.19f, 0.16f }, // erInternalInfill - { 0.84f, 0.20f, 0.84f }, // erSolidInfill - { 1.00f, 0.10f, 0.10f }, // erTopSolidInfill - { 1.00f, 0.55f, 0.41f }, // erIroning - { 0.60f, 0.60f, 1.00f }, // erBridgeInfill + { 0.59f, 0.33f, 0.80f }, // erSolidInfill + { 0.94f, 0.33f, 0.33f }, // erTopSolidInfill + { 1.00f, 0.55f, 0.41f }, // erIroning + { 0.30f, 0.50f, 0.73f }, // erBridgeInfill { 1.00f, 1.00f, 1.00f }, // erGapFill - { 0.52f, 0.48f, 0.13f }, // erSkirt + { 0.00f, 0.53f, 0.43f }, // erSkirt { 0.00f, 1.00f, 0.00f }, // erSupportMaterial { 0.00f, 0.50f, 0.00f }, // erSupportMaterialInterface { 0.70f, 0.89f, 0.67f }, // erWipeTower - { 0.16f, 0.80f, 0.58f }, // erCustom + { 0.37f, 0.82f, 0.58f }, // erCustom { 0.00f, 0.00f, 0.00f } // erMixed }}; +//const std::vector GCodeViewer::Extrusion_Role_Colors {{ +// { 0.75f, 0.75f, 0.75f }, // erNone +// { 1.00f, 1.00f, 0.40f }, // erPerimeter +// { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter +// { 0.00f, 0.00f, 1.00f }, // erOverhangPerimeter +// { 0.69f, 0.19f, 0.16f }, // erInternalInfill +// { 0.84f, 0.20f, 0.84f }, // erSolidInfill +// { 1.00f, 0.10f, 0.10f }, // erTopSolidInfill +// { 1.00f, 0.55f, 0.41f }, // erIroning +// { 0.60f, 0.60f, 1.00f }, // erBridgeInfill +// { 1.00f, 1.00f, 1.00f }, // erGapFill +// { 0.52f, 0.48f, 0.13f }, // erSkirt +// { 0.00f, 1.00f, 0.00f }, // erSupportMaterial +// { 0.00f, 0.50f, 0.00f }, // erSupportMaterialInterface +// { 0.70f, 0.89f, 0.67f }, // erWipeTower +// { 0.16f, 0.80f, 0.58f }, // erCustom +// { 0.00f, 0.00f, 0.00f } // erMixed +//}}; + const std::vector GCodeViewer::Options_Colors {{ { 1.00f, 0.00f, 1.00f }, // Retractions { 0.00f, 1.00f, 1.00f }, // Unretractions From 4913378dbe5d5e3377a56690d836813a87102d66 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 6 Aug 2020 15:54:12 +0200 Subject: [PATCH 262/826] Changed signature of the BitmapTextRenderer + Added experimental code for the rendering of the "markuped" text --- src/slic3r/GUI/ExtraRenderers.cpp | 30 ++++++++++++++++--------- src/slic3r/GUI/ExtraRenderers.hpp | 24 ++++++++------------ src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- src/slic3r/GUI/Search.cpp | 2 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 17 ++++++-------- src/slic3r/GUI/UnsavedChangesDialog.hpp | 2 +- 6 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index b49a3eb60e..046b8fa165 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -49,9 +49,9 @@ BitmapTextRenderer::~BitmapTextRenderer() #endif // SUPPORTS_MARKUP } -#ifdef SUPPORTS_MARKUP void BitmapTextRenderer::EnableMarkup(bool enable) { +#ifdef SUPPORTS_MARKUP #ifdef wxHAS_GENERIC_DATAVIEWCTRL if (enable) { if (!m_markupText) @@ -63,20 +63,30 @@ void BitmapTextRenderer::EnableMarkup(bool enable) m_markupText = nullptr; } } -#elseif - is_markupText = enable +#else + is_markupText = enable; #endif //wxHAS_GENERIC_DATAVIEWCTRL -} #endif // SUPPORTS_MARKUP +} bool BitmapTextRenderer::SetValue(const wxVariant &value) { m_value << value; -#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) +#ifdef SUPPORTS_MARKUP +#ifdef wxHAS_GENERIC_DATAVIEWCTRL if (m_markupText) m_markupText->SetMarkup(m_value.GetText()); -#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL +#else +#if defined(__WXGTK__) + GValue gvalue = G_VALUE_INIT; + g_value_init(&gvalue, G_TYPE_STRING); + g_value_set_string(&gvalue, wxGTK_CONV_FONT(str.GetText(), GetOwner()->GetOwner()->GetFont())); + g_object_set_property(G_OBJECT(m_renderer/*.GetText()*/), is_markupText ? "markup" : "text", &gvalue); + g_value_unset(&gvalue); +#endif // __WXGTK__ +#endif // wxHAS_GENERIC_DATAVIEWCTRL +#endif // SUPPORTS_MARKUP return true; } @@ -117,10 +127,8 @@ bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) #if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) if (m_markupText) { - int flags = 0; - rect.x += xoffset; - m_markupText->Render(GetView(), *dc, rect, flags, GetEllipsizeMode()); + m_markupText->Render(GetView(), *dc, rect, 0, GetEllipsizeMode()); } else #endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL @@ -161,7 +169,7 @@ wxSize BitmapTextRenderer::GetSize() const wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) { - if (!can_create_editor_ctrl()) + if (can_create_editor_ctrl && !can_create_editor_ctrl()) return nullptr; DataViewBitmapText data; @@ -261,7 +269,7 @@ wxSize BitmapChoiceRenderer::GetSize() const wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) { - if (!can_create_editor_ctrl()) + if (can_create_editor_ctrl && !can_create_editor_ctrl()) return nullptr; std::vector icons = get_extruder_color_icons(); diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index 41f0d7d32e..4c1fb09dec 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -61,7 +61,7 @@ class BitmapTextRenderer : public wxDataViewCustomRenderer #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING { public: - BitmapTextRenderer(wxWindow* parent, + BitmapTextRenderer(bool use_markup = false, wxDataViewCellMode mode = #ifdef __WXOSX__ wxDATAVIEW_CELL_INERT @@ -73,24 +73,19 @@ public: #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING ); #else - ) : - wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), - m_parent(parent) + ) : + wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) { -#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) - m_markupText = nullptr; -#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL + EnableMarkup(use_markup); } #endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING ~BitmapTextRenderer(); -#ifdef SUPPORTS_MARKUP void EnableMarkup(bool enable = true); -#endif // SUPPORTS_MARKUP - bool SetValue(const wxVariant& value); - bool GetValue(wxVariant& value) const; + bool SetValue(const wxVariant& value) override; + bool GetValue(wxVariant& value) const override; #if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY virtual wxString GetAccessibleDescription() const override; #endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING @@ -115,15 +110,14 @@ public: private: DataViewBitmapText m_value; bool m_was_unusable_symbol{ false }; - wxWindow* m_parent{ nullptr }; std::function can_create_editor_ctrl { nullptr }; #ifdef SUPPORTS_MARKUP #ifdef wxHAS_GENERIC_DATAVIEWCTRL - class wxItemMarkupText* m_markupText; - #elseif - bool is_markupText; + class wxItemMarkupText* m_markupText { nullptr };; + #else + bool is_markupText {false}; #endif #endif // SUPPORTS_MARKUP }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index bbc0f5760b..7648f7d233 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -277,7 +277,7 @@ void ObjectList::create_objects_ctrl() // column ItemName(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps - BitmapTextRenderer* bmp_text_renderer = new BitmapTextRenderer(this); + BitmapTextRenderer* bmp_text_renderer = new BitmapTextRenderer(); bmp_text_renderer->set_can_create_editor_ctrl_function([this]() { return m_objects_model->GetItemType(GetSelection()) & (itVolume | itObject); }); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index d13ef469f1..da9c8fe25d 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -323,7 +323,7 @@ const Option& OptionsSearcher::get_option(size_t pos_in_filter) const const Option& OptionsSearcher::get_option(const std::string& opt_key) const { - auto it = std::upper_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key) })); + auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key) })); assert(it != options.end()); return options[it - options.begin()]; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 4db41ffaa3..1bdc2959b7 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -35,7 +35,7 @@ static const std::map type_icon_names = { {Preset::TYPE_SLA_PRINT, "cog" }, {Preset::TYPE_FILAMENT, "spool" }, {Preset::TYPE_SLA_MATERIAL, "resin" }, - {Preset::TYPE_PRINTER, "sla_printer" }, + {Preset::TYPE_PRINTER, "printer" }, }; static std::string black = "#000000"; @@ -427,7 +427,7 @@ wxString UnsavedChangesModel::GetColumnType(unsigned int col) const //------------------------------------------ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) - : DPIDialog(NULL, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SetBackgroundColour(bgr_clr); @@ -440,15 +440,12 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) m_tree->AssociateModel(m_tree_model); m_tree_model->SetAssociatedControl(m_tree); - m_tree->AppendToggleColumn(/*L"\u2714"*/"", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em, wxALIGN_NOT);//2610,11,12 //2714 + m_tree->AppendColumn(new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE)); - BitmapTextRenderer* renderer = new BitmapTextRenderer(m_tree); -#ifdef SUPPORTS_MARKUP - renderer->EnableMarkup(); -#endif - m_tree->AppendColumn(new wxDataViewColumn("", renderer, UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE)); - m_tree->AppendColumn(new wxDataViewColumn("Old value", renderer, UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); - m_tree->AppendColumn(new wxDataViewColumn("New value", renderer, UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); + m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);//2610,11,12 //2714 + + m_tree->AppendColumn(new wxDataViewColumn("Old value", new BitmapTextRenderer(true), UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); + m_tree->AppendColumn(new wxDataViewColumn("New value", new BitmapTextRenderer(true), UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index c4a02d7bcc..3d5867ea44 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -134,8 +134,8 @@ class UnsavedChangesModel : public wxDataViewModel public: enum { - colToggle, colIconText, + colToggle, colOldValue, colNewValue, colMax From 3688ae350b3b5728629fa5f8de3e37f95548ea68 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 6 Aug 2020 16:28:12 +0200 Subject: [PATCH 263/826] Added missed includes for GTK --- src/slic3r/GUI/ExtraRenderers.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 046b8fa165..d6a1d7a99d 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -9,6 +9,12 @@ #include "wx/generic/private/rowheightcache.h" #include "wx/generic/private/widthcalc.h" #endif + +#ifdef __WXGTK__ +#include "wx/gtk/private.h" +#include "wx/gtk/private/value.h" +#endif + #if wxUSE_ACCESSIBILITY #include "wx/private/markupparser.h" #endif // wxUSE_ACCESSIBILITY From 94efb5185bc1444a6adea9ad6f5ec9ca369f5e6e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 6 Aug 2020 16:54:14 +0200 Subject: [PATCH 264/826] One more experiment --- src/slic3r/GUI/ExtraRenderers.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index d6a1d7a99d..e4c09dc268 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -9,12 +9,12 @@ #include "wx/generic/private/rowheightcache.h" #include "wx/generic/private/widthcalc.h" #endif - +/* #ifdef __WXGTK__ #include "wx/gtk/private.h" #include "wx/gtk/private/value.h" #endif - +*/ #if wxUSE_ACCESSIBILITY #include "wx/private/markupparser.h" #endif // wxUSE_ACCESSIBILITY @@ -83,14 +83,16 @@ bool BitmapTextRenderer::SetValue(const wxVariant &value) #ifdef wxHAS_GENERIC_DATAVIEWCTRL if (m_markupText) m_markupText->SetMarkup(m_value.GetText()); + /* #else #if defined(__WXGTK__) - GValue gvalue = G_VALUE_INIT; + GValue gvalue = G_VALUE_INIT; g_value_init(&gvalue, G_TYPE_STRING); g_value_set_string(&gvalue, wxGTK_CONV_FONT(str.GetText(), GetOwner()->GetOwner()->GetFont())); - g_object_set_property(G_OBJECT(m_renderer/*.GetText()*/), is_markupText ? "markup" : "text", &gvalue); + g_object_set_property(G_OBJECT(m_renderer/ *.GetText()* /), is_markupText ? "markup" : "text", &gvalue); g_value_unset(&gvalue); #endif // __WXGTK__ + */ #endif // wxHAS_GENERIC_DATAVIEWCTRL #endif // SUPPORTS_MARKUP From b6746a3937f89a27092e3119c38c1c2cbd9e373b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 Aug 2020 10:00:54 +0200 Subject: [PATCH 265/826] PhysicalPrinterDialog : Incompatible presets extracted to the separate group --- src/slic3r/GUI/PresetComboBoxes.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index da33ee51af..33ae4f54e9 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -179,7 +179,9 @@ void PresetComboBox::update(std::string select_preset_name) const std::deque& presets = m_collection->get_presets(); - std::map> nonsys_presets; + std::map> nonsys_presets; + std::map incomp_presets; + wxString selected = ""; if (!presets.front().is_visible) set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); @@ -206,15 +208,15 @@ void PresetComboBox::update(std::string select_preset_name) wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); - if (preset.is_default || preset.is_system) { - int item_id = Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); + if (!is_enabled) + incomp_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); + else if (preset.is_default || preset.is_system) + { + Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), *bmp); validate_selection(preset.name == select_preset_name); } else { - std::pair pair(bmp, is_enabled); nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), std::pair(bmp, is_enabled)); if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); @@ -233,6 +235,13 @@ void PresetComboBox::update(std::string select_preset_name) validate_selection(it->first == selected); } } + if (!incomp_presets.empty()) + { + set_label_marker(Append(separator(L("Incompatible presets")), wxNullBitmap)); + for (std::map::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) { + set_label_marker(Append(it->first, *it->second), LABEL_ITEM_DISABLED); + } + } update_selection(); Thaw(); From 08576ad7462f4724267fae4fcf5062e83b8b8586 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 Aug 2020 10:21:20 +0200 Subject: [PATCH 266/826] UnsavedChangesDialog :: Toggle column is on first place now --- src/slic3r/GUI/ExtraRenderers.cpp | 6 +++--- src/slic3r/GUI/UnsavedChangesDialog.cpp | 4 +--- src/slic3r/GUI/UnsavedChangesDialog.hpp | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index e4c09dc268..2915d498c0 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -159,14 +159,14 @@ wxSize BitmapTextRenderer::GetSize() const dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); size = m_markupText->Measure(dc); + + int lines = m_value.GetText().Freq('\n') + 1; + size.SetHeight(size.GetHeight() * lines); } else #endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL size = GetTextExtent(m_value.GetText()); - int lines = m_value.GetText().Freq('\n') + 1; - size.SetHeight(size.GetHeight() * lines); - if (m_value.GetBitmap().IsOk()) size.x += m_value.GetBitmap().GetWidth() + 4; return size; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 1bdc2959b7..ab2c36ebdf 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -440,10 +440,8 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) m_tree->AssociateModel(m_tree_model); m_tree_model->SetAssociatedControl(m_tree); - m_tree->AppendColumn(new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE)); - m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);//2610,11,12 //2714 - + m_tree->AppendColumn(new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE)); m_tree->AppendColumn(new wxDataViewColumn("Old value", new BitmapTextRenderer(true), UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); m_tree->AppendColumn(new wxDataViewColumn("New value", new BitmapTextRenderer(true), UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 3d5867ea44..c4a02d7bcc 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -134,8 +134,8 @@ class UnsavedChangesModel : public wxDataViewModel public: enum { - colIconText, colToggle, + colIconText, colOldValue, colNewValue, colMax From afcc14861f2c2a06f8d802c41af110e2e5476b1e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 7 Aug 2020 10:42:25 +0200 Subject: [PATCH 267/826] Fix build on GCC (missing forward declaration) --- src/slic3r/GUI/PresetComboBoxes.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index f31b67fbe6..7f51f775ee 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -15,6 +15,7 @@ class ScalableButton; class wxBoxSizer; class wxComboBox; class wxStaticBitmap; +class wxRadioBox; namespace Slic3r { From c4569c93f2afc988de3c8b11fd1c9ce51af41b55 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 Aug 2020 15:09:58 +0200 Subject: [PATCH 268/826] UnsavedChangesDialog: Fixed get_string_from_enum() in respect to the InfillPattern --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 47 ++++++++++++++++--------- src/slic3r/GUI/UnsavedChangesDialog.hpp | 11 ------ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index ab2c36ebdf..3f85ac742f 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -2,11 +2,9 @@ #include #include +#include #include #include -#include - -#include "wx/dataview.h" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" @@ -15,8 +13,8 @@ #include "Tab.hpp" #include "ObjectDataViewModel.hpp" -#define FTS_FUZZY_MATCH_IMPLEMENTATION -#include "fts_fuzzy_match.h" +//#define FTS_FUZZY_MATCH_IMPLEMENTATION +//#include "fts_fuzzy_match.h" #include "BitmapCache.hpp" @@ -195,8 +193,10 @@ ModelNode* UnsavedChangesModel::AddOption(ModelNode* group_node, wxString option { ModelNode* option = new ModelNode(group_node, option_name, old_value, new_value); group_node->Append(option); - ItemAdded(wxDataViewItem((void*)group_node), wxDataViewItem((void*)option)); + wxDataViewItem group_item = wxDataViewItem((void*)group_node); + ItemAdded(group_item, wxDataViewItem((void*)option)); + m_ctrl->Expand(group_item); return option; } @@ -204,9 +204,7 @@ ModelNode* UnsavedChangesModel::AddOptionWithGroup(ModelNode* category_node, wxS { ModelNode* group_node = new ModelNode(category_node, group_name); category_node->Append(group_node); - wxDataViewItem group_item = wxDataViewItem((void*)group_node); - ItemAdded(wxDataViewItem((void*)category_node), group_item); - m_ctrl->Expand(group_item); + ItemAdded(wxDataViewItem((void*)category_node), wxDataViewItem((void*)group_node)); return AddOption(group_node, option_name, old_value, new_value); } @@ -435,16 +433,19 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) int border = 10; int em = em_unit(); - m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 30), wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT); + m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 30), wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES); m_tree_model = new UnsavedChangesModel(this); m_tree->AssociateModel(m_tree_model); m_tree_model->SetAssociatedControl(m_tree); - m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);//2610,11,12 //2714 - m_tree->AppendColumn(new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE)); + m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE/*, 6 * em*/);//2610,11,12 //2714 + wxDataViewColumn* icon_text_clmn = new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); + m_tree->AppendColumn(icon_text_clmn); m_tree->AppendColumn(new wxDataViewColumn("Old value", new BitmapTextRenderer(true), UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); m_tree->AppendColumn(new wxDataViewColumn("New value", new BitmapTextRenderer(true), UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); + m_tree->SetExpanderColumn(icon_text_clmn); + m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); @@ -498,11 +499,25 @@ void UnsavedChangesDialog::close(Action action) } template -wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config) +wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false) { - const std::vector& names = config.def()->options.at(opt_key).enum_labels;//ConfigOptionEnum::get_enum_names(); + const ConfigOptionDef& def = config.def()->options.at(opt_key); + const std::vector& names = def.enum_labels;//ConfigOptionEnum::get_enum_names(); T val = config.option>(opt_key)->value; - return from_u8(_u8L(names[static_cast(val)])); + + // Each infill doesn't use all list of infill declared in PrintConfig.hpp. + // So we should "convert" val to the correct one + if (is_infill) { + for (auto key_val : *def.enum_keys_map) + if ((int)key_val.second == (int)val) { + auto it = std::find(def.enum_values.begin(), def.enum_values.end(), key_val.first); + if (it == def.enum_values.end()) + return ""; + return from_u8(_utf8(names[it - def.enum_values.begin()])); + } + return _L("Undef"); + } + return from_u8(_utf8(names[static_cast(val)])); } static wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config) @@ -575,7 +590,7 @@ static wxString get_string_value(const std::string& opt_key, const DynamicPrintC if (opt_key == "top_fill_pattern" || opt_key == "bottom_fill_pattern" || opt_key == "fill_pattern") - return get_string_from_enum(opt_key, config); + return get_string_from_enum(opt_key, config, true); if (opt_key == "gcode_flavor") return get_string_from_enum(opt_key, config); if (opt_key == "ironing_type") diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index c4a02d7bcc..8afd978961 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -1,19 +1,8 @@ #ifndef slic3r_UnsavedChangesDialog_hpp_ #define slic3r_UnsavedChangesDialog_hpp_ -#include -#include - -#include -#include -#include #include -#include - -#include -#include - #include "GUI_Utils.hpp" #include "wxExtensions.hpp" #include "libslic3r/Preset.hpp" From 64001c0fe5794dd747cb388b302cb8397e741ecc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 7 Aug 2020 15:30:08 +0200 Subject: [PATCH 269/826] GCodeProcessor -> Fixed export of estimated time to gcode filename --- src/libslic3r/GCode.cpp | 12 ++++++++++++ src/libslic3r/GCode/GCodeProcessor.hpp | 3 +++ src/libslic3r/Print.cpp | 8 +++----- src/libslic3r/Print.hpp | 2 +- src/libslic3r/Utils.hpp | 19 +++++++++++++++++++ src/slic3r/GUI/3DBed.cpp | 2 ++ 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 915694d12f..dec0a7a197 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -715,6 +715,17 @@ std::vector>> GCode::collec } #if ENABLE_GCODE_VIEWER +// free functions called by GCode::do_export() +namespace DoExport { + static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) + { + const GCodeProcessor::Result& result = processor.get_result(); + print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? + get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; + } +} // namespace DoExport + void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* result, ThumbnailsGeneratorCallback thumbnail_cb) #else void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb) @@ -777,6 +788,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ #if ENABLE_GCODE_VIEWER m_processor.process_file(path_tmp); + DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); if (result != nullptr) *result = std::move(m_processor.extract_result()); #else diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 101c320db8..e9c71038fe 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -317,6 +317,9 @@ namespace Slic3r { void apply_config(const PrintConfig& config); void apply_config(const DynamicPrintConfig& config); void enable_stealth_time_estimator(bool enabled); + bool is_stealth_time_estimator_enabled() const { + return m_time_processor.machines[static_cast(ETimeMode::Stealth)].enabled; + } void enable_producers(bool enabled) { m_producers_enabled = enabled; } void reset(); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 1b1aad2578..7924f18906 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2190,13 +2190,11 @@ std::string Print::output_filename(const std::string &filename_base) const DynamicConfig PrintStatistics::config() const { DynamicConfig config; -#if !ENABLE_GCODE_VIEWER std::string normal_print_time = short_time(this->estimated_normal_print_time); std::string silent_print_time = short_time(this->estimated_silent_print_time); - config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); - config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); - config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); -#endif // !ENABLE_GCODE_VIEWER + config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); config.set_key_value("used_filament", new ConfigOptionFloat(this->total_used_filament / 1000.)); config.set_key_value("extruded_volume", new ConfigOptionFloat(this->total_extruded_volume)); config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost)); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 73efbf49a1..da98bba5f2 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -303,9 +303,9 @@ private: struct PrintStatistics { PrintStatistics() { clear(); } -#if !ENABLE_GCODE_VIEWER std::string estimated_normal_print_time; std::string estimated_silent_print_time; +#if !ENABLE_GCODE_VIEWER std::vector> estimated_normal_custom_gcode_print_times; std::vector> estimated_silent_custom_gcode_print_times; #endif // !ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 5cdf75037c..f7ff29b7e0 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -337,6 +337,25 @@ inline std::string get_time_dhms(float time_in_secs) return buffer; } +inline std::string get_time_dhm(float time_in_secs) +{ + int days = (int)(time_in_secs / 86400.0f); + time_in_secs -= (float)days * 86400.0f; + int hours = (int)(time_in_secs / 3600.0f); + time_in_secs -= (float)hours * 3600.0f; + int minutes = (int)(time_in_secs / 60.0f); + + char buffer[64]; + if (days > 0) + ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs); + else if (hours > 0) + ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs); + else if (minutes > 0) + ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); + + return buffer; +} + } // namespace Slic3r #if WIN32 diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index b13f774dab..cbb74411e0 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -147,6 +147,8 @@ void Bed3D::Axes::set_stem_length(float length) } #else Bed3D::Axes::Axes() +: origin(Vec3d::Zero()) +, length(25.0 * Vec3d::Ones()) { m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) From f9e47b27028b7f513cea29843249894ae105ebc7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sat, 8 Aug 2020 17:03:20 +0200 Subject: [PATCH 270/826] Code refactoring: AppConfig.cpp(hpp) are removed from the GUI to libslic3r --- src/PrusaSlicer.cpp | 2 +- src/{slic3r/GUI => libslic3r}/AppConfig.cpp | 12 +++++++++--- src/{slic3r/GUI => libslic3r}/AppConfig.hpp | 3 ++- src/libslic3r/CMakeLists.txt | 2 ++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PresetBundle.hpp | 3 +-- src/slic3r/CMakeLists.txt | 2 -- src/slic3r/Config/Snapshot.cpp | 1 - src/slic3r/GUI/3DScene.cpp | 2 +- src/slic3r/GUI/Camera.cpp | 2 +- src/slic3r/GUI/ConfigWizard_private.hpp | 1 - src/slic3r/GUI/GUI_App.cpp | 9 +++++++-- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 1 - src/slic3r/GUI/MainFrame.cpp | 1 - src/slic3r/GUI/Mouse3DController.cpp | 1 - src/slic3r/GUI/Preferences.cpp | 2 +- src/slic3r/GUI/PrintHostDialogs.cpp | 2 +- 17 files changed, 27 insertions(+), 21 deletions(-) rename src/{slic3r/GUI => libslic3r}/AppConfig.cpp (97%) rename src/{slic3r/GUI => libslic3r}/AppConfig.hpp (99%) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index a0422f5fa0..c37afb805d 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -44,6 +44,7 @@ #include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Format/SL1.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/AppConfig.hpp" #include "PrusaSlicer.hpp" @@ -52,7 +53,6 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/InstanceCheck.hpp" - #include "slic3r/GUI/AppConfig.hpp" #include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/Plater.hpp" #endif /* SLIC3R_GUI */ diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/libslic3r/AppConfig.cpp similarity index 97% rename from src/slic3r/GUI/AppConfig.cpp rename to src/libslic3r/AppConfig.cpp index 93589e536e..8b41bd2716 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -15,8 +15,8 @@ #include #include -#include -#include "I18N.hpp" +//#include +//#include "I18N.hpp" namespace Slic3r { @@ -110,7 +110,7 @@ void AppConfig::set_defaults() erase("", "object_settings_size"); } -void AppConfig::load() +std::string AppConfig::load() { // 1) Read the complete config file into a boost::property_tree. namespace pt = boost::property_tree; @@ -120,10 +120,15 @@ void AppConfig::load() pt::read_ini(ifs, tree); } catch (pt::ptree_error& ex) { // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + // ! But to avoid the use of _utf8 (related to use of wxWidgets) + // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty + /* throw std::runtime_error( _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) + "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); + */ + return ex.what(); } // 2) Parse the property_tree, extract the sections and key / value pairs. @@ -169,6 +174,7 @@ void AppConfig::load() // Override missing or keys with their defaults. this->set_defaults(); m_dirty = false; + return ""; } void AppConfig::save() diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/libslic3r/AppConfig.hpp similarity index 99% rename from src/slic3r/GUI/AppConfig.hpp rename to src/libslic3r/AppConfig.hpp index 1e90d32e00..ffd1b9fdf5 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -29,7 +29,8 @@ public: void set_defaults(); // Load the slic3r.ini from a user profile directory (or a datadir, if configured). - void load(); + // return error string or empty strinf + std::string load(); // Store the slic3r.ini into a user profile directory (or a datadir, if configured). void save(); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index f11a6e7c2a..290b8953cc 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -151,6 +151,8 @@ add_library(libslic3r STATIC Preset.hpp PresetBundle.cpp PresetBundle.hpp + AppConfig.cpp + AppConfig.hpp Print.cpp Print.hpp PrintBase.cpp diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 44e6675d18..7176ca81a4 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1,7 +1,7 @@ #include #include "Preset.hpp" -#include "slic3r/GUI/AppConfig.hpp" +#include "AppConfig.hpp" #ifdef _MSC_VER #define WIN32_LEAN_AND_MEAN diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 2906584d33..567a12331f 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -2,13 +2,12 @@ #define slic3r_PresetBundle_hpp_ #include "Preset.hpp" +#include "AppConfig.hpp" #include #include #include -#include "slic3r/GUI/AppConfig.hpp" - namespace Slic3r { // Bundle of Print + Filament + Printer presets. diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 849460792b..f8598cea08 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -12,8 +12,6 @@ set(SLIC3R_GUI_SOURCES GUI/SysInfoDialog.hpp GUI/KBShortcutsDialog.cpp GUI/KBShortcutsDialog.hpp - GUI/AppConfig.cpp - GUI/AppConfig.hpp GUI/BackgroundSlicingProcess.cpp GUI/BackgroundSlicingProcess.hpp GUI/BitmapCache.cpp diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index f7d313418d..30596b614d 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -1,5 +1,4 @@ #include "Snapshot.hpp" -#include "../GUI/AppConfig.hpp" #include diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 5dc0d430f1..d683fd90c7 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -10,7 +10,6 @@ #if ENABLE_ENVIRONMENT_MAP #include "GUI_App.hpp" #include "Plater.hpp" -#include "AppConfig.hpp" #endif // ENABLE_ENVIRONMENT_MAP #include "libslic3r/ExtrusionEntity.hpp" @@ -24,6 +23,7 @@ #include "slic3r/GUI/BitmapCache.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/AppConfig.hpp" #include #include diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index ac32767c4b..c1bf8e825d 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -1,8 +1,8 @@ #include "libslic3r/libslic3r.h" +#include "libslic3r/AppConfig.hpp" #include "Camera.hpp" #include "GUI_App.hpp" -#include "AppConfig.hpp" #if ENABLE_CAMERA_STATISTICS #include "Mouse3DController.hpp" #endif // ENABLE_CAMERA_STATISTICS diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index be2919861f..9921552a73 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -22,7 +22,6 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" #include "slic3r/Utils/PresetUpdater.hpp" -#include "AppConfig.hpp" #include "BedShapeDialog.hpp" #include "GUI.hpp" #include "wxExtensions.hpp" diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 38bda5f5e5..82c2861bc2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -38,7 +38,6 @@ #include "GUI.hpp" #include "GUI_Utils.hpp" -#include "AppConfig.hpp" #include "3DScene.hpp" #include "MainFrame.hpp" #include "Plater.hpp" @@ -326,7 +325,13 @@ void GUI_App::init_app_config() // load settings app_conf_exists = app_config->exists(); if (app_conf_exists) { - app_config->load(); + std::string error = app_config->load(); + if (!error.empty()) + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + throw std::runtime_error( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + AppConfig::config_path() + "\n\n" + error); } } diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index cc779df2ad..2d5d5b0729 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -2,7 +2,6 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/AppConfig.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/Utils/SLAImport.hpp" diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 0842803009..bbc1da534e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -20,7 +20,6 @@ #include "Tab.hpp" #include "ProgressStatusBar.hpp" #include "3DScene.hpp" -#include "AppConfig.hpp" #include "PrintHostDialogs.hpp" #include "wxExtensions.hpp" #include "GUI_ObjectList.hpp" diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index 9bbbf92a06..4c5ee20762 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -4,7 +4,6 @@ #include "Camera.hpp" #include "GUI_App.hpp" -#include "AppConfig.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" #include "NotificationManager.hpp" diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 4b5808e16a..242c3d851d 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1,8 +1,8 @@ #include "Preferences.hpp" -#include "AppConfig.hpp" #include "OptionsGroup.hpp" #include "GUI_App.hpp" #include "I18N.hpp" +#include "libslic3r/AppConfig.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index bae60e47f9..216af5df49 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -15,11 +15,11 @@ #include "GUI.hpp" #include "GUI_App.hpp" -#include "AppConfig.hpp" #include "MsgDialog.hpp" #include "I18N.hpp" #include "../Utils/PrintHost.hpp" #include "wxExtensions.hpp" +#include "libslic3r/AppConfig.hpp" namespace fs = boost::filesystem; From 8b74ae4568db08dd1039204291b9b83e2842b7b4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Aug 2020 09:45:32 +0200 Subject: [PATCH 271/826] Use the wxDataViewIconTextRenderer instead of the DataViewBitmapTextRenderer under GTK --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 37 ++++++++++++++++++++++--- src/slic3r/GUI/UnsavedChangesDialog.hpp | 10 +++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 3f85ac742f..33f6f19de0 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -42,14 +42,14 @@ static std::string orange = "#ed6b21"; static void color_string(wxString& str, const std::string& color) { -#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) +#if defined(SUPPORTS_MARKUP) && /*!defined(__APPLE__)*/defined(wxHAS_GENERIC_DATAVIEWCTRL) str = from_u8((boost::format("%2%") % color % into_u8(str)).str()); #endif } static void make_string_bold(wxString& str) { -#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)//defined(wxHAS_GENERIC_DATAVIEWCTRL) str = from_u8((boost::format("%1%") % into_u8(str)).str()); #endif } @@ -60,7 +60,11 @@ ModelNode::ModelNode(Preset::Type preset_type, const wxString& text) : m_preset_type(preset_type), m_text(text) { +#ifdef __linux__ + m_icon.CopyFromBitmap(create_scaled_bitmap(type_icon_names.at(preset_type))); +#else m_icon = create_scaled_bitmap(type_icon_names.at(preset_type)); +#endif //__linux__ } // group node @@ -68,7 +72,11 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& m_parent(parent), m_text(text) { +#ifdef __linux__ + m_icon.CopyFromBitmap(create_scaled_bitmap(icon_name)); +#else m_icon = create_scaled_bitmap(icon_name); +#endif //__linux__ } // category node @@ -300,7 +308,11 @@ void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& ite variant = node->m_toggle; break; case colIconText: +#ifdef __linux__ + variant << wxDataViewIconText(node->m_text, node->m_icon); +#else variant << DataViewBitmapText(node->m_text, node->m_icon); +#endif //__linux__ break; case colOldValue: variant << DataViewBitmapText(node->m_old_value, node->m_old_color_bmp); @@ -322,10 +334,18 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte switch (col) { case colIconText: { +#ifdef __linux__ + wxDataViewIconText data; +#else DataViewBitmapText data; +#endif //__linux__ data << variant; - node->m_icon = data.GetBitmap(); node->m_text = data.GetText(); +#ifdef __linux__ + node->m_icon = data.GetIcon(); +#else + node->m_icon = data.GetBitmap(); +#endif //__linux__ return true; } case colToggle: node->m_toggle = variant.GetBool(); @@ -439,7 +459,16 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) m_tree_model->SetAssociatedControl(m_tree); m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE/*, 6 * em*/);//2610,11,12 //2714 - wxDataViewColumn* icon_text_clmn = new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); + +#ifdef __linux__ + wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer(); +#ifdef SUPPORTS_MARKUP + rd->EnableMarkup(true); +#endif + wxDataViewColumn* icon_text_clmn = new wxDataViewColumn("", rd, UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT); +#else + wxDataViewColumn* icon_text_clmn = new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); +#endif //__linux__ m_tree->AppendColumn(icon_text_clmn); m_tree->AppendColumn(new wxDataViewColumn("Old value", new BitmapTextRenderer(true), UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); m_tree->AppendColumn(new wxDataViewColumn("New value", new BitmapTextRenderer(true), UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 8afd978961..29926cb241 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -18,6 +18,12 @@ namespace GUI{ class ModelNode; WX_DEFINE_ARRAY_PTR(ModelNode*, ModelNodePtrArray); +// On all of 3 different platforms Bitmap+Text icon column looks different +// because of Markup text is missed or not implemented. +// As a temporary workaround, we will use: +// MSW - DataViewBitmapText (our custom renderer wxBitmap + wxString, supported Markup text) +// OSX - -//-, but Markup text is not implemented right now +// GTK - wxDataViewIconText (wxWidgets for GTK renderer wxIcon + wxString, supported Markup text) class ModelNode { wxWindow* m_parent_win{ nullptr }; @@ -47,7 +53,11 @@ class ModelNode public: bool m_toggle {true}; +#ifdef __linux__ + wxIcon m_icon; +#else wxBitmap m_icon; +#endif //__linux__ wxBitmap m_old_color_bmp; wxBitmap m_new_color_bmp; wxString m_text; From f87ca111e15dce1d63c5d78045236698659148ff Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Aug 2020 11:24:31 +0200 Subject: [PATCH 272/826] GTK specific: Use the wxDataViewIconTextRenderer instead of the DataViewBitmapRenderer for "Old/NewValue" columns too. + update ofthe enabling for the "Save/Move" buttons --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 110 ++++++++++++++++++------ src/slic3r/GUI/UnsavedChangesDialog.hpp | 26 ++++-- 2 files changed, 106 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 33f6f19de0..cf1bc13e5f 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -42,14 +42,14 @@ static std::string orange = "#ed6b21"; static void color_string(wxString& str, const std::string& color) { -#if defined(SUPPORTS_MARKUP) && /*!defined(__APPLE__)*/defined(wxHAS_GENERIC_DATAVIEWCTRL) +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) str = from_u8((boost::format("%2%") % color % into_u8(str)).str()); #endif } static void make_string_bold(wxString& str) { -#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__)//defined(wxHAS_GENERIC_DATAVIEWCTRL) +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) str = from_u8((boost::format("%1%") % into_u8(str)).str()); #endif } @@ -86,7 +86,11 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text) : { } +#ifdef __linux__ +wxIcon ModelNode::get_bitmap(const wxString& color); +#else wxBitmap ModelNode::get_bitmap(const wxString& color) +#endif // __linux__ { /* It's supposed that standard size of an icon is 48px*16px for 100% scaled display. * So set sizes for solid_colored icons used for filament preset @@ -100,7 +104,16 @@ wxBitmap ModelNode::get_bitmap(const wxString& color) unsigned char rgb[3]; BitmapCache::parse_color(into_u8(color), rgb); // there is no need to scale created solid bitmap - return bmp_cache.mksolid(icon_width, icon_height, rgb, true); + wxBitmap bmp = bmp_cache.mksolid(icon_width, icon_height, rgb, true); + +#ifdef __linux__ + wxIcon icon; + icon.CopyFromBitmap(create_scaled_bitmap(icon_name)); + return icon; +#else + return bmp; +#endif // __linux__ + } // option node @@ -297,6 +310,13 @@ void UnsavedChangesModel::UpdateItemEnabling(wxDataViewItem item) update_parents(node); } +bool UnsavedChangesModel::IsEnabledItem(const wxDataViewItem& item) +{ + assert(item.IsOk()); + ModelNode* node = (ModelNode*)item.GetID(); + return node->IsToggled(); +} + void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const { assert(item.IsOk()); @@ -307,12 +327,19 @@ void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& ite case colToggle: variant = node->m_toggle; break; - case colIconText: #ifdef __linux__ + case colIconText: variant << wxDataViewIconText(node->m_text, node->m_icon); + break; + case colOldValue: + variant << wxDataViewIconText(node->m_old_value, node->m_old_color_bmp); + break; + case colNewValue: + variant << wxDataViewIconText(node->m_new_value, node->m_new_color_bmp); + break; #else + case colIconText: variant << DataViewBitmapText(node->m_text, node->m_icon); -#endif //__linux__ break; case colOldValue: variant << DataViewBitmapText(node->m_old_value, node->m_old_color_bmp); @@ -320,6 +347,7 @@ void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& ite case colNewValue: variant << DataViewBitmapText(node->m_new_value, node->m_new_color_bmp); break; +#endif //__linux__ default: wxLogError("UnsavedChangesModel::GetValue: wrong column %d", col); @@ -333,23 +361,35 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte ModelNode* node = (ModelNode*)item.GetID(); switch (col) { - case colIconText: { -#ifdef __linux__ - wxDataViewIconText data; -#else - DataViewBitmapText data; -#endif //__linux__ - data << variant; - node->m_text = data.GetText(); -#ifdef __linux__ - node->m_icon = data.GetIcon(); -#else - node->m_icon = data.GetBitmap(); -#endif //__linux__ - return true; } case colToggle: node->m_toggle = variant.GetBool(); return true; +#ifdef __linux__ + case colIconText: { + wxDataViewIconText data; + data << variant; + node->m_icon = data.GetIcon(); + node->m_text = data.GetText(); + return true; } + case colOldValue: { + wxDataViewIconText data; + data << variant; + node->m_old_color_bmp = data.GetIcon(); + node->m_old_value = data.GetText(); + return true; } + case colNewValue: { + wxDataViewIconText data; + data << variant; + node->m_new_color_bmp = data.GetIcon(); + node->m_new_value = data.GetText(); + return true; } +#else + case colIconText: { + DataViewBitmapText data; + data << variant; + node->m_icon = data.GetBitmap(); + node->m_text = data.GetText(); + return true; } case colOldValue: { DataViewBitmapText data; data << variant; @@ -362,6 +402,7 @@ bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewIte node->m_new_color_bmp = data.GetBitmap(); node->m_new_value = data.GetText(); return true; } +#endif //__linux__ default: wxLogError("UnsavedChangesModel::SetValue: wrong column"); } @@ -458,7 +499,7 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) m_tree->AssociateModel(m_tree_model); m_tree_model->SetAssociatedControl(m_tree); - m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE/*, 6 * em*/);//2610,11,12 //2714 + m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);//2610,11,12 //2714 #ifdef __linux__ wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer(); @@ -473,7 +514,7 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) m_tree->AppendColumn(new wxDataViewColumn("Old value", new BitmapTextRenderer(true), UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); m_tree->AppendColumn(new wxDataViewColumn("New value", new BitmapTextRenderer(true), UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); - m_tree->SetExpanderColumn(icon_text_clmn); +// m_tree->SetExpanderColumn(icon_text_clmn); m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); @@ -486,14 +527,18 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) wxString label= from_u8((boost::format(_u8L("Save selected to preset:%1%"))% ("\"" + presets->get_selected_preset().name + "\"")).str()); auto save_btn = new wxButton(this, m_save_btn_id = NewControlId(), label); - save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); buttons->Insert(0, save_btn, 0, wxLEFT, 5); + save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); + save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + label = from_u8((boost::format(_u8L("Move selected to preset:%1%"))% ("\"NewSelectedPreset\"")).str()); auto move_btn = new wxButton(this, m_move_btn_id = NewControlId(), label); - move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); buttons->Insert(1, move_btn, 0, wxLEFT, 5); + move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); + move_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + auto continue_btn = new wxButton(this, m_continue_btn_id = NewControlId(), _L("Continue without changes")); continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); buttons->Insert(2, continue_btn, 0, wxLEFT, 5); @@ -519,6 +564,9 @@ void UnsavedChangesDialog::item_value_changed(wxDataViewEvent& event) m_tree_model->UpdateItemEnabling(item); m_tree->Refresh(); + + // update an enabling of the "save/move" buttons + m_empty_selection = get_selected_options().empty(); } void UnsavedChangesDialog::close(Action action) @@ -670,12 +718,24 @@ void UnsavedChangesDialog::update(Preset::Type type) for (const std::string& opt_key : presets->current_dirty_options()) { const Search::Option& option = searcher.get_option(opt_key); - m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, - get_string_value(opt_key, old_config), get_string_value(opt_key, new_config)); + m_items_map.emplace(m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, + get_string_value(opt_key, old_config), get_string_value(opt_key, new_config)), opt_key); } } +std::vector UnsavedChangesDialog::get_selected_options() +{ + std::vector ret; + + for (auto item : m_items_map) { + if (m_tree_model->IsEnabledItem(item.first)) + ret.emplace_back(item.second); + } + + return ret; +} + void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 29926cb241..5c23da9978 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -2,6 +2,8 @@ #define slic3r_UnsavedChangesDialog_hpp_ #include +#include +#include #include "GUI_Utils.hpp" #include "wxExtensions.hpp" @@ -48,18 +50,24 @@ class ModelNode // would be added to the control) bool m_container {true}; +#ifdef __linux__ + wxIcon get_bitmap(const wxString& color); +#else wxBitmap get_bitmap(const wxString& color); +#endif //__linux__ public: bool m_toggle {true}; #ifdef __linux__ wxIcon m_icon; + wxIcon m_old_color_bmp; + wxIcon m_new_color_bmp; #else wxBitmap m_icon; -#endif //__linux__ wxBitmap m_old_color_bmp; wxBitmap m_new_color_bmp; +#endif //__linux__ wxString m_text; wxString m_old_value; wxString m_new_value; @@ -150,6 +158,7 @@ public: wxString old_value, wxString new_value); void UpdateItemEnabling(wxDataViewItem item); + bool IsEnabledItem(const wxDataViewItem& item); unsigned int GetColumnCount() const override { return colMax; } wxString GetColumnType(unsigned int col) const override; @@ -176,15 +185,20 @@ class UnsavedChangesDialog : public DPIDialog wxDataViewCtrl* m_tree { nullptr }; UnsavedChangesModel* m_tree_model { nullptr }; - int m_save_btn_id { wxID_ANY }; - int m_move_btn_id { wxID_ANY }; - int m_continue_btn_id { wxID_ANY }; + bool m_empty_selection { false }; + int m_save_btn_id { wxID_ANY }; + int m_move_btn_id { wxID_ANY }; + int m_continue_btn_id { wxID_ANY }; enum class Action { + Undef, Save, Move, Continue - } m_action; + } m_action {Action::Undef}; + + + std::map m_items_map; public: UnsavedChangesDialog(Preset::Type type); @@ -198,6 +212,8 @@ public: bool move_preset() const { return m_action == Action::Move; } bool just_continue() const { return m_action == Action::Continue; } + std::vector get_selected_options(); + protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override; From 11c22e7fb2f38adc17d3007960f108c97700adb6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Aug 2020 11:41:19 +0200 Subject: [PATCH 273/826] Fixed Linux build --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index cf1bc13e5f..122e34e028 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -87,7 +87,7 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text) : } #ifdef __linux__ -wxIcon ModelNode::get_bitmap(const wxString& color); +wxIcon ModelNode::get_bitmap(const wxString& color) #else wxBitmap ModelNode::get_bitmap(const wxString& color) #endif // __linux__ @@ -113,7 +113,6 @@ wxBitmap ModelNode::get_bitmap(const wxString& color) #else return bmp; #endif // __linux__ - } // option node From 6ed2cb661de4d5175ebb8ee75b53c33009c9d1a8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 10 Aug 2020 14:22:05 +0200 Subject: [PATCH 274/826] GCodeProcessor -> Export remaining time (lines M73) to gcode --- src/libslic3r/GCode.cpp | 17 ++- src/libslic3r/GCode/GCodeProcessor.cpp | 160 +++++++++++++++++++++++-- src/libslic3r/GCode/GCodeProcessor.hpp | 10 +- 3 files changed, 173 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index dec0a7a197..c02d24a4ee 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1281,13 +1281,16 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu print.throw_if_canceled(); // adds tags for time estimators -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + if (print.config().remaining_times.value) + _writeln(file, GCodeProcessor::First_M73_Output_Placeholder_Tag); +#else if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag); if (m_silent_time_estimator_enabled) _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); @@ -1582,14 +1585,16 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write(file, m_writer.postamble()); // adds tags for time estimators -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER if (print.config().remaining_times.value) - { + _writeln(file, GCodeProcessor::Last_M73_Output_Placeholder_Tag); +#else + if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag); if (m_silent_time_estimator_enabled) _writeln(file, GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag); } -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER print.throw_if_canceled(); @@ -2087,7 +2092,7 @@ void GCode::process_layer( #if ENABLE_GCODE_VIEWER // export layer z char buf[64]; - sprintf(buf, ";Z%g\n", print_z); + sprintf(buf, ";Z:%g\n", print_z); gcode += buf; // export layer height float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7612af0e90..87bcde9d07 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -4,6 +4,7 @@ #include "GCodeProcessor.hpp" #include +#include #include #include @@ -28,6 +29,9 @@ const std::string GCodeProcessor::Color_Change_Tag = "Color change"; const std::string GCodeProcessor::Pause_Print_Tag = "Pause print"; const std::string GCodeProcessor::Custom_Code_Tag = "Custom gcode"; +const std::string GCodeProcessor::First_M73_Output_Placeholder_Tag = "; _GP_FIRST_M73_OUTPUT_PLACEHOLDER"; +const std::string GCodeProcessor::Last_M73_Output_Placeholder_Tag = "; _GP_LAST_M73_OUTPUT_PLACEHOLDER"; + static bool is_valid_extrusion_role(int value) { return (static_cast(erNone) <= value) && (value <= static_cast(erMixed)); @@ -161,6 +165,7 @@ void GCodeProcessor::TimeMachine::reset() prev.reset(); gcode_time.reset(); blocks = std::vector(); + g1_times_cache = std::vector(); std::fill(moves_time.begin(), moves_time.end(), 0.0f); std::fill(roles_time.begin(), roles_time.end(), 0.0f); } @@ -264,7 +269,6 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) recalculate_trapezoids(blocks); size_t n_blocks_process = blocks.size() - keep_last_n_blocks; -// m_g1_times.reserve(m_g1_times.size() + n_blocks_process); for (size_t i = 0; i < n_blocks_process; ++i) { const TimeBlock& block = blocks[i]; float block_time = block.time(); @@ -272,9 +276,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) gcode_time.cache += block_time; moves_time[static_cast(block.move_type)] += block_time; roles_time[static_cast(block.role)] += block_time; - -// if (block.g1_line_id >= 0) -// m_g1_times.emplace_back(block.g1_line_id, time); + g1_times_cache.push_back(time); } if (keep_last_n_blocks) @@ -286,6 +288,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) void GCodeProcessor::TimeProcessor::reset() { extruder_unloaded = true; + export_remaining_time_enabled = false; machine_limits = MachineEnvelopeConfig(); filament_load_times = std::vector(); filament_unload_times = std::vector(); @@ -295,6 +298,136 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast(ETimeMode::Normal)].enabled = true; } +void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) +{ + boost::nowide::ifstream in(filename); + if (!in.good()) + throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + + // temporary file to contain modified gcode + std::string out_path = filename + ".postprocess"; + FILE* out = boost::nowide::fopen(out_path.c_str(), "wb"); + if (out == nullptr) + throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + + auto time_in_minutes = [](float time_in_seconds) { + return int(::roundf(time_in_seconds / 60.0f)); + }; + + auto format_line_M73 = [](const std::string& mask, int percent, int time) { + char line_M73[64]; + sprintf(line_M73, mask.c_str(), + std::to_string(percent).c_str(), + std::to_string(time).c_str()); + return std::string(line_M73); + }; + + GCodeReader parser; + std::string gcode_line; + size_t g1_lines_counter = 0; + // keeps track of last exported pair + std::array, static_cast(ETimeMode::Count)> last_exported; + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + last_exported[i] = { 0, time_in_minutes(machines[i].time) }; + } + + // buffer line to export only when greater than 64K to reduce writing calls + std::string export_line; + + // replace placeholder lines with the proper final value + auto process_placeholders = [&](const std::string& gcode_line) { + std::string ret; + // remove trailing '\n' + std::string line = gcode_line.substr(0, gcode_line.length() - 1); + if (line == First_M73_Output_Placeholder_Tag || line == Last_M73_Output_Placeholder_Tag) { + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + if (machine.enabled) { + ret += format_line_M73(machine.line_m73_mask.c_str(), + (line == First_M73_Output_Placeholder_Tag) ? 0 : 100, + (line == First_M73_Output_Placeholder_Tag) ? time_in_minutes(machines[i].time) : 0); + } + } + } + return std::make_pair(!ret.empty(), ret.empty() ? gcode_line : ret); + }; + + // add lines M73 to exported gcode + auto process_line_G1 = [&]() { + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + if (machine.enabled && g1_lines_counter < machine.g1_times_cache.size()) { + float elapsed_time = machine.g1_times_cache[g1_lines_counter]; + std::pair to_export = { int(::roundf(100.0f * elapsed_time / machine.time)), + time_in_minutes(machine.time - elapsed_time) }; + if (last_exported[i] != to_export) { + export_line += format_line_M73(machine.line_m73_mask.c_str(), + to_export.first, to_export.second); + last_exported[i] = to_export; + } + } + } + }; + + // helper function to write to disk + auto write_string = [&](const std::string& str) { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out); + if (ferror(out)) { + in.close(); + fclose(out); + boost::nowide::remove(out_path.c_str()); + throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + } + export_line.clear(); + }; + + while (std::getline(in, gcode_line)) { + if (!in.good()) { + fclose(out); + throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + } + + gcode_line += "\n"; + auto [processed, result] = process_placeholders(gcode_line); + gcode_line = result; + if (!processed) { + parser.parse_line(gcode_line, + [&](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + if (line.cmd_is("G1")) { + process_line_G1(); + ++g1_lines_counter; + } + }); + } + + export_line += gcode_line; + if (export_line.length() > 65535) + write_string(export_line); + } + + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + ETimeMode mode = static_cast(i); + if (machine.enabled) { + char line[128]; + sprintf(line, "; estimated printing time (%s mode) = %s\n", + (mode == ETimeMode::Normal) ? "normal" : "silent", + get_time_dhms(machine.time).c_str()); + export_line += line; + } + } + + if (!export_line.empty()) + write_string(export_line); + + fclose(out); + in.close(); + + if (rename_file(out_path, filename)) + throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + + "Is " + out_path + " locked?" + '\n'); +} + const std::vector> GCodeProcessor::Producers = { { EProducer::PrusaSlicer, "PrusaSlicer" }, { EProducer::Cura, "Cura_SteamEngine" }, @@ -305,6 +438,13 @@ const std::vector> GCodeProces unsigned int GCodeProcessor::s_result_id = 0; +GCodeProcessor::GCodeProcessor() +{ + reset(); + m_time_processor.machines[static_cast(ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; +} + void GCodeProcessor::apply_config(const PrintConfig& config) { m_parser.apply_config(config); @@ -346,6 +486,8 @@ void GCodeProcessor::apply_config(const PrintConfig& config) float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; } + + m_time_processor.export_remaining_time_enabled = config.remaining_times.value; } void GCodeProcessor::apply_config(const DynamicPrintConfig& config) @@ -572,6 +714,10 @@ void GCodeProcessor::process_file(const std::string& filename) update_estimated_times_stats(); + // post-process to add M73 lines into the gcode + if (m_time_processor.export_remaining_time_enabled) + m_time_processor.post_process(filename); + #if ENABLE_GCODE_VIEWER_STATISTICS m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -1525,13 +1671,13 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); - if (line.has_y() && i < m_time_processor.machine_limits.machine_max_acceleration_y.values.size()) + if (line.has_y()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor); - if (line.has_z() && i < m_time_processor.machine_limits.machine_max_acceleration_z.values.size()) + if (line.has_z()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor); - if (line.has_e() && i < m_time_processor.machine_limits.machine_max_acceleration_e.values.size()) + if (line.has_e()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor); } } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index e9c71038fe..840b5373bc 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -72,6 +72,8 @@ namespace Slic3r { static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; static const std::string Custom_Code_Tag; + static const std::string First_M73_Output_Placeholder_Tag; + static const std::string Last_M73_Output_Placeholder_Tag; private: using AxisCoords = std::array; @@ -182,10 +184,12 @@ namespace Slic3r { float acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s + std::string line_m73_mask; State curr; State prev; CustomGCodeTime gcode_time; std::vector blocks; + std::vector g1_times_cache; std::array(EMoveType::Count)> moves_time; std::array(ExtrusionRole::erCount)> roles_time; @@ -212,6 +216,7 @@ namespace Slic3r { // This is currently only really used by the MK3 MMU2: // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. bool extruder_unloaded; + bool export_remaining_time_enabled; MachineEnvelopeConfig machine_limits; // Additional load / unload times for a filament exchange sequence. std::vector filament_load_times; @@ -219,6 +224,9 @@ namespace Slic3r { std::array(ETimeMode::Count)> machines; void reset(); + + // post process the file with the given filename to add remaining time lines M73 + void post_process(const std::string& filename); }; public: @@ -312,7 +320,7 @@ namespace Slic3r { static unsigned int s_result_id; public: - GCodeProcessor() { reset(); } + GCodeProcessor(); void apply_config(const PrintConfig& config); void apply_config(const DynamicPrintConfig& config); From 058e024d2dbe8d2b2d0d127e54af171cb7871c48 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Aug 2020 19:07:45 +0200 Subject: [PATCH 275/826] Implemented FullCompareDialog for show long string values + fixed build under GTK --- src/slic3r/GUI/Tab.cpp | 4 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 194 ++++++++++++++++++++---- src/slic3r/GUI/UnsavedChangesDialog.hpp | 51 ++++++- src/slic3r/GUI/wxExtensions.cpp | 16 +- src/slic3r/GUI/wxExtensions.hpp | 6 +- 5 files changed, 231 insertions(+), 40 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4a250dc526..6501fba27b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2990,7 +2990,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, bool canceled = false; bool technology_changed = false; m_dependent_tabs.clear(); - if (current_dirty && ! may_discard_current_dirty_preset()) { + if (current_dirty && ! may_discard_current_dirty_preset(nullptr, preset_name)) { canceled = true; } else if (print_tab) { // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material @@ -3132,7 +3132,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, // if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { - UnsavedChangesDialog dlg(m_type); + UnsavedChangesDialog dlg(m_type, new_printer_name); if (dlg.ShowModal() == wxID_CANCEL) return false; if (dlg.just_continue()) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 122e34e028..78d599f7aa 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -11,7 +11,8 @@ #include "GUI_App.hpp" #include "Plater.hpp" #include "Tab.hpp" -#include "ObjectDataViewModel.hpp" +#include "ExtraRenderers.hpp" +#include "wxExtensions.hpp" //#define FTS_FUZZY_MATCH_IMPLEMENTATION //#include "fts_fuzzy_match.h" @@ -104,14 +105,12 @@ wxBitmap ModelNode::get_bitmap(const wxString& color) unsigned char rgb[3]; BitmapCache::parse_color(into_u8(color), rgb); // there is no need to scale created solid bitmap - wxBitmap bmp = bmp_cache.mksolid(icon_width, icon_height, rgb, true); - -#ifdef __linux__ - wxIcon icon; - icon.CopyFromBitmap(create_scaled_bitmap(icon_name)); - return icon; +#ifndef __linux__ + return bmp_cache.mksolid(icon_width, icon_height, rgb, true); #else - return bmp; + wxIcon icon; + icon.CopyFromBitmap(bmp_cache.mksolid(icon_width, icon_height, rgb, true)); + return icon; #endif // __linux__ } @@ -484,7 +483,7 @@ wxString UnsavedChangesModel::GetColumnType(unsigned int col) const // UnsavedChangesDialog //------------------------------------------ -UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) +UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& new_selected_preset) : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -516,6 +515,8 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) // m_tree->SetExpanderColumn(icon_text_clmn); m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); + m_tree->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &UnsavedChangesDialog::context_menu, this); + m_tree->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); @@ -525,31 +526,42 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type) PresetCollection* presets = tab->get_presets(); wxString label= from_u8((boost::format(_u8L("Save selected to preset:%1%"))% ("\"" + presets->get_selected_preset().name + "\"")).str()); - auto save_btn = new wxButton(this, m_save_btn_id = NewControlId(), label); - buttons->Insert(0, save_btn, 0, wxLEFT, 5); + m_save_btn = new ScalableButton(this, m_save_btn_id = NewControlId(), "save", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); + buttons->Insert(0, m_save_btn, 0, wxLEFT, 5); - save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); - save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + m_save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); + m_save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + m_save_btn->Bind(wxEVT_MOTION, [this, presets](wxMouseEvent& e){ show_info_line(Action::Save, presets->get_selected_preset().name); e.Skip(); }); - label = from_u8((boost::format(_u8L("Move selected to preset:%1%"))% ("\"NewSelectedPreset\"")).str()); - auto move_btn = new wxButton(this, m_move_btn_id = NewControlId(), label); - buttons->Insert(1, move_btn, 0, wxLEFT, 5); + label = from_u8((boost::format(_u8L("Move selected to preset:%1%"))% ("\"" + from_u8(new_selected_preset) + "\"")).str()); + m_move_btn = new ScalableButton(this, m_move_btn_id = NewControlId(), "paste_menu", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); + buttons->Insert(1, m_move_btn, 0, wxLEFT, 5); - move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); - move_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + m_move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); + m_move_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + m_move_btn->Bind(wxEVT_MOTION, [this, new_selected_preset](wxMouseEvent& e){ show_info_line(Action::Move, new_selected_preset); e.Skip(); }); - auto continue_btn = new wxButton(this, m_continue_btn_id = NewControlId(), _L("Continue without changes")); - continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); - buttons->Insert(2, continue_btn, 0, wxLEFT, 5); + label = _L("Continue without changes"); + m_continue_btn = new ScalableButton(this, m_continue_btn_id = NewControlId(), "cross", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); + buttons->Insert(2, m_continue_btn, 0, wxLEFT, 5); + m_continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); + m_continue_btn->Bind(wxEVT_MOTION, [this](wxMouseEvent& e){ show_info_line(Action::Continue); e.Skip(); }); + + m_info_line = new wxStaticText(this, wxID_ANY, ""); + m_info_line->SetFont(wxGetApp().bold_font()); + m_info_line->Hide(); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There is unsaved changes for") + (": \"" + tab->title() + "\"")), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There are unsaved changes for") + (": \"" + tab->title() + "\"")), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border); topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); update(type); + this->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); + SetSizer(topSizer); topSizer->SetSizeHints(this); } @@ -568,9 +580,61 @@ void UnsavedChangesDialog::item_value_changed(wxDataViewEvent& event) m_empty_selection = get_selected_options().empty(); } +void UnsavedChangesDialog::context_menu(wxDataViewEvent& event) +{ + if (!m_has_long_strings) + return; + + wxDataViewItem item = event.GetItem(); + if (!item) + { + wxPoint mouse_pos = wxGetMousePosition() - m_tree->GetScreenPosition(); + wxDataViewColumn* col = nullptr; + m_tree->HitTest(mouse_pos, item, col); + + if (!item) + item = m_tree->GetSelection(); + + if (!item) + return; + } + + auto it = m_items_map.find(item); + if (it == m_items_map.end() || !it->second.is_long) + return; + FullCompareDialog(it->second.opt_name, it->second.old_val, it->second.new_val).ShowModal(); +} + +void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name) +{ + if (m_motion_action == action) + return; + if (action == Action::Undef && !m_has_long_strings) + m_info_line->Hide(); + else { + wxString text; + if (action == Action::Undef) + text = _L("Some fields are too long to fit. Right click on it to show full text."); + else if (action == Action::Continue) + text = _L("All changed options will be reverted."); + else { + std::string act_string = action == Action::Save ? _u8L("saved") : _u8L("moved"); + text = from_u8((boost::format("After press this button selected options will be %1% to the preset \"%2%\".") % act_string % preset_name).str()); + text += "\n" + _L("Unselected options will be reverted."); + } + m_info_line->SetLabel(text); + m_info_line->Show(); + } + + m_motion_action = action; + + Layout(); + Refresh(); +} + void UnsavedChangesDialog::close(Action action) { - m_action = action; + m_exit_action = action; this->EndModal(wxID_CLOSE); } @@ -698,6 +762,23 @@ static wxString get_string_value(const std::string& opt_key, const DynamicPrintC return out; } +wxString UnsavedChangesDialog::get_short_string(wxString full_string) +{ + int max_len = 30; + if (full_string.IsEmpty() || full_string.StartsWith("#") || + (full_string.Find("\n") == wxNOT_FOUND && full_string.Length() < max_len)) + return full_string; + + m_has_long_strings = true; + + int n_pos = full_string.Find("\n"); + if (n_pos != wxNOT_FOUND && n_pos < max_len) + max_len = n_pos; + + full_string.Truncate(max_len); + return full_string + dots; +} + void UnsavedChangesDialog::update(Preset::Type type) { Tab* tab = wxGetApp().get_tab(type); @@ -717,8 +798,14 @@ void UnsavedChangesDialog::update(Preset::Type type) for (const std::string& opt_key : presets->current_dirty_options()) { const Search::Option& option = searcher.get_option(opt_key); - m_items_map.emplace(m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, - get_string_value(opt_key, old_config), get_string_value(opt_key, new_config)), opt_key); + ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config) }; + + wxString old_val = get_short_string(item_data.old_val); + wxString new_val = get_short_string(item_data.new_val); + if (old_val != item_data.old_val || new_val != item_data.new_val) + item_data.is_long = true; + + m_items_map.emplace(m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, old_val, new_val), item_data); } } @@ -727,10 +814,9 @@ std::vector UnsavedChangesDialog::get_selected_options() { std::vector ret; - for (auto item : m_items_map) { + for (auto item : m_items_map) if (m_tree_model->IsEnabledItem(item.first)) - ret.emplace_back(item.second); - } + ret.emplace_back(item.second.opt_key); return ret; } @@ -757,6 +843,58 @@ void UnsavedChangesDialog::on_sys_color_changed() } +//------------------------------------------ +// FullCompareDialog +//------------------------------------------ + +FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value) + : wxDialog(nullptr, wxID_ANY, option_name, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + SetBackgroundColour(bgr_clr); + + int border = 10; + + wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this); + + wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(2, 2, 1, 0); + grid_sizer->SetFlexibleDirection(wxBOTH); + grid_sizer->AddGrowableCol(0,1); + grid_sizer->AddGrowableCol(1,1); + grid_sizer->AddGrowableRow(1,1); + + auto add_header = [grid_sizer, border, this](wxString label) { + wxStaticText* text = new wxStaticText(this, wxID_ANY, label); + text->SetFont(wxGetApp().bold_font()); + grid_sizer->Add(text, 0, wxALL, border); + }; + + auto add_value = [grid_sizer, border, this](wxString label, bool is_colored = false) { + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(300, -1), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE); + if (is_colored) + text->SetForegroundColour(wxColour(orange)); + grid_sizer->Add(text, 1, wxALL | wxEXPAND, border); + }; + + add_header(_L("Old value")); + add_header(_L("New value")); + add_value(old_value); + add_value(new_value, true); + + sizer->Add(grid_sizer, 1, wxEXPAND); + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + + } } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 5c23da9978..991c894422 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -9,8 +9,10 @@ #include "wxExtensions.hpp" #include "libslic3r/Preset.hpp" -namespace Slic3r { +class ScalableButton; +class wxStaticText; +namespace Slic3r { namespace GUI{ // ---------------------------------------------------------------------------- @@ -185,7 +187,13 @@ class UnsavedChangesDialog : public DPIDialog wxDataViewCtrl* m_tree { nullptr }; UnsavedChangesModel* m_tree_model { nullptr }; + ScalableButton* m_save_btn { nullptr }; + ScalableButton* m_move_btn { nullptr }; + ScalableButton* m_continue_btn { nullptr }; + wxStaticText* m_info_line { nullptr }; + bool m_empty_selection { false }; + bool m_has_long_strings { false }; int m_save_btn_id { wxID_ANY }; int m_move_btn_id { wxID_ANY }; int m_continue_btn_id { wxID_ANY }; @@ -195,22 +203,40 @@ class UnsavedChangesDialog : public DPIDialog Save, Move, Continue - } m_action {Action::Undef}; + }; + // selected action after Dialog closing + Action m_exit_action {Action::Undef}; - std::map m_items_map; + // Action during mouse motion + Action m_motion_action {Action::Undef}; + + struct ItemData + { + std::string opt_key; + wxString opt_name; + wxString old_val; + wxString new_val; + bool is_long {false}; + }; + // tree items related to the options + std::map m_items_map; public: - UnsavedChangesDialog(Preset::Type type); + UnsavedChangesDialog(Preset::Type type, const std::string& new_selected_preset); ~UnsavedChangesDialog() {} + wxString get_short_string(wxString full_string); + void update(Preset::Type type); void item_value_changed(wxDataViewEvent &event); + void context_menu(wxDataViewEvent &event); + void show_info_line(Action action, std::string preset_name = ""); void close(Action action); - bool save_preset() const { return m_action == Action::Save; } - bool move_preset() const { return m_action == Action::Move; } - bool just_continue() const { return m_action == Action::Continue; } + bool save_preset() const { return m_exit_action == Action::Save; } + bool move_preset() const { return m_exit_action == Action::Move; } + bool just_continue() const { return m_exit_action == Action::Continue; } std::vector get_selected_options(); @@ -220,6 +246,17 @@ protected: }; +//------------------------------------------ +// FullCompareDialog +//------------------------------------------ +class FullCompareDialog : public wxDialog +{ +public: + FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value); + ~FullCompareDialog() {} +}; + + } } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 67b5a18f79..0cf09b4aea 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -782,9 +782,11 @@ ScalableButton::ScalableButton( wxWindow * parent, const wxString& label /* = wxEmptyString*/, const wxSize& size /* = wxDefaultSize*/, const wxPoint& pos /* = wxDefaultPosition*/, - long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) : + long style /*= wxBU_EXACTFIT | wxNO_BORDER*/, + bool use_default_disabled_bitmap/* = false*/) : + m_parent(parent), m_current_icon_name(icon_name), - m_parent(parent) + m_use_default_disabled_bitmap (use_default_disabled_bitmap) { Create(parent, id, label, pos, size, style); #ifdef __WXMSW__ @@ -793,6 +795,8 @@ ScalableButton::ScalableButton( wxWindow * parent, #endif // __WXMSW__ SetBitmap(create_scaled_bitmap(icon_name, parent)); + if (m_use_default_disabled_bitmap) + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); if (size != wxDefaultSize) { @@ -842,11 +846,19 @@ int ScalableButton::GetBitmapHeight() #endif } +void ScalableButton::UseDefaultBitmapDisabled() +{ + m_use_default_disabled_bitmap = true; + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); +} + void ScalableButton::msw_rescale() { SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt)); if (!m_disabled_icon_name.empty()) SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); + else if (m_use_default_disabled_bitmap) + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); if (m_width > 0 || m_height>0) { diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 9be3361bda..8fe28b2e57 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -210,7 +210,8 @@ public: const wxString& label = wxEmptyString, const wxSize& size = wxDefaultSize, const wxPoint& pos = wxDefaultPosition, - long style = wxBU_EXACTFIT | wxNO_BORDER); + long style = wxBU_EXACTFIT | wxNO_BORDER, + bool use_default_disabled_bitmap = false); ScalableButton( wxWindow * parent, @@ -224,6 +225,7 @@ public: void SetBitmap_(const ScalableBitmap& bmp); void SetBitmapDisabled_(const ScalableBitmap &bmp); int GetBitmapHeight(); + void UseDefaultBitmapDisabled(); void msw_rescale(); @@ -234,6 +236,8 @@ private: int m_width {-1}; // should be multiplied to em_unit int m_height{-1}; // should be multiplied to em_unit + bool m_use_default_disabled_bitmap {false}; + // bitmap dimensions int m_px_cnt{ 16 }; }; From 6a33c967cfa62290b932be44b3f174f6ff27e067 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Aug 2020 09:17:52 +0200 Subject: [PATCH 276/826] Fixed GTK build --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 38 ++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 78d599f7aa..dcd1d760ae 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -499,33 +499,38 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);//2610,11,12 //2714 + auto append_bmp_text_column = [this](const wxString& label, unsigned model_column, int width, bool set_expander = false) + { #ifdef __linux__ - wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer(); + wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer(); #ifdef SUPPORTS_MARKUP - rd->EnableMarkup(true); + rd->EnableMarkup(true); #endif - wxDataViewColumn* icon_text_clmn = new wxDataViewColumn("", rd, UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT); + wxDataViewColumn* column = new wxDataViewColumn(label, rd, model_column, width, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT); #else - wxDataViewColumn* icon_text_clmn = new wxDataViewColumn("", new BitmapTextRenderer(true), UnsavedChangesModel::colIconText, 30 * em, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); + wxDataViewColumn* column = new wxDataViewColumn(label, new BitmapTextRenderer(true), model_column, width, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); #endif //__linux__ - m_tree->AppendColumn(icon_text_clmn); - m_tree->AppendColumn(new wxDataViewColumn("Old value", new BitmapTextRenderer(true), UnsavedChangesModel::colOldValue, 20 * em, wxALIGN_TOP)); - m_tree->AppendColumn(new wxDataViewColumn("New value", new BitmapTextRenderer(true), UnsavedChangesModel::colNewValue, 20 * em, wxALIGN_TOP)); + m_tree->AppendColumn(column); + if (set_expander) + m_tree->SetExpanderColumn(column); + }; -// m_tree->SetExpanderColumn(icon_text_clmn); + append_bmp_text_column("", UnsavedChangesModel::colIconText, 30 * em); + append_bmp_text_column(_L("Old Value"), UnsavedChangesModel::colOldValue, 20 * em); + append_bmp_text_column(_L("New Value"), UnsavedChangesModel::colNewValue, 20 * em); m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); m_tree->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &UnsavedChangesDialog::context_menu, this); m_tree->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); - wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); + // Add Buttons + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); Tab* tab = wxGetApp().get_tab(type); assert(tab); - PresetCollection* presets = tab->get_presets(); - wxString label= from_u8((boost::format(_u8L("Save selected to preset:%1%"))% ("\"" + presets->get_selected_preset().name + "\"")).str()); + wxString label= from_u8((boost::format(_u8L("Save selected to preset: %1%"))% ("\"" + presets->get_selected_preset().name + "\"")).str()); m_save_btn = new ScalableButton(this, m_save_btn_id = NewControlId(), "save", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); buttons->Insert(0, m_save_btn, 0, wxLEFT, 5); @@ -533,7 +538,7 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& m_save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); m_save_btn->Bind(wxEVT_MOTION, [this, presets](wxMouseEvent& e){ show_info_line(Action::Save, presets->get_selected_preset().name); e.Skip(); }); - label = from_u8((boost::format(_u8L("Move selected to preset:%1%"))% ("\"" + from_u8(new_selected_preset) + "\"")).str()); + label = from_u8((boost::format(_u8L("Move selected to preset: %1%"))% ("\"" + from_u8(new_selected_preset) + "\"")).str()); m_move_btn = new ScalableButton(this, m_move_btn_id = NewControlId(), "paste_menu", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); buttons->Insert(1, m_move_btn, 0, wxLEFT, 5); @@ -549,14 +554,13 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& m_info_line = new wxStaticText(this, wxID_ANY, ""); m_info_line->SetFont(wxGetApp().bold_font()); - m_info_line->Hide(); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There are unsaved changes for") + (": \"" + tab->title() + "\"")), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border); - topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); + topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border); + topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); update(type); @@ -564,6 +568,8 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& SetSizer(topSizer); topSizer->SetSizeHints(this); + + show_info_line(Action::Undef); } void UnsavedChangesDialog::item_value_changed(wxDataViewEvent& event) From cb407e46e5d833d5e286d4e7e2f092fb3f19e6ec Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Aug 2020 10:37:08 +0200 Subject: [PATCH 277/826] Fixed color update under GTK + FullCompareDialog : Use SetStile instead of SetForegroundColour for the wxTextCtrls --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index dcd1d760ae..9f9ec3d9f3 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -150,7 +150,7 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& ol void ModelNode::UpdateEnabling() { -#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) auto change_text_color = [](wxString& str, const std::string& clr_from, const std::string& clr_to) { std::string old_val = into_u8(str); @@ -876,9 +876,10 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString }; auto add_value = [grid_sizer, border, this](wxString label, bool is_colored = false) { - wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(300, -1), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE); + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(300, -1), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE | wxTE_RICH); if (is_colored) - text->SetForegroundColour(wxColour(orange)); +// text->SetForegroundColour(wxColour(orange)); + text->SetStyle(0, label.Len(), wxTextAttr(wxColour(orange))); grid_sizer->Add(text, 1, wxALL | wxEXPAND, border); }; From 5882c121cc2aca8fc85f1106a624d85330b3b968 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 11 Aug 2020 11:12:30 +0200 Subject: [PATCH 278/826] GCodeProcessor -> Fixed time estimate for stealth mode --- src/libslic3r/GCode/GCodeProcessor.cpp | 24 ++++++++++++++++++++++-- src/libslic3r/GCode/GCodeProcessor.hpp | 6 ++++++ src/slic3r/GUI/GCodeViewer.cpp | 14 +++++++++++--- src/slic3r/GUI/Plater.cpp | 2 +- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 87bcde9d07..e4e52abdbe 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -159,6 +159,7 @@ void GCodeProcessor::TimeMachine::reset() { enabled = false; acceleration = 0.0f; + max_acceleration = 0.0f; extrude_factor_override_percentage = 1.0f; time = 0.0f; curr.reset(); @@ -289,6 +290,7 @@ void GCodeProcessor::TimeProcessor::reset() { extruder_unloaded = true; export_remaining_time_enabled = false; + machine_envelope_processing_enabled = false; machine_limits = MachineEnvelopeConfig(); filament_load_times = std::vector(); filament_unload_times = std::vector(); @@ -484,6 +486,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); + m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; } @@ -628,6 +631,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); + m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; } } @@ -1304,6 +1308,9 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) return type; }; + // enable processing of lines M201/M203/M204/M205 + m_time_processor.machine_envelope_processing_enabled = true; + // updates axes positions from line for (unsigned char a = X; a <= E; ++a) { m_end_position[a] = absolute_position((Axis)a, line); @@ -1664,6 +1671,9 @@ void GCodeProcessor::process_M135(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) { + if (!m_time_processor.machine_envelope_processing_enabled) + return; + // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration float factor = (m_flavor != gcfRepRap && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; @@ -1684,6 +1694,9 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) { + if (!m_time_processor.machine_envelope_processing_enabled) + return; + // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate if (m_flavor == gcfRepetier) return; @@ -1709,6 +1722,9 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) { + if (!m_time_processor.machine_envelope_processing_enabled) + return; + float value; for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { if (line.has_value('S', value)) { @@ -1736,6 +1752,9 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) { + if (!m_time_processor.machine_envelope_processing_enabled) + return; + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { if (line.has_x()) { float max_jerk = line.x(); @@ -1968,8 +1987,9 @@ void GCodeProcessor::set_acceleration(ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { - float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, id); - m_time_processor.machines[id].acceleration = (max_acceleration == 0.0f) ? value : std::min(value, max_acceleration); + m_time_processor.machines[id].acceleration = (m_time_processor.machines[id].max_acceleration == 0.0f) ? value : + // Clamp the acceleration with the maximum. + std::min(value, m_time_processor.machines[id].max_acceleration); } } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 840b5373bc..ba8f921686 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -182,6 +182,8 @@ namespace Slic3r { bool enabled; float acceleration; // mm/s^2 + // hard limit for the acceleration, to which the firmware will clamp. + float max_acceleration; // mm/s^2 float extrude_factor_override_percentage; float time; // s std::string line_m73_mask; @@ -216,7 +218,10 @@ namespace Slic3r { // This is currently only really used by the MK3 MMU2: // extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit. bool extruder_unloaded; + // whether or not to export post-process the gcode to export lines M73 in it bool export_remaining_time_enabled; + // allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() + bool machine_envelope_processing_enabled; MachineEnvelopeConfig machine_limits; // Additional load / unload times for a filament exchange sequence. std::vector filament_load_times; @@ -328,6 +333,7 @@ namespace Slic3r { bool is_stealth_time_estimator_enabled() const { return m_time_processor.machines[static_cast(ETimeMode::Stealth)].enabled; } + void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } void enable_producers(bool enabled) { m_producers_enabled = enabled; } void reset(); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ad84184015..bbd357e980 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -9,7 +9,7 @@ #include "GUI_App.hpp" #include "MainFrame.hpp" #include "Plater.hpp" -#include "PresetBundle.hpp" +#include "libslic3r/PresetBundle.hpp" #include "Camera.hpp" #include "I18N.hpp" #include "GUI_Utils.hpp" @@ -1498,7 +1498,7 @@ void GCodeViewer::render_legend() const imgui.text(time); ImGui::SameLine(offsets[1]); pos = ImGui::GetCursorScreenPos(); - float width = percent_bar_size * percent / max_percent; + float width = std::max(1.0f, percent_bar_size * percent / max_percent); draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); ImGui::Dummy({ percent_bar_size, icon_size }); @@ -1649,7 +1649,15 @@ void GCodeViewer::render_legend() const imgui.text(short_time(get_time_dhms(time_mode.time))); auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { - if (m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + bool show = false; + for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { + if (i != static_cast(mode) && + short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { + show = true; + break; + } + } + if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { ImGui::SameLine(0.0f, 10.0f); if (imgui.button(label)) { m_time_estimate_mode = mode; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0b9f884516..dac3f6fae9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4577,8 +4577,8 @@ void Plater::load_gcode(const wxString& filename) // process gcode GCodeProcessor processor; -// processor.apply_config(config); processor.enable_producers(true); + processor.enable_machine_envelope_processing(true); processor.process_file(filename.ToUTF8().data()); p->gcode_result = std::move(processor.extract_result()); From 5a0e04807965d8443da7793e2c6cacc461718a65 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 11 Aug 2020 14:23:47 +0200 Subject: [PATCH 279/826] ENABLE_GCODE_VIEWER -> Drag and drop .gcode files into gcode viewer --- src/libslic3r/GCode.cpp | 8 ++- src/libslic3r/GCode/GCodeProcessor.cpp | 86 +++++++++++--------------- src/libslic3r/GCode/GCodeProcessor.hpp | 5 +- src/slic3r/GUI/Plater.cpp | 43 +++++++++---- 4 files changed, 76 insertions(+), 66 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c02d24a4ee..eb5ede0a5b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1283,7 +1283,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // adds tags for time estimators #if ENABLE_GCODE_VIEWER if (print.config().remaining_times.value) - _writeln(file, GCodeProcessor::First_M73_Output_Placeholder_Tag); + _writeln(file, GCodeProcessor::First_Line_M73_Placeholder_Tag); #else if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag); @@ -1587,7 +1587,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // adds tags for time estimators #if ENABLE_GCODE_VIEWER if (print.config().remaining_times.value) - _writeln(file, GCodeProcessor::Last_M73_Output_Placeholder_Tag); + _writeln(file, GCodeProcessor::Last_Line_M73_Placeholder_Tag); #else if (print.config().remaining_times.value) { _writeln(file, GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag); @@ -1620,7 +1620,9 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost); if (print.m_print_statistics.total_toolchanges > 0) _write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + _writeln(file, GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag); +#else _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); if (m_silent_time_estimator_enabled) _write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str()); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e4e52abdbe..530dfb1059 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -29,8 +29,9 @@ const std::string GCodeProcessor::Color_Change_Tag = "Color change"; const std::string GCodeProcessor::Pause_Print_Tag = "Pause print"; const std::string GCodeProcessor::Custom_Code_Tag = "Custom gcode"; -const std::string GCodeProcessor::First_M73_Output_Placeholder_Tag = "; _GP_FIRST_M73_OUTPUT_PLACEHOLDER"; -const std::string GCodeProcessor::Last_M73_Output_Placeholder_Tag = "; _GP_LAST_M73_OUTPUT_PLACEHOLDER"; +const std::string GCodeProcessor::First_Line_M73_Placeholder_Tag = "; _GP_FIRST_LINE_M73_PLACEHOLDER"; +const std::string GCodeProcessor::Last_Line_M73_Placeholder_Tag = "; _GP_LAST_LINE_M73_PLACEHOLDER"; +const std::string GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag = "; _GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER"; static bool is_valid_extrusion_role(int value) { @@ -338,16 +339,29 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) // replace placeholder lines with the proper final value auto process_placeholders = [&](const std::string& gcode_line) { - std::string ret; // remove trailing '\n' std::string line = gcode_line.substr(0, gcode_line.length() - 1); - if (line == First_M73_Output_Placeholder_Tag || line == Last_M73_Output_Placeholder_Tag) { + + std::string ret; + if (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag) { for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { ret += format_line_M73(machine.line_m73_mask.c_str(), - (line == First_M73_Output_Placeholder_Tag) ? 0 : 100, - (line == First_M73_Output_Placeholder_Tag) ? time_in_minutes(machines[i].time) : 0); + (line == First_Line_M73_Placeholder_Tag) ? 0 : 100, + (line == First_Line_M73_Placeholder_Tag) ? time_in_minutes(machines[i].time) : 0); + } + } + } + else if (line == Estimated_Printing_Time_Placeholder_Tag) { + for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + const TimeMachine& machine = machines[i]; + if (machine.enabled) { + char buf[128]; + sprintf(buf, "; estimated printing time (%s mode) = %s\n", + (static_cast(i) == ETimeMode::Normal) ? "normal" : "silent", + get_time_dhms(machine.time).c_str()); + ret += buf; } } } @@ -407,18 +421,6 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) write_string(export_line); } - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { - const TimeMachine& machine = machines[i]; - ETimeMode mode = static_cast(i); - if (machine.enabled) { - char line[128]; - sprintf(line, "; estimated printing time (%s mode) = %s\n", - (mode == ETimeMode::Normal) ? "normal" : "silent", - get_time_dhms(machine.time).c_str()); - export_line += line; - } - } - if (!export_line.empty()) write_string(export_line); @@ -870,12 +872,10 @@ void GCodeProcessor::process_tags(const std::string& comment) // width tag pos = comment.find(Width_Tag); if (pos != comment.npos) { - try - { + try { m_width = std::stof(comment.substr(pos + Width_Tag.length())); } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; } return; @@ -884,12 +884,10 @@ void GCodeProcessor::process_tags(const std::string& comment) // height tag pos = comment.find(Height_Tag); if (pos != comment.npos) { - try - { + try { m_height = std::stof(comment.substr(pos + Height_Tag.length())); } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; } return; @@ -899,8 +897,7 @@ void GCodeProcessor::process_tags(const std::string& comment) pos = comment.find(Color_Change_Tag); if (pos != comment.npos) { pos = comment.find_last_of(",T"); - try - { + try { unsigned char extruder_id = (pos == comment.npos) ? 0 : static_cast(std::stoi(comment.substr(pos + 1))); m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview @@ -915,8 +912,7 @@ void GCodeProcessor::process_tags(const std::string& comment) process_custom_gcode_time(CustomGCode::ColorChange); } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Color_Change (" << comment << ")."; } @@ -1115,24 +1111,20 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) size_t w_start = data.find(w_tag); size_t w_end = data.find_first_of(' ', w_start); if (h_start != data.npos) { - try - { + try { std::string test = data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end); m_height = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; } } if (w_start != data.npos) { - try - { + try { std::string test = data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end); m_width = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; } } @@ -1218,12 +1210,10 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) tag = "WIDTH:"; pos = comment.find(tag); if (pos != comment.npos) { - try - { + try { m_width = std::stof(comment.substr(pos + tag.length())); } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; } return true; @@ -1233,12 +1223,10 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) tag = "HEIGHT:"; pos = comment.find(tag); if (pos != comment.npos) { - try - { + try { m_height = std::stof(comment.substr(pos + tag.length())); } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; } return true; @@ -1871,8 +1859,7 @@ void GCodeProcessor::process_T(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_T(const std::string& command) { if (command.length() > 1) { - try - { + try { unsigned char id = static_cast(std::stoi(command.substr(1))); if (m_extruder_id != id) { unsigned char extruders_count = static_cast(m_extruder_offsets.size()); @@ -1895,8 +1882,7 @@ void GCodeProcessor::process_T(const std::string& command) store_move_vertex(EMoveType::Tool_change); } } - catch (...) - { + catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ")."; } } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index ba8f921686..e35d3a9735 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -72,8 +72,9 @@ namespace Slic3r { static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; static const std::string Custom_Code_Tag; - static const std::string First_M73_Output_Placeholder_Tag; - static const std::string Last_M73_Output_Placeholder_Tag; + static const std::string First_Line_M73_Placeholder_Tag; + static const std::string Last_Line_M73_Placeholder_Tag; + static const std::string Estimated_Printing_Time_Placeholder_Tag; private: using AxisCoords = std::array; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dac3f6fae9..79f8b5ad5d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1349,20 +1349,44 @@ private: Plater *plater; static const std::regex pattern_drop; +#if ENABLE_GCODE_VIEWER + static const std::regex pattern_gcode_drop; +#endif // ENABLE_GCODE_VIEWER }; const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase); +#if ENABLE_GCODE_VIEWER +const std::regex PlaterDropTarget::pattern_gcode_drop(".*[.](gcode)", std::regex::icase); +#endif // ENABLE_GCODE_VIEWER bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { std::vector paths; - for (const auto &filename : filenames) { - fs::path path(into_path(filename)); - if (std::regex_match(path.string(), pattern_drop)) { - paths.push_back(std::move(path)); - } else { +#if ENABLE_GCODE_VIEWER + if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { + for (const auto& filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_gcode_drop)) + paths.push_back(std::move(path)); + } + + if (paths.size() > 1) { + wxMessageDialog((wxWindow*)plater, _L("Only one gcode file at a time can be opened."), wxString(SLIC3R_APP_NAME) + " - " + _L("Open G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); return false; } + else if (paths.size() == 1) { + plater->load_gcode(from_path(paths.front())); + return true; + } + } +#endif // ENABLE_GCODE_VIEWER + + for (const auto &filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_drop)) + paths.push_back(std::move(path)); + else + return false; } wxString snapshot_label; @@ -1390,13 +1414,10 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi // because right now the plater is not cleared, we set the project file (from the latest imported .3mf or .amf file) // only if not set yet // if res is empty no data has been loaded - if (!res.empty() && plater->get_project_filename().empty()) - { - for (std::vector::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) - { + if (!res.empty() && plater->get_project_filename().empty()) { + for (std::vector::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) { std::string filename = (*it).filename().string(); - if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) - { + if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) { plater->set_project_filename(from_path(*it)); break; } From 4ca026d4b6ecfbb11bd8107a427bdbe86cfdc28e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 11 Aug 2020 15:44:32 +0200 Subject: [PATCH 280/826] ENABLE_GCODE_VIEWER -> More general drag and drop for .gcode files --- src/libslic3r/GCode.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 4 +++- src/slic3r/GUI/Plater.cpp | 42 +++++++++++++++++++++++++----------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index eb5ede0a5b..795aac898f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1626,7 +1626,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str()); if (m_silent_time_estimator_enabled) _write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str()); -#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER // Append full config. _write(file, "\n"); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 794da80b2a..6c3f2a4ea6 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1006,7 +1006,9 @@ void MainFrame::init_menubar() fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { - if (m_plater->model().objects.empty() || wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) + if (m_plater->model().objects.empty() || + wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES) set_mode(EMode::GCodeViewer); }, "", nullptr, [this]() { return m_plater != nullptr && m_plater->printer_technology() != ptSLA; }, this); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 79f8b5ad5d..67b531eae9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1362,25 +1362,43 @@ const std::regex PlaterDropTarget::pattern_gcode_drop(".*[.](gcode)", std::regex bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { std::vector paths; -#if ENABLE_GCODE_VIEWER - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { - for (const auto& filename : filenames) { - fs::path path(into_path(filename)); - if (std::regex_match(path.string(), pattern_gcode_drop)) - paths.push_back(std::move(path)); - } - if (paths.size() > 1) { - wxMessageDialog((wxWindow*)plater, _L("Only one gcode file at a time can be opened."), wxString(SLIC3R_APP_NAME) + " - " + _L("Open G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); - return false; - } - else if (paths.size() == 1) { +#if ENABLE_GCODE_VIEWER + // gcode section + for (const auto& filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_gcode_drop)) + paths.push_back(std::move(path)); + } + + if (paths.size() > 1) { + wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."), + wxString(SLIC3R_APP_NAME) + " - " + _L("Open G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); + return false; + } + else if (paths.size() == 1) { + if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { plater->load_gcode(from_path(paths.front())); return true; } + else { + if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Open G-code file"), wxYES_NO | wxCANCEL | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { + + if (plater->model().objects.empty() || + wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxCANCEL | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { + wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer); + plater->load_gcode(from_path(paths.front())); + return true; + } + } + return false; + } } #endif // ENABLE_GCODE_VIEWER + // model section for (const auto &filename : filenames) { fs::path path(into_path(filename)); if (std::regex_match(path.string(), pattern_drop)) From a6a5025a7600be41c305567ed16acfdf703194e4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Aug 2020 15:48:49 +0200 Subject: [PATCH 281/826] Fixed a crash appeared when we try to update PlaterPresetComboBox for empty selected preset. --- src/slic3r/GUI/PresetComboBoxes.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 33ae4f54e9..7539f36168 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -517,7 +517,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); // Wide icons are shown if the currently selected preset is not compatible with the current printer, // and red flag is drown in front of the selected preset. - bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible; + bool wide_icons = selected_preset && !selected_preset->is_compatible; float scale = m_em_unit*0.1f; int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0; @@ -707,10 +707,11 @@ void PlaterPresetComboBox::update() assert(selected_filament_preset); } - const Preset& selected_preset = m_type == Preset::TYPE_FILAMENT ? *selected_filament_preset : m_collection->get_selected_preset(); + bool has_selection = m_collection->get_selected_idx() != size_t(-1); + const Preset* selected_preset = m_type == Preset::TYPE_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr; // Show wide icons if the currently selected preset is not compatible with the current printer, // and draw a red flag in front of the selected preset. - bool wide_icons = !selected_preset.is_compatible; + bool wide_icons = selected_preset && !selected_preset->is_compatible; std::map nonsys_presets; From 12f43736bd612de51c89dded1e40b9f0d8516a8c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 12 Aug 2020 10:47:36 +0200 Subject: [PATCH 282/826] Fix of Support generator debugging functions after some refactoring --- src/libslic3r/SupportMaterial.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 43f582d5f9..95b4c334b1 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -409,7 +409,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) export_print_z_polygons_and_extrusions_to_svg( debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), layers_sorted.data() + i, j - i, - *object.support_layers[layer_id]); + *object.support_layers()[layer_id]); ++layer_id; } i = j; @@ -597,8 +597,8 @@ public: ::fwrite(&n_points, 4, 1, file); for (uint32_t j = 0; j < n_points; ++ j) { const Point &pt = poly.points[j]; - ::fwrite(&pt.x, sizeof(coord_t), 1, file); - ::fwrite(&pt.y, sizeof(coord_t), 1, file); + ::fwrite(&pt.x(), sizeof(coord_t), 1, file); + ::fwrite(&pt.y(), sizeof(coord_t), 1, file); } } n_polygons = m_trimming_polygons->size(); @@ -609,8 +609,8 @@ public: ::fwrite(&n_points, 4, 1, file); for (uint32_t j = 0; j < n_points; ++ j) { const Point &pt = poly.points[j]; - ::fwrite(&pt.x, sizeof(coord_t), 1, file); - ::fwrite(&pt.y, sizeof(coord_t), 1, file); + ::fwrite(&pt.x(), sizeof(coord_t), 1, file); + ::fwrite(&pt.y(), sizeof(coord_t), 1, file); } } ::fclose(file); @@ -1134,7 +1134,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ { ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", iRun, layer_id, - std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin()), + std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), get_extents(diff_polygons)); Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); svg.draw(expolys); @@ -1152,7 +1152,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg", iRun, layer_id, - std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin(), + std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), layer.print_z), union_ex(diff_polygons, false)); #endif /* SLIC3R_DEBUG */ @@ -1482,7 +1482,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta svg.draw(union_ex(top, false), "blue", 0.5f); svg.draw(union_ex(projection_raw, true), "red", 0.5f); svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f)); - svg.draw(layer.slices, "green", 0.5f); + svg.draw(layer.lslices, "green", 0.5f); } #endif /* SLIC3R_DEBUG */ From fd93d9768d686ff14d03d07509ab5858de9e782a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 11 Aug 2020 12:15:44 +0200 Subject: [PATCH 283/826] Fixes of two crashes in paint-on supports --- src/libslic3r/PrintObject.cpp | 3 ++- src/libslic3r/TriangleSelector.cpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 273fc9c108..0f1a91a920 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2823,8 +2823,9 @@ void PrintObject::project_and_append_custom_supports( // Now append the collected polygons to respective layers. for (auto& trg : projections_of_triangles) { int layer_id = trg.first_layer_id; - for (const LightPolygon& poly : trg.polygons) { + if (layer_id >= int(expolys.size())) + break; // part of triangle could be projected above top layer expolys[layer_id].emplace_back(std::move(poly.pts)); ++layer_id; } diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 763bf58616..9555c42a69 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -60,8 +60,9 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, if (select_triangle(facet, new_state)) { // add neighboring facets to list to be proccessed later for (int n=0; n<3; ++n) { - if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) - facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n]; + if (neighbor_idx >=0 && faces_camera(neighbor_idx)) + facets_to_check.push_back(neighbor_idx); } } } From 6d4bb10ec59bfee42bc95ef8ea7b3892573404f0 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 12 Aug 2020 11:28:30 +0200 Subject: [PATCH 284/826] Fix of custom supports: object offset for Clipper was incorrectly accounted for --- src/libslic3r/PrintObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0f1a91a920..c90a05ef31 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2733,7 +2733,7 @@ void PrintObject::project_and_append_custom_supports( std::array trianglef; for (int i=0; i<3; ++i) { trianglef[i] = Vec2f(facet[i].x(), facet[i].y()); - trianglef[i] += Vec2f(unscale(this->center_offset().x()), + trianglef[i] -= Vec2f(unscale(this->center_offset().x()), unscale(this->center_offset().y())); } From 6dafdc5bab81b45a26a56d0e32fdfd035c06461b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Aug 2020 13:36:26 +0200 Subject: [PATCH 285/826] Fixed rescaling under MSW --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 116 +++++++++++++++++------- src/slic3r/GUI/UnsavedChangesDialog.hpp | 9 +- 2 files changed, 90 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 9f9ec3d9f3..53dcf3f029 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -56,32 +56,29 @@ static void make_string_bold(wxString& str) } // preset(root) node -ModelNode::ModelNode(Preset::Type preset_type, const wxString& text) : +ModelNode::ModelNode(Preset::Type preset_type, const wxString& text, wxWindow* parent_win) : + m_parent_win(parent_win), m_parent(nullptr), m_preset_type(preset_type), + m_icon_name(type_icon_names.at(preset_type)), m_text(text) { -#ifdef __linux__ - m_icon.CopyFromBitmap(create_scaled_bitmap(type_icon_names.at(preset_type))); -#else - m_icon = create_scaled_bitmap(type_icon_names.at(preset_type)); -#endif //__linux__ + UpdateIcons(); } // group node ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name) : + m_parent_win(parent->m_parent_win), m_parent(parent), + m_icon_name(icon_name), m_text(text) { -#ifdef __linux__ - m_icon.CopyFromBitmap(create_scaled_bitmap(icon_name)); -#else - m_icon = create_scaled_bitmap(icon_name); -#endif //__linux__ + UpdateIcons(); } // category node ModelNode::ModelNode(ModelNode* parent, const wxString& text) : + m_parent_win(parent->m_parent_win), m_parent(parent), m_text(text) { @@ -150,12 +147,13 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& ol void ModelNode::UpdateEnabling() { -#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) auto change_text_color = [](wxString& str, const std::string& clr_from, const std::string& clr_to) { +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) std::string old_val = into_u8(str); boost::replace_all(old_val, clr_from, clr_to); str = from_u8(old_val); +#endif }; if (!m_toggle) { @@ -168,12 +166,27 @@ void ModelNode::UpdateEnabling() change_text_color(m_old_value, grey, black); change_text_color(m_new_value, grey, orange); } -#endif // update icons for the colors + UpdateIcons(); +} + +void ModelNode::UpdateIcons() +{ + // update icons for the colors, if any exists if (!m_old_color.IsEmpty()) - m_old_color_bmp = get_bitmap(m_toggle? m_old_color : grey); + m_old_color_bmp = get_bitmap(m_toggle ? m_old_color : grey); if (!m_new_color.IsEmpty()) - m_new_color_bmp = get_bitmap(m_toggle? m_new_color : grey); + m_new_color_bmp = get_bitmap(m_toggle ? m_new_color : grey); + + // update main icon, if any exists + if (m_icon_name.empty()) + return; + +#ifdef __linux__ + m_icon.CopyFromBitmap(create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle)); +#else + m_icon = create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle); +#endif //__linux__ } @@ -198,7 +211,7 @@ wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset color_string(preset_name, black); make_string_bold(preset_name); - auto preset = new ModelNode(type, preset_name); + auto preset = new ModelNode(type, preset_name, m_parent_win); m_preset_nodes.emplace_back(preset); wxDataViewItem child((void*)preset); @@ -478,6 +491,24 @@ wxString UnsavedChangesModel::GetColumnType(unsigned int col) const } } +static void rescale_children(ModelNode* parent) +{ + if (parent->IsContainer()) { + for (ModelNode* child : parent->GetChildren()) { + child->UpdateIcons(); + rescale_children(child); + } + } +} + +void UnsavedChangesModel::Rescale() +{ + for (ModelNode* node : m_preset_nodes) { + node->UpdateIcons(); + rescale_children(node); + } +} + //------------------------------------------ // UnsavedChangesDialog @@ -489,6 +520,13 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SetBackgroundColour(bgr_clr); +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + // ys_FIXME! temporary workaround for correct font scaling + // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, + // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT + this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + int border = 10; int em = em_unit(); @@ -521,7 +559,6 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); m_tree->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &UnsavedChangesDialog::context_menu, this); - m_tree->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); // Add Buttons wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); @@ -534,26 +571,30 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& m_save_btn = new ScalableButton(this, m_save_btn_id = NewControlId(), "save", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); buttons->Insert(0, m_save_btn, 0, wxLEFT, 5); - m_save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); - m_save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); - m_save_btn->Bind(wxEVT_MOTION, [this, presets](wxMouseEvent& e){ show_info_line(Action::Save, presets->get_selected_preset().name); e.Skip(); }); + m_save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); + m_save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + m_save_btn->Bind(wxEVT_ENTER_WINDOW,[this, presets](wxMouseEvent& e){ show_info_line(Action::Save, presets->get_selected_preset().name); e.Skip(); }); + m_save_btn->Bind(wxEVT_LEAVE_WINDOW,[this ](wxMouseEvent& e){ show_info_line(Action::Undef); e.Skip(); }); label = from_u8((boost::format(_u8L("Move selected to preset: %1%"))% ("\"" + from_u8(new_selected_preset) + "\"")).str()); m_move_btn = new ScalableButton(this, m_move_btn_id = NewControlId(), "paste_menu", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); buttons->Insert(1, m_move_btn, 0, wxLEFT, 5); - m_move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); - m_move_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); - m_move_btn->Bind(wxEVT_MOTION, [this, new_selected_preset](wxMouseEvent& e){ show_info_line(Action::Move, new_selected_preset); e.Skip(); }); + m_move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); + m_move_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + m_move_btn->Bind(wxEVT_ENTER_WINDOW,[this, new_selected_preset](wxMouseEvent& e){ show_info_line(Action::Move, new_selected_preset); e.Skip(); }); + m_move_btn->Bind(wxEVT_LEAVE_WINDOW,[this ](wxMouseEvent& e){ show_info_line(Action::Undef); e.Skip(); }); label = _L("Continue without changes"); m_continue_btn = new ScalableButton(this, m_continue_btn_id = NewControlId(), "cross", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); buttons->Insert(2, m_continue_btn, 0, wxLEFT, 5); - m_continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); - m_continue_btn->Bind(wxEVT_MOTION, [this](wxMouseEvent& e){ show_info_line(Action::Continue); e.Skip(); }); + m_continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); + m_continue_btn->Bind(wxEVT_ENTER_WINDOW,[this](wxMouseEvent& e){ show_info_line(Action::Continue); e.Skip(); }); + m_continue_btn->Bind(wxEVT_LEAVE_WINDOW,[this](wxMouseEvent& e){ show_info_line(Action::Undef); e.Skip(); }); m_info_line = new wxStaticText(this, wxID_ANY, ""); - m_info_line->SetFont(wxGetApp().bold_font()); + m_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + m_info_line->Hide(); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); @@ -564,8 +605,6 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& update(type); - this->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); - SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -829,21 +868,34 @@ std::vector UnsavedChangesDialog::get_selected_options() void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) { - const int& em = em_unit(); + int em = em_unit(); msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id }); + for (auto btn : { m_save_btn, m_move_btn, m_continue_btn } ) + btn->msw_rescale(); const wxSize& size = wxSize(80 * em, 30 * em); SetMinSize(size); + m_tree->GetColumn(UnsavedChangesModel::colToggle )->SetWidth(6 * em); + m_tree->GetColumn(UnsavedChangesModel::colIconText)->SetWidth(30 * em); + m_tree->GetColumn(UnsavedChangesModel::colOldValue)->SetWidth(20 * em); + m_tree->GetColumn(UnsavedChangesModel::colNewValue)->SetWidth(20 * em); + + m_tree_model->Rescale(); + m_tree->Refresh(); + Fit(); Refresh(); } void UnsavedChangesDialog::on_sys_color_changed() { + for (auto btn : { m_save_btn, m_move_btn, m_continue_btn } ) + btn->msw_rescale(); // msw_rescale updates just icons, so use it -// m_tree_model->msw_rescale(); + m_tree_model->Rescale(); + m_tree->Refresh(); Refresh(); } @@ -871,14 +923,14 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString auto add_header = [grid_sizer, border, this](wxString label) { wxStaticText* text = new wxStaticText(this, wxID_ANY, label); - text->SetFont(wxGetApp().bold_font()); + text->SetFont(this->GetFont().Bold()); grid_sizer->Add(text, 0, wxALL, border); }; auto add_value = [grid_sizer, border, this](wxString label, bool is_colored = false) { wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(300, -1), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE | wxTE_RICH); + text->SetFont(this->GetFont()); if (is_colored) -// text->SetForegroundColour(wxColour(orange)); text->SetStyle(0, label.Len(), wxTextAttr(wxColour(orange))); grid_sizer->Add(text, 1, wxALL | wxEXPAND, border); }; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 991c894422..49a9640e8c 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -30,13 +30,14 @@ WX_DEFINE_ARRAY_PTR(ModelNode*, ModelNodePtrArray); // GTK - wxDataViewIconText (wxWidgets for GTK renderer wxIcon + wxString, supported Markup text) class ModelNode { - wxWindow* m_parent_win{ nullptr }; + wxWindow* m_parent_win{ nullptr }; ModelNode* m_parent; ModelNodePtrArray m_children; wxBitmap m_empty_bmp; Preset::Type m_preset_type {Preset::TYPE_INVALID}; + std::string m_icon_name; // saved values for colors if they exist wxString m_old_color; wxString m_new_color; @@ -75,7 +76,7 @@ public: wxString m_new_value; // preset(root) node - ModelNode(Preset::Type preset_type, const wxString& text); + ModelNode(Preset::Type preset_type, const wxString& text, wxWindow* parent_win); // category node ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name); @@ -111,6 +112,7 @@ public: void Append(ModelNode* child) { m_children.Add(child); } void UpdateEnabling(); + void UpdateIcons(); }; @@ -120,7 +122,7 @@ public: class UnsavedChangesModel : public wxDataViewModel { - wxWindow* m_parent_win {nullptr}; + wxWindow* m_parent_win { nullptr }; std::vector m_preset_nodes; wxDataViewCtrl* m_ctrl{ nullptr }; @@ -164,6 +166,7 @@ public: unsigned int GetColumnCount() const override { return colMax; } wxString GetColumnType(unsigned int col) const override; + void Rescale(); wxDataViewItem GetParent(const wxDataViewItem& item) const override; unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; From 493d52850ebdc374c8ae530a664fdc75817402a8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 12 Aug 2020 15:07:02 +0200 Subject: [PATCH 286/826] ENABLE_GCODE_VIEWER -> Drag and drop for non .gcode files while gcode viewer mode is active --- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6c3f2a4ea6..3f000e3328 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1008,7 +1008,7 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { if (m_plater->model().objects.empty() || wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES) + wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES) set_mode(EMode::GCodeViewer); }, "", nullptr, [this]() { return m_plater != nullptr && m_plater->printer_technology() != ptSLA; }, this); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 67b531eae9..759be43f32 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1364,6 +1364,11 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi std::vector paths; #if ENABLE_GCODE_VIEWER +#ifdef WIN32 + // hides the system icon + this->MSWUpdateDragImageOnLeave(); +#endif // WIN32 + // gcode section for (const auto& filename : filenames) { fs::path path(into_path(filename)); @@ -1373,7 +1378,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi if (paths.size() > 1) { wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."), - wxString(SLIC3R_APP_NAME) + " - " + _L("Open G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); return false; } else if (paths.size() == 1) { @@ -1383,11 +1388,11 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi } else { if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Open G-code file"), wxYES_NO | wxCANCEL | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { if (plater->model().objects.empty() || wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxCANCEL | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { + wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer); plater->load_gcode(from_path(paths.front())); return true; @@ -1407,6 +1412,16 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi return false; } +#if ENABLE_GCODE_VIEWER + if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { + if (wxMessageDialog((wxWindow*)plater, _L("Do you want to exit G-code preview ?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop model file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) + wxGetApp().mainframe->set_mode(MainFrame::EMode::Editor); + else + return false; + } +#endif // ENABLE_GCODE_VIEWER + wxString snapshot_label; assert(! paths.empty()); if (paths.size() == 1) { From 32595f7659ea42a3e87ab11a237520696b727ead Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Aug 2020 16:12:51 +0200 Subject: [PATCH 287/826] Fixed scaling of the SavePresetDialog under MSW + fixed misunderstanding typo in PlaterPresetComboBox --- src/slic3r/GUI/PresetComboBoxes.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 7539f36168..c62c999f6b 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -715,7 +715,7 @@ void PlaterPresetComboBox::update() std::map nonsys_presets; - wxString selected = ""; + wxString selected_user_preset = ""; wxString tooltip = ""; const std::deque& presets = m_collection->get_presets(); @@ -742,7 +742,7 @@ void PlaterPresetComboBox::update() { // Assign an extruder color to the selected item if the extruder color is defined. filament_rgb = preset.config.opt_string("filament_colour", 0); - extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; + extruder_rgb = (is_selected && !extruder_color.empty()) ? extruder_color : filament_rgb; single_bar = filament_rgb == extruder_rgb; bitmap_key += single_bar ? filament_rgb : filament_rgb + extruder_rgb; @@ -764,7 +764,7 @@ void PlaterPresetComboBox::update() { nonsys_presets.emplace(wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), bmp); if (is_selected) { - selected = wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); + selected_user_preset = wxString::FromUTF8((name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); tooltip = wxString::FromUTF8(preset.name.c_str()); } } @@ -776,7 +776,7 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { Append(it->first, *it->second); - validate_selection(it->first == selected); + validate_selection(it->first == selected_user_preset); } } @@ -1166,7 +1166,13 @@ void SavePresetDialog::Item::accept() SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) { - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + // ys_FIXME! temporary workaround for correct font scaling + // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, + // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT + this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); From a81e3ee2245a7c5a0c145a5e5cb3e7e72bfe690f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Aug 2020 17:33:22 +0200 Subject: [PATCH 288/826] UnsavedChangesDialog : implemented "Save" function --- src/slic3r/GUI/Tab.cpp | 41 ++++++++++++++++++++----- src/slic3r/GUI/UnsavedChangesDialog.cpp | 14 +++++++-- src/slic3r/GUI/UnsavedChangesDialog.hpp | 1 + 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6501fba27b..19f3974f77 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -326,7 +326,7 @@ void Tab::add_scaled_button(wxWindow* parent, const wxString& label/* = wxEmptyString*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style); + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style, true); m_scaled_buttons.push_back(*btn); } @@ -3132,19 +3132,43 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, // if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { + if (presets == nullptr) presets = m_presets; + UnsavedChangesDialog dlg(m_type, new_printer_name); if (dlg.ShowModal() == wxID_CANCEL) return false; if (dlg.just_continue()) return true; - if (dlg.save_preset()) - // save selected changes - return false; + if (dlg.save_preset()) // save selected changes + { + std::vector unselected_options = dlg.get_unselected_options(); + const Preset& preset = presets->get_edited_preset(); + std::string name = preset.name; + + // for system/default/external presets we should take an edited name + if (preset.is_system || preset.is_default || preset.is_external) { + SavePresetDialog save_dlg(m_type, _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName")); + if (save_dlg.ShowModal() != wxID_OK) + return false; + name = save_dlg.get_name(); + } + + // if we want to save just some from selected options + if (!unselected_options.empty()) + { + DynamicPrintConfig& old_config = presets->get_selected_preset().config; + + for (const std::string& opt_key : unselected_options) + m_config->set_key_value(opt_key, old_config.option(opt_key)->clone()); + } + + save_preset(name); + return true; + } if (dlg.move_preset()) // move selected changes return false; - - if (presets == nullptr) presets = m_presets; +/* // Display a dialog showing the dirty options in a human readable form. const Preset& old_preset = presets->get_edited_preset(); std::string type_name = presets->name(); @@ -3157,13 +3181,13 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr wxString changes; for (const std::string &opt_key : presets->current_dirty_options()) { const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); - /*std::string*/wxString name = ""; + wxString name = ""; if (! opt.category.empty()) name += _(opt.category) + " > "; name += !opt.full_label.empty() ? _(opt.full_label) : _(opt.label); - changes += tab + /*from_u8*/(name) + "\n"; + changes += tab + (name) + "\n"; } // Show a confirmation dialog with the list of dirty options. wxString message = name + "\n\n"; @@ -3180,6 +3204,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); return confirm.ShowModal() == wxID_YES; + */ } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 53dcf3f029..f93aa35c2a 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -854,6 +854,16 @@ void UnsavedChangesDialog::update(Preset::Type type) } } +std::vector UnsavedChangesDialog::get_unselected_options() +{ + std::vector ret; + + for (auto item : m_items_map) + if (!m_tree_model->IsEnabledItem(item.first)) + ret.emplace_back(item.second.opt_key); + + return ret; +} std::vector UnsavedChangesDialog::get_selected_options() { @@ -929,9 +939,7 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString auto add_value = [grid_sizer, border, this](wxString label, bool is_colored = false) { wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(300, -1), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE | wxTE_RICH); - text->SetFont(this->GetFont()); - if (is_colored) - text->SetStyle(0, label.Len(), wxTextAttr(wxColour(orange))); + text->SetStyle(0, label.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont())); grid_sizer->Add(text, 1, wxALL | wxEXPAND, border); }; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 49a9640e8c..271d7595b6 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -241,6 +241,7 @@ public: bool move_preset() const { return m_exit_action == Action::Move; } bool just_continue() const { return m_exit_action == Action::Continue; } + std::vector get_unselected_options(); std::vector get_selected_options(); protected: From 491e7b16f95a242510c31015c8dfb09257bfa98b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Aug 2020 08:20:22 +0200 Subject: [PATCH 289/826] Fixed build under GTK --- src/slic3r/GUI/Tab.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 19f3974f77..5c54e9e46e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3168,6 +3168,8 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr if (dlg.move_preset()) // move selected changes return false; + + return false; /* // Display a dialog showing the dirty options in a human readable form. const Preset& old_preset = presets->get_edited_preset(); From b80bde11f3929137b2ede00848f99b4c4a1a9d8f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 13 Aug 2020 12:51:50 +0200 Subject: [PATCH 290/826] GCodeProcessor -> Extract toolpaths height from gcode moves --- src/libslic3r/GCode/GCodeProcessor.cpp | 46 ++++++++++++++++++------ src/libslic3r/GCode/GCodeProcessor.hpp | 1 + src/libslic3r/Utils.hpp | 5 +-- src/slic3r/GUI/GCodeViewer.cpp | 48 +++++++++----------------- src/slic3r/GUI/GCodeViewer.hpp | 10 ++++-- 5 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 530dfb1059..18878cd5ad 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -23,7 +23,7 @@ static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 namespace Slic3r { const std::string GCodeProcessor::Extrusion_Role_Tag = "ExtrType:"; -const std::string GCodeProcessor::Width_Tag = "PrusaSlicer__WIDTH:"; +const std::string GCodeProcessor::Width_Tag = "Width:"; const std::string GCodeProcessor::Height_Tag = "Height:"; const std::string GCodeProcessor::Color_Change_Tag = "Color change"; const std::string GCodeProcessor::Pause_Print_Tag = "Pause print"; @@ -81,6 +81,19 @@ static float acceleration_time_from_distance(float initial_feedrate, float dista return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f; } +float round_to_nearest(float value, unsigned int decimals) +{ + float res = 0.0f; + if (decimals == 0) + res = std::round(value); + else { + char buf[64]; + sprintf(buf, "%.*g", decimals, value); + res = std::stof(buf); + } + return res; +} + void GCodeProcessor::CachedPosition::reset() { std::fill(position.begin(), position.end(), FLT_MAX); @@ -666,6 +679,7 @@ void GCodeProcessor::reset() m_extruder_id = 0; m_extruder_colors = ExtruderColors(); m_filament_diameters = std::vector(); + m_extruded_last_z = 0.0f; m_cp_color.reset(); m_producer = EProducer::Unknown; @@ -1285,14 +1299,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) type = EMoveType::Travel; - if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) { - if (m_extrusion_role != erCustom) { - m_width = 0.5f; - m_height = 0.5f; - } - type = EMoveType::Travel; - } - return type; }; @@ -1325,8 +1331,26 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (type == EMoveType::Extrude) { if (delta_pos[E] > 0.0f) { float ds = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); - if (ds > 0.0f && static_cast(m_extruder_id) < m_filament_diameters.size()) - m_mm3_per_mm = round_nearest(delta_pos[E] * static_cast(M_PI) * sqr(static_cast(m_filament_diameters[m_extruder_id])) / (4.0f * ds), 3); + if (ds > 0.0f && static_cast(m_extruder_id) < m_filament_diameters.size()) { + // extruded filament volume / tool displacement + m_mm3_per_mm = round_to_nearest(static_cast(M_PI * sqr(m_filament_diameters[m_extruder_id]) * 0.25) * delta_pos[E] / ds, 4); + } + + if (m_end_position[Z] > m_extruded_last_z + EPSILON) { + m_height = round_to_nearest(m_end_position[Z] - m_extruded_last_z, 4); + m_extruded_last_z = m_end_position[Z]; + } + } + } + + if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) { + if ((m_width == 0.0f && m_height == 0.0f) || m_extrusion_role == erCustom) + type = EMoveType::Travel; + else { + if (m_width == 0.0f) + m_width = 0.5f; + if (m_height == 0.0f) + m_height = 0.5f; } } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index e35d3a9735..f103bab967 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -304,6 +304,7 @@ namespace Slic3r { unsigned char m_extruder_id; ExtruderColors m_extruder_colors; std::vector m_filament_diameters; + float m_extruded_last_z; CpColor m_cp_color; enum class EProducer diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index f7ff29b7e0..ef531169d1 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -110,19 +110,20 @@ std::string header_slic3r_generated(); // getpid platform wrapper extern unsigned get_current_pid(); +#if !ENABLE_GCODE_VIEWER template Real round_nearest(Real value, unsigned int decimals) { Real res = (Real)0; if (decimals == 0) res = ::round(value); - else - { + else { Real power = ::pow((Real)10, (int)decimals); res = ::round(value * power + (Real)0.5) / power; } return res; } +#endif // !ENABLE_GCODE_VIEWER // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index bbd357e980..b7bd81a0fa 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -235,38 +235,19 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.00f, 0.00f, 0.00f } // erMixed }}; -//const std::vector GCodeViewer::Extrusion_Role_Colors {{ -// { 0.75f, 0.75f, 0.75f }, // erNone -// { 1.00f, 1.00f, 0.40f }, // erPerimeter -// { 1.00f, 0.65f, 0.00f }, // erExternalPerimeter -// { 0.00f, 0.00f, 1.00f }, // erOverhangPerimeter -// { 0.69f, 0.19f, 0.16f }, // erInternalInfill -// { 0.84f, 0.20f, 0.84f }, // erSolidInfill -// { 1.00f, 0.10f, 0.10f }, // erTopSolidInfill -// { 1.00f, 0.55f, 0.41f }, // erIroning -// { 0.60f, 0.60f, 1.00f }, // erBridgeInfill -// { 1.00f, 1.00f, 1.00f }, // erGapFill -// { 0.52f, 0.48f, 0.13f }, // erSkirt -// { 0.00f, 1.00f, 0.00f }, // erSupportMaterial -// { 0.00f, 0.50f, 0.00f }, // erSupportMaterialInterface -// { 0.70f, 0.89f, 0.67f }, // erWipeTower -// { 0.16f, 0.80f, 0.58f }, // erCustom -// { 0.00f, 0.00f, 0.00f } // erMixed -//}}; - const std::vector GCodeViewer::Options_Colors {{ - { 1.00f, 0.00f, 1.00f }, // Retractions - { 0.00f, 1.00f, 1.00f }, // Unretractions - { 1.00f, 1.00f, 1.00f }, // ToolChanges - { 1.00f, 0.00f, 0.00f }, // ColorChanges - { 0.00f, 1.00f, 0.00f }, // PausePrints - { 0.00f, 0.00f, 1.00f } // CustomGCodes + { 0.803f, 0.135f, 0.839f }, // Retractions + { 0.287f, 0.679f, 0.810f }, // Unretractions + { 0.758f, 0.744f, 0.389f }, // ToolChanges + { 0.856f, 0.582f, 0.546f }, // ColorChanges + { 0.322f, 0.942f, 0.512f }, // PausePrints + { 0.886f, 0.825f, 0.262f } // CustomGCodes }}; const std::vector GCodeViewer::Travel_Colors {{ - { 0.0f, 0.0f, 0.5f }, // Move - { 0.0f, 0.5f, 0.0f }, // Extrude - { 0.5f, 0.0f, 0.0f } // Retract + { 0.219f, 0.282f, 0.609f }, // Move + { 0.112f, 0.422f, 0.103f }, // Extrude + { 0.505f, 0.064f, 0.028f } // Retract }}; const std::vector GCodeViewer::Range_Colors {{ @@ -279,7 +260,8 @@ const std::vector GCodeViewer::Range_Colors {{ { 0.961f, 0.808f, 0.039f }, { 0.890f, 0.533f, 0.125f }, { 0.820f, 0.408f, 0.188f }, - { 0.761f, 0.322f, 0.235f } // reddish + { 0.761f, 0.322f, 0.235f }, + { 0.581f, 0.149f, 0.087f } // reddish }}; bool GCodeViewer::init() @@ -1525,11 +1507,15 @@ void GCodeViewer::render_legend() const append_item(EItemType::Rect, Range_Colors[i], buf); }; - float step_size = range.step_size(); - if (step_size == 0.0f) + if (range.count == 1) // single item use case append_range_item(0, range.min, decimals); + else if (range.count == 2) { + append_range_item(static_cast(Range_Colors.size()) - 1, range.max, decimals); + append_range_item(0, range.min, decimals); + } else { + float step_size = range.step_size(); for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { append_range_item(i, range.min + static_cast(i) * step_size, decimals); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 038b837b2b..74506677a6 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -142,11 +142,17 @@ class GCodeViewer { float min; float max; + unsigned int count; Range() { reset(); } - void update_from(const float value) { min = std::min(min, value); max = std::max(max, value); } - void reset() { min = FLT_MAX; max = -FLT_MAX; } + void update_from(const float value) { + if (value != max && value != min) + ++count; + min = std::min(min, value); + max = std::max(max, value); + } + void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; } float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } Color get_color_at(float value) const; From 830d89576c6d18e47c2b69a238cf2bd2142d2840 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Aug 2020 11:14:44 +0200 Subject: [PATCH 291/826] Added description line for the disabling of "Object elevation" Deleted mirrored parameter "pad_around_object" from "Support" category --- src/slic3r/GUI/Tab.cpp | 16 +++++++++++++++- src/slic3r/GUI/Tab.hpp | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4f4beb202c..9970eb5b96 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3971,9 +3971,16 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_base_safety_distance"); // Mirrored parameter from Pad page for toggling elevation on the same page - optgroup->append_single_option_line("pad_around_object"); +// optgroup->append_single_option_line("pad_around_object"); optgroup->append_single_option_line("support_object_elevation"); + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_support_object_elevation_description_line); + }; + optgroup->append_line(line); + 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"); @@ -4047,6 +4054,13 @@ void TabSLAPrint::update() m_update_cnt++; m_config_manipulation.update_print_sla_config(m_config, true); + + m_support_object_elevation_description_line->SetText(!m_config->opt_bool("pad_around_object") ? "" : + from_u8((boost::format(_u8L("\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" + "To enable \"%1%\", please switch off \"%2%\"")) + % _L("Object elevation") % _L("Pad around object") % _L("Pad")).str())); + Layout(); + m_update_cnt--; if (m_update_cnt == 0) { diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 24f25e2d74..de4c5ccb91 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -460,6 +460,9 @@ public: // Tab(parent, _(L("Print Settings")), L("sla_print")) {} Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} ~TabSLAPrint() {} + + ogStaticText* m_support_object_elevation_description_line = nullptr; + void build() override; void reload_config() override; void update() override; From 9486901b93f4dfeaf84f2db05b4f25769bbe9b82 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 4 Aug 2020 12:54:21 +0200 Subject: [PATCH 292/826] Minor change to SLAPrinter interface --- src/libslic3r/Format/SL1.cpp | 4 ++-- src/libslic3r/Format/SL1.hpp | 2 +- src/libslic3r/SLAPrint.hpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index ba5e89330b..5c402ef5bf 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -126,9 +126,9 @@ uqptr SL1Archive::create_raster() const return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr); } -sla::EncodedRaster SL1Archive::encode_raster(const sla::RasterBase &rst) const +sla::RasterEncoder SL1Archive::get_encoder() const { - return rst.encode(sla::PNGRasterEncoder()); + return sla::PNGRasterEncoder{}; } void SL1Archive::export_print(Zipper& zipper, diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 1b9e95392b..fbb6d61604 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -13,7 +13,7 @@ class SL1Archive: public SLAPrinter { protected: uqptr create_raster() const override; - sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const override; + sla::RasterEncoder get_encoder() const override; public: diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index f4b220c58c..0ad544baa2 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -374,7 +374,7 @@ protected: std::vector m_layers; virtual uqptr create_raster() const = 0; - virtual sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const = 0; + virtual sla::RasterEncoder get_encoder() const = 0; public: virtual ~SLAPrinter() = default; @@ -389,7 +389,7 @@ public: [this, &drawfn](sla::EncodedRaster& enc, size_t idx) { auto rst = create_raster(); drawfn(*rst, idx); - enc = encode_raster(*rst); + enc = rst->encode(get_encoder()); }); } }; From 929cea59f34142e044b5b3c22edf0d1798f93ef9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 5 Aug 2020 15:49:36 +0200 Subject: [PATCH 293/826] replace ccr_::enumerate with flexible for_each enumerate is unusual and would only work effectively with random access iterators this for_each takes advantage of tbb blocked_range replace ccr_::enumerate with flexible for_each enumerate is unusual and would only work effectively with random access iterators this for_each takes advantage of tbb blocked_range --- src/libslic3r/SLA/Concurrency.hpp | 49 ++++-- src/libslic3r/SLA/IndexedMesh.cpp | 6 +- src/libslic3r/SLA/SupportPointGenerator.cpp | 178 ++++++++++---------- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 24 +-- src/libslic3r/SLAPrint.hpp | 13 +- src/libslic3r/SLAPrintSteps.cpp | 19 ++- src/libslic3r/libslic3r.h | 5 + 7 files changed, 169 insertions(+), 125 deletions(-) diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index 8620c67b19..12a6e41e37 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace Slic3r { namespace sla { @@ -17,16 +19,29 @@ template struct _ccr {}; template<> struct _ccr { using SpinningMutex = tbb::spin_mutex; - using BlockingMutex = tbb::mutex; - + using BlockingMutex = tbb::mutex; + template - static inline void enumerate(It from, It to, Fn fn) + static IteratorOnly for_each(It from, + It to, + Fn && fn, + size_t granularity = 1) { - auto iN = to - from; - size_t N = iN < 0 ? 0 : size_t(iN); - - tbb::parallel_for(size_t(0), N, [from, fn](size_t n) { - fn(*(from + decltype(iN)(n)), n); + tbb::parallel_for(tbb::blocked_range{from, to, granularity}, + [&fn, from](const auto &range) { + for (auto &el : range) fn(el); + }); + } + + template + static IntegerOnly for_each(I from, + I to, + Fn && fn, + size_t granularity = 1) + { + tbb::parallel_for(tbb::blocked_range{from, to, granularity}, + [&fn](const auto &range) { + for (I i = range.begin(); i < range.end(); ++i) fn(i); }); } }; @@ -39,11 +54,23 @@ private: public: using SpinningMutex = _Mtx; using BlockingMutex = _Mtx; - + template - static inline void enumerate(It from, It to, Fn fn) + static IteratorOnly for_each(It from, + It to, + Fn &&fn, + size_t /* ignore granularity */ = 1) { - for (auto it = from; it != to; ++it) fn(*it, size_t(it - from)); + for (auto it = from; it != to; ++it) fn(*it); + } + + template + static IntegerOnly for_each(I from, + I to, + Fn &&fn, + size_t /* ignore granularity */ = 1) + { + for (I i = from; i < to; ++i) fn(i); } }; diff --git a/src/libslic3r/SLA/IndexedMesh.cpp b/src/libslic3r/SLA/IndexedMesh.cpp index 573b62b6db..efcf09873e 100644 --- a/src/libslic3r/SLA/IndexedMesh.cpp +++ b/src/libslic3r/SLA/IndexedMesh.cpp @@ -320,10 +320,10 @@ PointSet normals(const PointSet& points, PointSet ret(range.size(), 3); // for (size_t ridx = 0; ridx < range.size(); ++ridx) - ccr::enumerate( - range.begin(), range.end(), - [&ret, &mesh, &points, thr, eps](unsigned el, size_t ridx) { + ccr::for_each(size_t(0), range.size(), + [&ret, &mesh, &points, thr, eps, &range](size_t ridx) { thr(); + unsigned el = range[ridx]; auto eidx = Eigen::Index(el); int faceid = 0; Vec3d p; diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 3cd075ae69..78b3349efd 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -4,6 +4,7 @@ #include #include "SupportPointGenerator.hpp" +#include "Concurrency.hpp" #include "Model.hpp" #include "ExPolygon.hpp" #include "SVG.hpp" @@ -87,27 +88,28 @@ void SupportPointGenerator::project_onto_mesh(std::vector& po // The function makes sure that all the points are really exactly placed on the mesh. // Use a reasonable granularity to account for the worker thread synchronization cost. - tbb::parallel_for(tbb::blocked_range(0, points.size(), 64), - [this, &points](const tbb::blocked_range& range) { - for (size_t point_id = range.begin(); point_id < range.end(); ++ point_id) { - if ((point_id % 16) == 0) - // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. - m_throw_on_cancel(); - Vec3f& p = points[point_id].pos; - // Project the point upward and downward and choose the closer intersection with the mesh. - sla::IndexedMesh::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); - sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); + static constexpr size_t gransize = 64; - bool up = hit_up.is_hit(); - bool down = hit_down.is_hit(); + ccr_par::for_each(size_t(0), points.size(), [this, &points](size_t idx) + { + if ((idx % 16) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + m_throw_on_cancel(); - if (!up && !down) - continue; + Vec3f& p = points[idx].pos; + // Project the point upward and downward and choose the closer intersection with the mesh. + sla::IndexedMesh::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); + sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); - sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; - p = p + (hit.distance() * hit.direction()).cast(); - } - }); + bool up = hit_up.is_hit(); + bool down = hit_down.is_hit(); + + if (!up && !down) + return; + + sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; + p = p + (hit.distance() * hit.direction()).cast(); + }, gransize); } static std::vector make_layers( @@ -126,78 +128,80 @@ static std::vector make_layers( //const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option("display_width") / wxGetApp().preset_bundle->project_config.option("display_pixels_x"), 2.f); // const float pixel_area = pow(0.047f, 2.f); - // Use a reasonable granularity to account for the worker thread synchronization cost. - tbb::parallel_for(tbb::blocked_range(0, layers.size(), 32), - [&layers, &slices, &heights, pixel_area, throw_on_cancel](const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { - if ((layer_id % 8) == 0) - // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. - throw_on_cancel(); - SupportPointGenerator::MyLayer &layer = layers[layer_id]; - const ExPolygons &islands = slices[layer_id]; - //FIXME WTF? - const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); - layer.islands.reserve(islands.size()); - for (const ExPolygon &island : islands) { - float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR); - if (area >= pixel_area) - //FIXME this is not a correct centroid of a polygon with holes. - layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast(), area, height); - } - } - }); + ccr_par::for_each(0ul, layers.size(), + [&layers, &slices, &heights, pixel_area, throw_on_cancel](size_t layer_id) + { + if ((layer_id % 8) == 0) + // Don't call the following function too often as it flushes + // CPU write caches due to synchronization primitves. + throw_on_cancel(); + + SupportPointGenerator::MyLayer &layer = layers[layer_id]; + const ExPolygons & islands = slices[layer_id]; + // FIXME WTF? + const float height = (layer_id > 2 ? + heights[layer_id - 3] : + heights[0] - (heights[1] - heights[0])); + layer.islands.reserve(islands.size()); + for (const ExPolygon &island : islands) { + float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR); + if (area >= pixel_area) + // FIXME this is not a correct centroid of a polygon with holes. + layer.islands.emplace_back(layer, island, get_extents(island.contour), + unscaled(island.contour.centroid()), area, height); + } + }, 32 /*gransize*/); // Calculate overlap of successive layers. Link overlapping islands. - tbb::parallel_for(tbb::blocked_range(1, layers.size(), 8), - [&layers, &heights, throw_on_cancel](const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { - if ((layer_id % 2) == 0) - // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. - throw_on_cancel(); - SupportPointGenerator::MyLayer &layer_above = layers[layer_id]; - SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1]; - //FIXME WTF? - const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); - const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports - const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); - const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports - const float slope_offset = float(scale_(layer_height / std::tan(slope_angle))); - //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. - for (SupportPointGenerator::Structure &top : layer_above.islands) { - for (SupportPointGenerator::Structure &bottom : layer_below.islands) { - float overlap_area = top.overlap_area(bottom); - if (overlap_area > 0) { - top.islands_below.emplace_back(&bottom, overlap_area); - bottom.islands_above.emplace_back(&top, overlap_area); - } - } - if (! top.islands_below.empty()) { - Polygons top_polygons = to_polygons(*top.polygon); - Polygons bottom_polygons = top.polygons_below(); - top.overhangs = diff_ex(top_polygons, bottom_polygons); - if (! top.overhangs.empty()) { - top.overhangs_area = 0.f; - std::vector> expolys_with_areas; - for (ExPolygon &ex : top.overhangs) { - float area = float(ex.area()); - expolys_with_areas.emplace_back(&ex, area); - top.overhangs_area += area; - } - std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), + ccr_par::for_each(1ul, layers.size(), + [&layers, &heights, throw_on_cancel] (size_t layer_id) + { + if ((layer_id % 2) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); + SupportPointGenerator::MyLayer &layer_above = layers[layer_id]; + SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1]; + //FIXME WTF? + const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); + const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports + const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); + const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports + const float slope_offset = float(scale_(layer_height / std::tan(slope_angle))); + //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. + for (SupportPointGenerator::Structure &top : layer_above.islands) { + for (SupportPointGenerator::Structure &bottom : layer_below.islands) { + float overlap_area = top.overlap_area(bottom); + if (overlap_area > 0) { + top.islands_below.emplace_back(&bottom, overlap_area); + bottom.islands_above.emplace_back(&top, overlap_area); + } + } + if (! top.islands_below.empty()) { + Polygons top_polygons = to_polygons(*top.polygon); + Polygons bottom_polygons = top.polygons_below(); + top.overhangs = diff_ex(top_polygons, bottom_polygons); + if (! top.overhangs.empty()) { + top.overhangs_area = 0.f; + std::vector> expolys_with_areas; + for (ExPolygon &ex : top.overhangs) { + float area = float(ex.area()); + expolys_with_areas.emplace_back(&ex, area); + top.overhangs_area += area; + } + std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), [](const std::pair &p1, const std::pair &p2) - { return p1.second > p2.second; }); - ExPolygons overhangs_sorted; - for (auto &p : expolys_with_areas) - overhangs_sorted.emplace_back(std::move(*p.first)); - top.overhangs = std::move(overhangs_sorted); - top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); - top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset)); - top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); - } - } - } - } - }); + { return p1.second > p2.second; }); + ExPolygons overhangs_sorted; + for (auto &p : expolys_with_areas) + overhangs_sorted.emplace_back(std::move(*p.first)); + top.overhangs = std::move(overhangs_sorted); + top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); + top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset)); + top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); + } + } + } + }, 8 /* gransize */); return layers; } diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 2b40f00828..0adcb85283 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -209,14 +209,16 @@ IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect( // of the pinhead robe (side) surface. The result will be the smallest // hit distance. - ccr::enumerate(hits.begin(), hits.end(), - [&m, &rings, sd](HitResult &hit, size_t i) { + ccr::for_each(size_t(0), hits.size(), + [&m, &rings, sd, &hits](size_t i) { // Point on the circle on the pin sphere Vec3d ps = rings.pinring(i); // This is the point on the circle on the back sphere Vec3d p = rings.backring(i); + auto &hit = hits[i]; + // Point ps is not on mesh but can be inside or // outside as well. This would cause many problems // with ray-casting. To detect the position we will @@ -265,8 +267,10 @@ IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect( // Hit results std::array hits; - ccr::enumerate(hits.begin(), hits.end(), - [this, r, src, /*ins_check,*/ &ring, dir, sd] (Hit &hit, size_t i) { + ccr::for_each(size_t(0), hits.size(), + [this, r, src, /*ins_check,*/ &ring, dir, sd, &hits] (size_t i) + { + Hit &hit = hits[i]; // Point on the circle on the pin sphere Vec3d p = ring.get(i, src, r + sd); @@ -744,10 +748,10 @@ void SupportTreeBuildsteps::filter() } }; - ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), - [this, &filterfn](unsigned fidx, size_t i) { - filterfn(fidx, i, m_cfg.head_back_radius_mm); - }); + ccr::for_each(0ul, filtered_indices.size(), + [this, &filterfn, &filtered_indices] (size_t i) { + filterfn(filtered_indices[i], i, m_cfg.head_back_radius_mm); + }); for (size_t i = 0; i < heads.size(); ++i) if (heads[i].is_valid()) { @@ -1033,8 +1037,8 @@ void SupportTreeBuildsteps::routing_to_model() // If it can be routed there with a bridge shorter than // min_bridge_distance. - ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), - [this] (const unsigned idx, size_t) { + ccr::for_each(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), + [this] (const unsigned idx) { m_thr(); auto& head = m_builder.head(idx); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 0ad544baa2..8948dc3312 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -385,12 +385,13 @@ public: template void draw_layers(size_t layer_num, Fn &&drawfn) { m_layers.resize(layer_num); - sla::ccr::enumerate(m_layers.begin(), m_layers.end(), - [this, &drawfn](sla::EncodedRaster& enc, size_t idx) { - auto rst = create_raster(); - drawfn(*rst, idx); - enc = rst->encode(get_encoder()); - }); + sla::ccr::for_each(0ul, m_layers.size(), + [this, &drawfn] (size_t idx) { + sla::EncodedRaster& enc = m_layers[idx]; + auto rst = create_raster(); + drawfn(*rst, idx); + enc = rst->encode(get_encoder()); + }); } }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 76bbf498d0..b4c994e8a2 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -264,11 +264,12 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) std::vector interior_slices; interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); - sla::ccr::enumerate(interior_slices.begin(), interior_slices.end(), - [&po](const ExPolygons &slice, size_t i) { - po.m_model_slices[i] = - diff_ex(po.m_model_slices[i], slice); - }); + sla::ccr::for_each(0ul, interior_slices.size(), + [&po, &interior_slices] (size_t i) { + const ExPolygons &slice = interior_slices[i]; + po.m_model_slices[i] = + diff_ex(po.m_model_slices[i], slice); + }); } auto mit = slindex_it; @@ -679,14 +680,16 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { using Lock = std::lock_guard; // Going to parallel: - auto printlayerfn = [ + auto printlayerfn = [this, // functions and read only vars areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, // write vars &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, - &fast_layers, &fade_layer_time](PrintLayer& layer, size_t sliced_layer_cnt) + &fast_layers, &fade_layer_time](size_t sliced_layer_cnt) { + PrintLayer &layer = m_print->m_printer_input[sliced_layer_cnt]; + // vector of slice record references auto& slicerecord_references = layer.slices(); @@ -789,7 +792,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // sequential version for debugging: // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); - sla::ccr::enumerate(printer_input.begin(), printer_input.end(), printlayerfn); + sla::ccr::for_each(0ul, printer_input.size(), printlayerfn); auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; print_statistics.support_used_material = supports_volume * SCALING2; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 5ceb62a5cf..5e57c45913 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -261,6 +261,11 @@ using IntegerOnly = std::enable_if_t::value, O>; template using ArithmeticOnly = std::enable_if_t::value, O>; +template +using IteratorOnly = std::enable_if_t< + !std::is_same_v::value_type, void>, O +>; + } // namespace Slic3r #endif From 399c5a9c98b2f9b6dbf28bc90420d0de831b72a9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Aug 2020 14:54:13 +0200 Subject: [PATCH 294/826] Show description for disabled elevation when pad or pad around is off Follow-up fix for 830d89 --- src/slic3r/GUI/Tab.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9970eb5b96..a56dadf4f5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4055,7 +4055,8 @@ void TabSLAPrint::update() m_config_manipulation.update_print_sla_config(m_config, true); - m_support_object_elevation_description_line->SetText(!m_config->opt_bool("pad_around_object") ? "" : + bool elev = !m_config->opt_bool("pad_enable") || !m_config->opt_bool("pad_around_object"); + m_support_object_elevation_description_line->SetText(elev ? "" : from_u8((boost::format(_u8L("\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" "To enable \"%1%\", please switch off \"%2%\"")) % _L("Object elevation") % _L("Pad around object") % _L("Pad")).str())); From 7158690ddd539d04ddcf67838ee7c9d5392f7c36 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Aug 2020 15:09:22 +0200 Subject: [PATCH 295/826] Fix build on win and rpi --- src/libslic3r/SLA/SupportPointGenerator.cpp | 4 ++-- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 2 +- src/libslic3r/SLAPrint.hpp | 2 +- src/libslic3r/SLAPrintSteps.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 78b3349efd..449269445d 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -128,7 +128,7 @@ static std::vector make_layers( //const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option("display_width") / wxGetApp().preset_bundle->project_config.option("display_pixels_x"), 2.f); // const float pixel_area = pow(0.047f, 2.f); - ccr_par::for_each(0ul, layers.size(), + ccr_par::for_each(size_t(0), layers.size(), [&layers, &slices, &heights, pixel_area, throw_on_cancel](size_t layer_id) { if ((layer_id % 8) == 0) @@ -153,7 +153,7 @@ static std::vector make_layers( }, 32 /*gransize*/); // Calculate overlap of successive layers. Link overlapping islands. - ccr_par::for_each(1ul, layers.size(), + ccr_par::for_each(size_t(1), layers.size(), [&layers, &heights, throw_on_cancel] (size_t layer_id) { if ((layer_id % 2) == 0) diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 0adcb85283..0e7af8d508 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -748,7 +748,7 @@ void SupportTreeBuildsteps::filter() } }; - ccr::for_each(0ul, filtered_indices.size(), + ccr::for_each(size_t(0), filtered_indices.size(), [this, &filterfn, &filtered_indices] (size_t i) { filterfn(filtered_indices[i], i, m_cfg.head_back_radius_mm); }); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 8948dc3312..5fbf8346c7 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -385,7 +385,7 @@ public: template void draw_layers(size_t layer_num, Fn &&drawfn) { m_layers.resize(layer_num); - sla::ccr::for_each(0ul, m_layers.size(), + sla::ccr::for_each(size_t(0), m_layers.size(), [this, &drawfn] (size_t idx) { sla::EncodedRaster& enc = m_layers[idx]; auto rst = create_raster(); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index b4c994e8a2..eaf9698198 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -264,7 +264,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) std::vector interior_slices; interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); - sla::ccr::for_each(0ul, interior_slices.size(), + sla::ccr::for_each(size_t(0), interior_slices.size(), [&po, &interior_slices] (size_t i) { const ExPolygons &slice = interior_slices[i]; po.m_model_slices[i] = @@ -792,7 +792,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // sequential version for debugging: // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); - sla::ccr::for_each(0ul, printer_input.size(), printlayerfn); + sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn); auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; print_statistics.support_used_material = supports_volume * SCALING2; From add3894e8c0ddc7533b6bedd97033a36e60695d8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Aug 2020 09:00:58 +0200 Subject: [PATCH 296/826] Add reserve_vector to libslic3r.h to be globally usable. --- src/libslic3r/MTUtils.hpp | 9 --------- src/libslic3r/libslic3r.h | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index a6c8d61622..555cfe5019 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -114,15 +114,6 @@ template struct remove_cvref template using remove_cvref_t = typename remove_cvref::type; -template // Arbitrary allocator can be used -inline IntegerOnly> reserve_vector(I capacity) -{ - std::vector ret; - if (capacity > I(0)) ret.reserve(size_t(capacity)); - - return ret; -} - /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html template> inline std::vector linspace_vector(const ArithmeticOnly &start, diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 5e57c45913..76ff271360 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -266,6 +266,15 @@ using IteratorOnly = std::enable_if_t< !std::is_same_v::value_type, void>, O >; +template // Arbitrary allocator can be used +IntegerOnly> reserve_vector(I capacity) +{ + std::vector ret; + if (capacity > I(0)) ret.reserve(size_t(capacity)); + + return ret; +} + } // namespace Slic3r #endif From d7176c64bdb22061e6b603dee7042af63c628967 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Aug 2020 15:45:16 +0200 Subject: [PATCH 297/826] Unsaved Changes : implemented "move" and improved "save" UnsavedChangesDialog : some code refactoring SavePresetDialog : processed empty name for the preset --- src/slic3r/GUI/PresetComboBoxes.cpp | 7 +++ src/slic3r/GUI/Tab.cpp | 80 ++++++++++-------------- src/slic3r/GUI/Tab.hpp | 1 + src/slic3r/GUI/UnsavedChangesDialog.cpp | 81 ++++++++++++++----------- src/slic3r/GUI/UnsavedChangesDialog.hpp | 5 +- 5 files changed, 90 insertions(+), 84 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 7539f36168..7a9ea582af 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1123,10 +1123,12 @@ void SavePresetDialog::Item::update() info_line = _L("Cannot overwrite a system profile."); m_valid_type = NoValid; } + if (m_valid_type == Valid && existing && (existing->is_external)) { info_line = _L("Cannot overwrite an external profile."); m_valid_type = NoValid; } + if (m_valid_type == Valid && existing && m_preset_name != m_presets->get_selected_preset_name()) { info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()) + "\n" + @@ -1134,6 +1136,11 @@ void SavePresetDialog::Item::update() m_valid_type = Warning; } + if (m_valid_type == Valid && m_preset_name.empty()) { + info_line = _L("The empty name is not available."); + m_valid_type = NoValid; + } + m_valid_label->SetLabel(info_line); m_valid_label->Show(!info_line.IsEmpty()); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 5c54e9e46e..ef124e0e6e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3124,6 +3124,12 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } + // check if there is something in the cache to move to the new selected preset + if (!m_cache_config.empty()) { + m_presets->get_edited_preset().config.apply(m_cache_config); + m_cache_config.clear(); + } + load_current_preset(); } } @@ -3134,11 +3140,10 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr { if (presets == nullptr) presets = m_presets; - UnsavedChangesDialog dlg(m_type, new_printer_name); + UnsavedChangesDialog dlg(m_type, presets, new_printer_name); if (dlg.ShowModal() == wxID_CANCEL) return false; - if (dlg.just_continue()) - return true; + if (dlg.save_preset()) // save selected changes { std::vector unselected_options = dlg.get_unselected_options(); @@ -3147,7 +3152,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr // for system/default/external presets we should take an edited name if (preset.is_system || preset.is_default || preset.is_external) { - SavePresetDialog save_dlg(m_type, _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName")); + SavePresetDialog save_dlg(preset.type, _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName")); if (save_dlg.ShowModal() != wxID_OK) return false; name = save_dlg.get_name(); @@ -3157,56 +3162,35 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr if (!unselected_options.empty()) { DynamicPrintConfig& old_config = presets->get_selected_preset().config; - + // revert unselected options to the old values for (const std::string& opt_key : unselected_options) - m_config->set_key_value(opt_key, old_config.option(opt_key)->clone()); + presets->get_edited_preset().config.set_key_value(opt_key, old_config.option(opt_key)->clone()); } - - save_preset(name); - return true; - } - if (dlg.move_preset()) - // move selected changes - return false; - return false; -/* - // Display a dialog showing the dirty options in a human readable form. - const Preset& old_preset = presets->get_edited_preset(); - std::string type_name = presets->name(); - wxString tab = " "; - wxString name = old_preset.is_default ? - from_u8((boost::format(_utf8(L("Default preset (%s)"))) % _utf8(type_name)).str()) : - from_u8((boost::format(_utf8(L("Preset (%s)"))) % _utf8(type_name)).str()) + "\n" + tab + old_preset.name; + if (m_type == presets->type()) // save changes for the current preset + save_preset(name); + else // save changes for dependent preset + { + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini + presets->save_current_preset(name); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. + m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); - // Collect descriptions of the dirty options. - wxString changes; - for (const std::string &opt_key : presets->current_dirty_options()) { - const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); - wxString name = ""; - if (! opt.category.empty()) - name += _(opt.category) + " > "; - name += !opt.full_label.empty() ? - _(opt.full_label) : - _(opt.label); - changes += tab + (name) + "\n"; + /* If filament preset is saved for multi-material printer preset, + * there are cases when filament comboboxs are updated for old (non-modified) colors, + * but in full_config a filament_colors option aren't.*/ + if (presets->type() == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + wxGetApp().plater()->force_filament_colors_update(); + } } - // Show a confirmation dialog with the list of dirty options. - wxString message = name + "\n\n"; - if (new_printer_name.empty()) - message += _(L("has the following unsaved changes:")); - else { - message += (m_type == Slic3r::Preset::TYPE_PRINTER) ? - _(L("is not compatible with printer")) : - _(L("is not compatible with print profile")); - message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; - message += _(L("and it has the following unsaved changes:")); + else if (dlg.move_preset()) // move selected changes + { + // copy selected options to the cache from edited preset + m_cache_config.apply_only(*m_config, dlg.get_selected_options()); } - wxMessageDialog confirm(parent(), - message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), - _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - return confirm.ShowModal() == wxID_YES; - */ + + return true; } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 24f25e2d74..c9639bd008 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -236,6 +236,7 @@ public: bool m_show_btn_incompatible_presets = false; PresetCollection* m_presets; DynamicPrintConfig* m_config; + DynamicPrintConfig m_cache_config; ogStaticText* m_parent_preset_description_line; ScalableButton* m_detach_preset_btn = nullptr; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index f93aa35c2a..67ccc6b6ab 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -514,7 +514,7 @@ void UnsavedChangesModel::Rescale() // UnsavedChangesDialog //------------------------------------------ -UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& new_selected_preset) +UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset) : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -530,6 +530,9 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& int border = 10; int em = em_unit(); + m_action_line = new wxStaticText(this, wxID_ANY, ""); + m_action_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 30), wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES); m_tree_model = new UnsavedChangesModel(this); m_tree->AssociateModel(m_tree_model); @@ -561,36 +564,24 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& m_tree->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &UnsavedChangesDialog::context_menu, this); // Add Buttons - wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); - Tab* tab = wxGetApp().get_tab(type); - assert(tab); - PresetCollection* presets = tab->get_presets(); + auto add_btn = [this, buttons](ScalableButton** btn, int& btn_id, const std::string& icon_name, Action close_act, int idx, bool process_enable = true) + { + *btn = new ScalableButton(this, btn_id = NewControlId(), icon_name, "", wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); + buttons->Insert(idx, *btn, 0, wxLEFT, 5); - wxString label= from_u8((boost::format(_u8L("Save selected to preset: %1%"))% ("\"" + presets->get_selected_preset().name + "\"")).str()); - m_save_btn = new ScalableButton(this, m_save_btn_id = NewControlId(), "save", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); - buttons->Insert(0, m_save_btn, 0, wxLEFT, 5); + (*btn)->Bind(wxEVT_BUTTON, [this, close_act](wxEvent&) { close(close_act); }); + if (process_enable) + (*btn)->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + (*btn)->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); + }; - m_save_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Save); }); - m_save_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); - m_save_btn->Bind(wxEVT_ENTER_WINDOW,[this, presets](wxMouseEvent& e){ show_info_line(Action::Save, presets->get_selected_preset().name); e.Skip(); }); - m_save_btn->Bind(wxEVT_LEAVE_WINDOW,[this ](wxMouseEvent& e){ show_info_line(Action::Undef); e.Skip(); }); - - label = from_u8((boost::format(_u8L("Move selected to preset: %1%"))% ("\"" + from_u8(new_selected_preset) + "\"")).str()); - m_move_btn = new ScalableButton(this, m_move_btn_id = NewControlId(), "paste_menu", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); - buttons->Insert(1, m_move_btn, 0, wxLEFT, 5); - - m_move_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Move); }); - m_move_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); - m_move_btn->Bind(wxEVT_ENTER_WINDOW,[this, new_selected_preset](wxMouseEvent& e){ show_info_line(Action::Move, new_selected_preset); e.Skip(); }); - m_move_btn->Bind(wxEVT_LEAVE_WINDOW,[this ](wxMouseEvent& e){ show_info_line(Action::Undef); e.Skip(); }); - - label = _L("Continue without changes"); - m_continue_btn = new ScalableButton(this, m_continue_btn_id = NewControlId(), "cross", label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); - buttons->Insert(2, m_continue_btn, 0, wxLEFT, 5); - m_continue_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { close(Action::Continue); }); - m_continue_btn->Bind(wxEVT_ENTER_WINDOW,[this](wxMouseEvent& e){ show_info_line(Action::Continue); e.Skip(); }); - m_continue_btn->Bind(wxEVT_LEAVE_WINDOW,[this](wxMouseEvent& e){ show_info_line(Action::Undef); e.Skip(); }); + int btn_idx = 0; + add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, btn_idx++); + if (type == dependent_presets->type()) + add_btn(&m_move_btn, m_move_btn_id, "paste_menu", Action::Move, btn_idx++); + add_btn(&m_continue_btn, m_continue_btn_id, "cross", Action::Continue, btn_idx, false); m_info_line = new wxStaticText(this, wxID_ANY, ""); m_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); @@ -598,12 +589,12 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, const std::string& wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - topSizer->Add(new wxStaticText(this, wxID_ANY, _L("There are unsaved changes for") + (": \"" + tab->title() + "\"")), 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_action_line,0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border); topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); - update(type); + update(type, dependent_presets, new_selected_preset); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -824,12 +815,34 @@ wxString UnsavedChangesDialog::get_short_string(wxString full_string) return full_string + dots; } -void UnsavedChangesDialog::update(Preset::Type type) +void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset) { - Tab* tab = wxGetApp().get_tab(type); - assert(tab); + PresetCollection* presets = dependent_presets; + + // activate buttons and labels + m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets->get_selected_preset().name); e.Skip(); }); + if (m_move_btn) + m_move_btn ->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset] (wxMouseEvent& e) { show_info_line(Action::Move, new_selected_preset); e.Skip(); }); + m_continue_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Continue); e.Skip(); }); + + m_save_btn->SetLabel(from_u8((boost::format(_u8L("Save selected to preset: %1%")) % ("\"" + presets->get_selected_preset().name + "\"")).str())); + m_continue_btn->SetLabel(_L("Continue without changes")); + + wxString action_msg; + if (type == dependent_presets->type()) { + action_msg = _L("has the following unsaved changes:"); + + m_move_btn->SetLabel(from_u8((boost::format(_u8L("Move selected to preset: %1%")) % ("\"" + new_selected_preset + "\"")).str())); + } + else { + action_msg = type == Preset::TYPE_PRINTER ? + _L("is not compatible with printer") : + _L("is not compatible with print profile"); + action_msg += " \"" + from_u8(new_selected_preset) + "\" "; + action_msg += _L("and it has the following unsaved changes:"); + } + m_action_line->SetLabel(from_u8((boost::format(_utf8(L("Preset \"%1%\" %2%"))) % _utf8(presets->get_edited_preset().name) % action_msg).str())); - PresetCollection* presets = tab->get_presets(); // Display a dialog showing the dirty options in a human readable form. const DynamicPrintConfig& old_config = presets->get_selected_preset().config; const DynamicPrintConfig& new_config = presets->get_edited_preset().config; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 271d7595b6..afb73a4f92 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -193,6 +193,7 @@ class UnsavedChangesDialog : public DPIDialog ScalableButton* m_save_btn { nullptr }; ScalableButton* m_move_btn { nullptr }; ScalableButton* m_continue_btn { nullptr }; + wxStaticText* m_action_line { nullptr }; wxStaticText* m_info_line { nullptr }; bool m_empty_selection { false }; @@ -226,12 +227,12 @@ class UnsavedChangesDialog : public DPIDialog std::map m_items_map; public: - UnsavedChangesDialog(Preset::Type type, const std::string& new_selected_preset); + UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); ~UnsavedChangesDialog() {} wxString get_short_string(wxString full_string); - void update(Preset::Type type); + void update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); void item_value_changed(wxDataViewEvent &event); void context_menu(wxDataViewEvent &event); void show_info_line(Action action, std::string preset_name = ""); From f2f5632c3ebc3ac2bbe47af60da60d97dc090202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=A4hnel?= <6225312+danhae@users.noreply.github.com> Date: Fri, 14 Aug 2020 14:44:00 +0200 Subject: [PATCH 298/826] Update How to build - Mac OS.md Additional section "TL; DR" --- doc/How to build - Mac OS.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/How to build - Mac OS.md b/doc/How to build - Mac OS.md index 082c560b7a..bab40ea265 100644 --- a/doc/How to build - Mac OS.md +++ b/doc/How to build - Mac OS.md @@ -79,3 +79,29 @@ This is set in the property list file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Info.plist To remove the limitation, simply delete the key `MinimumSDKVersion` from that file. + + +# TL; DR + +Works on a fresh installation of MacOS Catalina 10.15.6 + +- Install [brew](https://brew.sh/): +- Open Terminal + +- Enter: + +```brew install cmake git gettext +brew update +brew upgrade +git clone https://github.com/prusa3d/PrusaSlicer/ +cd PrusaSlicer/deps +mkdir build +cd build +cmake .. +make +cd ../.. +mkdir build +cd build +cmake .. -DCMAKE_PREFIX_PATH="$PWD/../deps/build/destdir/usr/local" +make +src/prusa-slicer From 618f04717fcd2a62978685f3058ee4bb23815c09 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 14 Aug 2020 18:17:16 +0200 Subject: [PATCH 299/826] Unsaved Changes : improvement for the GUI_App::check_unsaved_changes() Added use of UnsavedChangesDialog --- src/libslic3r/PresetBundle.cpp | 26 ++++++ src/libslic3r/PresetBundle.hpp | 4 + src/slic3r/GUI/GUI_App.cpp | 77 ++++++++++++---- src/slic3r/GUI/PresetComboBoxes.cpp | 54 ++++++++---- src/slic3r/GUI/PresetComboBoxes.hpp | 8 +- src/slic3r/GUI/Tab.cpp | 27 ++---- src/slic3r/GUI/UnsavedChangesDialog.cpp | 112 +++++++++++++++++------- src/slic3r/GUI/UnsavedChangesDialog.hpp | 18 ++-- 8 files changed, 228 insertions(+), 98 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 108985704c..ac1b0a7176 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -327,6 +327,32 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p return presets.get_preset_name_by_alias(alias); } +void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, + const std::vector& unselected_options) +{ + PresetCollection& presets = type == Preset::TYPE_PRINT ? prints : + type == Preset::TYPE_SLA_PRINT ? sla_prints : + type == Preset::TYPE_FILAMENT ? filaments : + type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers; + + // if we want to save just some from selected options + if (!unselected_options.empty()) { + // revert unselected options to the old values + presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options); + } + + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini + presets.save_current_preset(new_name); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. + update_compatible(PresetSelectCompatibleType::Never); + + if (type == Preset::TYPE_FILAMENT) { + // synchronize the first filament presets. + set_filament_preset(0, filaments.get_selected_preset_name()); + } +} + void PresetBundle::load_installed_filaments(AppConfig &config) { if (! config.has_section(AppConfig::SECTION_FILAMENTS)) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 567a12331f..ff02bbeae3 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -130,6 +130,10 @@ public: const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const; + // Save current preset of a required type under a new name. If the name is different from the old one, + // Unselected option would be reverted to the beginning values + void save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options); + static const char *PRUSA_BUNDLE; private: std::string load_system_presets(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 82c2861bc2..266fd8fd63 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -57,6 +57,8 @@ #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" #include "NotificationManager.hpp" +#include "UnsavedChangesDialog.hpp" +#include "PresetComboBoxes.hpp" #ifdef __WXMSW__ #include @@ -1157,29 +1159,66 @@ void GUI_App::add_config_menu(wxMenuBar *menu) // to notify the user whether he is aware that some preset changes will be lost. bool GUI_App::check_unsaved_changes(const wxString &header) { - wxString dirty; PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab *tab : tabs_list) + + bool has_unsaved_changes = false; + for (Tab* tab : tabs_list) if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) { - if (dirty.empty()) - dirty = tab->title(); - else - dirty += wxString(", ") + tab->title(); + has_unsaved_changes = true; + break; } - if (dirty.empty()) - // No changes, the application may close or reload presets. - return true; - // Ask the user. - wxString message; - if (! header.empty()) - message = header + "\n\n"; - message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")); - wxMessageDialog dialog(mainframe, - message, - wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")), - wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return dialog.ShowModal() == wxID_YES; + if (has_unsaved_changes) + { + UnsavedChangesDialog dlg(header); + if (dlg.ShowModal() == wxID_CANCEL) + return false; + + if (dlg.save_preset()) // save selected changes + { + struct NameType + { + std::string name; + Preset::Type type {Preset::TYPE_INVALID}; + }; + + std::vector names_and_types; + + // for system/default/external presets we should take an edited name + std::vector types; + for (Tab* tab : tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + { + const Preset& preset = tab->get_presets()->get_edited_preset(); + if (preset.is_system || preset.is_default || preset.is_external) + types.emplace_back(preset.type); + + names_and_types.emplace_back(NameType{ preset.name, preset.type }); + } + + + if (!types.empty()) { + SavePresetDialog save_dlg(types); + if (save_dlg.ShowModal() != wxID_OK) + return false; + + for (NameType& nt : names_and_types) { + const std::string name = save_dlg.get_name(nt.type); + if (!name.empty()) + nt.name = name; + } + } + + for (const NameType& nt : names_and_types) + preset_bundle->save_changes_for_preset(nt.name, nt.type, dlg.get_unselected_options(nt.type)); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + } + } + + return true; } bool GUI_App::checked_tab(Tab* tab) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 87db32ac69..9b0c9d0c86 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1170,8 +1170,26 @@ void SavePresetDialog::Item::accept() // SavePresetDialog //----------------------------------------------- -SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) +SavePresetDialog::SavePresetDialog(Preset::Type type, std::string suffix) : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) +{ + build(std::vector{type}, suffix); +} + +SavePresetDialog::SavePresetDialog(std::vector types, std::string suffix) + : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) +{ + build(types, suffix); +} + +SavePresetDialog::~SavePresetDialog() +{ + for (auto item : m_items) { + delete item; + } +} + +void SavePresetDialog::build(std::vector types, std::string suffix) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT @@ -1179,14 +1197,18 @@ SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); -#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + if (suffix.empty()) + suffix = _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); m_presets_sizer = new wxBoxSizer(wxVERTICAL); // Add first item - m_items.emplace_back(type, suffix, m_presets_sizer, this); + for (Preset::Type type : types) + AddItem(type, suffix); // Add dialog's buttons wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); @@ -1203,26 +1225,26 @@ SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) void SavePresetDialog::AddItem(Preset::Type type, const std::string& suffix) { - m_items.emplace_back(type, suffix, m_presets_sizer, this); + m_items.emplace_back(new Item{type, suffix, m_presets_sizer, this}); } std::string SavePresetDialog::get_name() { - return m_items.front().preset_name(); + return m_items.front()->preset_name(); } std::string SavePresetDialog::get_name(Preset::Type type) { - for (Item& item : m_items) - if (item.type() == type) - return item.preset_name(); + for (const Item* item : m_items) + if (item->type() == type) + return item->preset_name(); return ""; } bool SavePresetDialog::enable_ok_btn() const { - for (Item item : m_items) - if (!item.is_valid()) + for (const Item* item : m_items) + if (!item->is_valid()) return false; return true; @@ -1291,8 +1313,8 @@ void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - for (Item& item : m_items) - item.update_valid_bmp(); + for (Item* item : m_items) + item->update_valid_bmp(); //const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(/*size*/wxSize(100, 50)); @@ -1331,10 +1353,10 @@ void SavePresetDialog::update_physical_printers(const std::string& preset_name) void SavePresetDialog::accept() { - for (Item& item : m_items) { - item.accept(); - if (item.type() == Preset::TYPE_PRINTER) - update_physical_printers(item.preset_name()); + for (Item* item : m_items) { + item->accept(); + if (item->type() == Preset::TYPE_PRINTER) + update_physical_printers(item->preset_name()); } EndModal(wxID_OK); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 7f51f775ee..e33a2d753d 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -235,7 +235,7 @@ class SavePresetDialog : public DPIDialog void update(); }; - std::vector m_items; + std::vector m_items; wxBoxSizer* m_presets_sizer {nullptr}; wxStaticText* m_label {nullptr}; @@ -248,8 +248,9 @@ class SavePresetDialog : public DPIDialog public: - SavePresetDialog(Preset::Type type, const std::string& suffix); - ~SavePresetDialog() {} + SavePresetDialog(Preset::Type type, std::string suffix = ""); + SavePresetDialog(std::vector types, std::string suffix = ""); + ~SavePresetDialog(); void AddItem(Preset::Type type, const std::string& suffix); @@ -266,6 +267,7 @@ protected: void on_sys_color_changed() override {} private: + void build(std::vector types, std::string suffix = ""); void update_physical_printers(const std::string& preset_name); void accept(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index cf999a409d..1363ddcad7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3146,36 +3146,27 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr if (dlg.save_preset()) // save selected changes { - std::vector unselected_options = dlg.get_unselected_options(); + std::vector unselected_options = dlg.get_unselected_options(presets->type()); const Preset& preset = presets->get_edited_preset(); std::string name = preset.name; // for system/default/external presets we should take an edited name if (preset.is_system || preset.is_default || preset.is_external) { - SavePresetDialog save_dlg(preset.type, _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName")); + SavePresetDialog save_dlg(preset.type); if (save_dlg.ShowModal() != wxID_OK) return false; name = save_dlg.get_name(); } - // if we want to save just some from selected options - if (!unselected_options.empty()) + if (m_type == presets->type()) // save changes for the current preset from this tab { - DynamicPrintConfig& old_config = presets->get_selected_preset().config; // revert unselected options to the old values - for (const std::string& opt_key : unselected_options) - presets->get_edited_preset().config.set_key_value(opt_key, old_config.option(opt_key)->clone()); - } - - if (m_type == presets->type()) // save changes for the current preset + presets->get_edited_preset().config.apply_only(presets->get_selected_preset().config, unselected_options); save_preset(name); - else // save changes for dependent preset + } + else { - // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini - presets->save_current_preset(name); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. - m_preset_bundle->update_compatible(PresetSelectCompatibleType::Never); + m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options); /* If filament preset is saved for multi-material printer preset, * there are cases when filament comboboxs are updated for old (non-modified) colors, @@ -3283,10 +3274,8 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // focus currently.is there anything better than this ? //! m_treectrl->OnSetFocus(); - std::string suffix = detach ? _utf8(L("Detached")) : _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); - if (name.empty()) { - SavePresetDialog dlg(m_type, suffix); + SavePresetDialog dlg(m_type, detach ? _u8L("Detached") : ""); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 67ccc6b6ab..2fa89266e2 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -514,8 +514,19 @@ void UnsavedChangesModel::Rescale() // UnsavedChangesDialog //------------------------------------------ +UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header) + : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + build(Preset::TYPE_INVALID, nullptr, "", header); +} + UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset) : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + build(type, dependent_presets, new_selected_preset); +} + +void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header) { wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SetBackgroundColour(bgr_clr); @@ -579,7 +590,8 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection* int btn_idx = 0; add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, btn_idx++); - if (type == dependent_presets->type()) + if (type != Preset::TYPE_INVALID && type == dependent_presets->type() && + dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology()) add_btn(&m_move_btn, m_move_btn_id, "paste_menu", Action::Move, btn_idx++); add_btn(&m_continue_btn, m_continue_btn_id, "cross", Action::Continue, btn_idx, false); @@ -594,7 +606,7 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection* topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border); topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); - update(type, dependent_presets, new_selected_preset); + update(type, dependent_presets, new_selected_preset, header); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -654,8 +666,12 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name else if (action == Action::Continue) text = _L("All changed options will be reverted."); else { - std::string act_string = action == Action::Save ? _u8L("saved") : _u8L("moved"); - text = from_u8((boost::format("After press this button selected options will be %1% to the preset \"%2%\".") % act_string % preset_name).str()); + if (action == Action::Save && preset_name.empty()) + text = _L("After press this button selected options will be saved"); + else { + std::string act_string = action == Action::Save ? _u8L("saved") : _u8L("moved"); + text = from_u8((boost::format("After press this button selected options will be %1% to the preset \"%2%\".") % act_string % preset_name).str()); + } text += "\n" + _L("Unselected options will be reverted."); } m_info_line->SetLabel(text); @@ -815,64 +831,92 @@ wxString UnsavedChangesDialog::get_short_string(wxString full_string) return full_string + dots; } -void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset) +void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header) { PresetCollection* presets = dependent_presets; // activate buttons and labels - m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets->get_selected_preset().name); e.Skip(); }); + m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets ? presets->get_selected_preset().name : ""); e.Skip(); }); if (m_move_btn) m_move_btn ->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset] (wxMouseEvent& e) { show_info_line(Action::Move, new_selected_preset); e.Skip(); }); m_continue_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Continue); e.Skip(); }); - m_save_btn->SetLabel(from_u8((boost::format(_u8L("Save selected to preset: %1%")) % ("\"" + presets->get_selected_preset().name + "\"")).str())); m_continue_btn->SetLabel(_L("Continue without changes")); - wxString action_msg; - if (type == dependent_presets->type()) { - action_msg = _L("has the following unsaved changes:"); - - m_move_btn->SetLabel(from_u8((boost::format(_u8L("Move selected to preset: %1%")) % ("\"" + new_selected_preset + "\"")).str())); + if (type == Preset::TYPE_INVALID) { + m_action_line ->SetLabel(header + "\n" + _L("Next presets have the following unsaved changes:")); + m_save_btn ->SetLabel(_L("Save selected")); } else { - action_msg = type == Preset::TYPE_PRINTER ? - _L("is not compatible with printer") : - _L("is not compatible with print profile"); - action_msg += " \"" + from_u8(new_selected_preset) + "\" "; - action_msg += _L("and it has the following unsaved changes:"); + wxString action_msg; + if (type == dependent_presets->type()) { + action_msg = _L("has the following unsaved changes:"); + if (m_move_btn) + m_move_btn->SetLabel(from_u8((boost::format(_u8L("Move selected to preset: %1%")) % ("\"" + new_selected_preset + "\"")).str())); + } + else { + action_msg = type == Preset::TYPE_PRINTER ? + _L("is not compatible with printer") : + _L("is not compatible with print profile"); + action_msg += " \"" + from_u8(new_selected_preset) + "\"\n"; + action_msg += _L("and it has the following unsaved changes:"); + } + m_action_line->SetLabel(from_u8((boost::format(_utf8(L("Preset \"%1%\" %2%"))) % _utf8(presets->get_edited_preset().name) % action_msg).str())); + m_save_btn->SetLabel(from_u8((boost::format(_u8L("Save selected to preset: %1%")) % ("\"" + presets->get_selected_preset().name + "\"")).str())); } - m_action_line->SetLabel(from_u8((boost::format(_utf8(L("Preset \"%1%\" %2%"))) % _utf8(presets->get_edited_preset().name) % action_msg).str())); - // Display a dialog showing the dirty options in a human readable form. - const DynamicPrintConfig& old_config = presets->get_selected_preset().config; - const DynamicPrintConfig& new_config = presets->get_edited_preset().config; - - m_tree_model->AddPreset(type, from_u8(presets->get_edited_preset().name)); + update_tree(type, presets); +} +void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* presets_) +{ Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); searcher.sort_options_by_opt_key(); - // Collect dirty options. - for (const std::string& opt_key : presets->current_dirty_options()) { - const Search::Option& option = searcher.get_option(opt_key); + // list of the presets with unsaved changes + std::vector presets_list; + if (type == Preset::TYPE_INVALID) + { + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); - ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config) }; + for (Tab* tab : wxGetApp().tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + presets_list.emplace_back(tab->get_presets()); + } + else + presets_list.emplace_back(presets_); - wxString old_val = get_short_string(item_data.old_val); - wxString new_val = get_short_string(item_data.new_val); - if (old_val != item_data.old_val || new_val != item_data.new_val) - item_data.is_long = true; + // Display a dialog showing the dirty options in a human readable form. + for (PresetCollection* presets : presets_list) + { + const DynamicPrintConfig& old_config = presets->get_selected_preset().config; + const DynamicPrintConfig& new_config = presets->get_edited_preset().config; + type = presets->type(); - m_items_map.emplace(m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, old_val, new_val), item_data); + m_tree_model->AddPreset(type, from_u8(presets->get_edited_preset().name)); + + // Collect dirty options. + for (const std::string& opt_key : presets->current_dirty_options()) { + const Search::Option& option = searcher.get_option(opt_key); + + ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), type }; + + wxString old_val = get_short_string(item_data.old_val); + wxString new_val = get_short_string(item_data.new_val); + if (old_val != item_data.old_val || new_val != item_data.new_val) + item_data.is_long = true; + + m_items_map.emplace(m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, old_val, new_val), item_data); + } } } -std::vector UnsavedChangesDialog::get_unselected_options() +std::vector UnsavedChangesDialog::get_unselected_options(Preset::Type type) { std::vector ret; for (auto item : m_items_map) - if (!m_tree_model->IsEnabledItem(item.first)) + if (item.second.type == type && !m_tree_model->IsEnabledItem(item.first)) ret.emplace_back(item.second.opt_key); return ret; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index afb73a4f92..f78a1fec0e 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -217,22 +217,26 @@ class UnsavedChangesDialog : public DPIDialog struct ItemData { - std::string opt_key; - wxString opt_name; - wxString old_val; - wxString new_val; - bool is_long {false}; + std::string opt_key; + wxString opt_name; + wxString old_val; + wxString new_val; + Preset::Type type; + bool is_long {false}; }; // tree items related to the options std::map m_items_map; public: + UnsavedChangesDialog(const wxString& header); UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); ~UnsavedChangesDialog() {} wxString get_short_string(wxString full_string); - void update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); + void build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header = ""); + void update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header); + void update_tree(Preset::Type type, PresetCollection *presets); void item_value_changed(wxDataViewEvent &event); void context_menu(wxDataViewEvent &event); void show_info_line(Action action, std::string preset_name = ""); @@ -242,7 +246,7 @@ public: bool move_preset() const { return m_exit_action == Action::Move; } bool just_continue() const { return m_exit_action == Action::Continue; } - std::vector get_unselected_options(); + std::vector get_unselected_options(Preset::Type type); std::vector get_selected_options(); protected: From f2d02faef4f73c9e71c4b06a438920937be7255b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 17 Aug 2020 10:06:41 +0200 Subject: [PATCH 300/826] GCodeProcessor -> Added debug code to check toolpaths data extracted from gcode, as mm3 per mm, height and width --- src/libslic3r/GCode.cpp | 11 ++ src/libslic3r/GCode.hpp | 6 + src/libslic3r/GCode/GCodeProcessor.cpp | 210 ++++++++++++++++--------- src/libslic3r/GCode/GCodeProcessor.hpp | 113 ++++++++++--- src/libslic3r/GCode/WipeTower.cpp | 16 ++ src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 4 +- 7 files changed, 263 insertions(+), 98 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 795aac898f..24c758d502 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1186,6 +1186,9 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_last_width = 0.0f; m_last_height = 0.0f; m_last_layer_z = 0.0f; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_last_mm3_per_mm = 0.0; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; m_last_width = GCodeAnalyzer::Default_Width; @@ -3268,6 +3271,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); gcode += buf; } + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { + m_last_mm3_per_mm = path.mm3_per_mm; + sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); + gcode += buf; + } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else if (path.role() != m_last_analyzer_extrusion_role) { m_last_analyzer_extrusion_role = path.role(); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 6f97d5dbd3..76f897c63b 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -172,6 +172,9 @@ public: m_volumetric_speed(0), m_last_pos_defined(false), m_last_extrusion_role(erNone), +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_last_mm3_per_mm(0.0), +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #if !ENABLE_GCODE_VIEWER m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm), m_last_width(GCodeAnalyzer::Default_Width), @@ -381,6 +384,9 @@ private: float m_last_width{ 0.0f }; float m_last_height{ 0.0f }; float m_last_layer_z{ 0.0f }; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + double m_last_mm3_per_mm; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else // Support for G-Code Analyzer double m_last_mm3_per_mm; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 18878cd5ad..2e6736d1ec 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -33,6 +33,10 @@ const std::string GCodeProcessor::First_Line_M73_Placeholder_Tag = "; _ const std::string GCodeProcessor::Last_Line_M73_Placeholder_Tag = "; _GP_LAST_LINE_M73_PLACEHOLDER"; const std::string GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag = "; _GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER"; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "Mm3_Per_Mm:"; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + static bool is_valid_extrusion_role(int value) { return (static_cast(erNone) <= value) && (value <= static_cast(erMixed)); @@ -308,10 +312,10 @@ void GCodeProcessor::TimeProcessor::reset() machine_limits = MachineEnvelopeConfig(); filament_load_times = std::vector(); filament_unload_times = std::vector(); - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { machines[i].reset(); } - machines[static_cast(ETimeMode::Normal)].enabled = true; + machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].enabled = true; } void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) @@ -342,8 +346,8 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) std::string gcode_line; size_t g1_lines_counter = 0; // keeps track of last exported pair - std::array, static_cast(ETimeMode::Count)> last_exported; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported; + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { last_exported[i] = { 0, time_in_minutes(machines[i].time) }; } @@ -357,7 +361,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) std::string ret; if (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag) { - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { ret += format_line_M73(machine.line_m73_mask.c_str(), @@ -367,12 +371,12 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) } } else if (line == Estimated_Printing_Time_Placeholder_Tag) { - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { char buf[128]; sprintf(buf, "; estimated printing time (%s mode) = %s\n", - (static_cast(i) == ETimeMode::Normal) ? "normal" : "silent", + (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent", get_time_dhms(machine.time).c_str()); ret += buf; } @@ -383,7 +387,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) // add lines M73 to exported gcode auto process_line_G1 = [&]() { - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled && g1_lines_counter < machine.g1_times_cache.size()) { float elapsed_time = machine.g1_times_cache[g1_lines_counter]; @@ -458,8 +462,8 @@ unsigned int GCodeProcessor::s_result_id = 0; GCodeProcessor::GCodeProcessor() { reset(); - m_time_processor.machines[static_cast(ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; } void GCodeProcessor::apply_config(const PrintConfig& config) @@ -478,7 +482,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_extruder_colors.resize(extruders_count); for (size_t i = 0; i < extruders_count; ++i) { - m_extruder_colors[i] = static_cast(i); + m_extruder_colors[i] = static_cast(i); } m_filament_diameters.resize(config.filament_diameter.values.size()); @@ -499,7 +503,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); } - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -561,7 +565,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_extruder_colors.resize(m_result.extruder_colors.size()); for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { - m_extruder_colors[i] = static_cast(i); + m_extruder_colors[i] = static_cast(i); } const ConfigOptionFloats* filament_load_time = config.option("filament_load_time"); @@ -644,7 +648,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (machine_min_travel_rate != nullptr) m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -653,15 +657,17 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) void GCodeProcessor::enable_stealth_time_estimator(bool enabled) { - m_time_processor.machines[static_cast(ETimeMode::Stealth)].enabled = enabled; + m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled = enabled; } void GCodeProcessor::reset() { + static const size_t Min_Extruder_Count = 5; + m_units = EUnits::Millimeters; m_global_positioning_type = EPositioningType::Absolute; m_e_local_positioning_type = EPositioningType::Absolute; - m_extruder_offsets = std::vector(1, Vec3f::Zero()); + m_extruder_offsets = std::vector(Min_Extruder_Count, Vec3f::Zero()); m_flavor = gcfRepRap; m_start_position = { 0.0f, 0.0f, 0.0f, 0.0f }; @@ -677,8 +683,12 @@ void GCodeProcessor::reset() m_extrusion_role = erNone; m_extruder_id = 0; - m_extruder_colors = ExtruderColors(); - m_filament_diameters = std::vector(); + m_extruder_colors.resize(Min_Extruder_Count); + for (size_t i = 0; i < Min_Extruder_Count; ++i) { + m_extruder_colors[i] = static_cast(i); + } + + m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f); m_extruded_last_z = 0.0f; m_cp_color.reset(); @@ -689,6 +699,12 @@ void GCodeProcessor::reset() m_result.reset(); m_result.id = ++s_result_id; + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_mm3_per_mm_compare.reset(); + m_height_compare.reset(); + m_width_compare.reset(); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } void GCodeProcessor::process_file(const std::string& filename) @@ -724,7 +740,7 @@ void GCodeProcessor::process_file(const std::string& filename) m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); // process the remaining time blocks - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; machine.calculate_time(); @@ -738,25 +754,31 @@ void GCodeProcessor::process_file(const std::string& filename) if (m_time_processor.export_remaining_time_enabled) m_time_processor.post_process(filename); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_mm3_per_mm_compare.output(); + m_height_compare.output(); + m_width_compare.output(); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + #if ENABLE_GCODE_VIEWER_STATISTICS m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } -float GCodeProcessor::get_time(ETimeMode mode) const +float GCodeProcessor::get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const { - return (mode < ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; + return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; } -std::string GCodeProcessor::get_time_dhm(ETimeMode mode) const +std::string GCodeProcessor::get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const { - return (mode < ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); + return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } -std::vector>> GCodeProcessor::get_custom_gcode_times(ETimeMode mode, bool include_remaining) const +std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const { std::vector>> ret; - if (mode < ETimeMode::Count) { + if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; float total_time = 0.0f; for (const auto& [type, time] : machine.gcode_time.times) { @@ -768,10 +790,10 @@ std::vector>> GCodeProcesso return ret; } -std::vector> GCodeProcessor::get_moves_time(ETimeMode mode) const +std::vector> GCodeProcessor::get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < ETimeMode::Count) { + if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; if (time > 0.0f) @@ -781,10 +803,10 @@ std::vector> GCodeProcessor::get_moves_time(ETimeMod return ret; } -std::vector> GCodeProcessor::get_roles_time(ETimeMode mode) const +std::vector> GCodeProcessor::get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < ETimeMode::Count) { + if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; if (time > 0.0f) @@ -888,6 +910,9 @@ void GCodeProcessor::process_tags(const std::string& comment) if (pos != comment.npos) { try { m_width = std::stof(comment.substr(pos + Width_Tag.length())); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_width_compare.last_tag_value = m_width; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; @@ -900,6 +925,9 @@ void GCodeProcessor::process_tags(const std::string& comment) if (pos != comment.npos) { try { m_height = std::stof(comment.substr(pos + Height_Tag.length())); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.last_tag_value = m_height; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; @@ -947,6 +975,20 @@ void GCodeProcessor::process_tags(const std::string& comment) store_move_vertex(EMoveType::Custom_GCode); return; } + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + // mm3_per_mm print tag + pos = comment.find(Mm3_Per_Mm_Tag); + if (pos != comment.npos) { + try { + m_mm3_per_mm_compare.last_tag_value = std::stof(comment.substr(pos + Mm3_Per_Mm_Tag.length())); + } + catch (...) { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; + } + return; + } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } bool GCodeProcessor::process_producers_tags(const std::string& comment) @@ -1126,8 +1168,10 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) size_t w_end = data.find_first_of(' ', w_start); if (h_start != data.npos) { try { - std::string test = data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end); m_height = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.last_tag_value = m_height; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; @@ -1135,8 +1179,10 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) } if (w_start != data.npos) { try { - std::string test = data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end); m_width = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_width_compare.last_tag_value = m_width; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; @@ -1146,7 +1192,7 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) return true; } - std::cout << comment << "\n"; +// std::cout << comment << "\n"; return false; } @@ -1226,6 +1272,9 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) if (pos != comment.npos) { try { m_width = std::stof(comment.substr(pos + tag.length())); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_width_compare.last_tag_value = m_width; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; @@ -1239,6 +1288,9 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) if (pos != comment.npos) { try { m_height = std::stof(comment.substr(pos + tag.length())); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.last_tag_value = m_height; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; @@ -1327,19 +1379,29 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) return; EMoveType type = move_type(delta_pos); + if (type == EMoveType::Extrude && m_end_position[Z] == 0.0f) + type = EMoveType::Travel; if (type == EMoveType::Extrude) { - if (delta_pos[E] > 0.0f) { - float ds = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); - if (ds > 0.0f && static_cast(m_extruder_id) < m_filament_diameters.size()) { - // extruded filament volume / tool displacement - m_mm3_per_mm = round_to_nearest(static_cast(M_PI * sqr(m_filament_diameters[m_extruder_id]) * 0.25) * delta_pos[E] / ds, 4); - } + float d_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + float filament_diameter = (static_cast(m_extruder_id) < m_filament_diameters.size()) ? m_filament_diameters[m_extruder_id] : m_filament_diameters.back(); + float filament_radius = 0.5f * filament_diameter; + float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); + float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; + float area_toolpath_cross_section = volume_extruded_filament / d_xyz; - if (m_end_position[Z] > m_extruded_last_z + EPSILON) { - m_height = round_to_nearest(m_end_position[Z] - m_extruded_last_z, 4); - m_extruded_last_z = m_end_position[Z]; - } + // volume extruded filament / tool displacement = area toolpath cross section + m_mm3_per_mm = round_to_nearest(area_toolpath_cross_section, 3); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + if (m_end_position[Z] > m_extruded_last_z + EPSILON) { + m_height = round_to_nearest(m_end_position[Z] - m_extruded_last_z, 4); +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.update(m_height, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + m_extruded_last_z = m_end_position[Z]; } } @@ -1368,7 +1430,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) assert(distance != 0.0f); float inv_distance = 1.0f / distance; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -1378,8 +1440,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) std::vector& blocks = machine.blocks; curr.feedrate = (delta_pos[E] == 0.0f) ? - minimum_travel_feedrate(static_cast(i), m_feedrate) : - minimum_feedrate(static_cast(i), m_feedrate); + minimum_travel_feedrate(static_cast(i), m_feedrate) : + minimum_feedrate(static_cast(i), m_feedrate); TimeBlock block; block.move_type = type; @@ -1395,7 +1457,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } @@ -1412,11 +1474,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // calculates block acceleration float acceleration = is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i)); + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i)); for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration; } @@ -1427,7 +1489,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.safe_feedrate = block.feedrate_profile.cruise; for (unsigned char a = X; a <= E; ++a) { - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (curr.abs_axis_feedrate[a] > axis_max_jerk) curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); } @@ -1475,7 +1537,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // axis reversal std::max(-v_exit, v_entry)); - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (jerk > axis_max_jerk) { v_factor *= axis_max_jerk / jerk; limited = true; @@ -1689,7 +1751,7 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration float factor = (m_flavor != gcfRepRap && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); @@ -1717,7 +1779,7 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // http://smoothieware.org/supported-g-codes float factor = (m_flavor == gcfMarlin || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); @@ -1738,19 +1800,19 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) return; float value; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (line.has_value('S', value)) { // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware, // and it is also generated by Slic3r to control acceleration per extrusion type // (there is a separate acceleration settings in Slicer for perimeter, first layer etc). - set_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); if (line.has_value('T', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); } else { // New acceleration format, compatible with the upstream Marlin. if (line.has_value('P', value)) - set_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); if (line.has_value('R', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); if (line.has_value('T', value)) { @@ -1767,7 +1829,7 @@ void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) if (!m_time_processor.machine_envelope_processing_enabled) return; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (line.has_x()) { float max_jerk = line.x(); set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, max_jerk); @@ -1798,7 +1860,7 @@ void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) float value_t; if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { value_s *= 0.01f; - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].extrude_factor_override_percentage = value_s; } } @@ -1849,7 +1911,7 @@ void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) { - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); @@ -1930,7 +1992,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_result.moves.emplace_back(vertex); } -float GCodeProcessor::minimum_feedrate(ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) return feedrate; @@ -1938,7 +2000,7 @@ float GCodeProcessor::minimum_feedrate(ETimeMode mode, float feedrate) const return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); } -float GCodeProcessor::minimum_travel_feedrate(ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) return feedrate; @@ -1946,7 +2008,7 @@ float GCodeProcessor::minimum_travel_feedrate(ETimeMode mode, float feedrate) co return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); } -float GCodeProcessor::get_axis_max_feedrate(ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -1958,7 +2020,7 @@ float GCodeProcessor::get_axis_max_feedrate(ETimeMode mode, Axis axis) const } } -float GCodeProcessor::get_axis_max_acceleration(ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -1970,7 +2032,7 @@ float GCodeProcessor::get_axis_max_acceleration(ETimeMode mode, Axis axis) const } } -float GCodeProcessor::get_axis_max_jerk(ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -1982,18 +2044,18 @@ float GCodeProcessor::get_axis_max_jerk(ETimeMode mode, Axis axis) const } } -float GCodeProcessor::get_retract_acceleration(ETimeMode mode) const +float GCodeProcessor::get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast(mode)); } -float GCodeProcessor::get_acceleration(ETimeMode mode) const +float GCodeProcessor::get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const { size_t id = static_cast(mode); return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; } -void GCodeProcessor::set_acceleration(ETimeMode mode, float value) +void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { @@ -2021,7 +2083,7 @@ float GCodeProcessor::get_filament_unload_time(size_t extruder_id) void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) { - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -2040,14 +2102,14 @@ void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) void GCodeProcessor::simulate_st_synchronize(float additional_time) { - for (size_t i = 0; i < static_cast(ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].simulate_st_synchronize(additional_time); } } void GCodeProcessor::update_estimated_times_stats() { - auto update_mode = [this](GCodeProcessor::ETimeMode mode) { + auto update_mode = [this](PrintEstimatedTimeStatistics::ETimeMode mode) { PrintEstimatedTimeStatistics::Mode& data = m_result.time_statistics.modes[static_cast(mode)]; data.time = get_time(mode); data.custom_gcode_times = get_custom_gcode_times(mode, true); @@ -2055,11 +2117,11 @@ void GCodeProcessor::update_estimated_times_stats() data.roles_times = get_roles_time(mode); }; - update_mode(GCodeProcessor::ETimeMode::Normal); - if (m_time_processor.machines[static_cast(GCodeProcessor::ETimeMode::Stealth)].enabled) - update_mode(GCodeProcessor::ETimeMode::Stealth); + update_mode(PrintEstimatedTimeStatistics::ETimeMode::Normal); + if (m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled) + update_mode(PrintEstimatedTimeStatistics::ETimeMode::Stealth); else - m_result.time_statistics.modes[static_cast(GCodeProcessor::ETimeMode::Stealth)].reset(); + m_result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].reset(); } } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index f103bab967..166e1b8cca 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -76,6 +76,10 @@ namespace Slic3r { static const std::string Last_Line_M73_Placeholder_Tag; static const std::string Estimated_Printing_Time_Placeholder_Tag; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + static const std::string Mm3_Per_Mm_Tag; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + private: using AxisCoords = std::array; using ExtruderColors = std::vector; @@ -152,13 +156,6 @@ namespace Slic3r { float time() const; }; - enum class ETimeMode : unsigned char - { - Normal, - Stealth, - Count - }; - private: struct TimeMachine { @@ -227,7 +224,7 @@ namespace Slic3r { // Additional load / unload times for a filament exchange sequence. std::vector filament_load_times; std::vector filament_unload_times; - std::array(ETimeMode::Count)> machines; + std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> machines; void reset(); @@ -281,6 +278,72 @@ namespace Slic3r { #endif // ENABLE_GCODE_VIEWER_STATISTICS }; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + struct DataChecker + { + struct Error + { + float value; + float tag_value; + ExtrusionRole role; + }; + + std::string type; + float threshold{ 0.01f }; + float last_tag_value{ 0.0f }; + unsigned int count{ 0 }; + std::vector errors; + + DataChecker(const std::string& type, float threshold) + : type(type), threshold(threshold) + {} + + void update(float value, ExtrusionRole role) { + ++count; + if (last_tag_value != 0.0f) { + if (std::abs(value - last_tag_value) / last_tag_value > threshold) + errors.push_back({ value, last_tag_value, role }); + } + } + + void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; } + + std::pair get_min() const { + float delta_min = FLT_MAX; + float perc_min = 0.0f; + for (const Error& e : errors) { + if (delta_min > e.value - e.tag_value) { + delta_min = e.value - e.tag_value; + perc_min = 100.0f * delta_min / e.tag_value; + } + } + return { delta_min, perc_min }; + } + + std::pair get_max() const { + float delta_max = -FLT_MAX; + float perc_max = 0.0f; + for (const Error& e : errors) { + if (delta_max < e.value - e.tag_value) { + delta_max = e.value - e.tag_value; + perc_max = 100.0f * delta_max / e.tag_value; + } + } + return { delta_max, perc_max }; + } + + void output() const { + if (!errors.empty()) { + std::cout << type << ":\n"; + std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n"; + auto [min, perc_min] = get_min(); + auto [max, perc_max] = get_max(); + std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n"; + } + } + }; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + private: GCodeReader m_parser; @@ -326,6 +389,12 @@ namespace Slic3r { Result m_result; static unsigned int s_result_id; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f }; + DataChecker m_height_compare{ "height", 0.01f }; + DataChecker m_width_compare{ "width", 0.01f }; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + public: GCodeProcessor(); @@ -333,7 +402,7 @@ namespace Slic3r { void apply_config(const DynamicPrintConfig& config); void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { - return m_time_processor.machines[static_cast(ETimeMode::Stealth)].enabled; + return m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled; } void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } void enable_producers(bool enabled) { m_producers_enabled = enabled; } @@ -345,12 +414,12 @@ namespace Slic3r { // Process the gcode contained in the file with the given filename void process_file(const std::string& filename); - float get_time(ETimeMode mode) const; - std::string get_time_dhm(ETimeMode mode) const; - std::vector>> get_custom_gcode_times(ETimeMode mode, bool include_remaining) const; + float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; + std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const; + std::vector>> get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const; - std::vector> get_moves_time(ETimeMode mode) const; - std::vector> get_roles_time(ETimeMode mode) const; + std::vector> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; + std::vector> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; private: void process_gcode_line(const GCodeReader::GCodeLine& line); @@ -454,14 +523,14 @@ namespace Slic3r { void store_move_vertex(EMoveType type); - float minimum_feedrate(ETimeMode mode, float feedrate) const; - float minimum_travel_feedrate(ETimeMode mode, float feedrate) const; - float get_axis_max_feedrate(ETimeMode mode, Axis axis) const; - float get_axis_max_acceleration(ETimeMode mode, Axis axis) const; - float get_axis_max_jerk(ETimeMode mode, Axis axis) const; - float get_retract_acceleration(ETimeMode mode) const; - float get_acceleration(ETimeMode mode) const; - void set_acceleration(ETimeMode mode, float value); + float minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; + float minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; + float get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; + float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; + float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; + void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index c20009a487..e6e60b6186 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -84,6 +84,17 @@ public: return *this; } +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { + static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; + float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); + // adds tag for processor: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); + m_gcode += buf; + return *this; + } +#else #if !ENABLE_GCODE_VIEWER WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; @@ -95,6 +106,7 @@ public: return *this; } #endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { m_wipe_tower_width = width; @@ -167,9 +179,13 @@ public: Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.f) { +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + change_analyzer_mm3_per_mm(len, e); +#else #if !ENABLE_GCODE_VIEWER change_analyzer_mm3_per_mm(len, e); #endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width = e * m_filpar[0].filament_area / (len * m_layer_height); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 609aecf635..e10cabc6cf 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,6 +58,7 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) #define TIME_ESTIMATE_NONE 0 #define TIME_ESTIMATE_DEFAULT 1 diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b7bd81a0fa..17651fef37 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -975,8 +975,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (const TBuffer& buffer : m_buffers) { m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } - m_statistics.travel_segments_count = indices[buffer_id(GCodeProcessor::EMoveType::Travel)].size() / 2; - m_statistics.extrude_segments_count = indices[buffer_id(GCodeProcessor::EMoveType::Extrude)].size() / 2; + m_statistics.travel_segments_count = indices[buffer_id(EMoveType::Travel)].size() / 2; + m_statistics.extrude_segments_count = indices[buffer_id(EMoveType::Extrude)].size() / 2; #endif // ENABLE_GCODE_VIEWER_STATISTICS // layers zs / roles / extruder ids / cp color ids -> extract from result From 5b579aee9a4c97ccb0c0cd469750ecac3dcbb176 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 17 Aug 2020 10:54:41 +0200 Subject: [PATCH 301/826] GCodeProcessor -> Extract toolpaths width from gcode moves --- src/libslic3r/GCode/GCodeProcessor.cpp | 27 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 2e6736d1ec..a0661191cb 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -755,6 +755,7 @@ void GCodeProcessor::process_file(const std::string& filename) m_time_processor.post_process(filename); #if ENABLE_GCODE_VIEWER_DATA_CHECKING + std::cout << "\n"; m_mm3_per_mm_compare.output(); m_height_compare.output(); m_width_compare.output(); @@ -1403,18 +1404,24 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING m_extruded_last_z = m_end_position[Z]; } + + if (m_extrusion_role == erExternalPerimeter) + // cross section: rectangle + m_width = round_to_nearest(delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height), 3); + else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) + // cross section: circle + m_width = round_to_nearest(static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz), 3); + else + // cross section: rectangle + 2 semicircles + m_width = round_to_nearest(delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height, 3); + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_width_compare.update(m_width, m_extrusion_role); +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } - if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) { - if ((m_width == 0.0f && m_height == 0.0f) || m_extrusion_role == erCustom) - type = EMoveType::Travel; - else { - if (m_width == 0.0f) - m_width = 0.5f; - if (m_height == 0.0f) - m_height = 0.5f; - } - } + if (type == EMoveType::Extrude && (m_extrusion_role == erCustom || m_width == 0.0f || m_height == 0.0f)) + type = EMoveType::Travel; // time estimate section auto move_length = [](const AxisCoords& delta_pos) { From b1561534050a23acb323f355b6153e04a7a7e2fa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 17 Aug 2020 13:07:13 +0200 Subject: [PATCH 302/826] GCodeViewer -> Use rounded values for toolpaths height, width and volumetric rate to reduce the number of generated paths --- src/libslic3r/GCode/GCodeProcessor.cpp | 50 ++++++++++---------------- src/slic3r/GUI/GCodeViewer.cpp | 30 ++++++++++++---- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index a0661191cb..ab9a1039cd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -85,19 +85,6 @@ static float acceleration_time_from_distance(float initial_feedrate, float dista return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f; } -float round_to_nearest(float value, unsigned int decimals) -{ - float res = 0.0f; - if (decimals == 0) - res = std::round(value); - else { - char buf[64]; - sprintf(buf, "%.*g", decimals, value); - res = std::stof(buf); - } - return res; -} - void GCodeProcessor::CachedPosition::reset() { std::fill(position.begin(), position.end(), FLT_MAX); @@ -1392,13 +1379,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) float area_toolpath_cross_section = volume_extruded_filament / d_xyz; // volume extruded filament / tool displacement = area toolpath cross section - m_mm3_per_mm = round_to_nearest(area_toolpath_cross_section, 3); + m_mm3_per_mm = area_toolpath_cross_section; #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING if (m_end_position[Z] > m_extruded_last_z + EPSILON) { - m_height = round_to_nearest(m_end_position[Z] - m_extruded_last_z, 4); + m_height = m_end_position[Z] - m_extruded_last_z; #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_height_compare.update(m_height, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -1407,13 +1394,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (m_extrusion_role == erExternalPerimeter) // cross section: rectangle - m_width = round_to_nearest(delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height), 3); + m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height); else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) // cross section: circle - m_width = round_to_nearest(static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz), 3); + m_width = static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz); else // cross section: rectangle + 2 semicircles - m_width = round_to_nearest(delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height, 3); + m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_width_compare.update(m_width, m_extrusion_role); @@ -1983,19 +1970,20 @@ void GCodeProcessor::process_T(const std::string& command) void GCodeProcessor::store_move_vertex(EMoveType type) { - MoveVertex vertex; - vertex.type = type; - vertex.extrusion_role = m_extrusion_role; - vertex.position = Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z]) + m_extruder_offsets[m_extruder_id]; - vertex.delta_extruder = m_end_position[E] - m_start_position[E]; - vertex.feedrate = m_feedrate; - vertex.width = m_width; - vertex.height = m_height; - vertex.mm3_per_mm = m_mm3_per_mm; - vertex.fan_speed = m_fan_speed; - vertex.extruder_id = m_extruder_id; - vertex.cp_color_id = m_cp_color.current; - vertex.time = static_cast(m_result.moves.size()); + MoveVertex vertex = { + type, + m_extrusion_role, + m_extruder_id, + m_cp_color.current, + Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z]) + m_extruder_offsets[m_extruder_id], + m_end_position[E] - m_start_position[E], + m_feedrate, + m_width, + m_height, + m_mm3_per_mm, + m_fan_speed, + static_cast(m_result.moves.size()) + }; m_result.moves.emplace_back(vertex); } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 17651fef37..4cb3ae1c52 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -38,7 +38,7 @@ static EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(EMoveType::Retract) + id); } -std::array decode_color(const std::string& color) { +static std::array decode_color(const std::string& color) { static const float INV_255 = 1.0f / 255.0f; std::array ret = { 0.0f, 0.0f, 0.0f }; @@ -56,7 +56,7 @@ std::array decode_color(const std::string& color) { return ret; } -std::vector> decode_colors(const std::vector& colors) { +static std::vector> decode_colors(const std::vector& colors) { std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f }); for (size_t i = 0; i < colors.size(); ++i) { output[i] = decode_color(colors[i]); @@ -64,6 +64,19 @@ std::vector> decode_colors(const std::vector& return output; } +static float round_to_nearest(float value, unsigned int decimals) +{ + float res = 0.0f; + if (decimals == 0) + res = std::round(value); + else { + char buf[64]; + sprintf(buf, "%.*g", decimals, value); + res = std::stof(buf); + } + return res; +} + void GCodeViewer::VBuffer::reset() { // release gpu memory @@ -98,9 +111,11 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const case EMoveType::Unretract: case EMoveType::Extrude: { - return type == move.type && role == move.extrusion_role && height == move.height && width == move.width && - feedrate == move.feedrate && fan_speed == move.fan_speed && volumetric_rate == move.volumetric_rate() && - extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; + // use rounding to reduce the number of generated paths + return type == move.type && role == move.extrusion_role && height == round_to_nearest(move.height, 2) && + width == round_to_nearest(move.width, 2) && feedrate == move.feedrate && fan_speed == move.fan_speed && + volumetric_rate == round_to_nearest(move.volumetric_rate(), 2) && extruder_id == move.extruder_id && + cp_color_id == move.cp_color_id; } case EMoveType::Travel: { @@ -124,7 +139,10 @@ void GCodeViewer::TBuffer::reset() void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) { Path::Endpoint endpoint = { i_id, s_id, move.position }; - paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, move.height, move.width, move.feedrate, move.fan_speed, move.volumetric_rate(), move.extruder_id, move.cp_color_id }); + // use rounding to reduce the number of generated paths + paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, + round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, + round_to_nearest(move.volumetric_rate(), 2), move.extruder_id, move.cp_color_id }); } GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const From 73603e49371f12e73a0a93b54ca88f11b1bdbbd5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 17 Aug 2020 14:37:26 +0200 Subject: [PATCH 303/826] GCodeProcessor -> Do not export width tags to gcode --- src/libslic3r/GCode.cpp | 43 +++++++-- src/libslic3r/GCode.hpp | 10 ++- src/libslic3r/GCode/GCodeProcessor.cpp | 115 +++++++++++++++++++------ src/libslic3r/GCode/GCodeProcessor.hpp | 7 +- src/libslic3r/GCode/WipeTower.cpp | 89 ++++++++++++++++--- 5 files changed, 216 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 24c758d502..63f215b76d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1183,11 +1183,16 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // resets analyzer's tracking data #if ENABLE_GCODE_VIEWER - m_last_width = 0.0f; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// m_last_width = 0.0f; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_last_height = 0.0f; m_last_layer_z = 0.0f; #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm = 0.0; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_last_width = 0.0f; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; @@ -3255,8 +3260,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (m_enable_analyzer) { #endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER - // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines without updating m_last_height and m_last_width - // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height + // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines +// // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines without updating m_last_height and m_last_width +// // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); #else // PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width @@ -3278,6 +3287,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); gcode += buf; } + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (last_was_wipe_tower || m_last_width != path.width) { + m_last_width = path.width; + sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); + gcode += buf; + } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else if (path.role() != m_last_analyzer_extrusion_role) { @@ -3291,17 +3308,27 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); gcode += buf; } -#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (last_was_wipe_tower || m_last_width != path.width) { m_last_width = path.width; -#if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); -#else sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); -#endif // ENABLE_GCODE_VIEWER gcode += buf; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// if (last_was_wipe_tower || m_last_width != path.width) { +// m_last_width = path.width; +//#if ENABLE_GCODE_VIEWER +// sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); +//#else +// sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); +//#endif // ENABLE_GCODE_VIEWER +// gcode += buf; +// } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 76f897c63b..3c8cf64b74 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -174,6 +174,9 @@ public: m_last_extrusion_role(erNone), #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm(0.0), +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_last_width(0.0f), +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #if !ENABLE_GCODE_VIEWER m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm), @@ -381,11 +384,16 @@ private: ExtrusionRole m_last_extrusion_role; #if ENABLE_GCODE_VIEWER // Support for G-Code Processor - float m_last_width{ 0.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// float m_last_width{ 0.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ float m_last_height{ 0.0f }; float m_last_layer_z{ 0.0f }; #if ENABLE_GCODE_VIEWER_DATA_CHECKING double m_last_mm3_per_mm; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + float m_last_width{ 0.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else // Support for G-Code Analyzer diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index ab9a1039cd..39b9539c0c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -23,7 +23,9 @@ static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 namespace Slic3r { const std::string GCodeProcessor::Extrusion_Role_Tag = "ExtrType:"; -const std::string GCodeProcessor::Width_Tag = "Width:"; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//const std::string GCodeProcessor::Width_Tag = "Width:"; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const std::string GCodeProcessor::Height_Tag = "Height:"; const std::string GCodeProcessor::Color_Change_Tag = "Color change"; const std::string GCodeProcessor::Pause_Print_Tag = "Pause print"; @@ -34,6 +36,9 @@ const std::string GCodeProcessor::Last_Line_M73_Placeholder_Tag = "; _ const std::string GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag = "; _GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER"; #if ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +const std::string GCodeProcessor::Width_Tag = "Width:"; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "Mm3_Per_Mm:"; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -893,35 +898,67 @@ void GCodeProcessor::process_tags(const std::string& comment) return; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING // width tag pos = comment.find(Width_Tag); if (pos != comment.npos) { try { - m_width = std::stof(comment.substr(pos + Width_Tag.length())); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_width_compare.last_tag_value = m_width; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + m_width_compare.last_tag_value = std::stof(comment.substr(pos + Width_Tag.length())); } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; } return; } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +// // width tag +// pos = comment.find(Width_Tag); +// if (pos != comment.npos) { +// try { +// m_width = std::stof(comment.substr(pos + Width_Tag.length())); +//#if ENABLE_GCODE_VIEWER_DATA_CHECKING +// m_width_compare.last_tag_value = m_width; +//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +// } +// catch (...) { +// BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; +// } +// return; +// } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING // height tag pos = comment.find(Height_Tag); if (pos != comment.npos) { try { - m_height = std::stof(comment.substr(pos + Height_Tag.length())); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.last_tag_value = m_height; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + m_height_compare.last_tag_value = std::stof(comment.substr(pos + Height_Tag.length())); } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; } return; } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + +// // height tag +// pos = comment.find(Height_Tag); +// if (pos != comment.npos) { +// try { +// m_height = std::stof(comment.substr(pos + Height_Tag.length())); +//#if ENABLE_GCODE_VIEWER_DATA_CHECKING +// m_height_compare.last_tag_value = m_height; +//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +// } +// catch (...) { +// BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; +// } +// return; +// } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // color change tag pos = comment.find(Color_Change_Tag); @@ -1144,6 +1181,9 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) // geometry // ; tool +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::string tag = " tool"; pos = comment.find(tag); if (pos == 0) { @@ -1156,10 +1196,14 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) size_t w_end = data.find_first_of(' ', w_start); if (h_start != data.npos) { try { - m_height = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.last_tag_value = m_height; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_height_compare.last_tag_value = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); + +// m_height = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); +//#if ENABLE_GCODE_VIEWER_DATA_CHECKING +// m_height_compare.last_tag_value = m_height; +//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; @@ -1167,10 +1211,14 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) } if (w_start != data.npos) { try { - m_width = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_width_compare.last_tag_value = m_width; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_width_compare.last_tag_value = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); + +// m_width = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); +//#if ENABLE_GCODE_VIEWER_DATA_CHECKING +// m_width_compare.last_tag_value = m_width; +//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; @@ -1179,6 +1227,9 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) return true; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // std::cout << comment << "\n"; return false; @@ -1254,15 +1305,22 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) // geometry +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // width tag = "WIDTH:"; pos = comment.find(tag); if (pos != comment.npos) { try { - m_width = std::stof(comment.substr(pos + tag.length())); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_width_compare.last_tag_value = m_width; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_width_compare.last_tag_value = std::stof(comment.substr(pos + tag.length())); + +// m_width = std::stof(comment.substr(pos + tag.length())); +//#if ENABLE_GCODE_VIEWER_DATA_CHECKING +// m_width_compare.last_tag_value = m_width; +//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; @@ -1275,16 +1333,23 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) pos = comment.find(tag); if (pos != comment.npos) { try { - m_height = std::stof(comment.substr(pos + tag.length())); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.last_tag_value = m_height; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_height_compare.last_tag_value = std::stof(comment.substr(pos + tag.length())); + +// m_height = std::stof(comment.substr(pos + tag.length())); +//#if ENABLE_GCODE_VIEWER_DATA_CHECKING +// m_height_compare.last_tag_value = m_height; +//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; } return true; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return false; } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 166e1b8cca..52ae76f7ee 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -67,7 +67,9 @@ namespace Slic3r { { public: static const std::string Extrusion_Role_Tag; - static const std::string Width_Tag; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// static const std::string Width_Tag; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const std::string Height_Tag; static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; @@ -77,6 +79,9 @@ namespace Slic3r { static const std::string Estimated_Printing_Time_Placeholder_Tag; #if ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + static const std::string Width_Tag; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const std::string Mm3_Per_Mm_Tag; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index e6e60b6186..3eaf31e1cd 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -51,7 +51,13 @@ public: m_extrusion_flow(0.f), m_preview_suppressed(false), m_elapsed_time(0.f), +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_default_analyzer_line_width(line_width), +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_gcode_flavor(flavor), m_filpar(filament_parameters) { @@ -69,20 +75,48 @@ public: sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); #endif // ENABLE_GCODE_VIEWER m_gcode += buf; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ change_analyzer_line_width(line_width); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } - WipeTowerWriter& change_analyzer_line_width(float line_width) { - // adds tag for analyzer: - char buf[64]; -#if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); -#else - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); -#endif // ENABLE_GCODE_VIEWER - m_gcode += buf; - return *this; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + WipeTowerWriter& change_analyzer_line_width(float line_width) { + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); + m_gcode += buf; + return *this; } +#else +#if !ENABLE_GCODE_VIEWER + WipeTowerWriter& change_analyzer_line_width(float line_width) { + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); + m_gcode += buf; + return *this; +} +#endif // !ENABLE_GCODE_VIEWER +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + +// WipeTowerWriter& change_analyzer_line_width(float line_width) { +// // adds tag for analyzer: +// char buf[64]; +//#if ENABLE_GCODE_VIEWER +// sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); +//#else +// sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); +//#endif // ENABLE_GCODE_VIEWER +// m_gcode += buf; +// return *this; +// } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_DATA_CHECKING WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { @@ -141,8 +175,17 @@ public: // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. - WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } - WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } + WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + WipeTowerWriter& suppress_preview() { m_preview_suppressed = true; return *this; } + WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ WipeTowerWriter& feedrate(float f) { @@ -447,7 +490,13 @@ private: float m_wipe_tower_depth = 0.f; unsigned m_last_fan_speed = 0; int current_temp = -1; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const float m_default_analyzer_line_width; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; const std::vector& m_filpar; @@ -888,8 +937,16 @@ void WipeTower::toolchange_Unload( const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING writer.append("; CP TOOLCHANGE UNLOAD\n") - .change_analyzer_line_width(line_width); + .change_analyzer_line_width(line_width); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + writer.append("; CP TOOLCHANGE UNLOAD\n"); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ unsigned i = 0; // iterates through ramming_speed m_left_to_right = true; // current direction of ramming @@ -966,7 +1023,13 @@ void WipeTower::toolchange_Unload( } } Vec2f end_of_ramming(writer.x(),writer.y()); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Retraction: float old_x = writer.x(); From e050fb68bf3e486f46a41d0e6816c912300cac08 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Aug 2020 15:13:18 +0200 Subject: [PATCH 304/826] Fixed UI changing update for "Ramming" parameter --- src/slic3r/GUI/OptionsGroup.cpp | 7 ++++++- src/slic3r/GUI/Tab.cpp | 33 ++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 1bebb8827a..cc10d815fc 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -466,7 +466,8 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, } else if (m_opt_map.find(opt_key) == m_opt_map.end() || // This option don't have corresponded field - opt_key == "bed_shape" || opt_key == "compatible_printers" || opt_key == "compatible_prints" ) { + opt_key == "bed_shape" || opt_key == "filament_ramming_parameters" || + opt_key == "compatible_printers" || opt_key == "compatible_prints" ) { value = get_config_value(config, opt_key); change_opt_value(*m_config, opt_key, value); return; @@ -699,6 +700,10 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = config.option(opt_key)->values; break; } + if (opt_key == "filament_ramming_parameters") { + ret = config.opt_string(opt_key, static_cast(idx)); + break; + } if (config.option(opt_key)->values.empty()) ret = text_value; else if (opt->gui_flags.compare("serialized") == 0) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1363ddcad7..a16222c86b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -413,8 +413,9 @@ void Tab::update_labels_colour() else color = &m_modified_label_clr; } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { - wxStaticText* label = (m_colored_Labels.find(opt.first) == m_colored_Labels.end()) ? nullptr : m_colored_Labels.at(opt.first); + if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || + opt.first == "compatible_prints" || opt.first == "compatible_printers" ) { + wxStaticText* label = m_colored_Labels.find(opt.first) == m_colored_Labels.end() ? nullptr : m_colored_Labels.at(opt.first); if (label) { label->SetForegroundColour(*color); label->Refresh(true); @@ -504,7 +505,8 @@ void Tab::update_changed_ui() icon = &m_bmp_white_bullet; tt = &m_tt_white_bullet; } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { + if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || + opt.first == "compatible_prints" || opt.first == "compatible_printers") { wxStaticText* label = (m_colored_Labels.find(opt.first) == m_colored_Labels.end()) ? nullptr : m_colored_Labels.at(opt.first); if (label) { label->SetForegroundColour(*color); @@ -656,6 +658,9 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } + if (m_type == Preset::TYPE_FILAMENT && page->title() == "Advanced") { + get_sys_and_mod_flags("filament_ramming_parameters", sys_page, modified_page); + } if (page->title() == "Dependencies") { if (m_type == Slic3r::Preset::TYPE_PRINTER) { sys_page = m_presets->get_selected_preset_parent() != nullptr; @@ -734,7 +739,10 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); load_key_value("bed_shape", true/*some value*/, true); } - + } + if (group->title == "Toolchange parameters with single extruder MM printers") { + if ((m_options_list["filament_ramming_parameters"] & os) == 0) + to_sys ? group->back_to_sys_value("filament_ramming_parameters") : group->back_to_initial_value("filament_ramming_parameters"); } if (group->title == "Profile dependencies") { // "compatible_printers" option doesn't exists in Printer Settimgs Tab @@ -1737,22 +1745,21 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); - line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; - line.widget = [this](wxWindow* parent) { + create_line_with_widget(optgroup.get(), "filament_ramming_parameters", [this](wxWindow* parent) { auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(ramming_dialog_btn); - ramming_dialog_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + ramming_dialog_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { RammingDialog dlg(this,(m_config->option("filament_ramming_parameters"))->get_at(0)); - if (dlg.ShowModal() == wxID_OK) - (m_config->option("filament_ramming_parameters"))->get_at(0) = dlg.get_parameters(); - })); + if (dlg.ShowModal() == wxID_OK) { + load_key_value("filament_ramming_parameters", dlg.get_parameters()); + update_changed_ui(); + } + }); return sizer; - }; - optgroup->append_line(line); + }); add_filament_overrides_page(); From c81d87b470f5550938202a222d79c01f1aba500b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 17 Aug 2020 15:59:36 +0200 Subject: [PATCH 305/826] Code cleanup --- src/libslic3r/GCode.cpp | 50 +++----------- src/libslic3r/GCode.hpp | 7 -- src/libslic3r/GCode/GCodeProcessor.cpp | 96 +++----------------------- src/libslic3r/GCode/GCodeProcessor.hpp | 5 -- src/libslic3r/GCode/WipeTower.cpp | 67 ++++-------------- src/slic3r/GUI/GCodeViewer.cpp | 6 +- 6 files changed, 34 insertions(+), 197 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 63f215b76d..3de70d0611 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1183,16 +1183,11 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // resets analyzer's tracking data #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// m_last_width = 0.0f; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_last_height = 0.0f; m_last_layer_z = 0.0f; #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm = 0.0; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_last_width = 0.0f; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; @@ -3256,18 +3251,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } // adds processor tags and updates processor tracking data -#if !ENABLE_GCODE_VIEWER - if (m_enable_analyzer) { -#endif // !ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height - // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines -// // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines without updating m_last_height and m_last_width -// // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag and GCodeProcessor::Width_Tag lines -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); + // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height + // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines + bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); #else + if (m_enable_analyzer) { // PrusaMultiMaterial::Writer may generate GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines without updating m_last_height and m_last_width // so, if the last role was erWipeTower we force export of GCodeAnalyzer::Height_Tag and GCodeAnalyzer::Width_Tag lines bool last_was_wipe_tower = (m_last_analyzer_extrusion_role == erWipeTower); @@ -3288,14 +3277,18 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (last_was_wipe_tower || m_last_width != path.width) { m_last_width = path.width; sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); gcode += buf; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) { + m_last_height = path.height; + sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), m_last_height); + gcode += buf; + } #else if (path.role() != m_last_analyzer_extrusion_role) { m_last_analyzer_extrusion_role = path.role(); @@ -3309,35 +3302,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (last_was_wipe_tower || m_last_width != path.width) { m_last_width = path.width; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); gcode += buf; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// if (last_was_wipe_tower || m_last_width != path.width) { -// m_last_width = path.width; -//#if ENABLE_GCODE_VIEWER -// sprintf(buf, ";%s%g\n", GCodeProcessor::Width_Tag.c_str(), m_last_width); -//#else -// sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width); -//#endif // ENABLE_GCODE_VIEWER -// gcode += buf; -// } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - - -#if ENABLE_GCODE_VIEWER - if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) { - m_last_height = path.height; - sprintf(buf, ";%s%g\n", GCodeProcessor::Height_Tag.c_str(), m_last_height); - gcode += buf; - } -#else if (last_was_wipe_tower || m_last_height != path.height) { m_last_height = path.height; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 3c8cf64b74..8bae2ef43d 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -174,9 +174,7 @@ public: m_last_extrusion_role(erNone), #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm(0.0), -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_last_width(0.0f), -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #if !ENABLE_GCODE_VIEWER m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm), @@ -384,16 +382,11 @@ private: ExtrusionRole m_last_extrusion_role; #if ENABLE_GCODE_VIEWER // Support for G-Code Processor -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// float m_last_width{ 0.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ float m_last_height{ 0.0f }; float m_last_layer_z{ 0.0f }; #if ENABLE_GCODE_VIEWER_DATA_CHECKING double m_last_mm3_per_mm; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ float m_last_width{ 0.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING #else // Support for G-Code Analyzer diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 39b9539c0c..6684255918 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -22,24 +22,19 @@ static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 namespace Slic3r { -const std::string GCodeProcessor::Extrusion_Role_Tag = "ExtrType:"; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//const std::string GCodeProcessor::Width_Tag = "Width:"; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -const std::string GCodeProcessor::Height_Tag = "Height:"; -const std::string GCodeProcessor::Color_Change_Tag = "Color change"; -const std::string GCodeProcessor::Pause_Print_Tag = "Pause print"; -const std::string GCodeProcessor::Custom_Code_Tag = "Custom gcode"; +const std::string GCodeProcessor::Extrusion_Role_Tag = "TYPE:"; +const std::string GCodeProcessor::Height_Tag = "HEIGHT:"; +const std::string GCodeProcessor::Color_Change_Tag = "COLOR CHANGE"; +const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE PRINT"; +const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM GCODE"; const std::string GCodeProcessor::First_Line_M73_Placeholder_Tag = "; _GP_FIRST_LINE_M73_PLACEHOLDER"; const std::string GCodeProcessor::Last_Line_M73_Placeholder_Tag = "; _GP_LAST_LINE_M73_PLACEHOLDER"; const std::string GCodeProcessor::Estimated_Printing_Time_Placeholder_Tag = "; _GP_ESTIMATED_PRINTING_TIME_PLACEHOLDER"; #if ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -const std::string GCodeProcessor::Width_Tag = "Width:"; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "Mm3_Per_Mm:"; +const std::string GCodeProcessor::Width_Tag = "WIDTH:"; +const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:"; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING static bool is_valid_extrusion_role(int value) @@ -898,7 +893,6 @@ void GCodeProcessor::process_tags(const std::string& comment) return; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_DATA_CHECKING // width tag pos = comment.find(Width_Tag); @@ -911,26 +905,7 @@ void GCodeProcessor::process_tags(const std::string& comment) } return; } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -// // width tag -// pos = comment.find(Width_Tag); -// if (pos != comment.npos) { -// try { -// m_width = std::stof(comment.substr(pos + Width_Tag.length())); -//#if ENABLE_GCODE_VIEWER_DATA_CHECKING -// m_width_compare.last_tag_value = m_width; -//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -// } -// catch (...) { -// BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; -// } -// return; -// } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_GCODE_VIEWER_DATA_CHECKING // height tag pos = comment.find(Height_Tag); if (pos != comment.npos) { @@ -944,22 +919,6 @@ void GCodeProcessor::process_tags(const std::string& comment) } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -// // height tag -// pos = comment.find(Height_Tag); -// if (pos != comment.npos) { -// try { -// m_height = std::stof(comment.substr(pos + Height_Tag.length())); -//#if ENABLE_GCODE_VIEWER_DATA_CHECKING -// m_height_compare.last_tag_value = m_height; -//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -// } -// catch (...) { -// BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; -// } -// return; -// } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - // color change tag pos = comment.find(Color_Change_Tag); if (pos != comment.npos) { @@ -1178,12 +1137,10 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) return true; } +#if ENABLE_GCODE_VIEWER_DATA_CHECKING // geometry // ; tool -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::string tag = " tool"; pos = comment.find(tag); if (pos == 0) { @@ -1196,14 +1153,7 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) size_t w_end = data.find_first_of(' ', w_start); if (h_start != data.npos) { try { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_height_compare.last_tag_value = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); - -// m_height = std::stof(data.substr(h_start + 1, (h_end != data.npos) ? h_end - h_start - 1 : h_end)); -//#if ENABLE_GCODE_VIEWER_DATA_CHECKING -// m_height_compare.last_tag_value = m_height; -//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; @@ -1211,14 +1161,7 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) } if (w_start != data.npos) { try { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_width_compare.last_tag_value = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); - -// m_width = std::stof(data.substr(w_start + 1, (w_end != data.npos) ? w_end - w_start - 1 : w_end)); -//#if ENABLE_GCODE_VIEWER_DATA_CHECKING -// m_width_compare.last_tag_value = m_width; -//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; @@ -1227,11 +1170,8 @@ bool GCodeProcessor::process_simplify3d_tags(const std::string& comment) return true; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// std::cout << comment << "\n"; return false; } @@ -1303,24 +1243,15 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) return true; } +#if ENABLE_GCODE_VIEWER_DATA_CHECKING // geometry -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // width tag = "WIDTH:"; pos = comment.find(tag); if (pos != comment.npos) { try { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_width_compare.last_tag_value = std::stof(comment.substr(pos + tag.length())); - -// m_width = std::stof(comment.substr(pos + tag.length())); -//#if ENABLE_GCODE_VIEWER_DATA_CHECKING -// m_width_compare.last_tag_value = m_width; -//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Width (" << comment << ")."; @@ -1333,23 +1264,14 @@ bool GCodeProcessor::process_ideamaker_tags(const std::string& comment) pos = comment.find(tag); if (pos != comment.npos) { try { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_height_compare.last_tag_value = std::stof(comment.substr(pos + tag.length())); - -// m_height = std::stof(comment.substr(pos + tag.length())); -//#if ENABLE_GCODE_VIEWER_DATA_CHECKING -// m_height_compare.last_tag_value = m_height; -//#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } catch (...) { BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; } return true; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return false; } diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 52ae76f7ee..b19610801c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -67,9 +67,6 @@ namespace Slic3r { { public: static const std::string Extrusion_Role_Tag; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// static const std::string Width_Tag; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const std::string Height_Tag; static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; @@ -79,9 +76,7 @@ namespace Slic3r { static const std::string Estimated_Printing_Time_Placeholder_Tag; #if ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const std::string Width_Tag; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const std::string Mm3_Per_Mm_Tag; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 3eaf31e1cd..1e8323230b 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -51,13 +51,9 @@ public: m_extrusion_flow(0.f), m_preview_suppressed(false), m_elapsed_time(0.f), -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_default_analyzer_line_width(line_width), -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_gcode_flavor(flavor), m_filpar(filament_parameters) { @@ -65,26 +61,20 @@ public: char buf[64]; #if ENABLE_GCODE_VIEWER sprintf(buf, ";%s%f\n", GCodeProcessor::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming + m_gcode += buf; + sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str()); + m_gcode += buf; #else sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming -#endif // ENABLE_GCODE_VIEWER m_gcode += buf; -#if ENABLE_GCODE_VIEWER - sprintf(buf, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erWipeTower).c_str()); -#else sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); -#endif // ENABLE_GCODE_VIEWER m_gcode += buf; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ change_analyzer_line_width(line_width); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_DATA_CHECKING WipeTowerWriter& change_analyzer_line_width(float line_width) { // adds tag for analyzer: @@ -93,32 +83,7 @@ public: m_gcode += buf; return *this; } -#else -#if !ENABLE_GCODE_VIEWER - WipeTowerWriter& change_analyzer_line_width(float line_width) { - // adds tag for analyzer: - char buf[64]; - sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); - m_gcode += buf; - return *this; -} -#endif // !ENABLE_GCODE_VIEWER -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -// WipeTowerWriter& change_analyzer_line_width(float line_width) { -// // adds tag for analyzer: -// char buf[64]; -//#if ENABLE_GCODE_VIEWER -// sprintf(buf, ";%s%f\n", GCodeProcessor::Width_Tag.c_str(), line_width); -//#else -// sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); -//#endif // ENABLE_GCODE_VIEWER -// m_gcode += buf; -// return *this; -// } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); @@ -130,6 +95,14 @@ public: } #else #if !ENABLE_GCODE_VIEWER + WipeTowerWriter& change_analyzer_line_width(float line_width) { + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); + m_gcode += buf; + return *this; + } + WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); @@ -175,17 +148,13 @@ public: // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_DATA_CHECKING WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ WipeTowerWriter& suppress_preview() { m_preview_suppressed = true; return *this; } WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ WipeTowerWriter& feedrate(float f) { @@ -490,13 +459,9 @@ private: float m_wipe_tower_depth = 0.f; unsigned m_last_fan_speed = 0; int current_temp = -1; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if !ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const float m_default_analyzer_line_width; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // !ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; const std::vector& m_filpar; @@ -937,16 +902,12 @@ void WipeTower::toolchange_Unload( const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_DATA_CHECKING writer.append("; CP TOOLCHANGE UNLOAD\n") .change_analyzer_line_width(line_width); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ writer.append("; CP TOOLCHANGE UNLOAD\n"); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ unsigned i = 0; // iterates through ramming_speed m_left_to_right = true; // current direction of ramming @@ -1023,13 +984,9 @@ void WipeTower::toolchange_Unload( } } Vec2f end_of_ramming(writer.x(),writer.y()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Retraction: float old_x = writer.x(); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 4cb3ae1c52..1a4485402b 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -385,10 +385,10 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: { case EMoveType::Extrude: { - m_extrusions.ranges.height.update_from(curr.height); - m_extrusions.ranges.width.update_from(curr.width); + m_extrusions.ranges.height.update_from(round_to_nearest(curr.height, 2)); + m_extrusions.ranges.width.update_from(round_to_nearest(curr.width, 2)); m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); - m_extrusions.ranges.volumetric_rate.update_from(curr.volumetric_rate()); + m_extrusions.ranges.volumetric_rate.update_from(round_to_nearest(curr.volumetric_rate(), 2)); [[fallthrough]]; } case EMoveType::Travel: From 1172dfcb402b6fdd5df31262009d123fbb2ca918 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 18 Aug 2020 07:55:53 +0200 Subject: [PATCH 306/826] #4639 - ImGuiWrapper: fixed calls to ImGui::Text() and ImGui::TextColored() --- src/slic3r/GUI/ImGuiWrapper.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 266472ecaf..104680f502 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -362,7 +362,7 @@ bool ImGuiWrapper::checkbox(const wxString &label, bool &value) void ImGuiWrapper::text(const char *label) { - ImGui::Text(label, NULL); + ImGui::Text("%s", label); } void ImGuiWrapper::text(const std::string &label) @@ -378,7 +378,7 @@ void ImGuiWrapper::text(const wxString &label) void ImGuiWrapper::text_colored(const ImVec4& color, const char* label) { - ImGui::TextColored(color, label); + ImGui::TextColored(color, "%s", label); } void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label) From ca27d7296f5b892417508bdbedbbb789f4db8bbc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 18 Aug 2020 08:27:07 +0200 Subject: [PATCH 307/826] Fixed build when ENABLE_GCODE_VIEWER is disabled --- src/libslic3r/GCode/WipeTower.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 1e8323230b..0752b6dfcb 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -51,9 +51,9 @@ public: m_extrusion_flow(0.f), m_preview_suppressed(false), m_elapsed_time(0.f), -#if !ENABLE_GCODE_VIEWER_DATA_CHECKING +#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING m_default_analyzer_line_width(line_width), -#endif // !ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING m_gcode_flavor(flavor), m_filpar(filament_parameters) { @@ -148,13 +148,13 @@ public: // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. -#if ENABLE_GCODE_VIEWER_DATA_CHECKING +#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } #else WipeTowerWriter& suppress_preview() { m_preview_suppressed = true; return *this; } WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING WipeTowerWriter& feedrate(float f) { @@ -459,9 +459,9 @@ private: float m_wipe_tower_depth = 0.f; unsigned m_last_fan_speed = 0; int current_temp = -1; -#if !ENABLE_GCODE_VIEWER_DATA_CHECKING +#if !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING const float m_default_analyzer_line_width; -#endif // !ENABLE_GCODE_VIEWER_DATA_CHECKING +#endif // !ENABLE_GCODE_VIEWER || ENABLE_GCODE_VIEWER_DATA_CHECKING float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; const std::vector& m_filpar; From 4ef52af9066aff676818cd3b1d157083c72805b2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Aug 2020 11:41:14 +0200 Subject: [PATCH 308/826] Add dedicated tests for support point generation --- src/libslic3r/BoundingBox.hpp | 14 +++ src/libslic3r/Line.cpp | 18 --- src/libslic3r/Line.hpp | 31 +++++- src/libslic3r/Point.hpp | 2 + src/libslic3r/SLA/Hollowing.cpp | 9 ++ src/libslic3r/SLA/Hollowing.hpp | 2 + tests/sla_print/CMakeLists.txt | 5 +- tests/sla_print/sla_supptgen_tests.cpp | 148 +++++++++++++++++++++++++ tests/sla_print/sla_test_utils.cpp | 68 ++++++++++++ tests/sla_print/sla_test_utils.hpp | 9 ++ 10 files changed, 285 insertions(+), 21 deletions(-) create mode 100644 tests/sla_print/sla_supptgen_tests.cpp diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index f03733dd86..08f01d8d85 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -192,6 +192,20 @@ inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), sc inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } +template +auto cast(const BoundingBoxBase &b) +{ + return BoundingBoxBase>{b.min.template cast(), + b.max.template cast()}; +} + +template +auto cast(const BoundingBox3Base &b) +{ + return BoundingBox3Base>{b.min.template cast(), + b.max.template cast()}; +} + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 974f585dc2..0c43154a9b 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -33,24 +33,6 @@ bool Line::intersection_infinite(const Line &other, Point* point) const return true; } -// Distance to the closest point of line. -double Line::distance_to_squared(const Point &point, const Point &a, const Point &b) -{ - const Vec2d v = (b - a).cast(); - const Vec2d va = (point - a).cast(); - const double l2 = v.squaredNorm(); // avoid a sqrt - if (l2 == 0.0) - // a == b case - return va.squaredNorm(); - // Consider the line extending the segment, parameterized as a + t (b - a). - // We find projection of this point onto the line. - // It falls where t = [(this-a) . (b-a)] / |b-a|^2 - const double t = va.dot(v) / l2; - if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment - else if (t > 1.0) return (point - b).cast().squaredNorm(); // beyond the 'b' end of the segment - return (t * v - va).squaredNorm(); -} - double Line::perp_distance_to(const Point &point) const { const Line &line = *this; diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index caab809f5c..980303feda 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -18,6 +18,35 @@ typedef std::vector ThickLines; Linef3 transform(const Linef3& line, const Transform3d& t); +namespace line_alg { + +// Distance to the closest point of line. +template +double distance_to_squared(const L &line, const Vec &point) +{ + const Vec v = line.vector().template cast(); + const Vec va = (point - line.a).template cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + if (l2 == 0.0) + // a == b case + return va.squaredNorm(); + // Consider the line extending the segment, parameterized as a + t (b - a). + // We find projection of this point onto the line. + // It falls where t = [(this-a) . (b-a)] / |b-a|^2 + const double t = va.dot(v) / l2; + if (t < 0.0) return va.squaredNorm(); // beyond the 'a' end of the segment + else if (t > 1.0) return (point - line.b).template cast().squaredNorm(); // beyond the 'b' end of the segment + return (t * v - va).squaredNorm(); +} + +template +double distance_to(const L &line, const Vec &point) +{ + return std::sqrt(distance_to_squared(line, point)); +} + +} // namespace line_alg + class Line { public: @@ -47,7 +76,7 @@ public: // Clip a line with a bounding box. Returns false if the line is completely outside of the bounding box. bool clip_with_bbox(const BoundingBox &bbox); - static double distance_to_squared(const Point &point, const Point &a, const Point &b); + static inline double distance_to_squared(const Point &point, const Point &a, const Point &b) { return line_alg::distance_to_squared(Line{a, b}, Vec<2, coord_t>{point}); } static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); } Point a; diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 8c1c69fde3..84010c7eb1 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -88,6 +88,8 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + std: std::vector transform(const std::vector& points, const Transform3f& t); Pointf3s transform(const Pointf3s& points, const Transform3d& t); +template using Vec = Eigen::Matrix; + class Point : public Vec2crd { public: diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 5334054a05..6df752fd36 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -273,4 +273,13 @@ void cut_drainholes(std::vector & obj_slices, obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]); } +void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg) +{ + std::unique_ptr inter_ptr = + Slic3r::sla::generate_interior(mesh); + + if (inter_ptr) mesh.merge(*inter_ptr); + mesh.require_shared_vertices(); +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 1f65fa8b70..c7d27d946c 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -62,6 +62,8 @@ std::unique_ptr generate_interior(const TriangleMesh &mesh, const HollowingConfig & = {}, const JobController &ctl = {}); +void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg); + void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, float closing_radius, diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index f6b261fdaa..d071b49739 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -1,7 +1,8 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp - sla_print_tests.cpp +add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp + sla_print_tests.cpp sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp + sla_supptgen_tests.cpp sla_raycast_tests.cpp) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp new file mode 100644 index 0000000000..1d7a3ebee2 --- /dev/null +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -0,0 +1,148 @@ +#include +#include + +#include +#include + +#include "sla_test_utils.hpp" + +namespace Slic3r { namespace sla { + +TEST_CASE("Overhanging point should be supported", "[SupGen]") { + + // Pyramid with 45 deg slope + TriangleMesh mesh = make_pyramid(10.f, 10.f); + mesh.rotate_y(PI); + mesh.require_shared_vertices(); + mesh.WriteOBJFile("Pyramid.obj"); + + sla::SupportPoints pts = calc_support_pts(mesh); + + // The overhang, which is the upside-down pyramid's edge + Vec3f overh{0., 0., -10.}; + + REQUIRE(!pts.empty()); + + float dist = (overh - pts.front().pos).norm(); + + for (const auto &pt : pts) + dist = std::min(dist, (overh - pt.pos).norm()); + + // Should require exactly one support point at the overhang + REQUIRE(pts.size() > 0); + REQUIRE(dist < 1.f); +} + +double min_point_distance(const sla::SupportPoints &pts) +{ + sla::PointIndex index; + + for (size_t i = 0; i < pts.size(); ++i) + index.insert(pts[i].pos.cast(), i); + + auto d = std::numeric_limits::max(); + index.foreach([&d, &index](const sla::PointIndexEl &el) { + auto res = index.nearest(el.first, 2); + for (const sla::PointIndexEl &r : res) + if (r.second != el.second) + d = std::min(d, (el.first - r.first).norm()); + }); + + return d; +} + +TEST_CASE("Overhanging horizontal surface should be supported", "[SupGen]") { + double width = 10., depth = 10., height = 1.; + + TriangleMesh mesh = make_cube(width, depth, height); + mesh.translate(0., 0., 5.); // lift up + mesh.require_shared_vertices(); + mesh.WriteOBJFile("Cuboid.obj"); + + sla::SupportPointGenerator::Config cfg; + sla::SupportPoints pts = calc_support_pts(mesh, cfg); + + double mm2 = width * depth; + + REQUIRE(!pts.empty()); + REQUIRE(pts.size() * cfg.support_force() > mm2 * cfg.tear_pressure()); + REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); +} + +template auto&& center_around_bb(M &&mesh) +{ + auto bb = mesh.bounding_box(); + mesh.translate(-bb.center().template cast()); + + return std::forward(mesh); +} + +TEST_CASE("Overhanging edge should be supported", "[SupGen]") { + float width = 10.f, depth = 10.f, height = 5.f; + + TriangleMesh mesh = make_prism(width, depth, height); + mesh.rotate_y(PI); // rotate on its back + mesh.translate(0., 0., height); + mesh.require_shared_vertices(); + mesh.WriteOBJFile("Prism.obj"); + + sla::SupportPointGenerator::Config cfg; + sla::SupportPoints pts = calc_support_pts(mesh, cfg); + + REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); + + Linef3 overh{ {0.f, -depth / 2.f, 0.f}, {0.f, depth / 2.f, 0.f}}; + + // Get all the points closer that 1 mm to the overhanging edge: + sla::SupportPoints overh_pts; overh_pts.reserve(pts.size()); + + std::copy_if(pts.begin(), pts.end(), std::back_inserter(overh_pts), + [&overh](const sla::SupportPoint &pt){ + return line_alg::distance_to(overh, Vec3d{pt.pos.cast()}) < 1.; + }); + + REQUIRE(overh_pts.size() * cfg.support_force() > overh.length() * cfg.tear_pressure()); + REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); +} + +// FIXME: Not working yet +//TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowed]") { +// TriangleMesh mesh = make_cube(20., 20., 20.); + +// hollow_mesh(mesh, HollowingConfig{}); + +// mesh.WriteOBJFile("cube_hollowed.obj"); + +// auto bb = mesh.bounding_box(); +// auto h = float(bb.max.z() - bb.min.z()); +// Vec3f mv = bb.center().cast() - Vec3f{0.f, 0.f, 0.5f * h}; +// mesh.translate(-mv); +// mesh.require_shared_vertices(); + +// sla::SupportPointGenerator::Config cfg; +// sla::SupportPoints pts = calc_support_pts(mesh, cfg); +// sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON); + +// REQUIRE(!pts.empty()); +//} + +TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]") +{ + double width = 20., depth = 20., height = 1.; + + TriangleMesh mesh = center_around_bb(make_cube(width + 5., depth + 5., height)); + TriangleMesh mesh_high = center_around_bb(make_cube(width, depth, height)); + mesh_high.translate(0., 0., 10.); // lift up + mesh.merge(mesh_high); + mesh.require_shared_vertices(); + + mesh.WriteOBJFile("parallel_plates.obj"); + + sla::SupportPointGenerator::Config cfg; + sla::SupportPoints pts = calc_support_pts(mesh, cfg); + sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON); + + REQUIRE(!pts.empty()); +} + +}} // namespace Slic3r::sla diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 8978281d8c..f6b548fa05 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -411,3 +411,71 @@ double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) return error; } + + +// Make a 3D pyramid +TriangleMesh make_pyramid(float base, float height) +{ + float a = base / 2.f; + + TriangleMesh mesh( + { + {-a, -a, 0}, {a, -a, 0}, {a, a, 0}, + {-a, a, 0}, {0.f, 0.f, height} + }, + { + {0, 1, 2}, + {0, 2, 3}, + {0, 1, 4}, + {1, 2, 4}, + {2, 3, 4}, + {3, 0, 4} + }); + + mesh.repair(); + + return mesh; +} + + TriangleMesh make_prism(double width, double length, double height) +{ + // We need two upward facing triangles + + double x = width / 2., y = length / 2.; + + TriangleMesh mesh( + { + {-x, -y, 0.}, {x, -y, 0.}, {0., -y, height}, + {-x, y, 0.}, {x, y, 0.}, {0., y, height}, + }, + { + {0, 1, 2}, // side 1 + {4, 3, 5}, // side 2 + {1, 4, 2}, {2, 4, 5}, // roof 1 + {0, 2, 5}, {0, 5, 3}, // roof 2 + {3, 4, 1}, {3, 1, 0} // bottom + }); + + return mesh; +} + +sla::SupportPoints calc_support_pts( + const TriangleMesh & mesh, + const sla::SupportPointGenerator::Config &cfg) +{ + // Prepare the slice grid and the slices + std::vector slices; + auto bb = cast(mesh.bounding_box()); + std::vector heights = grid(bb.min.z(), bb.max.z(), 0.1f); + slice_mesh(mesh, heights, slices, CLOSING_RADIUS, [] {}); + + // Prepare the support point calculator + sla::IndexedMesh emesh{mesh}; + sla::SupportPointGenerator spgen{emesh, cfg, []{}, [](int){}}; + + // Calculate the support points + spgen.seed(0); + spgen.execute(slices, heights); + + return spgen.output(); +} diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index fdd883ed84..d10a85b259 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -185,4 +185,13 @@ long raster_pxsum(const sla::RasterGrayscaleAA &raster); double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); +// Make a 3D pyramid +TriangleMesh make_pyramid(float base, float height); + +TriangleMesh make_prism(double width, double length, double height); + +sla::SupportPoints calc_support_pts( + const TriangleMesh & mesh, + const sla::SupportPointGenerator::Config &cfg = {}); + #endif // SLA_TEST_UTILS_HPP From 7fd2209b48a6dfeb6c4b884200c617279e6de79a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 5 Aug 2020 13:30:05 +0200 Subject: [PATCH 309/826] Gizmos can be shown depending on current mode --- src/slic3r/GUI/GUI_App.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 3 ++- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 12 ++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 82c2861bc2..8f33a6e996 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -41,6 +41,7 @@ #include "3DScene.hpp" #include "MainFrame.hpp" #include "Plater.hpp" +#include "GLCanvas3D.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Utils/PrintHost.hpp" @@ -1012,6 +1013,7 @@ void GUI_App::update_mode() tab->update_mode(); plater()->update_object_menu(); + plater()->canvas3D()->update_gizmos_on_off_state(); } void GUI_App::add_config_menu(wxMenuBar *menu) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 309c7cf423..58b46f0b6a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -631,7 +631,8 @@ bool GLGizmoFdmSupports::on_is_activable() const bool GLGizmoFdmSupports::on_is_selectable() const { - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ); + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF + && wxGetApp().get_mode() != comSimple ); } std::string GLGizmoFdmSupports::on_get_name() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c33ba2850e..78998b92d9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -144,8 +144,11 @@ void GLGizmosManager::refresh_on_off_state() if (m_serializing || m_current == Undefined || m_gizmos.empty()) return; - if (m_current != Undefined && ! m_gizmos[m_current]->is_activable()) + if (m_current != Undefined + && (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) { activate_gizmo(Undefined); + update_data(); + } } void GLGizmosManager::reset_all_states() @@ -204,9 +207,10 @@ void GLGizmosManager::update_data() enable_grabber(Scale, i, enable_scale_xyz); } - m_common_gizmos_data->update(get_current() - ? get_current()->get_requirements() - : CommonGizmosDataID(0)); + if (m_common_gizmos_data) + m_common_gizmos_data->update(get_current() + ? get_current()->get_requirements() + : CommonGizmosDataID(0)); if (selection.is_single_full_instance()) { From 97bc092cce53b893aa796779f2735b87a266e888 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 5 Aug 2020 14:03:22 +0200 Subject: [PATCH 310/826] Renamed FacetSupportType to EnforcerBlockerType So it's not misleading if we use it for seam painting --- src/libslic3r/Model.cpp | 2 +- src/libslic3r/Model.hpp | 4 ++-- src/libslic3r/Print.hpp | 6 ++--- src/libslic3r/PrintObject.cpp | 2 +- src/libslic3r/TriangleSelector.cpp | 24 ++++++++++---------- src/libslic3r/TriangleSelector.hpp | 20 ++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 20 ++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3beb74f235..196e9c213b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1831,7 +1831,7 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const } -indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const { TriangleSelector selector(mv.mesh()); selector.deserialize(m_data); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 92dc84d17a..608ce670f6 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -394,7 +394,7 @@ enum class ModelVolumeType : int { SUPPORT_BLOCKER, }; -enum class FacetSupportType : int8_t { +enum class EnforcerBlockerType : int8_t { // Maximum is 3. The value is serialized in TriangleSelector into 2 bits! NONE = 0, ENFORCER = 1, @@ -407,7 +407,7 @@ public: const std::map>& get_data() const { return m_data; } bool set(const TriangleSelector& selector); - indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; + indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; void clear(); std::string get_triangle_as_string(int i) const; void set_triangle_from_string(int triangle_id, const std::string& str); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 05929dd2ef..08acb7a105 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -187,9 +187,9 @@ public: std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } // Helpers to project custom supports on slices - void project_and_append_custom_supports(FacetSupportType type, std::vector& expolys) const; - void project_and_append_custom_enforcers(std::vector& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); } - void project_and_append_custom_blockers(std::vector& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); } + void project_and_append_custom_supports(EnforcerBlockerType type, std::vector& expolys) const; + void project_and_append_custom_enforcers(std::vector& enforcers) const { project_and_append_custom_supports(EnforcerBlockerType::ENFORCER, enforcers); } + void project_and_append_custom_blockers(std::vector& blockers) const { project_and_append_custom_supports(EnforcerBlockerType::BLOCKER, blockers); } private: // to be called from Print only. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c90a05ef31..ddeee1e778 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2670,7 +2670,7 @@ void PrintObject::_generate_support_material() void PrintObject::project_and_append_custom_supports( - FacetSupportType type, std::vector& expolys) const + EnforcerBlockerType type, std::vector& expolys) const { for (const ModelVolume* mv : this->model_object()->volumes) { const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type); diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 9555c42a69..9f04374fdc 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -35,7 +35,7 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, const Vec3f& dir, - float radius, FacetSupportType new_state) + float radius, EnforcerBlockerType new_state) { assert(facet_start < m_orig_size_indices); assert(is_approx(dir.norm(), 1.f)); @@ -77,7 +77,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // the triangle recursively, selecting just subtriangles truly inside the circle. // This is done by an actual recursive call. Returns false if the triangle is // outside the cursor. -bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) +bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call) { assert(facet_idx < int(m_triangles.size())); @@ -140,7 +140,7 @@ bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, boo -void TriangleSelector::set_facet(int facet_idx, FacetSupportType state) +void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state) { assert(facet_idx < m_orig_size_indices); undivide_triangle(facet_idx); @@ -157,7 +157,7 @@ void TriangleSelector::split_triangle(int facet_idx) Triangle* tr = &m_triangles[facet_idx]; - FacetSupportType old_type = tr->get_state(); + EnforcerBlockerType old_type = tr->get_state(); if (tr->was_split_before() != 0) { // This triangle is not split at the moment, but was at one point @@ -323,7 +323,7 @@ void TriangleSelector::remove_useless_children(int facet_idx) // Return if a child is not leaf or two children differ in type. - FacetSupportType first_child_type = FacetSupportType::NONE; + EnforcerBlockerType first_child_type = EnforcerBlockerType::NONE; for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) return; @@ -456,7 +456,7 @@ void TriangleSelector::push_triangle(int a, int b, int c) } -void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +void TriangleSelector::perform_split(int facet_idx, EnforcerBlockerType old_state) { Triangle* tr = &m_triangles[facet_idx]; @@ -520,7 +520,7 @@ void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) -indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const +indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) const { indexed_triangle_set out; for (const Triangle& tr : m_triangles) { @@ -542,7 +542,7 @@ std::map> TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state // or how it is split. Each triangle is encoded by 4 bits (xxyy): - // leaf triangle: xx = FacetSupportType, yy = 0 + // leaf triangle: xx = EnforcerBlockerType, yy = 0 // non-leaf: xx = special side, yy = number of split sides // These are bitwise appended and formed into one 64-bit integer. @@ -553,7 +553,7 @@ std::map> TriangleSelector::serialize() const for (int i=0; i data; // complete encoding of this mesh triangle @@ -627,7 +627,7 @@ void TriangleSelector::deserialize(const std::map> data) int num_of_split_sides = (next_code & 0b11); int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; bool is_split = num_of_children != 0; - FacetSupportType state = FacetSupportType(next_code >> 2); + EnforcerBlockerType state = EnforcerBlockerType(next_code >> 2); int special_side = (next_code >> 2); // Take care of the first iteration separately, so handling of the others is simpler. @@ -641,7 +641,7 @@ void TriangleSelector::deserialize(const std::map> data) // then go to the next. parents.push_back({triangle_id, 0, num_of_children}); m_triangles[triangle_id].set_division(num_of_children-1, special_side); - perform_split(triangle_id, FacetSupportType::NONE); + perform_split(triangle_id, EnforcerBlockerType::NONE); continue; } } @@ -655,7 +655,7 @@ void TriangleSelector::deserialize(const std::map> data) const ProcessingInfo& last = parents.back(); int this_idx = m_triangles[last.facet_id].children[last.processed_children]; m_triangles[this_idx].set_division(num_of_children-1, special_side); - perform_split(this_idx, FacetSupportType::NONE); + perform_split(this_idx, EnforcerBlockerType::NONE); parents.push_back({this_idx, 0, num_of_children}); } else { // this triangle belongs to last split one diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index fb90cff769..be1b20ed40 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -9,7 +9,7 @@ namespace Slic3r { -enum class FacetSupportType : int8_t; +enum class EnforcerBlockerType : int8_t; @@ -29,13 +29,13 @@ public: const Vec3f& source, // camera position (mesh coords) const Vec3f& dir, // direction of the ray (mesh coords) float radius, // radius of the cursor - FacetSupportType new_state); // enforcer or blocker? + EnforcerBlockerType new_state); // enforcer or blocker? // Get facets currently in the given state. - indexed_triangle_set get_facets(FacetSupportType state) const; + indexed_triangle_set get_facets(EnforcerBlockerType state) const; // Set facet of the mesh to a given state. Only works for original triangles. - void set_facet(int facet_idx, FacetSupportType state); + void set_facet(int facet_idx, EnforcerBlockerType state); // Clear everything and make the tree empty. void reset(); @@ -59,7 +59,7 @@ protected: // It increments/decrements reference counter on vertices. Triangle(int a, int b, int c) : verts_idxs{a, b, c}, - state{FacetSupportType(0)}, + state{EnforcerBlockerType(0)}, number_of_splits{0}, special_side_idx{0}, old_number_of_splits{0} @@ -77,8 +77,8 @@ protected: void set_division(int sides_to_split, int special_side_idx = -1); // Get/set current state. - void set_state(FacetSupportType type) { assert(! is_split()); state = type; } - FacetSupportType get_state() const { assert(! is_split()); return state; } + void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; } + EnforcerBlockerType get_state() const { assert(! is_split()); return state; } // Get info on how it's split. bool is_split() const { return number_of_split_sides() != 0; } @@ -90,7 +90,7 @@ protected: private: int number_of_splits; int special_side_idx; - FacetSupportType state; + EnforcerBlockerType state; // How many children were spawned during last split? // Is not reset on remerging the triangle. @@ -133,7 +133,7 @@ protected: float m_old_cursor_radius; // Private functions: - bool select_triangle(int facet_idx, FacetSupportType type, + bool select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call = false); bool is_point_inside_cursor(const Vec3f& point) const; int vertices_inside(int facet_idx) const; @@ -144,7 +144,7 @@ protected: bool is_pointer_in_triangle(int facet_idx) const; bool is_edge_inside_cursor(int facet_idx) const; void push_triangle(int a, int b, int c); - void perform_split(int facet_idx, FacetSupportType old_state); + void perform_split(int facet_idx, EnforcerBlockerType old_state); }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 58b46f0b6a..f3b6db4f26 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -296,16 +296,16 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_triangle_selectors.empty()) return false; - FacetSupportType new_state = FacetSupportType::NONE; + EnforcerBlockerType new_state = EnforcerBlockerType::NONE; if (! shift_down) { if (action == SLAGizmoEventType::Dragging) new_state = m_button_down == Button::Left - ? FacetSupportType::ENFORCER - : FacetSupportType::BLOCKER; + ? EnforcerBlockerType::ENFORCER + : EnforcerBlockerType::BLOCKER; else new_state = action == SLAGizmoEventType::LeftDown - ? FacetSupportType::ENFORCER - : FacetSupportType::BLOCKER; + ? EnforcerBlockerType::ENFORCER + : EnforcerBlockerType::BLOCKER; } const Camera& camera = wxGetApp().plater()->get_camera(); @@ -465,8 +465,8 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) if (facet.normal.dot(down) > dot_limit) m_triangle_selectors[mesh_id]->set_facet(idx, block - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER); + ? EnforcerBlockerType::BLOCKER + : EnforcerBlockerType::ENFORCER); } } @@ -719,13 +719,13 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) m_iva_blockers.release_geometry(); for (const Triangle& tr : m_triangles) { - if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) + if (! tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE) continue; - GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER + GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : m_iva_blockers; - int& cnt = tr.get_state() == FacetSupportType::ENFORCER + int& cnt = tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index ce24ea8d28..e1dee373f2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -15,7 +15,7 @@ namespace Slic3r { -enum class FacetSupportType : int8_t; +enum class EnforcerBlockerType : int8_t; namespace GUI { From 6db1e5ab8fb6bacf0a1e47e7796beb8cc902dfda Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 12 Aug 2020 12:58:23 +0200 Subject: [PATCH 311/826] Slight code cleanup --- src/libslic3r/ExtrusionEntity.hpp | 4 ++-- src/libslic3r/GCode.cpp | 16 ++++------------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 879f564b6c..4749b52622 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -121,8 +121,8 @@ public: // Height of the extrusion, used for visualization purposes. float height; - ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}; - ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}; + ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {} + ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {} ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7d80677184..62eb4b920c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2463,7 +2463,7 @@ plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) } } -static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) +static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) { assert(polygon.points.size() >= 2); if (polygon.points.size() <= 1) @@ -2651,7 +2651,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Insert a projection of last_pos into the polygon. size_t last_pos_proj_idx; { - Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); + auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); last_pos_proj_idx = it - polygon.points.begin(); } @@ -2671,11 +2671,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou if (was_clockwise) ccwAngle = - ccwAngle; float penalty = 0; -// if (ccwAngle <- float(PI/3.)) if (ccwAngle <- float(0.6 * PI)) // Sharp reflex vertex. We love that, it hides the seam perfectly. penalty = 0.f; -// else if (ccwAngle > float(PI/3.)) else if (ccwAngle > float(0.6 * PI)) // Seams on sharp convex vertices are more visible than on reflex vertices. penalty = penaltyConvexVertex; @@ -2688,7 +2686,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); } // Give a negative penalty for points close to the last point or the prefered seam location. - //float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]); float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); @@ -2708,14 +2705,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Signed distance is positive outside the object, negative inside the object. // The point is considered at an overhang, if it is more than nozzle radius // outside of the lower layer contour. - #ifdef NDEBUG // to suppress unused variable warning in release mode - (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); - #else - bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); - #endif + [[maybe_unused]] bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, // then the signed distnace shall always be known. - assert(found); + assert(found); penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); } } @@ -2723,7 +2716,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - // if (seam_position == spAligned) // For all (aligned, nearest, rear) seams: { // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. From bd4e4535f902e1a935a5241b8df187709f7baced Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 18 Aug 2020 12:37:07 +0200 Subject: [PATCH 312/826] GCodeProcessor -> Calculate per layer time estimate --- src/libslic3r/GCode.cpp | 2 + src/libslic3r/GCode/GCodeProcessor.cpp | 55 ++++++++++++++++++++++++-- src/libslic3r/GCode/GCodeProcessor.hpp | 7 ++++ src/slic3r/GUI/GCodeViewer.cpp | 2 +- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3de70d0611..9e1600cb43 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2095,6 +2095,8 @@ void GCode::process_layer( std::string gcode; #if ENABLE_GCODE_VIEWER + // add tag for processor + gcode += "; " + GCodeProcessor::Layer_Change_Tag + "\n"; // export layer z char buf[64]; sprintf(buf, ";Z:%g\n", print_z); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 6684255918..5124e1a99d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -24,9 +24,10 @@ namespace Slic3r { const std::string GCodeProcessor::Extrusion_Role_Tag = "TYPE:"; const std::string GCodeProcessor::Height_Tag = "HEIGHT:"; -const std::string GCodeProcessor::Color_Change_Tag = "COLOR CHANGE"; -const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE PRINT"; -const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM GCODE"; +const std::string GCodeProcessor::Layer_Change_Tag = "LAYER_CHANGE"; +const std::string GCodeProcessor::Color_Change_Tag = "COLOR_CHANGE"; +const std::string GCodeProcessor::Pause_Print_Tag = "PAUSE_PRINT"; +const std::string GCodeProcessor::Custom_Code_Tag = "CUSTOM_GCODE"; const std::string GCodeProcessor::First_Line_M73_Placeholder_Tag = "; _GP_FIRST_LINE_M73_PLACEHOLDER"; const std::string GCodeProcessor::Last_Line_M73_Placeholder_Tag = "; _GP_LAST_LINE_M73_PLACEHOLDER"; @@ -174,6 +175,7 @@ void GCodeProcessor::TimeMachine::reset() g1_times_cache = std::vector(); std::fill(moves_time.begin(), moves_time.end(), 0.0f); std::fill(roles_time.begin(), roles_time.end(), 0.0f); + layers_time = std::vector(); } void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) @@ -282,6 +284,16 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks) gcode_time.cache += block_time; moves_time[static_cast(block.move_type)] += block_time; roles_time[static_cast(block.role)] += block_time; + if (block.layer_id > 0) { + if (block.layer_id >= layers_time.size()) { + size_t curr_size = layers_time.size(); + layers_time.resize(block.layer_id); + for (size_t i = curr_size; i < layers_time.size(); ++i) { + layers_time[i] = 0.0f; + } + } + layers_time[block.layer_id - 1] += block_time; + } g1_times_cache.push_back(time); } @@ -347,6 +359,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) std::string line = gcode_line.substr(0, gcode_line.length() - 1); std::string ret; + if (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag) { for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; @@ -369,9 +382,20 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) } } } + return std::make_pair(!ret.empty(), ret.empty() ? gcode_line : ret); }; + // check for temporary lines + auto is_temporary_decoration = [](const std::string& gcode_line) { + // remove trailing '\n' + std::string line = gcode_line.substr(0, gcode_line.length() - 1); + if (line == "; " + Layer_Change_Tag) + return true; + else + return false; + }; + // add lines M73 to exported gcode auto process_line_G1 = [&]() { for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { @@ -408,9 +432,15 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) } gcode_line += "\n"; + // replace placeholder lines auto [processed, result] = process_placeholders(gcode_line); gcode_line = result; if (!processed) { + // remove temporary lines + if (is_temporary_decoration(gcode_line)) + continue; + + // add lines M73 where needed parser.parse_line(gcode_line, [&](GCodeReader& reader, const GCodeReader::GCodeLine& line) { if (line.cmd_is("G1")) { @@ -677,6 +707,7 @@ void GCodeProcessor::reset() m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f); m_extruded_last_z = 0.0f; + m_layer_id = 0; m_cp_color.reset(); m_producer = EProducer::Unknown; @@ -726,7 +757,7 @@ void GCodeProcessor::process_file(const std::string& filename) m_result.moves.emplace_back(MoveVertex()); m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); - // process the remaining time blocks + // process the time blocks for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; @@ -804,6 +835,13 @@ std::vector> GCodeProcessor::get_roles_time(Prin return ret; } +std::vector GCodeProcessor::get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +{ + return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? + m_time_processor.machines[static_cast(mode)].layers_time : + std::vector(); +} + void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) { /* std::cout << line.raw() << std::endl; */ @@ -973,6 +1011,13 @@ void GCodeProcessor::process_tags(const std::string& comment) return; } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + // layer change tag + pos = comment.find(Layer_Change_Tag); + if (pos != comment.npos) { + ++m_layer_id; + return; + } } bool GCodeProcessor::process_producers_tags(const std::string& comment) @@ -1428,6 +1473,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) block.move_type = type; block.role = m_extrusion_role; block.distance = distance; + block.layer_id = m_layer_id; // calculates block cruise feedrate float min_feedrate_factor = 1.0f; @@ -2097,6 +2143,7 @@ void GCodeProcessor::update_estimated_times_stats() data.custom_gcode_times = get_custom_gcode_times(mode, true); data.moves_times = get_moves_time(mode); data.roles_times = get_roles_time(mode); + data.layers_times = get_layers_time(mode); }; update_mode(PrintEstimatedTimeStatistics::ETimeMode::Normal); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index b19610801c..90552e6582 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -43,12 +43,14 @@ namespace Slic3r { std::vector>> custom_gcode_times; std::vector> moves_times; std::vector> roles_times; + std::vector layers_times; void reset() { time = 0.0f; custom_gcode_times.clear(); moves_times.clear(); roles_times.clear(); + layers_times.clear(); } }; @@ -68,6 +70,7 @@ namespace Slic3r { public: static const std::string Extrusion_Role_Tag; static const std::string Height_Tag; + static const std::string Layer_Change_Tag; static const std::string Color_Change_Tag; static const std::string Pause_Print_Tag; static const std::string Custom_Code_Tag; @@ -142,6 +145,7 @@ namespace Slic3r { EMoveType move_type{ EMoveType::Noop }; ExtrusionRole role{ erNone }; + unsigned int layer_id{ 0 }; float distance{ 0.0f }; // mm float acceleration{ 0.0f }; // mm/s^2 float max_entry_speed{ 0.0f }; // mm/s @@ -192,6 +196,7 @@ namespace Slic3r { std::vector g1_times_cache; std::array(EMoveType::Count)> moves_time; std::array(ExtrusionRole::erCount)> roles_time; + std::vector layers_time; void reset(); @@ -368,6 +373,7 @@ namespace Slic3r { ExtruderColors m_extruder_colors; std::vector m_filament_diameters; float m_extruded_last_z; + unsigned int m_layer_id; CpColor m_cp_color; enum class EProducer @@ -420,6 +426,7 @@ namespace Slic3r { std::vector> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; std::vector> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; + std::vector get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; private: void process_gcode_line(const GCodeReader::GCodeLine& line); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 1a4485402b..ed1d5bc5c9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2352,7 +2352,7 @@ void GCodeViewer::render_time_estimate() const imgui.title(_u8L("Estimated printing time")); #endif // GCODE_VIEWER_TIME_ESTIMATE - // mode tabs + // mode tabs ImGui::BeginTabBar("mode_tabs"); const PrintEstimatedTimeStatistics::Mode& normal_mode = m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)]; if (normal_mode.time > 0.0f) { From 7b5f84b7dfdb9de22bfb87d3d69ab29e021a5a23 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 18 Aug 2020 12:39:46 +0200 Subject: [PATCH 313/826] Extended hover capability in DoubleSlider::Control --- src/slic3r/GUI/DoubleSlider.cpp | 26 ++++++++++++++++++-------- src/slic3r/GUI/DoubleSlider.hpp | 6 ++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index a7cb7d54d1..8a9ac34ea1 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -600,10 +600,8 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_ const wxString label = get_label(tick); dc.GetMultiLineTextExtent(label, &text_width, &text_height); wxPoint text_pos; - if (right_side) - { - if (is_horizontal()) - { + if (right_side) { + if (is_horizontal()) { int width; int height; get_size(&width, &height); @@ -614,17 +612,21 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_ } else text_pos = wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1); + + // update text rectangle + m_rect_lower_thumb_text = wxRect(text_pos, wxSize(text_width, text_height)); } - else - { - if (is_horizontal()) - { + else { + if (is_horizontal()) { int x = pos.x - text_width - 1; int xx = (x > 0) ? x : pos.x + 1; text_pos = wxPoint(xx, pos.y - m_thumb_size.x / 2 - text_height - 1); } else text_pos = wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1); + + // update text rectangle + m_rect_higher_thumb_text = wxRect(text_pos, wxSize(text_width, text_height)); } dc.DrawText(label, text_pos); @@ -1206,6 +1208,14 @@ void Control::OnMotion(wxMouseEvent& event) else if (m_mode == SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) && get_edited_tick_for_position(pos) >= 0 ) m_focus = fiColorBand; + else if (is_point_in_rect(pos, m_rect_lower_thumb)) + m_focus = fiLowerThumb; + else if (is_point_in_rect(pos, m_rect_higher_thumb)) + m_focus = fiHigherThumb; + else if (is_point_in_rect(pos, m_rect_lower_thumb_text)) + m_focus = fiLowerThumbText; + else if (is_point_in_rect(pos, m_rect_higher_thumb_text)) + m_focus = fiHigherThumbText; else { m_focus = fiTick; tick = get_tick_near_point(pos); diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index fc8ebced87..fb87ac4a97 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -44,6 +44,10 @@ enum FocusedItem { fiCogIcon, fiColorBand, fiActionIcon, + fiLowerThumb, + fiHigherThumb, + fiLowerThumbText, + fiHigherThumbText, fiTick }; @@ -360,6 +364,8 @@ private: wxRect m_rect_lower_thumb; wxRect m_rect_higher_thumb; + mutable wxRect m_rect_lower_thumb_text; + mutable wxRect m_rect_higher_thumb_text; wxRect m_rect_tick_action; wxRect m_rect_one_layer_icon; wxRect m_rect_revert_icon; From 5052149b819c438b107a28268e3da0f48f57e8cb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Aug 2020 13:45:18 +0200 Subject: [PATCH 314/826] Fix build on msvc --- src/libslic3r/Point.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 84010c7eb1..30a1a4942c 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -88,7 +88,7 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + std: std::vector transform(const std::vector& points, const Transform3f& t); Pointf3s transform(const Pointf3s& points, const Transform3d& t); -template using Vec = Eigen::Matrix; +template using Vec = Eigen::Matrix; class Point : public Vec2crd { From 4641d44544389324df278fbe5f062aa6db58382e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 Aug 2020 16:49:06 +0200 Subject: [PATCH 315/826] UnsavedChangesDialog : improvements * Processed changes in options with nullable values * Processed changes on the extruders count --- src/slic3r/GUI/Tab.cpp | 37 +++++++++- src/slic3r/GUI/Tab.hpp | 3 + src/slic3r/GUI/UnsavedChangesDialog.cpp | 93 ++++++++++++++++++------- 3 files changed, 106 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a16222c86b..898890f6e5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2866,7 +2866,7 @@ void Tab::load_current_preset() } on_presets_changed(); if (printer_technology == ptFFF) { - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + static_cast(this)->m_initial_extruders_count = static_cast(m_presets->get_selected_preset().config.option("nozzle_diameter"))->values.size(); //static_cast(this)->m_extruders_count; const Preset* parent_preset = m_presets->get_selected_preset_parent(); static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); @@ -3131,6 +3131,10 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } + // check and apply extruders count for printer preset + if (m_type == Preset::TYPE_PRINTER) + static_cast(this)->apply_extruder_cnt_from_cache(); + // check if there is something in the cache to move to the new selected preset if (!m_cache_config.empty()) { m_presets->get_edited_preset().config.apply(m_cache_config); @@ -3184,8 +3188,18 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr } else if (dlg.move_preset()) // move selected changes { + std::vector selected_options = dlg.get_selected_options(); + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + if (m_type == Preset::TYPE_PRINTER) + static_cast(this)->cache_extruder_cnt(); + } + // copy selected options to the cache from edited preset - m_cache_config.apply_only(*m_config, dlg.get_selected_options()); + m_cache_config.apply_only(*m_config, selected_options); } return true; @@ -3593,6 +3607,25 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) return sizer; } +void TabPrinter::cache_extruder_cnt() +{ + if (m_presets->get_edited_preset().printer_technology() == ptSLA) + return; + + m_cache_extruder_count = m_extruders_count; +} + +void TabPrinter::apply_extruder_cnt_from_cache() +{ + if (m_presets->get_edited_preset().printer_technology() == ptSLA) + return; + + if (m_cache_extruder_count > 0) { + m_presets->get_edited_preset().set_num_extruders(m_cache_extruder_count); + m_cache_extruder_count = 0; + } +} + void Tab::compatible_widget_reload(PresetDependencies &deps) { bool has_any = ! m_config->option(deps.key_list)->values.empty(); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index a97153f470..9bddebeab1 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -411,6 +411,7 @@ public: size_t m_extruders_count_old = 0; size_t m_initial_extruders_count; size_t m_sys_extruders_count; + size_t m_cache_extruder_count = 0; PrinterTechnology m_printer_technology = ptFFF; @@ -437,6 +438,8 @@ public: bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); + void cache_extruder_cnt(); + void apply_extruder_cnt_from_cache(); }; class TabSLAMaterial : public Tab diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 2fa89266e2..bebc01c782 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -712,47 +712,71 @@ wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConf return from_u8(_utf8(names[static_cast(val)])); } -static wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig& config) +static int get_id_from_opt_key(std::string opt_key) { + int pos = opt_key.find("#"); + if (pos > 0) { + boost::erase_head(opt_key, pos + 1); + return atoi(opt_key.c_str()); + } + return 0; +} + +static std::string get_pure_opt_key(std::string opt_key) +{ + int pos = opt_key.find("#"); + if (pos > 0) + boost::erase_tail(opt_key, opt_key.size() - pos); + return opt_key; +} + +static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config) +{ + int opt_idx = get_id_from_opt_key(opt_key); + opt_key = get_pure_opt_key(opt_key); + + if (config.option(opt_key)->is_nil()) + return _L("N/A"); + wxString out; // FIXME controll, if opt_key has index - int opt_idx = 0; - ConfigOptionType type = config.def()->options.at(opt_key).type; + const ConfigOptionDef* opt = config.def()->get(opt_key); + bool is_nullable = opt->nullable; - switch (type) { + switch (opt->type) { case coInt: return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str()); case coInts: { - const ConfigOptionInts* opt = config.opt(opt_key); - if (opt) - return from_u8((boost::format("%1%") % opt->get_at(opt_idx)).str()); - break; + int val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return from_u8((boost::format("%1%") % val).str()); } case coBool: return config.opt_bool(opt_key) ? "true" : "false"; case coBools: { - const ConfigOptionBools* opt = config.opt(opt_key); - if (opt) - return opt->get_at(opt_idx) ? "true" : "false"; - break; + bool val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return val ? "true" : "false"; } case coPercent: return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str()); case coPercents: { - const ConfigOptionPercents* opt = config.opt(opt_key); - if (opt) - return from_u8((boost::format("%1%%%") % int(opt->get_at(opt_idx))).str()); - break; + double val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return from_u8((boost::format("%1%%%") % int(val)).str()); } case coFloat: return double_to_string(config.opt_float(opt_key)); case coFloats: { - const ConfigOptionFloats* opt = config.opt(opt_key); - if (opt) - return double_to_string(opt->get_at(opt_idx)); - break; + double val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return double_to_string(val); } case coString: return from_u8(config.opt_string(opt_key)); @@ -896,7 +920,23 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres m_tree_model->AddPreset(type, from_u8(presets->get_edited_preset().name)); // Collect dirty options. - for (const std::string& opt_key : presets->current_dirty_options()) { + const bool deep_compare = (type == Preset::TYPE_PRINTER || type == Preset::TYPE_SLA_MATERIAL); + auto dirty_options = presets->current_dirty_options(deep_compare); + auto dirty_options_ = presets->current_dirty_options(); + + // process changes of extruders count + if (type == Preset::TYPE_PRINTER && + old_config.opt("extruder_colour")->values.size() != new_config.opt("extruder_colour")->values.size()) { + wxString local_label = _L("Extruders count"); + wxString old_val = from_u8((boost::format("%1%") % old_config.opt("extruder_colour")->values.size()).str()); + wxString new_val = from_u8((boost::format("%1%") % new_config.opt("extruder_colour")->values.size()).str()); + + ItemData item_data = { "extruders_count", local_label, old_val, new_val, type }; + m_items_map.emplace(m_tree_model->AddOption(type, _L("General"), _L("Capabilities"), local_label, old_val, new_val), item_data); + + } + + for (const std::string& opt_key : /*presets->current_dirty_options()*/dirty_options) { const Search::Option& option = searcher.get_option(opt_key); ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), type }; @@ -915,9 +955,12 @@ std::vector UnsavedChangesDialog::get_unselected_options(Preset::Ty { std::vector ret; - for (auto item : m_items_map) + for (auto item : m_items_map) { + if (item.second.opt_key == "extruders_count") + continue; if (item.second.type == type && !m_tree_model->IsEnabledItem(item.first)) - ret.emplace_back(item.second.opt_key); + ret.emplace_back(get_pure_opt_key(item.second.opt_key)); + } return ret; } @@ -926,9 +969,9 @@ std::vector UnsavedChangesDialog::get_selected_options() { std::vector ret; - for (auto item : m_items_map) + for (auto item : m_items_map) if (m_tree_model->IsEnabledItem(item.first)) - ret.emplace_back(item.second.opt_key); + ret.emplace_back(get_pure_opt_key(item.second.opt_key)); return ret; } From d91fc7b8ab0f5a3ce284483d2fa339fa04f643c3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 19 Aug 2020 11:25:12 +0200 Subject: [PATCH 316/826] ENABLE_GCODE_VIEWER -> Removed options_120_solid shader --- resources/shaders/options_120_solid.fs | 89 -------------------------- resources/shaders/options_120_solid.vs | 14 ---- src/slic3r/GUI/GCodeViewer.cpp | 6 +- src/slic3r/GUI/GCodeViewer.hpp | 4 +- src/slic3r/GUI/GLShadersManager.cpp | 4 +- 5 files changed, 5 insertions(+), 112 deletions(-) delete mode 100644 resources/shaders/options_120_solid.fs delete mode 100644 resources/shaders/options_120_solid.vs diff --git a/resources/shaders/options_120_solid.fs b/resources/shaders/options_120_solid.fs deleted file mode 100644 index 4480d7b147..0000000000 --- a/resources/shaders/options_120_solid.fs +++ /dev/null @@ -1,89 +0,0 @@ -// version 120 is needed for gl_PointCoord -#version 120 - -#define INTENSITY_CORRECTION 0.6 - -// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) -const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); -#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) -#define LIGHT_TOP_SHININESS 20.0 - -// normalized values for (1./1.43, 0.2/1.43, 1./1.43) -const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); -#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) - -#define INTENSITY_AMBIENT 0.3 - -uniform vec3 uniform_color; - -uniform ivec4 viewport; -uniform float point_size; -uniform mat4 inv_proj_matrix; - -varying vec3 eye_center; -// x = tainted, y = specular; -vec2 intensity; - -float radius = 0.5 * point_size; - -vec3 eye_position_from_fragment() -{ - // Convert screen coordinates to normalized device coordinates (NDC) - vec4 ndc = vec4((gl_FragCoord.x / viewport.z - 0.5) * 2.0, - (gl_FragCoord.y / viewport.w - 0.5) * 2.0, - (gl_FragCoord.z - 0.5) * 2.0, - gl_FragCoord.w); - // Convert NDC throuch inverse clip coordinates to view coordinates - vec4 clip = inv_proj_matrix * ndc; - return clip.xyz; -} - -vec3 eye_position_on_sphere(vec3 eye_fragment_position) -{ - vec3 eye_dir = normalize(eye_fragment_position); - float a = dot(eye_dir, eye_dir); - float b = 2.0 * dot(-eye_center, eye_dir); - float c = dot(eye_center, eye_center) - radius * radius; - float discriminant = b * b - 4 * a * c; - float t = -(b + sqrt(discriminant)) / (2.0 * a); - return t * eye_dir; -} - -vec4 on_sphere_color(vec3 eye_on_sphere_position) -{ - vec3 eye_normal = normalize(eye_on_sphere_position - eye_center); - - // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. - // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); - - intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_on_sphere_position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); - - // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); - intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - - return vec4(intensity.y + uniform_color.rgb * intensity.x, 1.0); -} - -float fragment_depth(vec3 eye_pos) -{ - vec4 clip_pos = gl_ProjectionMatrix * vec4(eye_pos, 1.0); - float ndc_depth = clip_pos.z / clip_pos.w; - return ((gl_DepthRange.far - gl_DepthRange.near) * ndc_depth + gl_DepthRange.near + gl_DepthRange.far) / 2.0; -} - -void main() -{ - vec2 pos = (gl_PointCoord - 0.5) * 2.0; - float radius = length(pos); - if (radius > 1.0) - discard; - - vec3 eye_on_sphere_position = eye_position_on_sphere(eye_position_from_fragment()); - -// gl_FragDepth = fragment_depth(eye_on_sphere_position); - gl_FragColor = on_sphere_color(eye_on_sphere_position); -} diff --git a/resources/shaders/options_120_solid.vs b/resources/shaders/options_120_solid.vs deleted file mode 100644 index 745ec8ddd1..0000000000 --- a/resources/shaders/options_120_solid.vs +++ /dev/null @@ -1,14 +0,0 @@ -#version 120 - -uniform float zoom; -uniform float point_size; -uniform float near_plane_height; - -varying vec3 eye_center; - -void main() -{ - eye_center = (gl_ModelViewMatrix * gl_Vertex).xyz; - gl_Position = ftransform(); - gl_PointSize = (gl_Position.w == 1.0) ? zoom * near_plane_height * point_size : near_plane_height * point_size / gl_Position.w; -} diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ed1d5bc5c9..40c31d257d 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2479,8 +2479,8 @@ void GCodeViewer::render_statistics() const void GCodeViewer::render_shaders_editor() const { auto set_shader = [this](const std::string& shader) { - unsigned char begin_id = buffer_id(GCodeProcessor::EMoveType::Retract); - unsigned char end_id = buffer_id(GCodeProcessor::EMoveType::Custom_GCode); + unsigned char begin_id = buffer_id(EMoveType::Retract); + unsigned char end_id = buffer_id(EMoveType::Custom_GCode); for (unsigned char i = begin_id; i <= end_id; ++i) { m_buffers[i].shader = shader; } @@ -2497,7 +2497,6 @@ void GCodeViewer::render_shaders_editor() const if (ImGui::TreeNode("GLSL version")) { ImGui::RadioButton("1.10 (low end PCs)", &m_shaders_editor.points.shader_version, 0); ImGui::RadioButton("1.20 flat (billboards) [default]", &m_shaders_editor.points.shader_version, 1); - ImGui::RadioButton("1.20 solid (spheres)", &m_shaders_editor.points.shader_version, 2); ImGui::TreePop(); } @@ -2505,7 +2504,6 @@ void GCodeViewer::render_shaders_editor() const { case 0: { set_shader("options_110"); break; } case 1: { set_shader("options_120_flat"); break; } - case 2: { set_shader("options_120_solid"); break; } } if (ImGui::TreeNode("Options")) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 74506677a6..0be17f790a 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -33,7 +33,7 @@ class GCodeViewer CustomGCodes }; - // vbo buffer containing vertices data for a specific toolpath type + // vbo buffer containing vertices data used to rendder a specific toolpath type struct VBuffer { enum class EFormat : unsigned char @@ -65,7 +65,7 @@ class GCodeViewer void reset(); }; - // ibo buffer containing indices data for a specific toolpath type + // ibo buffer containing indices data (triangles) used to render a specific toolpath type struct IBuffer { // ibo id diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index e62a81d39b..5f726d4ef1 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -35,10 +35,8 @@ std::pair GLShadersManager::init() valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); // used to render options in gcode preview valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); - if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) { + if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) valid &= append_shader("options_120_flat", { "options_120_flat.vs", "options_120_flat.fs" }); - valid &= append_shader("options_120_solid", { "options_120_solid.vs", "options_120_solid.fs" }); - } // used to render extrusion and travel paths in gcode preview valid &= append_shader("toolpaths", { "toolpaths.vs", "toolpaths.fs" }); // used to render objects in 3d editor From 41579db708fa45544db685121a809fc8d81b9f7b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 19 Aug 2020 11:55:18 +0200 Subject: [PATCH 317/826] GCodeViewer -> Use only white texts in legend --- src/slic3r/GUI/GCodeViewer.cpp | 24 ++++++++++++------------ src/slic3r/GUI/ImGuiWrapper.cpp | 2 -- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 40c31d257d..22d8615ca0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1542,11 +1542,11 @@ void GCodeViewer::render_legend() const #if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[0]); + imgui.text(texts[0]); ImGui::SameLine(offsets[0]); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[1]); + imgui.text(texts[1]); ImGui::SameLine(offsets[1]); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, texts[2]); + imgui.text(texts[2]); ImGui::Separator(); }; @@ -1648,7 +1648,7 @@ void GCodeViewer::render_legend() const if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { ImGui::AlignTextToFramePadding(); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Estimated printing time") + ":"); + imgui.text(_u8L("Estimated printing time") + ":"); ImGui::SameLine(); imgui.text(short_time(get_time_dhms(time_mode.time))); @@ -1703,7 +1703,7 @@ void GCodeViewer::render_legend() const case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%%)")); break; } + case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } @@ -2071,11 +2071,11 @@ void GCodeViewer::render_time_estimate() const using PartialTimes = std::vector; auto append_headers = [&imgui](const Headers& headers, const ColumnOffsets& offsets) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, headers[0]); + imgui.text(headers[0]); ImGui::SameLine(offsets[0]); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, headers[1]); + imgui.text(headers[1]); ImGui::SameLine(offsets[1]); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, headers[2]); + imgui.text(headers[2]); ImGui::Separator(); }; @@ -2135,7 +2135,7 @@ void GCodeViewer::render_time_estimate() const { case PartialTime::EType::Print: { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Print")); + imgui.text(_u8L("Print")); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(item.times.second))); ImGui::SameLine(offsets[1]); @@ -2144,7 +2144,7 @@ void GCodeViewer::render_time_estimate() const } case PartialTime::EType::Pause: { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Pause")); + imgui.text(_u8L("Pause")); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); break; @@ -2175,7 +2175,7 @@ void GCodeViewer::render_time_estimate() const }; auto append_time_item = [&imgui] (const std::string& label, float time, float percentage, const ImVec4& color, const ColumnOffsets& offsets) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + imgui.text(label); ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(time))); ImGui::SameLine(offsets[1]); @@ -2254,7 +2254,7 @@ void GCodeViewer::render_time_estimate() const return ret; }; - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Time") + ":"); + imgui.text(_u8L("Time") + ":"); ImGui::SameLine(); imgui.text(short_time(get_time_dhms(total_time))); append_partial_times(items, partial_times_headers); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index b79f119d45..7c27545026 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -803,9 +803,7 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co void ImGuiWrapper::title(const std::string& str) { - ImGui::PushStyleColor(ImGuiCol_Text, ImGuiWrapper::COL_ORANGE_LIGHT); text(str); - ImGui::PopStyleColor(); ImGui::Separator(); } From eca4f0a4cd31279fa27f84490dfb15f72c5d10e3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 19 Aug 2020 12:59:50 +0200 Subject: [PATCH 318/826] GCodeViewer -> Changed layout of sliders in preview --- src/slic3r/GUI/GUI_Preview.cpp | 51 +++++++++++++++++++--------------- src/slic3r/GUI/GUI_Preview.hpp | 1 + 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2c5a6fe88b..d62b4dd506 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -186,6 +186,7 @@ Preview::Preview( : m_canvas_widget(nullptr) , m_canvas(nullptr) #if ENABLE_GCODE_VIEWER + , m_left_sizer(nullptr) , m_layers_slider_sizer(nullptr) , m_bottom_toolbar_panel(nullptr) #else @@ -237,6 +238,8 @@ bool Preview::init(wxWindow* parent, Model* model) if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + m_canvas_widget = OpenGLManager::create_wxglcanvas(*this); if (m_canvas_widget == nullptr) return false; @@ -255,9 +258,7 @@ bool Preview::init(wxWindow* parent, Model* model) m_layers_slider_sizer = create_layers_slider_sizer(); m_bottom_toolbar_panel = new wxPanel(this); - m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View")); - m_choice_view_type = new wxChoice(m_bottom_toolbar_panel, wxID_ANY); #else m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); @@ -340,15 +341,13 @@ bool Preview::init(wxWindow* parent, Model* model) m_checkbox_legend->SetValue(true); #endif // ENABLE_GCODE_VIEWER - wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); #if ENABLE_GCODE_VIEWER - top_sizer->Add(m_layers_slider_sizer, 0, wxEXPAND, 0); -#else - top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); -#endif // ENABLE_GCODE_VIEWER + m_left_sizer = new wxBoxSizer(wxVERTICAL); + m_left_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); + + wxBoxSizer* right_sizer = new wxBoxSizer(wxVERTICAL); + right_sizer->Add(m_layers_slider_sizer, 1, wxEXPAND, 0); -#if ENABLE_GCODE_VIEWER m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL); m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView); @@ -366,7 +365,18 @@ bool Preview::init(wxWindow* parent, Model* model) bottom_toolbar_sizer->AddSpacer(5); bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0); m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer); + + m_left_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0); + m_left_sizer->Hide(m_bottom_toolbar_panel); + + wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL); + main_sizer->Add(m_left_sizer, 1, wxALL | wxEXPAND, 0); + main_sizer->Add(right_sizer, 0, wxALL | wxEXPAND, 0); #else + wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); + top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); + top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); + wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL); bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5); bottom_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5); @@ -383,16 +393,12 @@ bool Preview::init(wxWindow* parent, Model* model) bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(20); bottom_sizer->Add(m_checkbox_legend, 0, wxEXPAND | wxALL, 5); -#endif // ENABLE_GCODE_VIEWER wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0); -#if ENABLE_GCODE_VIEWER - main_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0); - main_sizer->Hide(m_bottom_toolbar_panel); -#else main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0); #endif // ENABLE_GCODE_VIEWER + SetSizer(main_sizer); SetMinSize(GetSize()); GetSizer()->SetSizeHints(this); @@ -1233,8 +1239,8 @@ void Preview::load_print_as_fff(bool keep_z_range) { #if ENABLE_GCODE_VIEWER hide_layers_slider(); - GetSizer()->Hide(m_bottom_toolbar_panel); - GetSizer()->Layout(); + m_left_sizer->Hide(m_bottom_toolbar_panel); + m_left_sizer->Layout(); Refresh(); #else reset_sliders(true); @@ -1309,8 +1315,8 @@ void Preview::load_print_as_fff(bool keep_z_range) #if ENABLE_GCODE_VIEWER m_canvas->load_gcode_preview(*m_gcode_result); m_canvas->refresh_gcode_preview(*m_gcode_result, colors); - GetSizer()->Show(m_bottom_toolbar_panel); - GetSizer()->Layout(); + m_left_sizer->Show(m_bottom_toolbar_panel); + m_left_sizer->Layout(); Refresh(); zs = m_canvas->get_gcode_layers_zs(); #else @@ -1321,8 +1327,8 @@ void Preview::load_print_as_fff(bool keep_z_range) // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); #if ENABLE_GCODE_VIEWER - GetSizer()->Hide(m_bottom_toolbar_panel); - GetSizer()->Layout(); + m_left_sizer->Hide(m_bottom_toolbar_panel); + m_left_sizer->Layout(); Refresh(); zs = m_canvas->get_volumes_print_zs(true); #endif // ENABLE_GCODE_VIEWER @@ -1384,8 +1390,9 @@ void Preview::load_print_as_sla() { m_canvas->load_sla_preview(); #if ENABLE_GCODE_VIEWER - GetSizer()->Hide(m_bottom_toolbar_panel); - GetSizer()->Layout(); + m_left_sizer->Hide(m_bottom_toolbar_panel); + m_left_sizer->Hide(m_bottom_toolbar_panel); + m_left_sizer->Layout(); Refresh(); #else show_hide_ui_elements("none"); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index c74dccc5c1..ddb7af86fb 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -84,6 +84,7 @@ class Preview : public wxPanel wxGLCanvas* m_canvas_widget; GLCanvas3D* m_canvas; #if ENABLE_GCODE_VIEWER + wxBoxSizer* m_left_sizer; wxBoxSizer* m_layers_slider_sizer; wxPanel* m_bottom_toolbar_panel; #else From 15285a68a0437c1d933d48964e655f6508c7ab5f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 19 Aug 2020 13:04:51 +0200 Subject: [PATCH 319/826] BedShape is extracted to the separate structure --- src/slic3r/GUI/BedShapeDialog.cpp | 102 +++++++++++++++++++++--- src/slic3r/GUI/BedShapeDialog.hpp | 96 ++++++++++++++++++++++ src/slic3r/GUI/UnsavedChangesDialog.cpp | 4 +- 3 files changed, 190 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index f08b1a3794..98bd9a267f 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -59,6 +59,81 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) const std::string BedShapePanel::NONE = "None"; const std::string BedShapePanel::EMPTY_STRING = ""; +static std::string get_option_label(BedShape::Parameter param) +{ + switch (param) { + case BedShape::Parameter::RectSize : return L("Size"); + case BedShape::Parameter::RectOrigin: return L("Origin"); + case BedShape::Parameter::Diameter : return L("Diameter"); + default: return ""; + } +} + +void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter param) +{ + ConfigOptionDef def; + + if (param == Parameter::RectSize) { + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); + def.min = 0; + def.max = 1200; + def.label = get_option_label(param); + def.tooltip = L("Size in X and Y of the rectangular plate."); + + Option option(def, "rect_size"); + optgroup->append_single_option_line(option); + } + else if (param == Parameter::RectOrigin) { + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); + def.min = -600; + def.max = 600; + def.label = get_option_label(param); + def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); + + Option option(def, "rect_origin"); + optgroup->append_single_option_line(option); + } + else if (param == Parameter::Diameter) { + def.type = coFloat; + def.set_default_value(new ConfigOptionFloat(200)); + def.sidetext = L("mm"); + def.label = get_option_label(param); + def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); + + Option option(def, "diameter"); + optgroup->append_single_option_line(option); + } +} + +wxString BedShape::get_name(Type type) +{ + switch (type) { + case Type::Rectangular : return _L("Rectangular"); + case Type::Circular : return _L("Circular"); + case Type::Custom : return _L("Custom"); + case Type::Invalid : + default : return _L("Invalid"); + } +} + +wxString BedShape::get_full_name_with_params() +{ + wxString out = _L("Shape") + ": " + get_name(type); + + if (type == Type::Rectangular) { + out += "\n" + get_option_label(Parameter::RectSize) + +": [" + ConfigOptionPoint(rectSize).serialize() + "]"; + out += "\n" + get_option_label(Parameter::RectOrigin) + +": [" + ConfigOptionPoint(rectOrigin).serialize() + "]"; + } + else if (type == Type::Circular) + out += "\n" + get_option_label(Parameter::Diameter) + +": [" + double_to_string(diameter) + "]"; + else if (type == Type::Custom) + out += "\n" + double_to_string(diameter); + + return out; +} + void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) { m_shape = default_pt.values; @@ -72,7 +147,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); sbsizer->Add(m_shape_options_book); - auto optgroup = init_shape_options_page(_(L("Rectangular"))); +/* auto optgroup = init_shape_options_page(_(L("Rectangular"))); ConfigOptionDef def; def.type = coPoints; def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); @@ -100,22 +175,31 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); option = Option(def, "diameter"); optgroup->append_single_option_line(option); +*/ + + auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); + BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); + BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); + + optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); + BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); + +// optgroup = init_shape_options_page(_(L("Custom"))); + optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); - optgroup = init_shape_options_page(_(L("Custom"))); Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - wxButton* shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); + wxButton* shape_btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL...")); wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); shape_sizer->Add(shape_btn, 1, wxEXPAND); wxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(shape_sizer, 1, wxEXPAND); - shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + shape_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { load_stl(); - })); + }); return sizer; }; @@ -494,8 +578,8 @@ void BedShapePanel::load_stl() if (dialog.ShowModal() != wxID_OK) return; - std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(file_name, ".stl")) + m_custom_shape = dialog.GetPath().ToUTF8().data(); + if (!boost::algorithm::iends_with(m_custom_shape, ".stl")) { show_error(this, _(L("Invalid file format."))); return; @@ -505,7 +589,7 @@ void BedShapePanel::load_stl() Model model; try { - model = Model::read_from_file(file_name); + model = Model::read_from_file(m_custom_shape); } catch (std::exception &) { show_error(this, _(L("Error! Invalid model"))); diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index b583eca4a1..9064e7ddca 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -16,6 +16,101 @@ namespace GUI { class ConfigOptionsGroup; using ConfigOptionsGroupShp = std::shared_ptr; + +struct BedShape { + + enum class Type { + Rectangular = 0, + Circular, + Custom, + Invalid + }; + + enum class Parameter { + RectSize, + RectOrigin, + Diameter + }; + + BedShape(const ConfigOptionPoints& points) { + auto polygon = Polygon::new_scale(points.values); + + // is this a rectangle ? + if (points.size() == 4) { + auto lines = polygon.lines(); + if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { + // okay, it's a rectangle + // find origin + coordf_t x_min, x_max, y_min, y_max; + x_max = x_min = points.values[0](0); + y_max = y_min = points.values[0](1); + for (auto pt : points.values) + { + x_min = std::min(x_min, pt(0)); + x_max = std::max(x_max, pt(0)); + y_min = std::min(y_min, pt(1)); + y_max = std::max(y_max, pt(1)); + } + + type = Type::Rectangular; + rectSize = Vec2d(x_max - x_min, y_max - y_min); + rectOrigin = Vec2d(-x_min, -y_min); + + return; + } + } + + // is this a circle ? + { + // Analyze the array of points.Do they reside on a circle ? + auto center = polygon.bounding_box().center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt : polygon.points) + { + double distance = (pt - center).cast().norm(); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + bool defined_value = true; + for (auto el : vertex_distances) + { + if (abs(el - avg_dist) > 10 * SCALED_EPSILON) + defined_value = false; + break; + } + if (defined_value) { + // all vertices are equidistant to center + type = Type::Circular; + diameter = unscale(avg_dist * 2); + + return; + } + } + + if (points.size() < 3) { + type = Type::Invalid; + return; + } + + // This is a custom bed shape, use the polygon provided. + type = Type::Custom; + } + + static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param); + static wxString get_name(Type type); + + wxString get_full_name_with_params(); + + Type type = Type::Invalid; + Vec2d rectSize; + Vec2d rectOrigin; + + double diameter; +}; + class BedShapePanel : public wxPanel { static const std::string NONE; @@ -24,6 +119,7 @@ class BedShapePanel : public wxPanel Bed_2D* m_canvas; std::vector m_shape; std::vector m_loaded_shape; + std::string m_custom_shape; std::string m_custom_texture; std::string m_custom_model; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index bebc01c782..079bd9922a 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -732,16 +732,14 @@ static std::string get_pure_opt_key(std::string opt_key) static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config) { - int opt_idx = get_id_from_opt_key(opt_key); opt_key = get_pure_opt_key(opt_key); if (config.option(opt_key)->is_nil()) return _L("N/A"); + int opt_idx = get_id_from_opt_key(opt_key); wxString out; - // FIXME controll, if opt_key has index - const ConfigOptionDef* opt = config.def()->get(opt_key); bool is_nullable = opt->nullable; From db77f806819bcb1c395fff338a15b25d67d862a5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 19 Aug 2020 13:14:47 +0200 Subject: [PATCH 320/826] Follow-up of eca4f0a4cd31279fa27f84490dfb15f72c5d10e3. Fixed preview background on all platforms --- src/slic3r/GUI/GUI_Preview.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index d62b4dd506..57b1158f65 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -238,7 +238,14 @@ bool Preview::init(wxWindow* parent, Model* model) if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; +#if ENABLE_GCODE_VIEWER + // to match the background of the sliders +#ifdef _WIN32 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#else + SetBackgroundColour(GetParent()->GetBackgroundColour()); +#endif // _WIN32 +#endif // ENABLE_GCODE_VIEWER m_canvas_widget = OpenGLManager::create_wxglcanvas(*this); if (m_canvas_widget == nullptr) From 992d7065b2c456f8aa964969c9e003de7450e60a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 19 Aug 2020 15:19:07 +0200 Subject: [PATCH 321/826] GCodeViewer -> Modified shape of printbed for the unknown size case --- src/slic3r/GUI/3DBed.cpp | 109 +++++++++++---------------------- src/slic3r/GUI/GCodeViewer.cpp | 19 ++++-- 2 files changed, 52 insertions(+), 76 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 11fa745f49..aa9bf38035 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -45,10 +45,8 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool float max_y = min_y; unsigned int v_count = 0; - for (const Polygon& t : triangles) - { - for (unsigned int i = 0; i < 3; ++i) - { + for (const Polygon& t : triangles) { + for (unsigned int i = 0; i < 3; ++i) { Vertex& v = m_vertices[v_count]; const Point& p = t.points[i]; @@ -59,8 +57,7 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool v.position[1] = y; v.position[2] = z; - if (generate_tex_coords) - { + if (generate_tex_coords) { v.tex_coords[0] = x; v.tex_coords[1] = y; @@ -74,17 +71,14 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool } } - if (generate_tex_coords) - { + if (generate_tex_coords) { float size_x = max_x - min_x; float size_y = max_y - min_y; - if ((size_x != 0.0f) && (size_y != 0.0f)) - { + if ((size_x != 0.0f) && (size_y != 0.0f)) { float inv_size_x = 1.0f / size_x; float inv_size_y = -1.0f / size_y; - for (Vertex& v : m_vertices) - { + for (Vertex& v : m_vertices) { v.tex_coords[0] = (v.tex_coords[0] - min_x) * inv_size_x; v.tex_coords[1] = (v.tex_coords[1] - min_y) * inv_size_y; } @@ -105,8 +99,7 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z) m_vertices = std::vector(v_size, Vertex()); unsigned int v_count = 0; - for (const Line& l : lines) - { + for (const Line& l : lines) { Vertex& v1 = m_vertices[v_count]; v1.position[0] = unscale(l.a(0)); v1.position[1] = unscale(l.a(1)); @@ -360,8 +353,7 @@ void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, void Bed3D::calc_bounding_boxes() const { m_bounding_box = BoundingBoxf3(); - for (const Vec2d& p : m_shape) - { + for (const Vec2d& p : m_shape) { m_bounding_box.merge(Vec3d(p(0), p(1), 0.0)); } @@ -374,8 +366,7 @@ void Bed3D::calc_bounding_boxes() const // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); - if (model_bb.defined) - { + if (model_bb.defined) { model_bb.translate(m_model_offset); m_extended_bounding_box.merge(model_bb); } @@ -390,7 +381,7 @@ void Bed3D::calc_bounding_boxes() const void Bed3D::calc_triangles(const ExPolygon& poly) { Polygons triangles; - poly.triangulate(&triangles); + poly.triangulate_p2t(&triangles); if (!m_triangles.set_from_triangles(triangles, GROUND_Z, true)) printf("Unable to create bed triangles\n"); @@ -399,15 +390,13 @@ void Bed3D::calc_triangles(const ExPolygon& poly) void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) { Polylines axes_lines; - for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0)) - { + for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0)) { Polyline line; line.append(Point(x, bed_bbox.min(1))); line.append(Point(x, bed_bbox.max(1))); axes_lines.push_back(line); } - for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0)) - { + for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0)) { Polyline line; line.append(Point(bed_bbox.min(0), y)); line.append(Point(bed_bbox.max(0), y)); @@ -482,26 +471,21 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) co void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const { - if (m_texture_filename.empty()) - { + if (m_texture_filename.empty()) { m_texture.reset(); render_default(bottom); return; } - if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename)) - { + if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename)) { m_texture.reset(); - if (boost::algorithm::iends_with(m_texture_filename, ".svg")) - { + if (boost::algorithm::iends_with(m_texture_filename, ".svg")) { // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(); - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) - { + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) - { + if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { render_default(bottom); return; } @@ -509,19 +493,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) - { + if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { render_default(bottom); return; } - } - else if (boost::algorithm::iends_with(m_texture_filename, ".png")) - { + } + else if (boost::algorithm::iends_with(m_texture_filename, ".png")) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) - { - if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) - { + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) { + if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) { render_default(bottom); return; } @@ -529,20 +509,17 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) - { + if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { render_default(bottom); return; } } - else - { + else { render_default(bottom); return; } } - else if (m_texture.unsent_compressed_data_available()) - { + else if (m_texture.unsent_compressed_data_available()) { // sends to gpu the already available compressed levels of the main texture m_texture.send_compressed_data_to_gpu(); @@ -554,17 +531,14 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const } - if (m_triangles.get_vertices_count() > 0) - { + if (m_triangles.get_vertices_count() > 0) { GLShaderProgram* shader = wxGetApp().get_shader("printbed"); - if (shader != nullptr) - { + if (shader != nullptr) { shader->start_using(); shader->set_uniform("transparent_background", bottom); shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); - if (m_vbo_id == 0) - { + if (m_vbo_id == 0) { glsafe(::glGenBuffers(1, &m_vbo_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); @@ -593,13 +567,11 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - if (position_id != -1) - { + if (position_id != -1) { glsafe(::glEnableVertexAttribArray(position_id)); glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); } - if (tex_coords_id != -1) - { + if (tex_coords_id != -1) { glsafe(::glEnableVertexAttribArray(tex_coords_id)); glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); } @@ -631,8 +603,7 @@ void Bed3D::render_model() const if (m_model_filename.empty()) return; - if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename)) - { + if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename)) { // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad Vec3d shift = m_bounding_box.center(); shift(2) = -0.03; @@ -646,11 +617,9 @@ void Bed3D::render_model() const calc_bounding_boxes(); } - if (!m_model.get_filename().empty()) - { + if (!m_model.get_filename().empty()) { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader != nullptr) - { + if (shader != nullptr) { shader->start_using(); #if ENABLE_GCODE_VIEWER shader->set_uniform("uniform_color", m_model_color); @@ -668,8 +637,7 @@ void Bed3D::render_model() const void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) const { - if (m_texture_filename.empty() && m_model_filename.empty()) - { + if (m_texture_filename.empty() && m_model_filename.empty()) { render_default(bottom); return; } @@ -686,8 +654,7 @@ void Bed3D::render_default(bool bottom) const m_texture.reset(); unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { + if (triangles_vcount > 0) { bool has_model = !m_model.get_filename().empty(); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -696,8 +663,7 @@ void Bed3D::render_default(bool bottom) const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - if (!has_model && !bottom) - { + if (!has_model && !bottom) { // draw background glsafe(::glDepthMask(GL_FALSE)); #if ENABLE_GCODE_VIEWER @@ -728,8 +694,7 @@ void Bed3D::render_default(bool bottom) const void Bed3D::reset() { - if (m_vbo_id > 0) - { + if (m_vbo_id > 0) { glsafe(::glDeleteBuffers(1, &m_vbo_id)); m_vbo_id = 0; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 22d8615ca0..db126552f6 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -343,10 +343,21 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& const double margin = 10.0; Vec2d min(m_paths_bounding_box.min(0) - margin, m_paths_bounding_box.min(1) - margin); Vec2d max(m_paths_bounding_box.max(0) + margin, m_paths_bounding_box.max(1) + margin); - bed_shape = { { min(0), min(1) }, - { max(0), min(1) }, - { max(0), max(1) }, - { min(0), max(1) } }; + + Vec2d size = max - min; + bed_shape = { + { min(0), min(1) }, + { max(0), min(1) }, + { max(0), min(1) + 0.442265 * size[1]}, + { max(0) - 10.0, min(1) + 0.4711325 * size[1]}, + { max(0) + 10.0, min(1) + 0.5288675 * size[1]}, + { max(0), min(1) + 0.557735 * size[1]}, + { max(0), max(1) }, + { min(0) + 0.557735 * size[0], max(1)}, + { min(0) + 0.5288675 * size[0], max(1) - 10.0}, + { min(0) + 0.4711325 * size[0], max(1) + 10.0}, + { min(0) + 0.442265 * size[0], max(1)}, + { min(0), max(1) } }; } wxGetApp().plater()->set_bed_shape(bed_shape, "", "", true); } From 8af49d7d877d5bdf1dac3c0c034c90eb6e3086fa Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 19 Aug 2020 15:35:58 +0200 Subject: [PATCH 322/826] Code refactoring for last commit --- src/slic3r/GUI/BedShapeDialog.cpp | 319 +++++++++++------------- src/slic3r/GUI/BedShapeDialog.hpp | 86 +------ src/slic3r/GUI/UnsavedChangesDialog.cpp | 17 +- 3 files changed, 167 insertions(+), 255 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 98bd9a267f..2fc7d10366 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -21,44 +21,72 @@ namespace Slic3r { namespace GUI { -void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) +BedShape::BedShape(const ConfigOptionPoints& points) { - SetFont(wxGetApp().normal_font()); - m_panel = new BedShapePanel(this); - m_panel->build_panel(default_pt, custom_texture, custom_model); + auto polygon = Polygon::new_scale(points.values); - auto main_sizer = new wxBoxSizer(wxVERTICAL); - main_sizer->Add(m_panel, 1, wxEXPAND); - main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + // is this a rectangle ? + if (points.size() == 4) { + auto lines = polygon.lines(); + if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { + // okay, it's a rectangle + // find origin + coordf_t x_min, x_max, y_min, y_max; + x_max = x_min = points.values[0](0); + y_max = y_min = points.values[0](1); + for (auto pt : points.values) + { + x_min = std::min(x_min, pt(0)); + x_max = std::max(x_max, pt(0)); + y_min = std::min(y_min, pt(1)); + y_max = std::max(y_max, pt(1)); + } - SetSizer(main_sizer); - SetMinSize(GetSize()); - main_sizer->SetSizeHints(this); + m_type = Type::Rectangular; + m_rectSize = Vec2d(x_max - x_min, y_max - y_min); + m_rectOrigin = Vec2d(-x_min, -y_min); - this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) { - EndModal(wxID_CANCEL); - })); + return; + } + } + + // is this a circle ? + { + // Analyze the array of points.Do they reside on a circle ? + auto center = polygon.bounding_box().center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt : polygon.points) + { + double distance = (pt - center).cast().norm(); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + bool defined_value = true; + for (auto el : vertex_distances) + { + if (abs(el - avg_dist) > 10 * SCALED_EPSILON) + defined_value = false; + break; + } + if (defined_value) { + // all vertices are equidistant to center + m_type = Type::Circular; + m_diameter = unscale(avg_dist * 2); + + return; + } + } + + if (points.size() < 3) + return; + + // This is a custom bed shape, use the polygon provided. + m_type = Type::Custom; } -void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) -{ - const int& em = em_unit(); - m_panel->m_shape_options_book->SetMinSize(wxSize(25 * em, -1)); - - for (auto og : m_panel->m_optgroups) - og->msw_rescale(); - - const wxSize& size = wxSize(50 * em, -1); - - SetMinSize(size); - SetSize(size); - - Refresh(); -} - -const std::string BedShapePanel::NONE = "None"; -const std::string BedShapePanel::EMPTY_STRING = ""; - static std::string get_option_label(BedShape::Parameter param) { switch (param) { @@ -118,22 +146,73 @@ wxString BedShape::get_name(Type type) } } +size_t BedShape::get_type() +{ + return static_cast(m_type == Type::Invalid ? Type::Rectangular : m_type); +} + wxString BedShape::get_full_name_with_params() { - wxString out = _L("Shape") + ": " + get_name(type); + wxString out = _L("Shape") + ": " + get_name(m_type); - if (type == Type::Rectangular) { - out += "\n" + get_option_label(Parameter::RectSize) + +": [" + ConfigOptionPoint(rectSize).serialize() + "]"; - out += "\n" + get_option_label(Parameter::RectOrigin) + +": [" + ConfigOptionPoint(rectOrigin).serialize() + "]"; + if (m_type == Type::Rectangular) { + out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(m_rectSize).serialize() + "]"; + out += "\n" + _(get_option_label(Parameter::RectOrigin))+ ": [" + ConfigOptionPoint(m_rectOrigin).serialize() + "]"; } - else if (type == Type::Circular) - out += "\n" + get_option_label(Parameter::Diameter) + +": [" + double_to_string(diameter) + "]"; - else if (type == Type::Custom) - out += "\n" + double_to_string(diameter); + else if (m_type == Type::Circular) + out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(m_diameter) + "]"; return out; } +void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup) +{ + if (m_type == Type::Rectangular || m_type == Type::Invalid) { + optgroup->set_value("rect_size" , new ConfigOptionPoints{ m_rectSize }); + optgroup->set_value("rect_origin" , new ConfigOptionPoints{ m_rectOrigin }); + } + else if (m_type == Type::Circular) + optgroup->set_value("diameter", double_to_string(m_diameter)); +} + +void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) +{ + SetFont(wxGetApp().normal_font()); + m_panel = new BedShapePanel(this); + m_panel->build_panel(default_pt, custom_texture, custom_model); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_panel, 1, wxEXPAND); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + + SetSizer(main_sizer); + SetMinSize(GetSize()); + main_sizer->SetSizeHints(this); + + this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) { + EndModal(wxID_CANCEL); + })); +} + +void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + const int& em = em_unit(); + m_panel->m_shape_options_book->SetMinSize(wxSize(25 * em, -1)); + + for (auto og : m_panel->m_optgroups) + og->msw_rescale(); + + const wxSize& size = wxSize(50 * em, -1); + + SetMinSize(size); + SetSize(size); + + Refresh(); +} + +const std::string BedShapePanel::NONE = "None"; +const std::string BedShapePanel::EMPTY_STRING = ""; + void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) { m_shape = default_pt.values; @@ -147,36 +226,6 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); sbsizer->Add(m_shape_options_book); -/* auto optgroup = init_shape_options_page(_(L("Rectangular"))); - ConfigOptionDef def; - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); - def.min = 0; - def.max = 1200; - def.label = L("Size"); - def.tooltip = L("Size in X and Y of the rectangular plate."); - Option option(def, "rect_size"); - optgroup->append_single_option_line(option); - - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); - def.min = -600; - def.max = 600; - def.label = L("Origin"); - def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); - option = Option(def, "rect_origin"); - optgroup->append_single_option_line(option); - - optgroup = init_shape_options_page(_(L("Circular"))); - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat(200)); - def.sidetext = L("mm"); - def.label = L("Diameter"); - def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); - option = Option(def, "diameter"); - optgroup->append_single_option_line(option); -*/ - auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); @@ -184,7 +233,6 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); -// optgroup = init_shape_options_page(_(L("Custom"))); optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); Line line{ "", "" }; @@ -233,10 +281,6 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf update_preview(); } -#define SHAPE_RECTANGULAR 0 -#define SHAPE_CIRCULAR 1 -#define SHAPE_CUSTOM 2 - // Called from the constructor. // Create a panel for a rectangular / circular / custom bed shape. ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) @@ -421,83 +465,18 @@ wxPanel* BedShapePanel::init_model_panel() // with the list of points in the ini file directly. void BedShapePanel::set_shape(const ConfigOptionPoints& points) { - auto polygon = Polygon::new_scale(points.values); + BedShape shape(points); - // is this a rectangle ? - if (points.size() == 4) { - auto lines = polygon.lines(); - if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { - // okay, it's a rectangle - // find origin - coordf_t x_min, x_max, y_min, y_max; - x_max = x_min = points.values[0](0); - y_max = y_min = points.values[0](1); - for (auto pt : points.values) - { - x_min = std::min(x_min, pt(0)); - x_max = std::max(x_max, pt(0)); - y_min = std::min(y_min, pt(1)); - y_max = std::max(y_max, pt(1)); - } + m_shape_options_book->SetSelection(shape.get_type()); + shape.apply_optgroup_values(m_optgroups[shape.get_type()]); - auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) }; + // Copy the polygon to the canvas, make a copy of the array, if custom shape is selected + if (shape.is_custom()) + m_loaded_shape = points.values; - m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); - auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; - optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]); - optgroup->set_value("rect_origin", origin); - update_shape(); - return; - } - } - - // is this a circle ? - { - // Analyze the array of points.Do they reside on a circle ? - auto center = polygon.bounding_box().center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = (pt - center).cast().norm(); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - bool defined_value = true; - for (auto el: vertex_distances) - { - if (abs(el - avg_dist) > 10 * SCALED_EPSILON) - defined_value = false; - break; - } - if (defined_value) { - // all vertices are equidistant to center - m_shape_options_book->SetSelection(SHAPE_CIRCULAR); - auto optgroup = m_optgroups[SHAPE_CIRCULAR]; - boost::any ret = wxNumberFormatter::ToString(unscale(avg_dist * 2), 0); - optgroup->set_value("diameter", ret); - update_shape(); - return; - } - } - - if (points.size() < 3) { - // Invalid polygon.Revert to default bed dimensions. - m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); - auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; - optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) }); - optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) }); - update_shape(); - return; - } - - // This is a custom bed shape, use the polygon provided. - m_shape_options_book->SetSelection(SHAPE_CUSTOM); - // Copy the polygon to the canvas, make a copy of the array. - m_loaded_shape = points.values; update_shape(); + + return; } void BedShapePanel::update_preview() @@ -510,21 +489,20 @@ void BedShapePanel::update_preview() void BedShapePanel::update_shape() { auto page_idx = m_shape_options_book->GetSelection(); - if (page_idx == SHAPE_RECTANGULAR) { + auto opt_group = m_optgroups[page_idx]; + + BedShape::Type page_type = static_cast(page_idx); + + if (page_type == BedShape::Type::Rectangular) { Vec2d rect_size(Vec2d::Zero()); Vec2d rect_origin(Vec2d::Zero()); - try{ - rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); } - catch (const std::exception & /* e */) { - return; - } - try { - rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin")); - } - catch (const std::exception & /* e */) { - return; - } - + + try { rect_size = boost::any_cast(opt_group->get_value("rect_size")); } + catch (const std::exception& /* e */) { return; } + + try { rect_origin = boost::any_cast(opt_group->get_value("rect_origin")); } + catch (const std::exception & /* e */) { return; } + auto x = rect_size(0); auto y = rect_size(1); // empty strings or '-' or other things @@ -546,14 +524,11 @@ void BedShapePanel::update_shape() Vec2d(x1, y1), Vec2d(x0, y1) }; } - else if(page_idx == SHAPE_CIRCULAR) { + else if (page_type == BedShape::Type::Circular) { double diameter; - try{ - diameter = boost::any_cast(m_optgroups[SHAPE_CIRCULAR]->get_value("diameter")); - } - catch (const std::exception & /* e */) { - return; - } + try { diameter = boost::any_cast(opt_group->get_value("diameter")); } + catch (const std::exception & /* e */) { return; } + if (diameter == 0.0) return ; auto r = diameter / 2; auto twopi = 2 * PI; @@ -565,7 +540,7 @@ void BedShapePanel::update_shape() } m_shape = points; } - else if (page_idx == SHAPE_CUSTOM) + else if (page_type == BedShape::Type::Custom) m_shape = m_loaded_shape; update_preview(); @@ -578,8 +553,8 @@ void BedShapePanel::load_stl() if (dialog.ShowModal() != wxID_OK) return; - m_custom_shape = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(m_custom_shape, ".stl")) + std::string file_name = dialog.GetPath().ToUTF8().data(); + if (!boost::algorithm::iends_with(file_name, ".stl")) { show_error(this, _(L("Invalid file format."))); return; @@ -589,7 +564,7 @@ void BedShapePanel::load_stl() Model model; try { - model = Model::read_from_file(m_custom_shape); + model = Model::read_from_file(file_name); } catch (std::exception &) { show_error(this, _(L("Error! Invalid model"))); diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 9064e7ddca..2cfbc73aec 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -17,8 +17,8 @@ class ConfigOptionsGroup; using ConfigOptionsGroupShp = std::shared_ptr; -struct BedShape { - +struct BedShape +{ enum class Type { Rectangular = 0, Circular, @@ -32,83 +32,24 @@ struct BedShape { Diameter }; - BedShape(const ConfigOptionPoints& points) { - auto polygon = Polygon::new_scale(points.values); + BedShape(const ConfigOptionPoints& points); - // is this a rectangle ? - if (points.size() == 4) { - auto lines = polygon.lines(); - if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { - // okay, it's a rectangle - // find origin - coordf_t x_min, x_max, y_min, y_max; - x_max = x_min = points.values[0](0); - y_max = y_min = points.values[0](1); - for (auto pt : points.values) - { - x_min = std::min(x_min, pt(0)); - x_max = std::max(x_max, pt(0)); - y_min = std::min(y_min, pt(1)); - y_max = std::max(y_max, pt(1)); - } - - type = Type::Rectangular; - rectSize = Vec2d(x_max - x_min, y_max - y_min); - rectOrigin = Vec2d(-x_min, -y_min); - - return; - } - } - - // is this a circle ? - { - // Analyze the array of points.Do they reside on a circle ? - auto center = polygon.bounding_box().center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt : polygon.points) - { - double distance = (pt - center).cast().norm(); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - bool defined_value = true; - for (auto el : vertex_distances) - { - if (abs(el - avg_dist) > 10 * SCALED_EPSILON) - defined_value = false; - break; - } - if (defined_value) { - // all vertices are equidistant to center - type = Type::Circular; - diameter = unscale(avg_dist * 2); - - return; - } - } - - if (points.size() < 3) { - type = Type::Invalid; - return; - } - - // This is a custom bed shape, use the polygon provided. - type = Type::Custom; - } + bool is_custom() { return m_type == Type::Custom; } static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param); static wxString get_name(Type type); + // convert Type to size_t + size_t get_type(); + wxString get_full_name_with_params(); + void apply_optgroup_values(ConfigOptionsGroupShp optgroup); - Type type = Type::Invalid; - Vec2d rectSize; - Vec2d rectOrigin; - - double diameter; +private: + Type m_type {Type::Invalid}; + Vec2d m_rectSize {200, 200}; + Vec2d m_rectOrigin {0, 0}; + double m_diameter {0}; }; class BedShapePanel : public wxPanel @@ -119,7 +60,6 @@ class BedShapePanel : public wxPanel Bed_2D* m_canvas; std::vector m_shape; std::vector m_loaded_shape; - std::string m_custom_shape; std::string m_custom_texture; std::string m_custom_model; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 079bd9922a..c147d3e2c2 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -667,10 +667,10 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name text = _L("All changed options will be reverted."); else { if (action == Action::Save && preset_name.empty()) - text = _L("After press this button selected options will be saved"); + text = _L("Press to save the selected options"); else { std::string act_string = action == Action::Save ? _u8L("saved") : _u8L("moved"); - text = from_u8((boost::format("After press this button selected options will be %1% to the preset \"%2%\".") % act_string % preset_name).str()); + text = from_u8((boost::format("Press to %1% selected options to the preset \"%2%\".") % act_string % preset_name).str()); } text += "\n" + _L("Unselected options will be reverted."); } @@ -732,12 +732,12 @@ static std::string get_pure_opt_key(std::string opt_key) static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config) { + int opt_idx = get_id_from_opt_key(opt_key); opt_key = get_pure_opt_key(opt_key); if (config.option(opt_key)->is_nil()) return _L("N/A"); - int opt_idx = get_id_from_opt_key(opt_key); wxString out; const ConfigOptionDef* opt = config.def()->get(opt_key); @@ -820,15 +820,12 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& break; } case coPoints: { - /* if (opt_key == "bed_shape") { - config.option(opt_key)->values = boost::any_cast>(value); - break; + BedShape shape(*config.option(opt_key)); + return shape.get_full_name_with_params(); } - ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) }; - config.option(opt_key)->set_at(vec_new, opt_index, 0); - */ - return "Points"; + Vec2d val = config.opt(opt_key)->get_at(opt_idx); + return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str()); } default: break; From 739cd2a4a20f50dace8cd053b671f3451fb9160a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 19 Aug 2020 17:15:01 +0200 Subject: [PATCH 323/826] Fixed several indentation-related warnings --- src/libslic3r/PrintBase.hpp | 12 ++++++------ src/slic3r/GUI/Plater.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 5e94e011a7..647c24c1ce 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -507,9 +507,9 @@ protected: bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } PrintStateBase::TimeStamp set_done(PrintStepEnum step) { std::pair status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); - if (status.second) - this->status_update_warnings(this->id(), static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); - return status.first; + if (status.second) + this->status_update_warnings(this->id(), static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; } bool invalidate_step(PrintStepEnum step) { return m_state.invalidate(step, this->cancel_callback()); } @@ -556,9 +556,9 @@ protected: { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) { std::pair status = m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); - if (status.second) - this->status_update_warnings(m_print, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); - return status.first; + if (status.second) + this->status_update_warnings(m_print, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; } bool invalidate_step(PrintObjectStepEnum step) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2c330b60e6..027611750a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2841,7 +2841,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; - show_warning_dialog = true; + show_warning_dialog = true; if (! output_path.empty()) { background_process.schedule_export(output_path.string(), output_path_on_removable_media); } else { @@ -4697,8 +4697,8 @@ void Plater::export_gcode(bool prefer_removable) if (p->model.objects.empty()) return; - if (p->process_completed_with_error)//here - return; + if (p->process_completed_with_error)//here + return; // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. From f6d25d0634c0c40855aafce02bad4c3de734eea2 Mon Sep 17 00:00:00 2001 From: Paul Arden Date: Fri, 21 Aug 2020 14:07:50 +1000 Subject: [PATCH 324/826] Rework G10 temperature support to be enabled only for a new Firmware type `RepRapFirmware` leaving the `RepRap/Sprinter` behaviour alone. Rename the enum for `gcfRepRap` to `gcfRepRapSprinter` and add new `gcfRepRapFirmware` enum value. Also adds code to only use the G10 searching in custom G-code if the flavour is RepRapFirmware. --- src/libslic3r/GCode.cpp | 5 +++-- src/libslic3r/GCode/WipeTower.cpp | 4 ++-- src/libslic3r/GCodeTimeEstimator.cpp | 7 ++++--- src/libslic3r/GCodeWriter.cpp | 16 +++++++++++----- src/libslic3r/Print.cpp | 5 +++-- src/libslic3r/PrintConfig.cpp | 9 ++++++--- src/libslic3r/PrintConfig.hpp | 5 +++-- 7 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 975e15c6c7..f863fd3ee8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -500,7 +500,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) // Disable linear advance for the wipe tower operations. - //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + //gcode += (gcodegen.config().gcode_flavor == gcfRepRapSprinter ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); for (const WipeTower::ToolChangeResult& tcr : m_priming) { if (!tcr.extrusions.empty()) @@ -1720,7 +1720,8 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c { // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; - if (custom_gcode_sets_temperature(gcode, 104, 109, true, temp_by_gcode)) { + bool include_g10 = (print.config().gcode_flavor == gcfRepRapFirmware); + if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) { // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp_by_gcode >= 0 && temp_by_gcode < 1000) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index c0f778687c..b2534361f3 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -102,7 +102,7 @@ public: } WipeTowerWriter& disable_linear_advance() { - m_gcode += (m_gcode_flavor == gcfRepRap + m_gcode += (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware ? (std::string("M572 D") + std::to_string(m_current_tool) + " S0\n") : std::string("M900 K0\n")); return *this; @@ -351,7 +351,7 @@ public: // Set digital trimpot motor WipeTowerWriter& set_extruder_trimpot(int current) { - if (m_gcode_flavor == gcfRepRap) + if (m_gcode_flavor == gcfRepRapSprinter || m_gcode_flavor == gcfRepRapFirmware) m_gcode += "M906 E"; else m_gcode += "M907 E"; diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 9e8137ef0e..795fecbeb1 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -622,7 +622,7 @@ namespace Slic3r { void GCodeTimeEstimator::set_default() { set_units(Millimeters); - set_dialect(gcfRepRap); + set_dialect(gcfRepRapSprinter); set_global_positioning_type(Absolute); set_e_local_positioning_type(Absolute); @@ -1201,7 +1201,8 @@ namespace Slic3r { if ((dialect == gcfRepetier) || (dialect == gcfMarlin) || (dialect == gcfSmoothie) || - (dialect == gcfRepRap)) + (dialect == gcfRepRapSprinter) || + (dialect == gcfRepRapFirmware)) { if (line.has_value('S', value)) extra_time += value; @@ -1313,7 +1314,7 @@ namespace Slic3r { GCodeFlavor dialect = get_dialect(); // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration - float factor = ((dialect != gcfRepRap) && (get_units() == GCodeTimeEstimator::Inches)) ? INCHES_TO_MM : 1.0f; + float factor = ((dialect != gcfRepRapSprinter && dialect != gcfRepRapFirmware) && (get_units() == GCodeTimeEstimator::Inches)) ? INCHES_TO_MM : 1.0f; if (line.has_x()) set_axis_max_acceleration(X, line.x() * factor); diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 98a6ed4a49..569ae65e58 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -46,7 +46,13 @@ std::string GCodeWriter::preamble() gcode << "G21 ; set units to millimeters\n"; gcode << "G90 ; use absolute coordinates\n"; } - if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) { + if (FLAVOR_IS(gcfRepRapSprinter) || + FLAVOR_IS(gcfRepRapFirmware) || + FLAVOR_IS(gcfMarlin) || + FLAVOR_IS(gcfTeacup) || + FLAVOR_IS(gcfRepetier) || + FLAVOR_IS(gcfSmoothie)) + { if (this->config.use_relative_e_distances) { gcode << "M83 ; use relative distances for extrusion\n"; } else { @@ -72,11 +78,11 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in return ""; std::string code, comment; - if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRap)) { + if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) { code = "M109"; comment = "set temperature and wait for it to be reached"; } else { - if (FLAVOR_IS(gcfRepRap)) { // M104 is deprecated on RepRapFirmware + if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware code = "G10"; } else { code = "M104"; @@ -94,7 +100,7 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in gcode << temperature; bool multiple_tools = this->multiple_extruders && ! m_single_extruder_multi_material; if (tool != -1 && (multiple_tools || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) { - if (FLAVOR_IS(gcfRepRap)) { + if (FLAVOR_IS(gcfRepRapFirmware)) { gcode << " P" << tool; } else { gcode << " T" << tool; @@ -102,7 +108,7 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in } gcode << " ; " << comment << "\n"; - if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRap)) && wait) + if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRapFirmware)) && wait) gcode << "M116 ; wait for temperature to be reached\n"; return gcode.str(); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 0c8a11fcf0..878380955a 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1259,8 +1259,9 @@ std::string Print::validate() const "and use filaments of the same diameter."); } - if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) - return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); + if (m_config.gcode_flavor != gcfRepRapSprinter && m_config.gcode_flavor != gcfRepRapFirmware && + m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) + return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); if (m_config.ooze_prevention) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d6a23d75d3..0d7ddeecdf 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -953,6 +953,7 @@ void PrintConfigDef::init_fff_params() "The \"No extrusion\" flavor prevents PrusaSlicer from exporting any extrusion value at all."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("reprap"); + def->enum_values.push_back("reprapfirmware"); def->enum_values.push_back("repetier"); def->enum_values.push_back("teacup"); def->enum_values.push_back("makerware"); @@ -963,6 +964,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("smoothie"); def->enum_values.push_back("no-extrusion"); def->enum_labels.push_back("RepRap/Sprinter"); + def->enum_labels.push_back("RepRapFirmware"); def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("Teacup"); def->enum_labels.push_back("MakerWare (MakerBot)"); @@ -973,7 +975,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("Smoothie"); def->enum_labels.push_back(L("No extrusion")); def->mode = comExpert; - def->set_default_value(new ConfigOptionEnum(gcfRepRap)); + def->set_default_value(new ConfigOptionEnum(gcfRepRapSprinter)); def = this->add("gcode_label_objects", coBool); def->label = L("Label objects"); @@ -3290,11 +3292,12 @@ std::string FullPrintConfig::validate() if (this->use_firmware_retraction.value && this->gcode_flavor.value != gcfSmoothie && - this->gcode_flavor.value != gcfRepRap && + this->gcode_flavor.value != gcfRepRapSprinter && + this->gcode_flavor.value != gcfRepRapFirmware && this->gcode_flavor.value != gcfMarlin && this->gcode_flavor.value != gcfMachinekit && this->gcode_flavor.value != gcfRepetier) - return "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware"; + return "--use-firmware-retraction is only supported by Marlin, Smoothie, RepRapFirmware, Repetier and Machinekit firmware"; if (this->use_firmware_retraction.value) for (unsigned char wipe : this->wipe.values) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f28ef2a228..0ab51718bd 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -25,7 +25,7 @@ namespace Slic3r { enum GCodeFlavor : unsigned char { - gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, + gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, }; @@ -84,7 +84,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { - keys_map["reprap"] = gcfRepRap; + keys_map["reprap"] = gcfRepRapSprinter; + keys_map["reprapfirmware"] = gcfRepRapFirmware; keys_map["repetier"] = gcfRepetier; keys_map["teacup"] = gcfTeacup; keys_map["makerware"] = gcfMakerWare; From c29171790930a1a9f9b0374b6a5ab8ccec1e88a9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 18 Aug 2020 15:17:26 +0200 Subject: [PATCH 325/826] Forbid translation of objects when SLA/Hollow/FDM gizmos are active --- src/slic3r/GUI/GLCanvas3D.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9bed5fde7c..94f6f6ef32 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3658,6 +3658,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { m_mouse.dragging = true; + // Translation of objects is forbidden when SLA supports/hollowing/fdm + // supports gizmo is active. + if (m_gizmos.get_current_type() == GLGizmosManager::SlaSupports + || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports + || m_gizmos.get_current_type() == GLGizmosManager::Hollow) + return; + Vec3d cur_pos = m_mouse.drag.start_position_3D; // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag if (m_selection.contains_volume(get_first_hover_volume_idx())) From 320964a68c0004a59972ba9ec841b0572ef783e4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 18 Aug 2020 15:18:00 +0200 Subject: [PATCH 326/826] TriangleSelector paints continuously when dragging fast Previously there would be distinct circles with gaps in between --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 195 +++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 1 + 2 files changed, 110 insertions(+), 86 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 309c7cf423..e5a648d11e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -314,106 +314,128 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; const Transform3d& instance_trafo = mi->get_transformation().get_matrix(); - std::vector>> hit_positions_and_facet_ids; - bool clipped_mesh_was_hit = false; + // List of mouse positions that will be used as seeds for painting. + std::vector mouse_positions{mouse_position}; - Vec3f normal = Vec3f::Zero(); - Vec3f hit = Vec3f::Zero(); - size_t facet = 0; - Vec3f closest_hit = Vec3f::Zero(); - double closest_hit_squared_distance = std::numeric_limits::max(); - size_t closest_facet = 0; - int closest_hit_mesh_id = -1; + // In case current mouse position is far from the last one, + // add several positions from between into the list, so there + // are no gaps in the painted region. + { + if (m_last_mouse_position == Vec2d::Zero()) + m_last_mouse_position = mouse_position; + // resolution describes minimal distance limit using circle radius + // as a unit (e.g., 2 would mean the patches will be touching). + double resolution = 0.7; + double diameter_px = resolution * m_cursor_radius * camera.get_zoom(); + int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px); + if (patches_in_between > 0) { + Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1); + for (int i=1; i<=patches_in_between; ++i) + mouse_positions.emplace_back(m_last_mouse_position + i*diff); + } + } + m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved - // Transformations of individual meshes - std::vector trafo_matrices; + // Now "click" into all the prepared points and spill paint around them. + for (const Vec2d& mp : mouse_positions) { + std::vector>> hit_positions_and_facet_ids; + bool clipped_mesh_was_hit = false; - int mesh_id = -1; - // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + size_t closest_facet = 0; + int closest_hit_mesh_id = -1; - ++mesh_id; + // Transformations of individual meshes + std::vector trafo_matrices; - trafo_matrices.push_back(instance_trafo * mv->get_matrix()); - hit_positions_and_facet_ids.push_back(std::vector>()); - - if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( - mouse_position, - trafo_matrices[mesh_id], - camera, - hit, - normal, - m_clipping_plane.get(), - &facet)) - { - // In case this hit is clipped, skip it. - if (is_mesh_point_clipped(hit.cast())) { - clipped_mesh_was_hit = true; + int mesh_id = -1; + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) continue; - } - // Is this hit the closest to the camera so far? - double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); - if (hit_squared_distance < closest_hit_squared_distance) { - closest_hit_squared_distance = hit_squared_distance; - closest_facet = facet; - closest_hit_mesh_id = mesh_id; - closest_hit = hit; + ++mesh_id; + + trafo_matrices.push_back(instance_trafo * mv->get_matrix()); + hit_positions_and_facet_ids.push_back(std::vector>()); + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( + mp, + trafo_matrices[mesh_id], + camera, + hit, + normal, + m_clipping_plane.get(), + &facet)) + { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast())) { + clipped_mesh_was_hit = true; + continue; + } + + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_facet = facet; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + } } } - } - bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); + bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); - // The mouse button click detection is enabled when there is a valid hit - // or when the user clicks the clipping plane. Missing the object entirely - // shall not capture the mouse. - if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { - if (m_button_down == Button::None) - m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); - } - - if (closest_hit_mesh_id == -1) { - // In case we have no valid hit, we can return. The event will - // be stopped in following two cases: - // 1. clicking the clipping plane - // 2. dragging while painting (to prevent scene rotations and moving the object) - return clipped_mesh_was_hit - || dragging_while_painting; - } - - // Find respective mesh id. - // FIXME We need a separate TriangleSelector for each volume mesh. - mesh_id = -1; - //const TriangleMesh* mesh = nullptr; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++mesh_id; - if (mesh_id == closest_hit_mesh_id) { - //mesh = &mv->mesh(); - break; + // The mouse button click detection is enabled when there is a valid hit + // or when the user clicks the clipping plane. Missing the object entirely + // shall not capture the mouse. + if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { + if (m_button_down == Button::None) + m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); } + + if (closest_hit_mesh_id == -1) { + // In case we have no valid hit, we can return. The event will + // be stopped in following two cases: + // 1. clicking the clipping plane + // 2. dragging while painting (to prevent scene rotations and moving the object) + return clipped_mesh_was_hit + || dragging_while_painting; + } + + // Find respective mesh id. + mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++mesh_id; + if (mesh_id == closest_hit_mesh_id) + break; + } + + const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; + + // Calculate how far can a point be from the line (in mesh coords). + // FIXME: The scaling of the mesh can be non-uniform. + const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); + const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; + const float limit = m_cursor_radius/avg_scaling; + + // Calculate direction from camera to the hit (in mesh coords): + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + Vec3f dir = (closest_hit - camera_pos).normalized(); + + assert(mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, + dir, limit, new_state); + m_last_mouse_position = mouse_position; } - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; - - // Calculate how far can a point be from the line (in mesh coords). - // FIXME: The scaling of the mesh can be non-uniform. - const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); - const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = m_cursor_radius/avg_scaling; - - // Calculate direction from camera to the hit (in mesh coords): - Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); - Vec3f dir = (closest_hit - camera_pos).normalized(); - - assert(mesh_id < int(m_triangle_selectors.size())); - m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, - dir, limit, new_state); - return true; } @@ -430,6 +452,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous update_model_object(); m_button_down = Button::None; + m_last_mouse_position = Vec2d::Zero(); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index ce24ea8d28..350f7c8908 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -92,6 +92,7 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; + Vec2d m_last_mouse_position = Vec2d::Zero(); // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. From 7a093b08fd12537a347cbafeabe8307c3a295d55 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 21 Aug 2020 10:59:07 +0200 Subject: [PATCH 327/826] GCodeViewer -> Show printbed model and texture for system printers detected when loading gcode files produced by PrusaSlicer --- src/libslic3r/GCode/GCodeProcessor.cpp | 4 ++++ src/libslic3r/GCode/GCodeProcessor.hpp | 1 + src/libslic3r/Preset.cpp | 20 ++++++++++++++++++++ src/libslic3r/Preset.hpp | 4 ++++ src/slic3r/GUI/3DBed.cpp | 7 +++++++ src/slic3r/GUI/GCodeViewer.cpp | 17 +++++++++++++++-- 6 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 5124e1a99d..54addbd979 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -541,6 +541,10 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) if (bed_shape != nullptr) m_result.bed_shape = bed_shape->values; + const ConfigOptionString* printer_settings_id = config.option("printer_settings_id"); + if (printer_settings_id != nullptr) + m_result.printer_settings_id = printer_settings_id->value; + const ConfigOptionFloats* filament_diameters = config.option("filament_diameter"); if (filament_diameters != nullptr) { for (double diam : filament_diameters->values) { diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 90552e6582..22aeed7620 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -261,6 +261,7 @@ namespace Slic3r { unsigned int id; std::vector moves; Pointfs bed_shape; + std::string printer_settings_id; std::vector extruder_colors; PrintEstimatedTimeStatistics time_statistics; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7176ca81a4..a5160d2db3 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1812,6 +1812,26 @@ namespace PresetUtils { } return out; } + +#if ENABLE_GCODE_VIEWER + std::string system_printer_bed_model(const Preset& preset) + { + std::string out; + const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset); + if (pm != nullptr && !pm->bed_model.empty()) + out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model; + return out; + } + + std::string system_printer_bed_texture(const Preset& preset) + { + std::string out; + const VendorProfile::PrinterModel* pm = PresetUtils::system_printer_model(preset); + if (pm != nullptr && !pm->bed_texture.empty()) + out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture; + return out; + } +#endif // ENABLE_GCODE_VIEWER } // namespace PresetUtils } // namespace Slic3r diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index e34fca4dd7..30edfc859a 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -527,6 +527,10 @@ public: namespace PresetUtils { // PrinterModel of a system profile, from which this preset is derived, or null if it is not derived from a system profile. const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); +#if ENABLE_GCODE_VIEWER + std::string system_printer_bed_model(const Preset& preset); + std::string system_printer_bed_texture(const Preset& preset); +#endif // ENABLE_GCODE_VIEWER } // namespace PresetUtils diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index aa9bf38035..8a29d08bd8 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -414,6 +414,7 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) printf("Unable to create bed grid lines\n"); } +#if !ENABLE_GCODE_VIEWER static std::string system_print_bed_model(const Preset &preset) { std::string out; @@ -431,6 +432,7 @@ static std::string system_print_bed_texture(const Preset &preset) out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture; return out; } +#endif // !ENABLE_GCODE_VIEWER std::tuple Bed3D::detect_type(const Pointfs& shape) const { @@ -440,8 +442,13 @@ std::tuple Bed3D::detect_type(const Poin while (curr != nullptr) { if (curr->config.has("bed_shape")) { if (shape == dynamic_cast(curr->config.option("bed_shape"))->values) { +#if ENABLE_GCODE_VIEWER + std::string model_filename = PresetUtils::system_printer_bed_model(*curr); + std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr); +#else std::string model_filename = system_print_bed_model(*curr); std::string texture_filename = system_print_bed_texture(*curr); +#endif // ENABLE_GCODE_VIEWER if (!model_filename.empty() && !texture_filename.empty()) return { System, model_filename, texture_filename }; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index db126552f6..80a5e15ed5 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -335,9 +335,21 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& load_shells(print, initialized); else { Pointfs bed_shape; - if (!gcode_result.bed_shape.empty()) + std::string texture; + std::string model; + + if (!gcode_result.bed_shape.empty()) { // bed shape detected in the gcode bed_shape = gcode_result.bed_shape; + auto bundle = wxGetApp().preset_bundle; + if (bundle != nullptr && !gcode_result.printer_settings_id.empty()) { + const Preset* preset = bundle->printers.find_preset(gcode_result.printer_settings_id); + if (preset != nullptr) { + model = PresetUtils::system_printer_bed_model(*preset); + texture = PresetUtils::system_printer_bed_texture(*preset); + } + } + } else { // adjust printbed size in dependence of toolpaths bbox const double margin = 10.0; @@ -359,7 +371,8 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& { min(0) + 0.442265 * size[0], max(1)}, { min(0), max(1) } }; } - wxGetApp().plater()->set_bed_shape(bed_shape, "", "", true); + + wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty()); } #if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE From 99a15af03d75c976264364d2ec25cb99948dc3c9 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 21 Aug 2020 11:36:08 +0200 Subject: [PATCH 328/826] GCodeViewer -> Allow to switch to gcode viewer state when an sla printer is selected --- src/slic3r/GUI/MainFrame.cpp | 16 ++++++++++++++-- src/slic3r/GUI/MainFrame.hpp | 1 + src/slic3r/GUI/Plater.cpp | 15 +++++++++++++++ src/slic3r/GUI/Plater.hpp | 4 ++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 3f000e3328..ce8772eedc 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -434,6 +434,10 @@ void MainFrame::shutdown() // restore sidebar if it was hidden when switching to gcode viewer mode if (m_restore_from_gcode_viewer.collapsed_sidebar) m_plater->collapse_sidebar(false); + + // restore sla printer if it was deselected when switching to gcode viewer mode + if (m_restore_from_gcode_viewer.sla_technology) + m_plater->set_printer_technology(ptSLA); #endif // ENABLE_GCODE_VIEWER // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). @@ -1010,8 +1014,7 @@ void MainFrame::init_menubar() wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES) set_mode(EMode::GCodeViewer); - }, "", nullptr, - [this]() { return m_plater != nullptr && m_plater->printer_technology() != ptSLA; }, this); + }, "", nullptr); #endif // ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), @@ -1329,6 +1332,12 @@ void MainFrame::set_mode(EMode mode) m_plater->clear_undo_redo_stack_main(); m_plater->take_snapshot(_L("New Project")); + // restore sla printer if it was deselected when switching to gcode viewer mode + if (m_restore_from_gcode_viewer.sla_technology) { + m_plater->set_printer_technology(ptSLA); + m_restore_from_gcode_viewer.sla_technology = false; + } + // switch view m_plater->select_view_3D("3D"); m_plater->select_view("iso"); @@ -1371,6 +1380,9 @@ void MainFrame::set_mode(EMode mode) m_plater->clear_undo_redo_stack_main(); m_plater->take_snapshot(_L("New Project")); + // switch to FFF printer mode + m_restore_from_gcode_viewer.sla_technology = m_plater->set_printer_technology(ptFFF); + // switch view m_plater->select_view_3D("Preview"); m_plater->select_view("iso"); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 8961ad7172..53d8488768 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -76,6 +76,7 @@ class MainFrame : public DPIFrame { bool collapsed_sidebar{ false }; bool collapse_toolbar_enabled{ false }; + bool sla_technology{ false }; }; RestoreFromGCodeViewer m_restore_from_gcode_viewer; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 97372b2474..1fe32fd2dc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5596,12 +5596,23 @@ PrinterTechnology Plater::printer_technology() const const DynamicPrintConfig * Plater::config() const { return p->config; } +#if ENABLE_GCODE_VIEWER +bool Plater::set_printer_technology(PrinterTechnology printer_technology) +#else void Plater::set_printer_technology(PrinterTechnology printer_technology) +#endif // ENABLE_GCODE_VIEWER { p->printer_technology = printer_technology; +#if ENABLE_GCODE_VIEWER + bool ret = p->background_process.select_technology(printer_technology); + if (ret) { + // Update the active presets. + } +#else if (p->background_process.select_technology(printer_technology)) { // Update the active presets. } +#endif // ENABLE_GCODE_VIEWER //FIXME for SLA synchronize //p->background_process.apply(Model)! @@ -5618,6 +5629,10 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) p->update_main_toolbar_tooltips(); p->sidebar->get_searcher().set_printer_technology(printer_technology); + +#if ENABLE_GCODE_VIEWER + return ret; +#endif // ENABLE_GCODE_VIEWER } void Plater::changed_object(int obj_idx) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9c1270bcec..cc80186202 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -263,7 +263,11 @@ public: PrinterTechnology printer_technology() const; const DynamicPrintConfig * config() const; +#if ENABLE_GCODE_VIEWER + bool set_printer_technology(PrinterTechnology printer_technology); +#else void set_printer_technology(PrinterTechnology printer_technology); +#endif // ENABLE_GCODE_VIEWER void copy_selection_to_clipboard(); void paste_from_clipboard(); From 5ff6f3045edf5e488312344c064c822afe868fcc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 21 Aug 2020 11:50:05 +0200 Subject: [PATCH 329/826] ENABLE_GCODE_VIEWER -> KBShortcutsDialog shows only related tabs when gcode viewer state is active --- src/slic3r/GUI/KBShortcutsDialog.cpp | 197 +++++++++++++-------------- src/slic3r/GUI/KBShortcutsDialog.hpp | 4 - 2 files changed, 98 insertions(+), 103 deletions(-) diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 2d4b65a987..1a551216e7 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -7,6 +7,9 @@ #include #include "GUI_App.hpp" #include "wxExtensions.hpp" +#if ENABLE_GCODE_VIEWER +#include "MainFrame.hpp" +#endif // ENABLE_GCODE_VIEWER #define NOTEBOOK_TOP 1 #define NOTEBOOK_LEFT 2 @@ -31,11 +34,7 @@ namespace GUI { KBShortcutsDialog::KBShortcutsDialog() : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Keyboard Shortcuts"), -#if ENABLE_SCROLLABLE wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -#else - wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) -#endif // ENABLE_SCROLLABLE { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -66,13 +65,9 @@ main_sizer->Add(book, 1, wxEXPAND | wxALL, 10); fill_shortcuts(); for (size_t i = 0; i < m_full_shortcuts.size(); ++i) { -#if ENABLE_SCROLLABLE wxPanel* page = create_page(book, m_full_shortcuts[i], font, bold_font); m_pages.push_back(page); book->AddPage(page, m_full_shortcuts[i].first, i == 0); -#else - book->AddPage(create_page(book, m_full_shortcuts[i], font, bold_font), m_full_shortcuts[i].first, i == 0); -#endif // ENABLE_SCROLLABLE } wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); @@ -99,104 +94,112 @@ void KBShortcutsDialog::fill_shortcuts() const std::string& ctrl = GUI::shortkey_ctrl_prefix(); const std::string& alt = GUI::shortkey_alt_prefix(); - Shortcuts commands_shortcuts = { - // File - { ctrl + "N", L("New project, clear plater") }, - { ctrl + "O", L("Open project STL/OBJ/AMF/3MF with config, clear plater") }, - { ctrl + "S", L("Save project (3mf)") }, - { ctrl + alt + "S", L("Save project as (3mf)") }, - { ctrl + "R", L("(Re)slice") }, - // File>Import - { ctrl + "I", L("Import STL/OBJ/AMF/3MF without config, keep plater") }, - { ctrl + "L", L("Import Config from ini/amf/3mf/gcode") }, - { ctrl + alt + "L", L("Load Config from ini/amf/3mf/gcode and merge") }, - // File>Export - { ctrl + "G", L("Export G-code") }, - { ctrl + "Shift+" + "G", L("Send G-code") }, - { ctrl + "E", L("Export config") }, - { ctrl + "U", L("Export to SD card / Flash drive") }, - { ctrl + "T", L("Eject SD card / Flash drive") }, - // Edit - { ctrl + "A", L("Select all objects") }, - { "Esc", L("Deselect all") }, - { "Del", L("Delete selected") }, - { ctrl + "Del", L("Delete all") }, - { ctrl + "Z", L("Undo") }, - { ctrl + "Y", L("Redo") }, - { ctrl + "C", L("Copy to clipboard") }, - { ctrl + "V", L("Paste from clipboard") }, - { "F5", L("Reload plater from disk") }, - { ctrl + "F", L("Search") }, - // Window - { ctrl + "1", L("Select Plater Tab") }, - { ctrl + "2", L("Select Print Settings Tab") }, - { ctrl + "3", L("Select Filament Settings Tab") }, - { ctrl + "4", L("Select Printer Settings Tab") }, - { ctrl + "5", L("Switch to 3D") }, - { ctrl + "6", L("Switch to Preview") }, - { ctrl + "J", L("Print host upload queue") }, - // View - { "0-6", L("Camera view") }, - { "E", L("Show/Hide object/instance labels") }, +#if ENABLE_GCODE_VIEWER + bool is_gcode_viewer = wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer; + + if (!is_gcode_viewer) { +#endif // ENABLE_GCODE_VIEWER + Shortcuts commands_shortcuts = { + // File + { ctrl + "N", L("New project, clear plater") }, + { ctrl + "O", L("Open project STL/OBJ/AMF/3MF with config, clear plater") }, + { ctrl + "S", L("Save project (3mf)") }, + { ctrl + alt + "S", L("Save project as (3mf)") }, + { ctrl + "R", L("(Re)slice") }, + // File>Import + { ctrl + "I", L("Import STL/OBJ/AMF/3MF without config, keep plater") }, + { ctrl + "L", L("Import Config from ini/amf/3mf/gcode") }, + { ctrl + alt + "L", L("Load Config from ini/amf/3mf/gcode and merge") }, + // File>Export + { ctrl + "G", L("Export G-code") }, + { ctrl + "Shift+" + "G", L("Send G-code") }, + { ctrl + "E", L("Export config") }, + { ctrl + "U", L("Export to SD card / Flash drive") }, + { ctrl + "T", L("Eject SD card / Flash drive") }, + // Edit + { ctrl + "A", L("Select all objects") }, + { "Esc", L("Deselect all") }, + { "Del", L("Delete selected") }, + { ctrl + "Del", L("Delete all") }, + { ctrl + "Z", L("Undo") }, + { ctrl + "Y", L("Redo") }, + { ctrl + "C", L("Copy to clipboard") }, + { ctrl + "V", L("Paste from clipboard") }, + { "F5", L("Reload plater from disk") }, + { ctrl + "F", L("Search") }, + // Window + { ctrl + "1", L("Select Plater Tab") }, + { ctrl + "2", L("Select Print Settings Tab") }, + { ctrl + "3", L("Select Filament Settings Tab") }, + { ctrl + "4", L("Select Printer Settings Tab") }, + { ctrl + "5", L("Switch to 3D") }, + { ctrl + "6", L("Switch to Preview") }, + { ctrl + "J", L("Print host upload queue") }, + // View + { "0-6", L("Camera view") }, + { "E", L("Show/Hide object/instance labels") }, #if ENABLE_SLOPE_RENDERING - { "D", L("Turn On/Off facets' slope rendering") }, + { "D", L("Turn On/Off facets' slope rendering") }, #endif // ENABLE_SLOPE_RENDERING - // Configuration - { ctrl + "P", L("Preferences") }, - // Help - { "?", L("Show keyboard shortcuts list") } - }; + // Configuration + { ctrl + "P", L("Preferences") }, + // Help + { "?", L("Show keyboard shortcuts list") } + }; - m_full_shortcuts.push_back(std::make_pair(_L("Commands"), commands_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Commands"), commands_shortcuts)); - Shortcuts plater_shortcuts = { - { "A", L("Arrange") }, - { "Shift+A", L("Arrange selection") }, - { "+", L("Add Instance of the selected object") }, - { "-", L("Remove Instance of the selected object") }, - { ctrl, L("Press to select multiple objects\nor move multiple objects with mouse") }, - { "Shift+", L("Press to activate selection rectangle") }, - { alt, L("Press to activate deselection rectangle") }, - { L("Arrow Up"), L("Move selection 10 mm in positive Y direction") }, - { L("Arrow Down"), L("Move selection 10 mm in negative Y direction") }, - { L("Arrow Left"), L("Move selection 10 mm in negative X direction") }, - { L("Arrow Right"), L("Move selection 10 mm in positive X direction") }, - { std::string("Shift+") + L("Any arrow"), L("Movement step set to 1 mm") }, - { ctrl + L("Any arrow"), L("Movement in camera space") }, - { L("Page Up"), L("Rotate selection 45 degrees CCW") }, - { L("Page Down"), L("Rotate selection 45 degrees CW") }, - { "M", L("Gizmo move") }, - { "S", L("Gizmo scale") }, - { "R", L("Gizmo rotate") }, - { "C", L("Gizmo cut") }, - { "F", L("Gizmo Place face on bed") }, - { "H", L("Gizmo SLA hollow") }, - { "L", L("Gizmo SLA support points") }, - { "Esc", L("Unselect gizmo or clear selection") }, - { "K", L("Change camera type (perspective, orthographic)") }, - { "B", L("Zoom to Bed") }, - { "Z", L("Zoom to selected object\nor all objects in scene, if none selected") }, - { "I", L("Zoom in") }, - { "O", L("Zoom out") }, + Shortcuts plater_shortcuts = { + { "A", L("Arrange") }, + { "Shift+A", L("Arrange selection") }, + { "+", L("Add Instance of the selected object") }, + { "-", L("Remove Instance of the selected object") }, + { ctrl, L("Press to select multiple objects\nor move multiple objects with mouse") }, + { "Shift+", L("Press to activate selection rectangle") }, + { alt, L("Press to activate deselection rectangle") }, + { L("Arrow Up"), L("Move selection 10 mm in positive Y direction") }, + { L("Arrow Down"), L("Move selection 10 mm in negative Y direction") }, + { L("Arrow Left"), L("Move selection 10 mm in negative X direction") }, + { L("Arrow Right"), L("Move selection 10 mm in positive X direction") }, + { std::string("Shift+") + L("Any arrow"), L("Movement step set to 1 mm") }, + { ctrl + L("Any arrow"), L("Movement in camera space") }, + { L("Page Up"), L("Rotate selection 45 degrees CCW") }, + { L("Page Down"), L("Rotate selection 45 degrees CW") }, + { "M", L("Gizmo move") }, + { "S", L("Gizmo scale") }, + { "R", L("Gizmo rotate") }, + { "C", L("Gizmo cut") }, + { "F", L("Gizmo Place face on bed") }, + { "H", L("Gizmo SLA hollow") }, + { "L", L("Gizmo SLA support points") }, + { "Esc", L("Unselect gizmo or clear selection") }, + { "K", L("Change camera type (perspective, orthographic)") }, + { "B", L("Zoom to Bed") }, + { "Z", L("Zoom to selected object\nor all objects in scene, if none selected") }, + { "I", L("Zoom in") }, + { "O", L("Zoom out") }, #ifdef __linux__ - { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") }, + { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") }, #endif // __linux__ #if ENABLE_RENDER_PICKING_PASS - // Don't localize debugging texts. - { "P", "Toggle picking pass texture rendering on/off" }, + // Don't localize debugging texts. + { "P", "Toggle picking pass texture rendering on/off" }, #endif // ENABLE_RENDER_PICKING_PASS - }; + }; - m_full_shortcuts.push_back(std::make_pair(_L("Plater"), plater_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Plater"), plater_shortcuts)); - Shortcuts gizmos_shortcuts = { - { "Shift+", L("Press to snap by 5% in Gizmo scale\nor to snap by 1mm in Gizmo move") }, - { "F", L("Scale selection to fit print volume\nin Gizmo scale") }, - { ctrl, L("Press to activate one direction scaling in Gizmo scale") }, - { alt, L("Press to scale (in Gizmo scale) or rotate (in Gizmo rotate)\nselected objects around their own center") }, - }; + Shortcuts gizmos_shortcuts = { + { "Shift+", L("Press to snap by 5% in Gizmo scale\nor to snap by 1mm in Gizmo move") }, + { "F", L("Scale selection to fit print volume\nin Gizmo scale") }, + { ctrl, L("Press to activate one direction scaling in Gizmo scale") }, + { alt, L("Press to scale (in Gizmo scale) or rotate (in Gizmo rotate)\nselected objects around their own center") }, + }; - m_full_shortcuts.push_back(std::make_pair(_L("Gizmos"), gizmos_shortcuts)); + m_full_shortcuts.push_back(std::make_pair(_L("Gizmos"), gizmos_shortcuts)); +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER Shortcuts preview_shortcuts = { { L("Arrow Up"), L("Upper Layer") }, @@ -277,13 +280,9 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const std::pairSetScrollbars(20, 20, 50, 50); page->SetInitialSize(wxSize(850, 450)); -#else - wxPanel* page = new wxPanel(parent); -#endif // ENABLE_SCROLLABLE #if (BOOK_TYPE == LISTBOOK_TOP) || (BOOK_TYPE == LISTBOOK_LEFT) wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, page, " " + shortcuts.first + " "); diff --git a/src/slic3r/GUI/KBShortcutsDialog.hpp b/src/slic3r/GUI/KBShortcutsDialog.hpp index 70820ae774..a8ec4e4267 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.hpp +++ b/src/slic3r/GUI/KBShortcutsDialog.hpp @@ -8,8 +8,6 @@ #include "GUI_Utils.hpp" #include "wxExtensions.hpp" -#define ENABLE_SCROLLABLE 1 - namespace Slic3r { namespace GUI { @@ -22,9 +20,7 @@ class KBShortcutsDialog : public DPIDialog ShortcutsVec m_full_shortcuts; ScalableBitmap m_logo_bmp; wxStaticBitmap* m_header_bitmap; -#if ENABLE_SCROLLABLE std::vector m_pages; -#endif // ENABLE_SCROLLABLE public: KBShortcutsDialog(); From a95509ce36a125086b64ad56f2debc7cd3e82837 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 19 Aug 2020 15:24:55 +0200 Subject: [PATCH 330/826] Changed internal coordinates of drain holes Drain holes reference position was saved slightly above the mesh to avoid problem when the hole is placed on flat or nearly flat surface The depth of the hole was internally bigger than what the user has set to compensato for it However, this leads to problem with scaling and makes reprojection of the holes on the mesh complicated This commit changes the reference point to the point on the mesh and the extra elevation is handled when rendering and drilling the hole. The change is reflected in 3MF drain holes versioning so that old 3MFs are loaded correctly. Reprojection on the mesh after reload from disk/fix through netfabb has been enabled. --- src/libslic3r/Format/3mf.cpp | 12 ++++++++++- src/libslic3r/Format/3mf.hpp | 11 ++++++++-- src/libslic3r/SLA/Hollowing.hpp | 2 ++ src/libslic3r/SLA/ReprojectPointsOnMesh.hpp | 14 ++++--------- src/libslic3r/SLAPrint.cpp | 6 ++++++ src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 21 +++++++------------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 8 ++++---- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 2 -- 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 59dc85a0ae..c25b7b96a5 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1103,7 +1103,7 @@ namespace Slic3r { sla::DrainHoles sla_drain_holes; - if (version == 1) { + if (version == 1 || version == 2) { for (unsigned int i=0; i; +constexpr float HoleStickOutLength = 1.f; + std::unique_ptr generate_interior(const TriangleMesh &mesh, const HollowingConfig & = {}, const JobController &ctl = {}); diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp index 4737a6c212..20804193e2 100644 --- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp +++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp @@ -28,15 +28,9 @@ void reproject_support_points(const IndexedMesh &mesh, std::vector &p inline void reproject_points_and_holes(ModelObject *object) { bool has_sppoints = !object->sla_support_points.empty(); + bool has_holes = !object->sla_drain_holes.empty(); - // Disabling reprojection of holes as they have a significant offset away - // from the model body which tolerates minor geometrical changes. - // - // TODO: uncomment and ensure the right offset of the hole points if - // reprojection would still be necessary. - // bool has_holes = !object->sla_drain_holes.empty(); - - if (!object || (/*!has_holes &&*/ !has_sppoints)) return; + if (!object || (!has_holes && !has_sppoints)) return; TriangleMesh rmsh = object->raw_mesh(); rmsh.require_shared_vertices(); @@ -45,8 +39,8 @@ inline void reproject_points_and_holes(ModelObject *object) if (has_sppoints) reproject_support_points(emesh, object->sla_support_points); -// if (has_holes) -// reproject_support_points(emesh, object->sla_drain_holes); + if (has_holes) + reproject_support_points(emesh, object->sla_drain_holes); } }} diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 4395bea461..07ec380160 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1181,6 +1181,12 @@ sla::DrainHoles SLAPrintObject::transformed_drainhole_points() const hl.normal = Vec3f(hl.normal(0)/(sc(0)*sc(0)), hl.normal(1)/(sc(1)*sc(1)), hl.normal(2)/(sc(2)*sc(2))); + + // Now shift the hole a bit above the object and make it deeper to + // compensate for it. This is to avoid problems when the hole is placed + // on (nearly) flat surface. + hl.pos -= hl.normal.normalized() * sla::HoleStickOutLength; + hl.height += sla::HoleStickOutLength; } return pts; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 273384da2e..04ada52536 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -130,7 +130,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons const sla::DrainHole& drain_hole = drain_holes[i]; const bool& point_selected = m_selected[i]; - if (is_mesh_point_clipped((drain_hole.pos+HoleStickOutLength*drain_hole.normal).cast())) + if (is_mesh_point_clipped(drain_hole.pos.cast())) continue; // First decide about the color of the point. @@ -174,10 +174,10 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); glsafe(::glPushMatrix()); glsafe(::glTranslated(0., 0., -drain_hole.height)); - ::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1); - glsafe(::glTranslated(0., 0., drain_hole.height)); + ::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength, 24, 1); + glsafe(::glTranslated(0., 0., drain_hole.height + sla::HoleStickOutLength)); ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); - glsafe(::glTranslated(0., 0., -drain_hole.height)); + glsafe(::glTranslated(0., 0., -drain_hole.height - sla::HoleStickOutLength)); glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f)); ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); glsafe(::glPopMatrix()); @@ -307,13 +307,8 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); - Vec3d scaling = mo->instances[active_inst]->get_scaling_factor(); - Vec3f normal_transformed(pos_and_normal.second(0)/scaling(0), - pos_and_normal.second(1)/scaling(1), - pos_and_normal.second(2)/scaling(2)); - - mo->sla_drain_holes.emplace_back(pos_and_normal.first + HoleStickOutLength * pos_and_normal.second/* normal_transformed.normalized()*/, - -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); + mo->sla_drain_holes.emplace_back(pos_and_normal.first, + -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); m_selected.push_back(false); assert(m_selected.size() == mo->sla_drain_holes.size()); m_parent.set_as_dirty(); @@ -447,7 +442,7 @@ void GLGizmoHollow::on_update(const UpdateData& data) std::pair pos_and_normal; if (! unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) return; - drain_holes[m_hover_id].pos = pos_and_normal.first + HoleStickOutLength * pos_and_normal.second; + drain_holes[m_hover_id].pos = pos_and_normal.first; drain_holes[m_hover_id].normal = -pos_and_normal.second; } } @@ -661,9 +656,7 @@ RENDER_AGAIN: m_imgui->text(m_desc["hole_depth"]); ImGui::SameLine(diameter_slider_left); - m_new_hole_height -= HoleStickOutLength; ImGui::SliderFloat(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm"); - m_new_hole_height += HoleStickOutLength; clicked |= ImGui::IsItemClicked(); edited |= ImGui::IsItemEdited(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 2856bb35de..bc29da6d2b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -222,7 +222,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) render_color[3] = 0.7f; glsafe(::glColor4fv(render_color)); for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { - if (is_mesh_point_clipped((drain_hole.pos+HoleStickOutLength*drain_hole.normal).cast())) + if (is_mesh_point_clipped(drain_hole.pos.cast())) continue; // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. @@ -241,10 +241,10 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); glsafe(::glPushMatrix()); glsafe(::glTranslated(0., 0., -drain_hole.height)); - ::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1); - glsafe(::glTranslated(0., 0., drain_hole.height)); + ::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength, 24, 1); + glsafe(::glTranslated(0., 0., drain_hole.height + sla::HoleStickOutLength)); ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); - glsafe(::glTranslated(0., 0., -drain_hole.height)); + glsafe(::glTranslated(0., 0., -drain_hole.height - sla::HoleStickOutLength)); glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f)); ::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1); glsafe(::glPopMatrix()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 31c473bac7..aedf782e89 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -15,8 +15,6 @@ namespace GUI { class GLCanvas3D; -static constexpr float HoleStickOutLength = 1.f; - enum class SLAGizmoEventType : unsigned char { LeftDown = 1, LeftUp, From c51a45ee0f8c08cbee0d874d81da3ba5d42d9cea Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 24 Aug 2020 08:04:16 +0200 Subject: [PATCH 331/826] Drainholes are saved elevated for 3MF compatibility This is a follow-up of previous commit --- src/libslic3r/Format/3mf.cpp | 30 ++++++++++++++++++++---------- src/libslic3r/Format/3mf.hpp | 9 +-------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index c25b7b96a5..52a3335ee4 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1103,7 +1103,7 @@ namespace Slic3r { sla::DrainHoles sla_drain_holes; - if (version == 1 || version == 2) { + if (version == 1) { for (unsigned int i=0; isla_drain_holes; + sla::DrainHoles drain_holes = object->sla_drain_holes; + + // The holes were placed 1mm above the mesh in the first implementation. + // This was a bad idea and the reference point was changed in 2.3 so + // to be on the mesh exactly. The elevated position is still saved + // in 3MFs for compatibility reasons. + for (sla::DrainHole& hole : drain_holes) { + hole.pos -= hole.normal.normalized(); + hole.height += 1.f; + } + + if (!drain_holes.empty()) { out += string_printf(fmt, count); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index 02e2bd1d3c..ccfd9356d8 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -20,15 +20,8 @@ namespace Slic3r { support_points_format_version = 1 }; - - /* The same for holes. - - * version 0: undefined - * version 1: holes saved a bit above the mesh and deeper - * version 2: holes are saved on the mesh exactly - */ enum { - drain_holes_format_version = 2 + drain_holes_format_version = 1 }; class Model; From ac8a6fccbe6e649bb55452d1e5859a2029257678 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 25 Aug 2020 08:12:28 +0200 Subject: [PATCH 332/826] Renamed shaders --- .../{options_120_flat.fs => options_120.fs} | 0 .../{options_120_flat.vs => options_120.vs} | 0 .../{toolpaths.fs => toolpaths_lines.fs} | 0 .../{toolpaths.vs => toolpaths_lines.vs} | 0 src/slic3r/GUI/GCodeViewer.cpp | 25 +++++++++---------- src/slic3r/GUI/GCodeViewer.hpp | 2 +- src/slic3r/GUI/GLShadersManager.cpp | 4 +-- 7 files changed, 15 insertions(+), 16 deletions(-) rename resources/shaders/{options_120_flat.fs => options_120.fs} (100%) rename resources/shaders/{options_120_flat.vs => options_120.vs} (100%) rename resources/shaders/{toolpaths.fs => toolpaths_lines.fs} (100%) rename resources/shaders/{toolpaths.vs => toolpaths_lines.vs} (100%) diff --git a/resources/shaders/options_120_flat.fs b/resources/shaders/options_120.fs similarity index 100% rename from resources/shaders/options_120_flat.fs rename to resources/shaders/options_120.fs diff --git a/resources/shaders/options_120_flat.vs b/resources/shaders/options_120.vs similarity index 100% rename from resources/shaders/options_120_flat.vs rename to resources/shaders/options_120.vs diff --git a/resources/shaders/toolpaths.fs b/resources/shaders/toolpaths_lines.fs similarity index 100% rename from resources/shaders/toolpaths.fs rename to resources/shaders/toolpaths_lines.fs diff --git a/resources/shaders/toolpaths.vs b/resources/shaders/toolpaths_lines.vs similarity index 100% rename from resources/shaders/toolpaths.vs rename to resources/shaders/toolpaths_lines.vs diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 80a5e15ed5..bcb4a0a268 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -606,8 +606,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID); unsigned int colors_count = 1; - for (const Color& color : colors) - { + for (const Color& color : colors) { fprintf(fp, "\nnewmtl material_%d\n", colors_count++); fprintf(fp, "Ka 1 1 1\n"); fprintf(fp, "Kd %f %f %f\n", color[0], color[1], color[2]); @@ -858,14 +857,14 @@ void GCodeViewer::init_shaders() for (unsigned char i = begin_id; i < end_id; ++i) { switch (buffer_type(i)) { - case EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120_flat" : "options_110"; break; } - case EMoveType::Extrude: { m_buffers[i].shader = "toolpaths"; break; } - case EMoveType::Travel: { m_buffers[i].shader = "toolpaths"; break; } + case EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } + case EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } + case EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } + case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } + case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } + case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } + case EMoveType::Extrude: { m_buffers[i].shader = "toolpaths_lines"; break; } + case EMoveType::Travel: { m_buffers[i].shader = "toolpaths_lines"; break; } default: { break; } } } @@ -1463,8 +1462,8 @@ void GCodeViewer::render_legend() const else draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); #else - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120_flat") { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); float radius = 0.5f * icon_size; @@ -2527,7 +2526,7 @@ void GCodeViewer::render_shaders_editor() const switch (m_shaders_editor.points.shader_version) { case 0: { set_shader("options_110"); break; } - case 1: { set_shader("options_120_flat"); break; } + case 1: { set_shader("options_120"); break; } } if (ImGui::TreeNode("Options")) { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 0be17f790a..e6c3e26eaa 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -65,7 +65,7 @@ class GCodeViewer void reset(); }; - // ibo buffer containing indices data (triangles) used to render a specific toolpath type + // ibo buffer containing indices data (lines/triangles) used to render a specific toolpath type struct IBuffer { // ibo id diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 5f726d4ef1..6baf46f6b4 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -36,9 +36,9 @@ std::pair GLShadersManager::init() // used to render options in gcode preview valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) - valid &= append_shader("options_120_flat", { "options_120_flat.vs", "options_120_flat.fs" }); + valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" }); // used to render extrusion and travel paths in gcode preview - valid &= append_shader("toolpaths", { "toolpaths.vs", "toolpaths.fs" }); + valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }); // used to render variable layers heights in 3d editor From 2783653369fd080b2b585d02525a905eee524432 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 26 Aug 2020 13:01:54 +0200 Subject: [PATCH 333/826] Added icon for gcode viewer mode --- .../icons/PrusaSlicerGCodeViewer_128px.png | Bin 0 -> 15949 bytes src/slic3r/GUI/MainFrame.cpp | 48 +++++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 resources/icons/PrusaSlicerGCodeViewer_128px.png diff --git a/resources/icons/PrusaSlicerGCodeViewer_128px.png b/resources/icons/PrusaSlicerGCodeViewer_128px.png new file mode 100644 index 0000000000000000000000000000000000000000..0bf85abbd6ea0585d127686a8200b4a647a030b8 GIT binary patch literal 15949 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Ʊ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 AMF0Q* literal 0 HcmV?d00001 diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ce8772eedc..7e12be4fcd 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -84,16 +84,19 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #endif // Font is already set in DPIFrame constructor */ - // Load the icon either from the exe, or from the ico file. -#if _WIN32 - { - TCHAR szExeFileName[MAX_PATH]; - GetModuleFileName(nullptr, szExeFileName, MAX_PATH); - SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); - } -#else + SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -#endif // _WIN32 +// // Load the icon either from the exe, or from the ico file. +//#if _WIN32 +// { +// +// TCHAR szExeFileName[MAX_PATH]; +// GetModuleFileName(nullptr, szExeFileName, MAX_PATH); +// SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); +// } +//#else +// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +//#endif // _WIN32 // initialize status bar m_statusbar = std::make_shared(this); @@ -1364,6 +1367,8 @@ void MainFrame::set_mode(EMode mode) m_plater->Thaw(); + SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); + break; } case EMode::GCodeViewer: @@ -1409,6 +1414,8 @@ void MainFrame::set_mode(EMode mode) m_plater->Thaw(); + SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG)); + break; } } @@ -1912,16 +1919,19 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) this->SetFont(wxGetApp().normal_font()); this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - // Load the icon either from the exe, or from the ico file. -#if _WIN32 - { - TCHAR szExeFileName[MAX_PATH]; - GetModuleFileName(nullptr, szExeFileName, MAX_PATH); - SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); - } -#else - SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -#endif // _WIN32 + + SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +// // Load the icon either from the exe, or from the ico file. +//#if _WIN32 +// { +// +// TCHAR szExeFileName[MAX_PATH]; +// GetModuleFileName(nullptr, szExeFileName, MAX_PATH); +// SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); +// } +//#else +// SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +//#endif // _WIN32 this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) { From 56431d26e5b01d9f206ad52a7eff4de443ec8038 Mon Sep 17 00:00:00 2001 From: Slic3rPE Date: Wed, 26 Aug 2020 14:56:26 +0200 Subject: [PATCH 334/826] Starting a new Slicer instance from the menu --- src/slic3r/GUI/MainFrame.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index bbc1da534e..122d9c6108 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -8,9 +8,12 @@ #include #include //#include +#include +#include #include #include +#include #include "libslic3r/Print.hpp" #include "libslic3r/Polygon.hpp" @@ -979,6 +982,18 @@ void MainFrame::init_menubar() append_menu_item(windowMenu, wxID_ANY, _(L("Print &Host Upload Queue")) + "\tCtrl+J", _(L("Display the Print Host Upload Queue window")), [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, [this]() {return true; }, this); + + windowMenu->AppendSeparator(); + append_menu_item(windowMenu, wxID_ANY, _(L("Open new instance")) + "\tCtrl+I", _(L("Open a new PrusaSlicer instance")), + [this](wxCommandEvent&) { + wxString path = wxStandardPaths::Get().GetExecutablePath(); +#ifdef __APPLE__ + boost::process::spawn((const char*)path.c_str()); +#else + wxExecute(wxStandardPaths::Get().GetExecutablePath(), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); +#endif + }, "upload_queue", nullptr, + [this]() {return true; }, this); } // View menu From ba9c3a74ed6f46725fd092a43ae3895c38a19bc7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 26 Aug 2020 15:29:33 +0200 Subject: [PATCH 335/826] GCodeViewer -> 1st iteration of rendering of extrude toolpaths as solid --- resources/shaders/options_110.fs | 4 +- resources/shaders/options_120.fs | 10 +- resources/shaders/toolpaths_lines.fs | 4 +- src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 330 ++++++++++++++++++++------- src/slic3r/GUI/GCodeViewer.hpp | 64 +++++- src/slic3r/GUI/GLShadersManager.cpp | 2 +- 7 files changed, 303 insertions(+), 112 deletions(-) diff --git a/resources/shaders/options_110.fs b/resources/shaders/options_110.fs index 474e355e09..ab656998df 100644 --- a/resources/shaders/options_110.fs +++ b/resources/shaders/options_110.fs @@ -1,8 +1,8 @@ #version 110 -uniform vec3 uniform_color; +uniform vec4 uniform_color; void main() { - gl_FragColor = vec4(uniform_color, 1.0); + gl_FragColor = uniform_color; } diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs index 5bcc718761..d897a8ca73 100644 --- a/resources/shaders/options_120.fs +++ b/resources/shaders/options_120.fs @@ -1,19 +1,15 @@ // version 120 is needed for gl_PointCoord #version 120 -uniform vec3 uniform_color; +uniform vec4 uniform_color; uniform float percent_outline_radius; uniform float percent_center_radius; -vec4 hardcoded_color(float radius, vec3 color) -{ - return ((radius < 0.15) || (radius > 0.85)) ? vec4(0.5 * color, 1.0) : vec4(color, 1.0); -} -vec4 customizable_color(float radius, vec3 color) +vec4 customizable_color(float radius, vec4 color) { return ((radius < percent_center_radius) || (radius > 1.0 - percent_outline_radius)) ? - vec4(0.5 * color, 1.0) : vec4(color, 1.0); + vec4(0.5 * color.rgb, color.a) : color; } void main() diff --git a/resources/shaders/toolpaths_lines.fs b/resources/shaders/toolpaths_lines.fs index 13f60c0a82..7202a2e3e4 100644 --- a/resources/shaders/toolpaths_lines.fs +++ b/resources/shaders/toolpaths_lines.fs @@ -6,7 +6,7 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); // x = ambient, y = top diffuse, z = front diffuse, w = global uniform vec4 light_intensity; -uniform vec3 uniform_color; +uniform vec4 uniform_color; varying vec3 eye_position; varying vec3 eye_normal; @@ -27,5 +27,5 @@ void main() NdotL = abs(dot(normal, LIGHT_FRONT_DIR)); intensity += NdotL * light_intensity.z; - gl_FragColor = vec4(uniform_color * light_intensity.w * intensity, 1.0); + gl_FragColor = vec4(uniform_color.rgb * light_intensity.w * intensity, uniform_color.a); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e10cabc6cf..00c899201a 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,7 @@ #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES (1 && ENABLE_GCODE_VIEWER) #define TIME_ESTIMATE_NONE 0 #define TIME_ESTIMATE_DEFAULT 1 diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index bcb4a0a268..70b571fe00 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -286,6 +286,7 @@ bool GCodeViewer::init() { for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& buffer = m_buffers[i]; switch (buffer_type(i)) { default: { break; } @@ -296,13 +297,25 @@ bool GCodeViewer::init() case EMoveType::Retract: case EMoveType::Unretract: { - m_buffers[i].vertices.format = VBuffer::EFormat::Position; + buffer.primitive_type = TBuffer::EPrimitiveType::Point; + buffer.vertices.format = VBuffer::EFormat::Position; break; } case EMoveType::Extrude: + { +#if ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES + buffer.primitive_type = TBuffer::EPrimitiveType::Triangle; + buffer.vertices.format = VBuffer::EFormat::PositionNormal3; +#else + buffer.primitive_type = TBuffer::EPrimitiveType::Line; + buffer.vertices.format = VBuffer::EFormat::PositionNormal1; +#endif // ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES + break; + } case EMoveType::Travel: { - m_buffers[i].vertices.format = VBuffer::EFormat::PositionNormal; + buffer.primitive_type = TBuffer::EPrimitiveType::Line; + buffer.vertices.format = VBuffer::EFormat::PositionNormal1; break; } } @@ -863,7 +876,11 @@ void GCodeViewer::init_shaders() case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } +#if ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES + case EMoveType::Extrude: { m_buffers[i].shader = "gouraud_light"; break; } +#else case EMoveType::Extrude: { m_buffers[i].shader = "toolpaths_lines"; break; } +#endif // ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES case EMoveType::Travel: { m_buffers[i].shader = "toolpaths_lines"; break; } default: { break; } } @@ -901,6 +918,127 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); + // format data into the buffers to be rendered as points + auto add_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(curr.position[j]); + } + buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id)); + buffer_indices.push_back(static_cast(buffer_indices.size())); + }; + + // format data into the buffers to be rendered as lines + auto add_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + // add starting vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(prev.position[j]); + } + // add starting vertex normal x component + buffer_vertices.push_back(normal_x); + // add starting index + buffer_indices.push_back(static_cast(buffer_indices.size())); + buffer.add_path(curr, static_cast(buffer_indices.size() - 1), static_cast(move_id - 1)); + buffer.paths.back().first.position = prev.position; + } + + Path& last_path = buffer.paths.back(); + if (last_path.first.i_id != last_path.last.i_id) { + // add previous vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(prev.position[j]); + } + // add previous vertex normal x component + buffer_vertices.push_back(normal_x); + // add previous index + buffer_indices.push_back(static_cast(buffer_indices.size())); + } + + // add current vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(curr.position[j]); + } + // add current vertex normal x component + buffer_vertices.push_back(normal_x); + // add current index + buffer_indices.push_back(static_cast(buffer_indices.size())); + last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + }; + + // format data into the buffers to be rendered as solid + auto add_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { + auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { + // append position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(position[j]); + } + // append normal + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(normal[j]); + } + }; + auto store_triangle = [](std::vector& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { + buffer_indices.push_back(i1); + buffer_indices.push_back(i2); + buffer_indices.push_back(i3); + }; + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f up = right.cross(dir); + float prev_half_width = 0.5f * prev.width; + float prev_half_height = 0.5f * prev.height; + float curr_half_width = 0.5f * curr.width; + float curr_half_height = 0.5f * curr.height; + Vec3f prev_pos = Vec3f(prev.position[0], prev.position[1], prev.position[2]) - prev_half_height * up; + Vec3f curr_pos = Vec3f(curr.position[0], curr.position[1], curr.position[2]) - curr_half_height * up; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id - 1)); + buffer.paths.back().first.position = prev.position; + } + + unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); + + // vertices 1st endpoint + store_vertex(buffer_vertices, prev_pos + prev_half_height * up, up); // top + store_vertex(buffer_vertices, prev_pos + prev_half_width * right, right); // right + store_vertex(buffer_vertices, prev_pos - prev_half_height * up, -up); // bottom + store_vertex(buffer_vertices, prev_pos - prev_half_width * right, -right); // left + + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + curr_half_height * up, up); // top + store_vertex(buffer_vertices, curr_pos + curr_half_width * right, right); // right + store_vertex(buffer_vertices, curr_pos - curr_half_height * up, -up); // bottom + store_vertex(buffer_vertices, curr_pos - curr_half_width * right, -right); // left + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); + + buffer.paths.back().last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + }; + // toolpaths data -> extract from result std::vector> vertices(m_buffers.size()); std::vector> indices(m_buffers.size()); @@ -926,55 +1064,21 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case EMoveType::Retract: case EMoveType::Unretract: { - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(i)); - buffer_indices.push_back(static_cast(buffer_indices.size())); + add_as_point(curr, buffer, buffer_vertices, buffer_indices, i); break; } +#if ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES case EMoveType::Extrude: + { + add_as_solid(prev, curr, buffer, buffer_vertices, buffer_indices, i); + break; + } +#else + case EMoveType::Extrude: +#endif // ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES case EMoveType::Travel: { - // x component of the normal to the current segment (the normal is parallel to the XY plane) - float normal_x = (curr.position - prev.position).normalized()[1]; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); - } - // add starting vertex normal x component - buffer_vertices.push_back(normal_x); - // add starting index - buffer_indices.push_back(buffer_indices.size()); - buffer.add_path(curr, static_cast(buffer_indices.size() - 1), static_cast(i - 1)); - Path& last_path = buffer.paths.back(); - last_path.first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - if (last_path.first.i_id != last_path.last.i_id) - { - // add previous vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); - } - // add previous vertex normal x component - buffer_vertices.push_back(normal_x); - // add previous index - buffer_indices.push_back(buffer_indices.size()); - } - - // add current vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } - // add current vertex normal x component - buffer_vertices.push_back(normal_x); - // add current index - buffer_indices.push_back(buffer_indices.size()); - last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(i), curr.position }; + add_as_line(prev, curr, buffer, buffer_vertices, buffer_indices, i); break; } default: { break; } @@ -989,7 +1093,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) const std::vector& buffer_vertices = vertices[i]; buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.vertices_gpu_size = buffer_vertices.size() * sizeof(float); + m_statistics.vertices_gpu_size += buffer_vertices.size() * sizeof(float); #endif // ENABLE_GCODE_VIEWER_STATISTICS glsafe(::glGenBuffers(1, &buffer.vertices.id)); @@ -1199,15 +1303,24 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // searches the path containing the current position for (const Path& path : buffer.paths) { if (path.first.s_id <= m_sequential_view.current.last && m_sequential_view.current.last <= path.last.s_id) { - size_t offset = m_sequential_view.current.last - path.first.s_id; - if (offset > 0 && (path.type == EMoveType::Travel || path.type == EMoveType::Extrude)) - offset = 1 + 2 * (offset - 1); - + unsigned int offset = m_sequential_view.current.last - path.first.s_id; + if (offset > 0) { + if (buffer.primitive_type == TBuffer::EPrimitiveType::Line) + offset = 2 * offset - 1; + else if (buffer.primitive_type == TBuffer::EPrimitiveType::Triangle) + offset = 36 * (offset - 1) + 30; + } offset += path.first.i_id; + // gets the index from the index buffer on gpu + unsigned int index = 0; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(unsigned int)), static_cast(sizeof(unsigned int)), static_cast(&index))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + // gets the position from the vertices buffer on gpu glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(offset * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(m_sequential_view.current_position.data()))); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(m_sequential_view.current_position.data()))); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); found = true; break; @@ -1238,15 +1351,23 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool it->path_id = id; } - unsigned int size = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; - if (path.type == EMoveType::Extrude || path.type == EMoveType::Travel) - size = 2 * (size - 1); + unsigned int size_in_vertices = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; + unsigned int size_in_indices = 0; + switch (buffer->primitive_type) + { + case TBuffer::EPrimitiveType::Point: { size_in_indices = size_in_vertices; break; } + case TBuffer::EPrimitiveType::Line: { size_in_indices = 2 * (size_in_vertices - 1); break; } + case TBuffer::EPrimitiveType::Triangle: { size_in_indices = 36 * (size_in_vertices - 1); break; } + } + it->sizes.push_back(size_in_indices); - it->sizes.push_back(size); unsigned int delta_1st = 0; - if ((path.first.s_id < m_sequential_view.current.first) && (m_sequential_view.current.first <= path.last.s_id)) + if (path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= path.last.s_id) delta_1st = m_sequential_view.current.first - path.first.s_id; + if (buffer->primitive_type == TBuffer::EPrimitiveType::Triangle) + delta_1st *= 36; + it->offsets.push_back(static_cast((path.first.i_id + delta_1st) * sizeof(unsigned int))); } @@ -1266,8 +1387,15 @@ void GCodeViewer::render_toolpaths() const { #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR float point_size = m_shaders_editor.points.point_size; + std::array light_intensity = { + m_shaders_editor.lines.lights.ambient, + m_shaders_editor.lines.lights.top_diffuse, + m_shaders_editor.lines.lights.front_diffuse, + m_shaders_editor.lines.lights.global + }; #else float point_size = 0.8f; + std::array light_intensity = { 0.25f, 0.7f, 0.75f, 0.75f }; #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR const Camera& camera = wxGetApp().plater()->get_camera(); double zoom = camera.get_zoom(); @@ -1275,10 +1403,13 @@ void GCodeViewer::render_toolpaths() const float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : static_cast(viewport[3]) * 0.0005; - Transform3d inv_proj = camera.get_projection_matrix().inverse(); + auto set_uniform_color = [](const std::array& color, GLShaderProgram& shader) { + std::array color4 = { color[0], color[1], color[2], 1.0f }; + shader.set_uniform("uniform_color", color4); + }; - auto render_as_points = [this, zoom, inv_proj, viewport, point_size, near_plane_height](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { - shader.set_uniform("uniform_color", Options_Colors[static_cast(color_id)]); + auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { + set_uniform_color(Options_Colors[static_cast(color_id)], shader); shader.set_uniform("zoom", zoom); #if ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.points.percent_outline)); @@ -1287,8 +1418,6 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("percent_outline_radius", 0.0f); shader.set_uniform("percent_center_radius", 0.33f); #endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR - shader.set_uniform("viewport", viewport); - shader.set_uniform("inv_proj_matrix", inv_proj); shader.set_uniform("point_size", point_size); shader.set_uniform("near_plane_height", near_plane_height); @@ -1306,12 +1435,23 @@ void GCodeViewer::render_toolpaths() const glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [this](const TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { - shader.set_uniform("uniform_color", path.color); + set_uniform_color(path.color, shader); glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_line_strip_calls_count; + ++m_statistics.gl_multi_lines_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + }; + + auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + for (const RenderPath& path : buffer.render_paths) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } }; @@ -1338,40 +1478,50 @@ void GCodeViewer::render_toolpaths() const shader->start_using(); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glVertexPointer(buffer.vertices.vertex_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)0)); + glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_size())); glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (has_normals) { + glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_size())); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - switch (buffer_type(i)) + switch (buffer.primitive_type) { - default: { break; } - case EMoveType::Tool_change: { render_as_points(buffer, EOptionsColors::ToolChanges, *shader); break; } - case EMoveType::Color_change: { render_as_points(buffer, EOptionsColors::ColorChanges, *shader); break; } - case EMoveType::Pause_Print: { render_as_points(buffer, EOptionsColors::PausePrints, *shader); break; } - case EMoveType::Custom_GCode: { render_as_points(buffer, EOptionsColors::CustomGCodes, *shader); break; } - case EMoveType::Retract: { render_as_points(buffer, EOptionsColors::Retractions, *shader); break; } - case EMoveType::Unretract: { render_as_points(buffer, EOptionsColors::Unretractions, *shader); break; } - case EMoveType::Extrude: - case EMoveType::Travel: + case TBuffer::EPrimitiveType::Point: + { + EOptionsColors color; + switch (buffer_type(i)) + { + case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } + case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } + case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } + case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } + case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } + case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + } + render_as_points(buffer, color, *shader); + break; + } + case TBuffer::EPrimitiveType::Line: { -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - std::array light_intensity = { - m_shaders_editor.lines.lights.ambient, - m_shaders_editor.lines.lights.top_diffuse, - m_shaders_editor.lines.lights.front_diffuse, - m_shaders_editor.lines.lights.global }; -#else - std::array light_intensity = { 0.25f, 0.7f, 0.75f, 0.75f }; -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR - shader->set_uniform("light_intensity", light_intensity); render_as_lines(buffer, *shader); break; } + case TBuffer::EPrimitiveType::Triangle: + { + render_as_triangles(buffer, *shader); + break; + } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (has_normals) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); @@ -2454,9 +2604,13 @@ void GCodeViewer::render_statistics() const ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count)); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_LINE_STRIP calls:")); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_LINES calls:")); ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_line_strip_calls_count)); + imgui.text(std::to_string(m_statistics.gl_multi_lines_calls_count)); + + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_TRIANGLES calls:")); + ImGui::SameLine(offset); + imgui.text(std::to_string(m_statistics.gl_multi_triangles_calls_count)); ImGui::Separator(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index e6c3e26eaa..919b9de6e1 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -38,8 +38,12 @@ class GCodeViewer { enum class EFormat : unsigned char { + // vertex format: 3 floats -> position.x|position.y|position.z Position, - PositionNormal + // vertex format: 4 floats -> position.x|position.y|position.z|normal.x + PositionNormal1, + // vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z + PositionNormal3 }; EFormat format{ EFormat::Position }; @@ -49,18 +53,45 @@ class GCodeViewer size_t count{ 0 }; size_t data_size_bytes() const { return count * vertex_size_bytes(); } - size_t vertex_size_floats() const + + size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); } + size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } + + size_t position_offset_floats() const { return 0; } + size_t position_offset_size() const { return position_offset_floats() * sizeof(float); } + size_t position_size_floats() const { switch (format) { - // vertex format: 3 floats -> position.x|position.y|position.z - case EFormat::Position: { return 3; } - // vertex format: 4 floats -> position.x|position.y|position.z|normal.x - case EFormat::PositionNormal: { return 4; } - default: { return 0; } + case EFormat::Position: + case EFormat::PositionNormal3: { return 3; } + case EFormat::PositionNormal1: { return 4; } + default: { return 0; } } } - size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } + size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } + + size_t normal_offset_floats() const + { + switch (format) + { + case EFormat::Position: + case EFormat::PositionNormal1: { return 0; } + case EFormat::PositionNormal3: { return 3; } + default: { return 0; } + } + } + size_t normal_offset_size() const { return normal_offset_floats() * sizeof(float); } + size_t normal_size_floats() const { + switch (format) + { + default: + case EFormat::Position: + case EFormat::PositionNormal1: { return 0; } + case EFormat::PositionNormal3: { return 3; } + } + } + size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); } void reset(); }; @@ -116,6 +147,14 @@ class GCodeViewer // buffer containing data for rendering a specific toolpath type struct TBuffer { + enum class EPrimitiveType : unsigned char + { + Point, + Line, + Triangle + }; + + EPrimitiveType primitive_type; VBuffer vertices; IBuffer indices; @@ -185,8 +224,7 @@ class GCodeViewer void reset_role_visibility_flags() { role_visibility_flags = 0; - for (unsigned int i = 0; i < erCount; ++i) - { + for (unsigned int i = 0; i < erCount; ++i) { role_visibility_flags |= 1 << i; } } @@ -204,7 +242,8 @@ class GCodeViewer long long refresh_paths_time{ 0 }; // opengl calls long long gl_multi_points_calls_count{ 0 }; - long long gl_multi_line_strip_calls_count{ 0 }; + long long gl_multi_lines_calls_count{ 0 }; + long long gl_multi_triangles_calls_count{ 0 }; // memory long long results_size{ 0 }; long long vertices_gpu_size{ 0 }; @@ -231,7 +270,8 @@ class GCodeViewer void reset_opengl() { gl_multi_points_calls_count = 0; - gl_multi_line_strip_calls_count = 0; + gl_multi_lines_calls_count = 0; + gl_multi_triangles_calls_count = 0; } void reset_sizes() { diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 6baf46f6b4..1041faa3dc 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -37,7 +37,7 @@ std::pair GLShadersManager::init() valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" }); - // used to render extrusion and travel paths in gcode preview + // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" }); From e0e75f4a0e9a3942edd5f635ba309adb2f8208ef Mon Sep 17 00:00:00 2001 From: Slic3rPE Date: Wed, 26 Aug 2020 15:50:05 +0200 Subject: [PATCH 336/826] Starting a new Slicer instance from the menu - fix of Windows build --- src/slic3r/GUI/MainFrame.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 122d9c6108..8f2aeef8ab 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -13,7 +13,6 @@ #include #include -#include #include "libslic3r/Print.hpp" #include "libslic3r/Polygon.hpp" @@ -41,6 +40,12 @@ #include #endif // _WIN32 +// For starting another PrusaSlicer instance on OSX. +// Fails to compile on Windows on the build server. +#ifdef __APPLE__ + #include +#endif + namespace Slic3r { namespace GUI { From 42a7f2b1d873907b96502889d319c7056eee2f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 16:51:34 +0200 Subject: [PATCH 337/826] Preparation for new infill --- src/libslic3r/CMakeLists.txt | 2 ++ src/libslic3r/Fill/Fill.cpp | 1 + src/libslic3r/Fill/FillAdaptive.cpp | 19 +++++++++++ src/libslic3r/Fill/FillAdaptive.hpp | 53 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillBase.cpp | 2 ++ src/libslic3r/Fill/FillBase.hpp | 6 ++++ src/libslic3r/Print.hpp | 8 +++++ src/libslic3r/PrintConfig.cpp | 2 ++ src/libslic3r/PrintConfig.hpp | 3 +- 9 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/libslic3r/Fill/FillAdaptive.cpp create mode 100644 src/libslic3r/Fill/FillAdaptive.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 290b8953cc..afec759746 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -46,6 +46,8 @@ add_library(libslic3r STATIC Fill/Fill.hpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp + Fill/FillAdaptive.cpp + Fill/FillAdaptive.hpp Fill/FillBase.cpp Fill/FillBase.hpp Fill/FillConcentric.cpp diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 3c16527f07..c948df400e 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -345,6 +345,7 @@ void Layer::make_fills() f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; + f->adapt_fill_octree = this->object()->adaptiveInfillOctree(); // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp new file mode 100644 index 0000000000..cb11385983 --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -0,0 +1,19 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" + +#include "FillAdaptive.hpp" + +namespace Slic3r { + +void FillAdaptive::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out) +{ + +} + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp new file mode 100644 index 0000000000..e0a97a1b9b --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -0,0 +1,53 @@ +#ifndef slic3r_FillAdaptive_hpp_ +#define slic3r_FillAdaptive_hpp_ + +#include "FillBase.hpp" + +namespace Slic3r { + +namespace FillAdaptive_Internal +{ + struct CubeProperties + { + double edge_length; // Lenght of edge of a cube + double height; // Height of rotated cube (standing on the corner) + double diagonal_length; // Length of diagonal of a cube a face + double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created + double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created + }; + + struct Cube + { + Vec3d center; + size_t depth; + CubeProperties properties; + std::vector children; + }; + + struct Octree + { + Cube *root_cube; + Vec3d origin; + }; +}; // namespace FillAdaptive_Internal + +class FillAdaptive : public Fill +{ +public: + virtual ~FillAdaptive() {} + +protected: + virtual Fill* clone() const { return new FillAdaptive(*this); }; + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out); + + virtual bool no_sort() const { return true; } +}; + +} // namespace Slic3r + +#endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c760218c01..c1f38dad5c 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -16,6 +16,7 @@ #include "FillRectilinear.hpp" #include "FillRectilinear2.hpp" #include "FillRectilinear3.hpp" +#include "FillAdaptive.hpp" namespace Slic3r { @@ -37,6 +38,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipArchimedeanChords: return new FillArchimedeanChords(); case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); + case ipAdaptiveCubic: return new FillAdaptive(); default: throw std::invalid_argument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 2e9b647354..9f70b69e08 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -19,6 +19,10 @@ class ExPolygon; class Surface; enum InfillPattern : int; +namespace FillAdaptive_Internal { + struct Octree; +}; + class InfillFailedException : public std::runtime_error { public: InfillFailedException() : std::runtime_error("Infill failed") {} @@ -69,6 +73,8 @@ public: // In scaled coordinates. Bounding box of the 2D projection of the object. BoundingBox bounding_box; + FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr; + public: virtual ~Fill() {} diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 05929dd2ef..5f8613a2db 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -25,6 +25,10 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; +namespace FillAdaptive_Internal { + struct Octree; +}; + // Print step IDs for keeping track of the print state. enum PrintStep { psSkirt, @@ -191,6 +195,7 @@ public: void project_and_append_custom_enforcers(std::vector& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); } void project_and_append_custom_blockers(std::vector& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); } + FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree; } private: // to be called from Print only. friend class Print; @@ -232,6 +237,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); + void prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; @@ -252,6 +258,8 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; + FillAdaptive_Internal::Octree* m_adapt_fill_octree = nullptr; + std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const; std::vector slice_modifiers(size_t region_id, const std::vector &z) const; std::vector slice_volumes(const std::vector &z, SlicingMode mode, const std::vector &volumes) const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3401dcc020..a855e4b1a8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -881,6 +881,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("adaptivecubic"); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); def->enum_labels.push_back(L("Triangles")); @@ -894,6 +895,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Adaptive Cubic")); def->set_default_value(new ConfigOptionEnum(ipStars)); def = this->add("first_layer_acceleration", coFloat); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c4566c983e..abd19a7fea 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -39,7 +39,7 @@ enum AuthorizationType { enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipCount, }; enum class IroningType { @@ -139,6 +139,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g keys_map["hilbertcurve"] = ipHilbertCurve; keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["octagramspiral"] = ipOctagramSpiral; + keys_map["adaptivecubic"] = ipAdaptiveCubic; } return keys_map; } From 3ac16d9c9cd2d796db45c266964c507c423a7992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 18:15:59 +0200 Subject: [PATCH 338/826] Building octree based on distance from mesh --- src/libslic3r/Fill/FillAdaptive.cpp | 86 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillAdaptive.hpp | 16 ++++++ src/libslic3r/PrintObject.cpp | 23 ++++++++ 3 files changed, 125 insertions(+) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index cb11385983..ce779ad005 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -1,6 +1,8 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Surface.hpp" +#include "../Geometry.hpp" +#include "../AABBTreeIndirect.hpp" #include "FillAdaptive.hpp" @@ -16,4 +18,88 @@ void FillAdaptive::_fill_surface_single( } +FillAdaptive_Internal::Octree* FillAdaptive::build_octree( + TriangleMesh &triangleMesh, + coordf_t line_spacing, + const BoundingBoxf3 &printer_volume, + const Vec3d &cube_center) +{ + using namespace FillAdaptive_Internal; + + if(line_spacing <= 0) + { + return nullptr; + } + + // The furthest point from center of bed. + double furthest_point = std::sqrt(((printer_volume.size()[0] * printer_volume.size()[0]) / 4.0) + + ((printer_volume.size()[1] * printer_volume.size()[1]) / 4.0) + + (printer_volume.size()[2] * printer_volume.size()[2])); + double max_cube_edge_length = furthest_point * 2; + + std::vector cubes_properties; + for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) + { + CubeProperties props{}; + props.edge_length = edge_length; + props.height = edge_length * sqrt(3); + props.diagonal_length = edge_length * sqrt(2); + props.line_z_distance = edge_length / sqrt(3); + props.line_xy_distance = edge_length / sqrt(6); + cubes_properties.push_back(props); + } + + if (triangleMesh.its.vertices.empty()) + { + triangleMesh.require_shared_vertices(); + } + + Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.0), Geometry::deg2rad(30.0)); + Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); + + AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); + Octree *octree = new Octree{new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}, cube_center}; + + FillAdaptive::expand_cube(octree->root_cube, cubes_properties, rotation_matrix, aabbTree, triangleMesh); + + return octree; +} + +void FillAdaptive::expand_cube( + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d &rotation_matrix, + const AABBTreeIndirect::Tree3f &distanceTree, + const TriangleMesh &triangleMesh) +{ + using namespace FillAdaptive_Internal; + + if (cube == nullptr || cube->depth == 0) + { + return; + } + + std::vector child_centers = { + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d(-1, -1, 1), + Vec3d( 1, 1, 1), Vec3d(-1, 1, 1), Vec3d( 1, -1, 1), Vec3d( 1, 1, -1) + }; + + double cube_radius_squared = (cube->properties.height * cube->properties.height) / 16; + + for (const Vec3d &child_center : child_centers) { + Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); + Vec3d closest_point = Vec3d::Zero(); + size_t closest_triangle_idx = 0; + + double distance_squared = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, + closest_triangle_idx,closest_point); + + if(distance_squared <= cube_radius_squared) { + cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); + FillAdaptive::expand_cube(cube->children.back(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); + } + } +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index e0a97a1b9b..49c5276a93 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_FillAdaptive_hpp_ #define slic3r_FillAdaptive_hpp_ +#include "../AABBTreeIndirect.hpp" + #include "FillBase.hpp" namespace Slic3r { @@ -46,6 +48,20 @@ protected: Polylines &polylines_out); virtual bool no_sort() const { return true; } + +public: + static FillAdaptive_Internal::Octree* build_octree( + TriangleMesh &triangleMesh, + coordf_t line_spacing, + const BoundingBoxf3 &printer_volume, + const Vec3d &cube_center); + + static void expand_cube( + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d &rotation_matrix, + const AABBTreeIndirect::Tree3f &distanceTree, + const TriangleMesh &triangleMesh); }; } // namespace Slic3r diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c90a05ef31..5752452ad7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -9,6 +9,8 @@ #include "Surface.hpp" #include "Slicing.hpp" #include "Utils.hpp" +#include "AABBTreeIndirect.hpp" +#include "Fill/FillAdaptive.hpp" #include #include @@ -360,6 +362,8 @@ void PrintObject::prepare_infill() } // for each layer #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + this->prepare_adaptive_infill_data(); + this->set_done(posPrepareInfill); } @@ -428,6 +432,25 @@ void PrintObject::generate_support_material() } } +void PrintObject::prepare_adaptive_infill_data() +{ + float fill_density = this->print()->full_print_config().opt_float("fill_density"); + float infill_extrusion_width = this->print()->full_print_config().opt_float("infill_extrusion_width"); + + coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); + + BoundingBoxf bed_shape(this->print()->config().bed_shape.values); + BoundingBoxf3 printer_volume(Vec3d(bed_shape.min(0), bed_shape.min(1), 0), + Vec3d(bed_shape.max(0), bed_shape.max(1), this->print()->config().max_print_height)); + + Vec3d model_center = this->model_object()->bounding_box().center(); + model_center(2) = 0.0f; // Set position in Z axis to 0 + // Center of the first cube in octree + + TriangleMesh mesh = this->model_object()->mesh(); + this->m_adapt_fill_octree = FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); +} + void PrintObject::clear_layers() { for (Layer *l : m_layers) From 41f474a884bdad01b973cd6bb734cea3a31384fc Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 26 Aug 2020 21:51:50 +0200 Subject: [PATCH 339/826] Fixed performance issues when adding / removing Presets into PresetCollection. This improves application startup time by 25-33%. --- src/libslic3r/PrintConfig.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c4566c983e..b133a2e4eb 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -239,9 +239,13 @@ class DynamicPrintConfig : public DynamicConfig public: DynamicPrintConfig() {} DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {} + DynamicPrintConfig(DynamicPrintConfig &&rhs) noexcept : DynamicConfig(std::move(rhs)) {} explicit DynamicPrintConfig(const StaticPrintConfig &rhs); explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {} + DynamicPrintConfig& operator=(const DynamicPrintConfig &rhs) { DynamicConfig::operator=(rhs); return *this; } + DynamicPrintConfig& operator=(DynamicPrintConfig &&rhs) noexcept { DynamicConfig::operator=(std::move(rhs)); return *this; } + static DynamicPrintConfig full_print_config(); static DynamicPrintConfig* new_from_defaults_keys(const std::vector &keys); From eaaff4e707e8321b7bc1b9e9151fb94a4befc3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 22:18:51 +0200 Subject: [PATCH 340/826] Generating polylines from octree --- src/libslic3r/Fill/FillAdaptive.cpp | 57 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillAdaptive.hpp | 7 ++++ 2 files changed, 64 insertions(+) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index ce779ad005..cac9c1c3b2 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -15,7 +15,64 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { + Polylines infill_polylines; + this->generate_polylines(this->adapt_fill_octree->root_cube, this->z, this->adapt_fill_octree->origin, infill_polylines); + // Crop all polylines + polylines_out = intersection_pl(infill_polylines, to_polygons(expolygon)); +} + +void FillAdaptive::generate_polylines( + FillAdaptive_Internal::Cube *cube, + double z_position, + const Vec3d &origin, + Polylines &polylines_out) +{ + using namespace FillAdaptive_Internal; + + if(cube == nullptr) + { + return; + } + + double z_diff = std::abs(z_position - cube->center.z()); + + if (z_diff > cube->properties.height / 2) + { + return; + } + + if (z_diff < cube->properties.line_z_distance) + { + Point from( + scale_((cube->properties.diagonal_length / 2) * (cube->properties.line_z_distance - z_diff) / cube->properties.line_z_distance), + scale_(cube->properties.line_xy_distance - ((z_position - (cube->center.z() - cube->properties.line_z_distance)) / sqrt(2)))); + Point to(-from.x(), from.y()); + // Relative to cube center + + float rotation_angle = Geometry::deg2rad(120.0); + + for (int dir_idx = 0; dir_idx < 3; dir_idx++) + { + Vec3d offset = cube->center - origin; + Point from_abs(from), to_abs(to); + + from_abs.x() += scale_(offset.x()); + from_abs.y() += scale_(offset.y()); + to_abs.x() += scale_(offset.x()); + to_abs.y() += scale_(offset.y()); + + polylines_out.push_back(Polyline(from_abs, to_abs)); + + from.rotate(rotation_angle); + to.rotate(rotation_angle); + } + } + + for(Cube *child : cube->children) + { + generate_polylines(child, z_position, origin, polylines_out); + } } FillAdaptive_Internal::Octree* FillAdaptive::build_octree( diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 49c5276a93..9e1a196af1 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -33,6 +33,11 @@ namespace FillAdaptive_Internal }; }; // namespace FillAdaptive_Internal +// +// Some of the algorithms used by class FillAdaptive were inspired by +// Cura Engine's class SubDivCube +// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h +// class FillAdaptive : public Fill { public: @@ -49,6 +54,8 @@ protected: virtual bool no_sort() const { return true; } + void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, Polylines &polylines_out); + public: static FillAdaptive_Internal::Octree* build_octree( TriangleMesh &triangleMesh, From fb24d8167ad2ab0d3464d9928d227d402bcee931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 23:28:52 +0200 Subject: [PATCH 341/826] Add function for check existence of triangle in define radius --- src/libslic3r/AABBTreeIndirect.hpp | 34 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillAdaptive.cpp | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index ec9b14a7ae..17d918aeb3 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -692,6 +692,40 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); } +// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// Returns true if exists some triangle in defined radius, false otherwise. +template +inline bool is_any_triangle_in_radius( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Point to which the closest point on the indexed triangle set is searched for. + const VectorType &point, + // Maximum distance in which triangle is search for + typename VectorType::Scalar &max_distance) +{ + using Scalar = typename VectorType::Scalar; + auto distancer = detail::IndexedTriangleSetDistancer + { vertices, faces, tree, point }; + + size_t hit_idx; + VectorType hit_point = VectorType::Ones() * (std::nan("")); + + if(tree.empty()) + { + return false; + } + + detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point); + + return hit_point.allFinite(); +} + } // namespace AABBTreeIndirect } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index cac9c1c3b2..ae067e659e 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -152,7 +152,7 @@ void FillAdaptive::expand_cube( triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, closest_triangle_idx,closest_point); - if(distance_squared <= cube_radius_squared) { + if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); FillAdaptive::expand_cube(cube->children.back(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); } From cb328c99aad32d559d2cc08c7c1084009e89c0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 27 Aug 2020 01:59:35 +0200 Subject: [PATCH 342/826] Polylines merging --- src/libslic3r/Fill/FillAdaptive.cpp | 84 ++++++++++++++++++++++++----- src/libslic3r/Fill/FillAdaptive.hpp | 4 +- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index ae067e659e..adc6c0c6fb 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -15,18 +15,54 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - Polylines infill_polylines; + std::vector infill_polylines(3); this->generate_polylines(this->adapt_fill_octree->root_cube, this->z, this->adapt_fill_octree->origin, infill_polylines); - // Crop all polylines - polylines_out = intersection_pl(infill_polylines, to_polygons(expolygon)); + for (Polylines &infill_polyline : infill_polylines) { + // Crop all polylines + infill_polyline = intersection_pl(infill_polyline, to_polygons(expolygon)); + polylines_out.insert(polylines_out.end(), infill_polyline.begin(), infill_polyline.end()); + } + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRuna = 0; + BoundingBox bbox_svg = this->bounding_box; + { + ::Slic3r::SVG svg(debug_out_path("FillAdaptive-%d.svg", iRuna), bbox_svg); + for (const Polyline &polyline : polylines_out) + { + for (const Line &line : polyline.lines()) + { + Point from = line.a; + Point to = line.b; + Point diff = to - from; + + float shrink_length = scale_(0.4); + float line_slope = (float)diff.y() / diff.x(); + float shrink_x = shrink_length / (float)std::sqrt(1.0 + (line_slope * line_slope)); + float shrink_y = line_slope * shrink_x; + + to.x() -= shrink_x; + to.y() -= shrink_y; + from.x() += shrink_x; + from.y() += shrink_y; + + svg.draw(Line(from, to)); + } + } + } + + iRuna++; + } +#endif /* SLIC3R_DEBUG */ } void FillAdaptive::generate_polylines( FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, - Polylines &polylines_out) + std::vector &polylines_out) { using namespace FillAdaptive_Internal; @@ -52,7 +88,7 @@ void FillAdaptive::generate_polylines( float rotation_angle = Geometry::deg2rad(120.0); - for (int dir_idx = 0; dir_idx < 3; dir_idx++) + for (int i = 0; i < 3; i++) { Vec3d offset = cube->center - origin; Point from_abs(from), to_abs(to); @@ -62,7 +98,8 @@ void FillAdaptive::generate_polylines( to_abs.x() += scale_(offset.x()); to_abs.y() += scale_(offset.y()); - polylines_out.push_back(Polyline(from_abs, to_abs)); +// polylines_out[i].push_back(Polyline(from_abs, to_abs)); + this->merge_polylines(polylines_out[i], Line(from_abs, to_abs)); from.rotate(rotation_angle); to.rotate(rotation_angle); @@ -75,6 +112,35 @@ void FillAdaptive::generate_polylines( } } +void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) +{ + int eps = scale_(0.10); + bool modified = false; + + for (Polyline &polyline : polylines) + { + if (std::abs(new_line.a.x() - polyline.points[1].x()) < eps && std::abs(new_line.a.y() - polyline.points[1].y()) < eps) + { + polyline.points[1].x() = new_line.b.x(); + polyline.points[1].y() = new_line.b.y(); + modified = true; + } + + if (std::abs(new_line.b.x() - polyline.points[0].x()) < eps && std::abs(new_line.b.y() - polyline.points[0].y()) < eps) + { + polyline.points[0].x() = new_line.a.x(); + polyline.points[0].y() = new_line.a.y(); + modified = true; + } + } + + if(!modified) + { + polylines.emplace_back(Polyline(new_line.a, new_line.b)); + } +} + + FillAdaptive_Internal::Octree* FillAdaptive::build_octree( TriangleMesh &triangleMesh, coordf_t line_spacing, @@ -145,12 +211,6 @@ void FillAdaptive::expand_cube( for (const Vec3d &child_center : child_centers) { Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); - Vec3d closest_point = Vec3d::Zero(); - size_t closest_triangle_idx = 0; - - double distance_squared = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( - triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, - closest_triangle_idx,closest_point); if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 9e1a196af1..b2f4e37b1e 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -54,7 +54,9 @@ protected: virtual bool no_sort() const { return true; } - void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, Polylines &polylines_out); + void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &polylines_out); + + void merge_polylines(Polylines &polylines, const Line &new_line); public: static FillAdaptive_Internal::Octree* build_octree( From c5a73a7cd6a5126cbe8cb92bafd077d4bb56cb0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 27 Aug 2020 07:28:43 +0200 Subject: [PATCH 343/826] Switch to smart pointers --- src/libslic3r/Fill/FillAdaptive.cpp | 17 +++++++++-------- src/libslic3r/Fill/FillAdaptive.hpp | 6 +++--- src/libslic3r/Print.hpp | 8 +++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index adc6c0c6fb..96509923c8 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -16,7 +16,7 @@ void FillAdaptive::_fill_surface_single( Polylines &polylines_out) { std::vector infill_polylines(3); - this->generate_polylines(this->adapt_fill_octree->root_cube, this->z, this->adapt_fill_octree->origin, infill_polylines); + this->generate_polylines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_polylines); for (Polylines &infill_polyline : infill_polylines) { // Crop all polylines @@ -106,9 +106,9 @@ void FillAdaptive::generate_polylines( } } - for(Cube *child : cube->children) + for(const std::unique_ptr &child : cube->children) { - generate_polylines(child, z_position, origin, polylines_out); + generate_polylines(child.get(), z_position, origin, polylines_out); } } @@ -141,7 +141,7 @@ void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) } -FillAdaptive_Internal::Octree* FillAdaptive::build_octree( +std::unique_ptr FillAdaptive::build_octree( TriangleMesh &triangleMesh, coordf_t line_spacing, const BoundingBoxf3 &printer_volume, @@ -181,9 +181,10 @@ FillAdaptive_Internal::Octree* FillAdaptive::build_octree( Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); - Octree *octree = new Octree{new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}, cube_center}; + std::unique_ptr octree = std::unique_ptr( + new Octree{std::unique_ptr(new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}), cube_center}); - FillAdaptive::expand_cube(octree->root_cube, cubes_properties, rotation_matrix, aabbTree, triangleMesh); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangleMesh); return octree; } @@ -213,8 +214,8 @@ void FillAdaptive::expand_cube( Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { - cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); - FillAdaptive::expand_cube(cube->children.back(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); + cube->children.push_back(std::unique_ptr(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]})); + FillAdaptive::expand_cube(cube->children.back().get(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); } } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index b2f4e37b1e..fb1f2da8e3 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -23,12 +23,12 @@ namespace FillAdaptive_Internal Vec3d center; size_t depth; CubeProperties properties; - std::vector children; + std::vector> children; }; struct Octree { - Cube *root_cube; + std::unique_ptr root_cube; Vec3d origin; }; }; // namespace FillAdaptive_Internal @@ -59,7 +59,7 @@ protected: void merge_polylines(Polylines &polylines, const Line &new_line); public: - static FillAdaptive_Internal::Octree* build_octree( + static std::unique_ptr build_octree( TriangleMesh &triangleMesh, coordf_t line_spacing, const BoundingBoxf3 &printer_volume, diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 5f8613a2db..2e2746a345 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -11,6 +11,7 @@ #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "GCode/ThumbnailData.hpp" +#include "Fill/FillAdaptive.hpp" #include "libslic3r.h" @@ -25,9 +26,6 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; -namespace FillAdaptive_Internal { - struct Octree; -}; // Print step IDs for keeping track of the print state. enum PrintStep { @@ -195,7 +193,7 @@ public: void project_and_append_custom_enforcers(std::vector& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); } void project_and_append_custom_blockers(std::vector& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); } - FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree; } + FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree.get(); } private: // to be called from Print only. friend class Print; @@ -258,7 +256,7 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - FillAdaptive_Internal::Octree* m_adapt_fill_octree = nullptr; + std::unique_ptr m_adapt_fill_octree = nullptr; std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const; std::vector slice_modifiers(size_t region_id, const std::vector &z) const; From af30a3ab7ef303852575c1aaa54210bc48388dd4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 27 Aug 2020 09:13:30 +0200 Subject: [PATCH 344/826] Code cleanup --- resources/shaders/options_120.fs | 5 +- resources/shaders/toolpaths_lines.fs | 5 +- resources/shaders/toolpaths_lines.vs | 2 - src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/GCodeViewer.cpp | 102 +-------------------------- src/slic3r/GUI/GCodeViewer.hpp | 35 --------- 6 files changed, 4 insertions(+), 146 deletions(-) diff --git a/resources/shaders/options_120.fs b/resources/shaders/options_120.fs index d897a8ca73..e9b61304f2 100644 --- a/resources/shaders/options_120.fs +++ b/resources/shaders/options_120.fs @@ -5,8 +5,7 @@ uniform vec4 uniform_color; uniform float percent_outline_radius; uniform float percent_center_radius; - -vec4 customizable_color(float radius, vec4 color) +vec4 calc_color(float radius, vec4 color) { return ((radius < percent_center_radius) || (radius > 1.0 - percent_outline_radius)) ? vec4(0.5 * color.rgb, color.a) : color; @@ -19,5 +18,5 @@ void main() if (radius > 1.0) discard; - gl_FragColor = customizable_color(radius, uniform_color); + gl_FragColor = calc_color(radius, uniform_color); } diff --git a/resources/shaders/toolpaths_lines.fs b/resources/shaders/toolpaths_lines.fs index 7202a2e3e4..31151cdc17 100644 --- a/resources/shaders/toolpaths_lines.fs +++ b/resources/shaders/toolpaths_lines.fs @@ -8,11 +8,8 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.0, 0.0, 1.0); uniform vec4 light_intensity; uniform vec4 uniform_color; -varying vec3 eye_position; varying vec3 eye_normal; -float intensity; - void main() { vec3 normal = normalize(eye_normal); @@ -21,7 +18,7 @@ void main() // Since these two are normalized the cosine is the dot product. Take the abs value to light the lines no matter in which direction the normal points. float NdotL = abs(dot(normal, LIGHT_TOP_DIR)); - intensity = light_intensity.x + NdotL * light_intensity.y; + float intensity = light_intensity.x + NdotL * light_intensity.y; // Perform the same lighting calculation for the 2nd light source. NdotL = abs(dot(normal, LIGHT_FRONT_DIR)); diff --git a/resources/shaders/toolpaths_lines.vs b/resources/shaders/toolpaths_lines.vs index 34d141bfe1..85d5c641f3 100644 --- a/resources/shaders/toolpaths_lines.vs +++ b/resources/shaders/toolpaths_lines.vs @@ -1,6 +1,5 @@ #version 110 -varying vec3 eye_position; varying vec3 eye_normal; vec3 world_normal() @@ -16,6 +15,5 @@ void main() { vec4 world_position = vec4(gl_Vertex.xyz, 1.0); gl_Position = gl_ModelViewProjectionMatrix * world_position; - eye_position = (gl_ModelViewMatrix * world_position).xyz; eye_normal = gl_NormalMatrix * world_normal(); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 00c899201a..de5933c7cf 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -57,7 +57,6 @@ // Enable G-Code viewer #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_SHADERS_EDITOR (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES (1 && ENABLE_GCODE_VIEWER) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 70b571fe00..5e15889c86 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -504,9 +504,6 @@ void GCodeViewer::render() const #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - render_shaders_editor(); -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR } bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const @@ -1385,18 +1382,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool void GCodeViewer::render_toolpaths() const { -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - float point_size = m_shaders_editor.points.point_size; - std::array light_intensity = { - m_shaders_editor.lines.lights.ambient, - m_shaders_editor.lines.lights.top_diffuse, - m_shaders_editor.lines.lights.front_diffuse, - m_shaders_editor.lines.lights.global - }; -#else float point_size = 0.8f; - std::array light_intensity = { 0.25f, 0.7f, 0.75f, 0.75f }; -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR + std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; const Camera& camera = wxGetApp().plater()->get_camera(); double zoom = camera.get_zoom(); const std::array& viewport = camera.get_viewport(); @@ -1411,13 +1398,8 @@ void GCodeViewer::render_toolpaths() const auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { set_uniform_color(Options_Colors[static_cast(color_id)], shader); shader.set_uniform("zoom", zoom); -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - shader.set_uniform("percent_outline_radius", 0.01f * static_cast(m_shaders_editor.points.percent_outline)); - shader.set_uniform("percent_center_radius", 0.01f * static_cast(m_shaders_editor.points.percent_center)); -#else shader.set_uniform("percent_outline_radius", 0.0f); shader.set_uniform("percent_center_radius", 0.33f); -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR shader.set_uniform("point_size", point_size); shader.set_uniform("near_plane_height", near_plane_height); @@ -1597,21 +1579,6 @@ void GCodeViewer::render_legend() const } case EItemType::Circle: { -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_shaders_editor.points.shader_version == 1) { - draw_list->AddCircleFilled(center, 0.5f * icon_size, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size * (1.0f - 0.01f * static_cast(m_shaders_editor.points.percent_outline)); - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - if (m_shaders_editor.points.percent_center > 0) { - radius = 0.5f * icon_size * 0.01f * static_cast(m_shaders_editor.points.percent_center); - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - } - } - else - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); -#else ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { draw_list->AddCircleFilled(center, 0.5f * icon_size, @@ -1623,7 +1590,6 @@ void GCodeViewer::render_legend() const } else draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR break; } @@ -2175,11 +2141,7 @@ void GCodeViewer::render_legend() const auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { const TBuffer& buffer = m_buffers[buffer_id(move_type)]; if (buffer.visible && buffer.indices.count > 0) -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - append_item((m_shaders_editor.points.shader_version == 0) ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); -#else append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR }; // options section @@ -2652,68 +2614,6 @@ void GCodeViewer::render_statistics() const } #endif // ENABLE_GCODE_VIEWER_STATISTICS -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR -void GCodeViewer::render_shaders_editor() const -{ - auto set_shader = [this](const std::string& shader) { - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Custom_GCode); - for (unsigned char i = begin_id; i <= end_id; ++i) { - m_buffers[i].shader = shader; - } - }; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - - Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); - imgui.set_next_window_pos(static_cast(cnv_size.get_width()), 0.5f * static_cast(cnv_size.get_height()), ImGuiCond_Once, 1.0f, 0.5f); - - imgui.begin(std::string("Shaders editor (DEV only)"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); - - if (ImGui::CollapsingHeader("Points", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::TreeNode("GLSL version")) { - ImGui::RadioButton("1.10 (low end PCs)", &m_shaders_editor.points.shader_version, 0); - ImGui::RadioButton("1.20 flat (billboards) [default]", &m_shaders_editor.points.shader_version, 1); - ImGui::TreePop(); - } - - switch (m_shaders_editor.points.shader_version) - { - case 0: { set_shader("options_110"); break; } - case 1: { set_shader("options_120"); break; } - } - - if (ImGui::TreeNode("Options")) { - ImGui::SliderFloat("point size", &m_shaders_editor.points.point_size, 0.5f, 3.0f, "%.2f"); - if (m_shaders_editor.points.shader_version == 1) { - ImGui::SliderInt("% outline", &m_shaders_editor.points.percent_outline, 0, 50); - ImGui::SliderInt("% center", &m_shaders_editor.points.percent_center, 0, 50); - } - ImGui::TreePop(); - } - } - - if (ImGui::CollapsingHeader("Lines", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::TreeNode("Lights")) { - ImGui::SliderFloat("ambient", &m_shaders_editor.lines.lights.ambient, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat("top diffuse", &m_shaders_editor.lines.lights.top_diffuse, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat("front diffuse", &m_shaders_editor.lines.lights.front_diffuse, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat("global", &m_shaders_editor.lines.lights.global, 0.0f, 1.0f, "%.2f"); - ImGui::TreePop(); - } - } - - ImGui::SetWindowSize(ImVec2(std::max(ImGui::GetWindowWidth(), 600.0f), -1.0f), ImGuiCond_Always); - if (ImGui::GetWindowPos().x + ImGui::GetWindowWidth() > static_cast(cnv_size.get_width())) { - ImGui::SetWindowPos(ImVec2(static_cast(cnv_size.get_width()) - ImGui::GetWindowWidth(), ImGui::GetWindowPos().y), ImGuiCond_Always); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - - imgui.end(); -} -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR - bool GCodeViewer::is_travel_in_z_range(size_t id) const { const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 919b9de6e1..55c8ea1993 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -289,35 +289,6 @@ class GCodeViewer }; #endif // ENABLE_GCODE_VIEWER_STATISTICS -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - struct ShadersEditor - { - struct Points - { - int shader_version{ 1 }; - float point_size{ 0.8f }; - int percent_outline{ 0 }; - int percent_center{ 33 }; - }; - - struct Lines - { - struct Lights - { - float ambient{ 0.25f }; - float top_diffuse{ 0.7f }; - float front_diffuse{ 0.75f }; - float global{ 0.75f }; - }; - - Lights lights; - }; - - Points points; - Lines lines; - }; -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR - public: struct SequentialView { @@ -402,9 +373,6 @@ private: #if ENABLE_GCODE_VIEWER_STATISTICS mutable Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - mutable ShadersEditor m_shaders_editor; -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR std::array m_detected_point_sizes = { 0.0f, 0.0f }; public: @@ -475,9 +443,6 @@ private: #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS -#if ENABLE_GCODE_VIEWER_SHADERS_EDITOR - void render_shaders_editor() const; -#endif // ENABLE_GCODE_VIEWER_SHADERS_EDITOR bool is_visible(ExtrusionRole role) const { return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; } From 689c8691ee45947d1b7d1f626d8bd65b622f3cbd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 27 Aug 2020 10:15:07 +0200 Subject: [PATCH 345/826] Another code cleanup --- src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/GCodeViewer.cpp | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index de5933c7cf..af08d16401 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,7 +58,6 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES (1 && ENABLE_GCODE_VIEWER) #define TIME_ESTIMATE_NONE 0 #define TIME_ESTIMATE_DEFAULT 1 diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 5e15889c86..64916182ad 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -303,13 +303,8 @@ bool GCodeViewer::init() } case EMoveType::Extrude: { -#if ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES buffer.primitive_type = TBuffer::EPrimitiveType::Triangle; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; -#else - buffer.primitive_type = TBuffer::EPrimitiveType::Line; - buffer.vertices.format = VBuffer::EFormat::PositionNormal1; -#endif // ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES break; } case EMoveType::Travel: @@ -873,11 +868,7 @@ void GCodeViewer::init_shaders() case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } -#if ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES case EMoveType::Extrude: { m_buffers[i].shader = "gouraud_light"; break; } -#else - case EMoveType::Extrude: { m_buffers[i].shader = "toolpaths_lines"; break; } -#endif // ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES case EMoveType::Travel: { m_buffers[i].shader = "toolpaths_lines"; break; } default: { break; } } @@ -1064,15 +1055,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) add_as_point(curr, buffer, buffer_vertices, buffer_indices, i); break; } -#if ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES case EMoveType::Extrude: { add_as_solid(prev, curr, buffer, buffer_vertices, buffer_indices, i); break; } -#else - case EMoveType::Extrude: -#endif // ENABLE_GCODE_RENDER_EXTRUSION_AS_TRIANGLES case EMoveType::Travel: { add_as_line(prev, curr, buffer, buffer_vertices, buffer_indices, i); From 17170b81b51dfd7a99d1f3ef7f7d9038bf3502c0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 27 Aug 2020 12:14:49 +0200 Subject: [PATCH 346/826] Clean-up of Shiny profiler integration, so that the intrusiver profiling can be controlled per module. --- src/clipper/clipper.cpp | 58 ++++++++++++++++++++-------------- src/libslic3r/ClipperUtils.cpp | 25 ++++++++++----- src/slic3r/CMakeLists.txt | 1 + src/slic3r/Utils/Profile.hpp | 19 +++++++++++ 4 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 src/slic3r/Utils/Profile.hpp diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index b85cf9025e..be4cb4a6a8 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -48,9 +48,19 @@ #include #include #include -#include #include +// Profiling support using the Shiny intrusive profiler +//#define CLIPPERLIB_PROFILE +#if defined(SLIC3R_PROFILE) && defined(CLIPPERLIB_PROFILE) + #include + #define CLIPPERLIB_PROFILE_FUNC() PROFILE_FUNC() + #define CLIPPERLIB_PROFILE_BLOCK(name) PROFILE_BLOCK(name) +#else + #define CLIPPERLIB_PROFILE_FUNC() + #define CLIPPERLIB_PROFILE_BLOCK(name) +#endif + #ifdef use_xyz namespace ClipperLib_Z { #else /* use_xyz */ @@ -263,7 +273,7 @@ int PointInPolygon (const IntPoint &pt, OutPt *op) // This is potentially very expensive! O(n^2)! bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); OutPt* op = OutPt1; do { @@ -714,7 +724,7 @@ TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); // Remove duplicate end point from a closed input path. // Remove duplicate points from the end of the input path. int highI = (int)pg.size() -1; @@ -738,7 +748,7 @@ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); std::vector num_edges(ppg.size(), 0); int num_edges_total = 0; for (size_t i = 0; i < ppg.size(); ++ i) { @@ -780,7 +790,7 @@ bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, bool Closed, TEdge* edges) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); #ifdef use_lines if (!Closed && PolyTyp == ptClip) throw clipperException("AddPath: Open paths must be subject."); @@ -954,7 +964,7 @@ bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, b void ClipperBase::Clear() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); m_MinimaList.clear(); m_edges.clear(); m_UseFullRange = false; @@ -966,7 +976,7 @@ void ClipperBase::Clear() // Sort the LML entries, initialize the left / right bound edges of each Local Minima. void ClipperBase::Reset() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); if (m_MinimaList.empty()) return; //ie nothing to process std::sort(m_MinimaList.begin(), m_MinimaList.end(), [](const LocalMinimum& lm1, const LocalMinimum& lm2){ return lm1.Y < lm2.Y; }); @@ -995,7 +1005,7 @@ void ClipperBase::Reset() // Returns (0,0,0,0) for an empty rectangle. IntRect ClipperBase::GetBounds() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); IntRect result; auto lm = m_MinimaList.begin(); if (lm == m_MinimaList.end()) @@ -1056,7 +1066,7 @@ Clipper::Clipper(int initOptions) : void Clipper::Reset() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); ClipperBase::Reset(); m_Scanbeam = std::priority_queue(); m_Maxima.clear(); @@ -1071,7 +1081,7 @@ void Clipper::Reset() bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, PolyFillType clipFillType) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); if (m_HasOpenPaths) throw clipperException("Error: PolyTree struct is needed for open path clipping."); solution.resize(0); @@ -1089,7 +1099,7 @@ bool Clipper::Execute(ClipType clipType, Paths &solution, bool Clipper::Execute(ClipType clipType, PolyTree& polytree, PolyFillType subjFillType, PolyFillType clipFillType) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; @@ -1103,10 +1113,10 @@ bool Clipper::Execute(ClipType clipType, PolyTree& polytree, bool Clipper::ExecuteInternal() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); bool succeeded = true; try { - PROFILE_BLOCK(Clipper_ExecuteInternal_Process); + CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Process); Reset(); if (m_MinimaList.empty()) return true; cInt botY = m_Scanbeam.top(); @@ -1131,13 +1141,13 @@ bool Clipper::ExecuteInternal() if (succeeded) { - PROFILE_BLOCK(Clipper_ExecuteInternal_Fix); + CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Fix); //fix orientations ... //FIXME Vojtech: Does it not invalidate the loop hierarchy maintained as OutRec::FirstLeft pointers? //FIXME Vojtech: The area is calculated with floats, it may not be numerically stable! { - PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_orientations); + CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_orientations); for (OutRec *outRec : m_PolyOuts) if (outRec->Pts && !outRec->IsOpen && (outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) ReversePolyPtLinks(outRec->Pts); @@ -1147,7 +1157,7 @@ bool Clipper::ExecuteInternal() //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() { - PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_fixup); + CLIPPERLIB_PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_fixup); for (OutRec *outRec : m_PolyOuts) if (outRec->Pts) { if (outRec->IsOpen) @@ -1401,7 +1411,7 @@ bool Clipper::IsContributing(const TEdge& edge) const // Called from Clipper::InsertLocalMinimaIntoAEL() and Clipper::IntersectEdges(). OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); OutPt* result; TEdge *e, *prevE; if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) @@ -1493,7 +1503,7 @@ void Clipper::CopyAELToSEL() // Called from Clipper::ExecuteInternal() void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); while (!m_MinimaList.empty() && m_MinimaList.back().Y == botY) { TEdge* lb = m_MinimaList.back().LeftBound; @@ -2043,7 +2053,7 @@ OutPt* Clipper::GetLastOutPt(TEdge *e) void Clipper::ProcessHorizontals() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); TEdge* horzEdge = m_SortedEdges; while(horzEdge) { @@ -2414,7 +2424,7 @@ void Clipper::UpdateEdgeIntoAEL(TEdge *&e) bool Clipper::ProcessIntersections(const cInt topY) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); if( !m_ActiveEdges ) return true; try { BuildIntersectList(topY); @@ -2569,7 +2579,7 @@ void Clipper::DoMaxima(TEdge *e) void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); TEdge* e = m_ActiveEdges; while( e ) { @@ -3177,7 +3187,7 @@ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) // This is potentially very expensive! O(n^3)! void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); //tests if NewOutRec contains the polygon before reassigning FirstLeft for (OutRec *outRec : m_PolyOuts) { @@ -3201,7 +3211,7 @@ void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const void Clipper::JoinCommonEdges() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); for (Join &join : m_Joins) { OutRec *outRec1 = GetOutRec(join.OutPt1->Idx); @@ -3771,7 +3781,7 @@ void ClipperOffset::DoRound(int j, int k) // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/StrictlySimple.htm void Clipper::DoSimplePolygons() { - PROFILE_FUNC(); + CLIPPERLIB_PROFILE_FUNC(); size_t i = 0; while (i < m_PolyOuts.size()) { diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index d40d79b3d8..16d985e9c9 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -8,7 +8,16 @@ #include "SVG.hpp" #endif /* CLIPPER_UTILS_DEBUG */ -#include +// Profiling support using the Shiny intrusive profiler +//#define CLIPPER_UTILS_PROFILE +#if defined(SLIC3R_PROFILE) && defined(CLIPPER_UTILS_PROFILE) + #include + #define CLIPPERUTILS_PROFILE_FUNC() PROFILE_FUNC() + #define CLIPPERUTILS_PROFILE_BLOCK(name) PROFILE_BLOCK(name) +#else + #define CLIPPERUTILS_PROFILE_FUNC() + #define CLIPPERUTILS_PROFILE_BLOCK(name) +#endif #define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f) @@ -50,7 +59,7 @@ err: void scaleClipperPolygon(ClipperLib::Path &polygon) { - PROFILE_FUNC(); + CLIPPERUTILS_PROFILE_FUNC(); for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { pit->X <<= CLIPPER_OFFSET_POWER_OF_2; pit->Y <<= CLIPPER_OFFSET_POWER_OF_2; @@ -59,7 +68,7 @@ void scaleClipperPolygon(ClipperLib::Path &polygon) void scaleClipperPolygons(ClipperLib::Paths &polygons) { - PROFILE_FUNC(); + CLIPPERUTILS_PROFILE_FUNC(); for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { pit->X <<= CLIPPER_OFFSET_POWER_OF_2; @@ -69,7 +78,7 @@ void scaleClipperPolygons(ClipperLib::Paths &polygons) void unscaleClipperPolygon(ClipperLib::Path &polygon) { - PROFILE_FUNC(); + CLIPPERUTILS_PROFILE_FUNC(); for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; @@ -80,7 +89,7 @@ void unscaleClipperPolygon(ClipperLib::Path &polygon) void unscaleClipperPolygons(ClipperLib::Paths &polygons) { - PROFILE_FUNC(); + CLIPPERUTILS_PROFILE_FUNC(); for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; @@ -790,7 +799,7 @@ ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear void safety_offset(ClipperLib::Paths* paths) { - PROFILE_FUNC(); + CLIPPERUTILS_PROFILE_FUNC(); // scale input scaleClipperPolygons(*paths); @@ -812,11 +821,11 @@ void safety_offset(ClipperLib::Paths* paths) if (! ccw) std::reverse(path.begin(), path.end()); { - PROFILE_BLOCK(safety_offset_AddPaths); + CLIPPERUTILS_PROFILE_BLOCK(safety_offset_AddPaths); co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon); } { - PROFILE_BLOCK(safety_offset_Execute); + CLIPPERUTILS_PROFILE_BLOCK(safety_offset_Execute); // offset outside by 10um ClipperLib::Paths out_this; co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE)); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f8598cea08..8a2672c774 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -181,6 +181,7 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp + Utils/Profile.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp Utils/HexFile.cpp diff --git a/src/slic3r/Utils/Profile.hpp b/src/slic3r/Utils/Profile.hpp new file mode 100644 index 0000000000..5fb1e31167 --- /dev/null +++ b/src/slic3r/Utils/Profile.hpp @@ -0,0 +1,19 @@ +#ifndef slic3r_GUI_Profile_hpp_ +#define slic3r_GUI_Profile_hpp_ + +// Profiling support using the Shiny intrusive profiler +//#define SLIC3R_PROFILE_GUI +#if defined(SLIC3R_PROFILE) && defined(SLIC3R_PROFILE_GUI) + #include + #define SLIC3R_GUI_PROFILE_FUNC() PROFILE_FUNC() + #define SLIC3R_GUI_PROFILE_BLOCK(name) PROFILE_BLOCK(name) + #define SLIC3R_GUI_PROFILE_UPDATE() PROFILE_UPDATE() + #define SLIC3R_GUI_PROFILE_OUTPUT(x) PROFILE_OUTPUT(x) +#else + #define SLIC3R_GUI_PROFILE_FUNC() + #define SLIC3R_GUI_PROFILE_BLOCK(name) + #define SLIC3R_GUI_PROFILE_UPDATE() + #define SLIC3R_GUI_PROFILE_OUTPUT(x) +#endif + +#endif // slic3r_GUI_Profile_hpp_ From b28f9b89353aaa7b382ba56adfa0dc737281fc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 27 Aug 2020 13:04:53 +0200 Subject: [PATCH 347/826] Fix discontinuous extrusion lines for adaptive infill --- src/libslic3r/Fill/FillAdaptive.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 96509923c8..a3068989ef 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -88,7 +88,7 @@ void FillAdaptive::generate_polylines( float rotation_angle = Geometry::deg2rad(120.0); - for (int i = 0; i < 3; i++) + for (int i = 0; i < polylines_out.size(); i++) { Vec3d offset = cube->center - origin; Point from_abs(from), to_abs(to); @@ -177,7 +177,7 @@ std::unique_ptr FillAdaptive::build_octree( triangleMesh.require_shared_vertices(); } - Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.0), Geometry::deg2rad(30.0)); + Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.264), Geometry::deg2rad(30.0)); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); From 8af3659e9890fd2c5d146cf9226b7517652278c5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 27 Aug 2020 13:11:28 +0200 Subject: [PATCH 348/826] GCodeViewer -> Fixed generation of solid toolpaths --- src/slic3r/GUI/GCodeViewer.cpp | 59 +++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 64916182ad..8cb18f4e18 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -977,16 +977,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer_indices.push_back(i3); }; - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f up = right.cross(dir); - float prev_half_width = 0.5f * prev.width; - float prev_half_height = 0.5f * prev.height; - float curr_half_width = 0.5f * curr.width; - float curr_half_height = 0.5f * curr.height; - Vec3f prev_pos = Vec3f(prev.position[0], prev.position[1], prev.position[2]) - prev_half_height * up; - Vec3f curr_pos = Vec3f(curr.position[0], curr.position[1], curr.position[2]) - curr_half_height * up; - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id - 1)); buffer.paths.back().first.position = prev.position; @@ -994,17 +984,27 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f up = right.cross(dir); + + float half_width = 0.5f * round_to_nearest(curr.width, 2); + float half_height = 0.5f * round_to_nearest(curr.height, 2); + + Vec3f prev_pos = prev.position - half_height * up; + Vec3f curr_pos = curr.position - half_height * up; + // vertices 1st endpoint - store_vertex(buffer_vertices, prev_pos + prev_half_height * up, up); // top - store_vertex(buffer_vertices, prev_pos + prev_half_width * right, right); // right - store_vertex(buffer_vertices, prev_pos - prev_half_height * up, -up); // bottom - store_vertex(buffer_vertices, prev_pos - prev_half_width * right, -right); // left + store_vertex(buffer_vertices, prev_pos + half_height * up, up); // top + store_vertex(buffer_vertices, prev_pos + half_width * right, right); // right + store_vertex(buffer_vertices, prev_pos - half_height * up, -up); // bottom + store_vertex(buffer_vertices, prev_pos - half_width * right, -right); // left // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + curr_half_height * up, up); // top - store_vertex(buffer_vertices, curr_pos + curr_half_width * right, right); // right - store_vertex(buffer_vertices, curr_pos - curr_half_height * up, -up); // bottom - store_vertex(buffer_vertices, curr_pos - curr_half_width * right, -right); // left + store_vertex(buffer_vertices, curr_pos + half_height * up, up); // top + store_vertex(buffer_vertices, curr_pos + half_width * right, right); // right + store_vertex(buffer_vertices, curr_pos - half_height * up, -up); // bottom + store_vertex(buffer_vertices, curr_pos - half_width * right, -right); // left // triangles starting cap store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); @@ -1104,8 +1104,21 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (const TBuffer& buffer : m_buffers) { m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } - m_statistics.travel_segments_count = indices[buffer_id(EMoveType::Travel)].size() / 2; - m_statistics.extrude_segments_count = indices[buffer_id(EMoveType::Extrude)].size() / 2; + unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); + switch (m_buffers[travel_buffer_id].primitive_type) + { + case TBuffer::EPrimitiveType::Line: { m_statistics.travel_segments_count = indices[travel_buffer_id].size() / 2; break; } + case TBuffer::EPrimitiveType::Triangle: { m_statistics.travel_segments_count = indices[travel_buffer_id].size() / 36; break; } + default: { break; } + } + + unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); + switch (m_buffers[extrude_buffer_id].primitive_type) + { + case TBuffer::EPrimitiveType::Line: { m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / 2; break; } + case TBuffer::EPrimitiveType::Triangle: { m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / 36; break; } + default: { break; } + } #endif // ENABLE_GCODE_VIEWER_STATISTICS // layers zs / roles / extruder ids / cp color ids -> extract from result @@ -2583,17 +2596,17 @@ void GCodeViewer::render_statistics() const ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Indices GPU:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); ImGui::Separator(); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Travel segments count:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.travel_segments_count)); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Extrude segments:")); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Extrude segments count:")); ImGui::SameLine(offset); imgui.text(std::to_string(m_statistics.extrude_segments_count)); From 19e1d877aa0c9d7517a0f5e0aca03838871e962c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 24 Apr 2020 12:46:35 +0200 Subject: [PATCH 349/826] Don't use sla::EncodedRaster in SLAImport, revive opencsg sandbox --- sandboxes/opencsg/CMakeLists.txt | 1 + sandboxes/opencsg/main.cpp | 2 +- src/libslic3r/SLA/AGGRaster.hpp | 9 +++++---- src/slic3r/Utils/SLAImport.cpp | 21 +++++++++++---------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/sandboxes/opencsg/CMakeLists.txt b/sandboxes/opencsg/CMakeLists.txt index ec1f4cae91..ace8f4d539 100644 --- a/sandboxes/opencsg/CMakeLists.txt +++ b/sandboxes/opencsg/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(opencsg_example WIN32 main.cpp Engine.hpp Engine.cpp ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/Jobs/Job.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp ${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp) diff --git a/sandboxes/opencsg/main.cpp b/sandboxes/opencsg/main.cpp index adf9cc457f..f5fb124935 100644 --- a/sandboxes/opencsg/main.cpp +++ b/sandboxes/opencsg/main.cpp @@ -26,7 +26,7 @@ #include "libslic3r/Format/3mf.hpp" #include "libslic3r/SLAPrint.hpp" -#include "slic3r/GUI/Job.hpp" +#include "slic3r/GUI/Jobs/Job.hpp" #include "slic3r/GUI/ProgressStatusBar.hpp" using namespace Slic3r::GL; diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index 37baed9e88..917f718e98 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -128,12 +128,13 @@ protected: } public: - template AGGRaster(const Resolution &res, + template + AGGRaster(const Resolution &res, const PixelDim & pd, const Trafo & trafo, - const TColor & foreground, - const TColor & background, - GammaFn && gammafn) + const TColor & foreground, + const TColor & background, + GammaFn && gammafn) : m_resolution(res) , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) , m_buf(res.pixels()) diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp index 442025a77d..65ec46343d 100644 --- a/src/slic3r/Utils/SLAImport.cpp +++ b/src/slic3r/Utils/SLAImport.cpp @@ -43,9 +43,11 @@ namespace Slic3r { namespace { +struct PNGBuffer { std::vector buf; std::string fname; }; + struct ArchiveData { boost::property_tree::ptree profile, config; - std::vector images; + std::vector images; }; static const constexpr char *CONFIG_FNAME = "config.ini"; @@ -66,7 +68,7 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, return tree; } -sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry, +PNGBuffer read_png(const mz_zip_archive_file_stat &entry, MZ_Archive & zip, const std::string & name) { @@ -75,9 +77,8 @@ sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry, if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, buf.data(), buf.size(), 0)) throw std::runtime_error(zip.get_errorstr()); - - return sla::EncodedRaster(std::move(buf), - name.empty() ? entry.m_filename : name); + + return {std::move(buf), (name.empty() ? entry.m_filename : name)}; } ArchiveData extract_sla_archive(const std::string &zipfname, @@ -113,9 +114,9 @@ ArchiveData extract_sla_archive(const std::string &zipfname, if (boost::filesystem::path(name).extension().string() == ".png") { auto it = std::lower_bound( - arch.images.begin(), arch.images.end(), sla::EncodedRaster({}, name), - [](const sla::EncodedRaster &r1, const sla::EncodedRaster &r2) { - return std::less()(r1.extension(), r2.extension()); + arch.images.begin(), arch.images.end(), PNGBuffer{{}, name}, + [](const PNGBuffer &r1, const PNGBuffer &r2) { + return std::less()(r1.fname, r2.fname); }); arch.images.insert(it, read_png(entry, zip, name)); @@ -258,8 +259,8 @@ std::vector extract_slices_from_sla_archive( } } - auto &buf = arch.images[i]; - wxMemoryInputStream stream{buf.data(), buf.size()}; + PNGBuffer &png = arch.images[i]; + wxMemoryInputStream stream{png.buf.data(), png.buf.size()}; wxImage img{stream}; auto rings = marchsq::execute(img, 128, rstp.win); From 2bcd36d155d0fce0041faac6a89a3fe211830041 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 27 Apr 2020 18:43:47 +0200 Subject: [PATCH 350/826] PNG image read with libpng --- src/libslic3r/CMakeLists.txt | 4 +++ src/libslic3r/PNGRead.cpp | 59 +++++++++++++++++++++++++++++++++ src/libslic3r/PNGRead.hpp | 30 +++++++++++++++++ src/slic3r/Utils/SLAImport.cpp | 32 +++++++++--------- tests/libslic3r/CMakeLists.txt | 2 ++ tests/libslic3r/test_png_io.cpp | 52 +++++++++++++++++++++++++++++ 6 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 src/libslic3r/PNGRead.cpp create mode 100644 src/libslic3r/PNGRead.hpp create mode 100644 tests/libslic3r/test_png_io.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 290b8953cc..aea3247220 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -161,6 +161,8 @@ add_library(libslic3r STATIC PrintConfig.hpp PrintObject.cpp PrintRegion.cpp + PNGRead.hpp + PNGRead.cpp Semver.cpp ShortestPath.cpp ShortestPath.hpp @@ -308,6 +310,8 @@ target_link_libraries(libslic3r TBB::tbb libslic3r_cgal ${CMAKE_DL_LIBS} + PNG::PNG + ZLIB::ZLIB ) if (TARGET OpenVDB::openvdb) diff --git a/src/libslic3r/PNGRead.cpp b/src/libslic3r/PNGRead.cpp new file mode 100644 index 0000000000..8bfa3cb951 --- /dev/null +++ b/src/libslic3r/PNGRead.cpp @@ -0,0 +1,59 @@ +#include "PNGRead.hpp" + +#include + +#include +#include + +namespace Slic3r { namespace png { + +struct png_deleter { void operator()(png_struct *p) { + png_destroy_read_struct( &p, nullptr, nullptr); } +}; + +using png_ptr_t = std::unique_ptr; + +bool is_png(const ReadBuf &rb) +{ + static const constexpr int PNG_SIG_BYTES = 8; + + return rb.sz >= PNG_SIG_BYTES && + !png_sig_cmp(static_cast(rb.buf), 0, PNG_SIG_BYTES); +} + +bool decode_png(const ReadBuf &rb, ImageGreyscale &img) +{ + if (!is_png(rb)) return false; + + png_ptr_t png{png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr)}; + + if(!png) return false; + + png_infop info = png_create_info_struct(png.get()); + if(!info) return {}; + + FILE *io = ::fmemopen(const_cast(rb.buf), rb.sz, "rb"); + png_init_io(png.get(), io); + + png_read_info(png.get(), info); + + img.cols = png_get_image_width(png.get(), info); + img.rows = png_get_image_height(png.get(), info); + size_t color_type = png_get_color_type(png.get(), info); + size_t bit_depth = png_get_bit_depth(png.get(), info); + + if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8) + return false; + + img.buf.resize(img.rows * img.cols); + + auto readbuf = static_cast(img.buf.data()); + for (size_t r = 0; r < img.rows; ++r) + png_read_row(png.get(), readbuf + r * img.cols, nullptr); + + fclose(io); + + return true; +} + +}} diff --git a/src/libslic3r/PNGRead.hpp b/src/libslic3r/PNGRead.hpp new file mode 100644 index 0000000000..88a948395b --- /dev/null +++ b/src/libslic3r/PNGRead.hpp @@ -0,0 +1,30 @@ +#ifndef PNGREAD_HPP +#define PNGREAD_HPP + +#include +#include + +namespace Slic3r { namespace png { + +struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; }; + +template struct Image { + std::vector buf; + size_t rows, cols; + PxT get(size_t row, size_t col) const { return buf[row * cols + col]; } +}; + +struct RGB { uint8_t r, g, b; }; + +using ImageRGB = Image; +using ImageGreyscale = Image; + +// TODO +// bool decode_png(Buffer &&pngbuf, ImageRGB &img); + +bool is_png(const ReadBuf &pngbuf); + +bool decode_png(const ReadBuf &pngbuf, ImageGreyscale &img); + +}} +#endif // PNGREAD_HPP diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp index 65ec46343d..13ea603396 100644 --- a/src/slic3r/Utils/SLAImport.cpp +++ b/src/slic3r/Utils/SLAImport.cpp @@ -9,32 +9,31 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/SLA/RasterBase.hpp" #include "libslic3r/miniz_extension.hpp" +#include "libslic3r/PNGRead.hpp" #include #include #include -#include #include namespace marchsq { -// Specialize this struct to register a raster type for the Marching squares alg -template<> struct _RasterTraits { - using Rst = wxImage; - +template<> struct _RasterTraits { + using Rst = Slic3r::png::ImageGreyscale; + // The type of pixel cell in the raster using ValueType = uint8_t; - + // Value at a given position static uint8_t get(const Rst &rst, size_t row, size_t col) { - return rst.GetRed(col, row); + return rst.get(row, col); } // Number of rows and cols of the raster - static size_t rows(const Rst &rst) { return rst.GetHeight(); } - static size_t cols(const Rst &rst) { return rst.GetWidth(); } + static size_t rows(const Rst &rst) { return rst.rows; } + static size_t cols(const Rst &rst) { return rst.cols; } }; } // namespace marchsq @@ -44,7 +43,6 @@ namespace Slic3r { namespace { struct PNGBuffer { std::vector buf; std::string fname; }; - struct ArchiveData { boost::property_tree::ptree profile, config; std::vector images; @@ -69,8 +67,8 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, } PNGBuffer read_png(const mz_zip_archive_file_stat &entry, - MZ_Archive & zip, - const std::string & name) + MZ_Archive & zip, + const std::string & name) { std::vector buf(entry.m_uncomp_size); @@ -259,9 +257,13 @@ std::vector extract_slices_from_sla_archive( } } - PNGBuffer &png = arch.images[i]; - wxMemoryInputStream stream{png.buf.data(), png.buf.size()}; - wxImage img{stream}; +// PNGBuffer &png = arch.images[i]; +// wxMemoryInputStream stream{png.buf.data(), png.buf.size()}; +// wxImage img{stream}; + + png::ImageGreyscale img; + png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()}; + png::decode_png(rb, img); auto rings = marchsq::execute(img, 128, rstp.win); ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 5a1e8f18b7..30b93eafc9 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(${_TEST_NAME}_tests test_marchingsquares.cpp test_timeutils.cpp test_voronoi.cpp + test_png_io.cpp + test_timeutils.cpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp new file mode 100644 index 0000000000..3378d0062e --- /dev/null +++ b/tests/libslic3r/test_png_io.cpp @@ -0,0 +1,52 @@ +#define NOMINMAX +#include + +#include "libslic3r/PNGRead.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" + +using namespace Slic3r; + +static sla::RasterGrayscaleAA create_raster(const sla::RasterBase::Resolution &res) +{ + sla::RasterBase::PixelDim pixdim{1., 1.}; + + auto bb = BoundingBox({0, 0}, {scaled(1.), scaled(1.)}); + sla::RasterBase::Trafo trafo; + trafo.center_x = bb.center().x(); + trafo.center_y = bb.center().y(); + + return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)}; +} + +TEST_CASE("PNG read", "[PNG]") { + auto rst = create_raster({100, 100}); + + size_t rstsum = 0; + for (size_t r = 0; r < rst.resolution().height_px; ++r) + for (size_t c = 0; c < rst.resolution().width_px; ++c) + rstsum += rst.read_pixel(c, r); + + SECTION("Correct png buffer should be recognized as such.") { + auto enc_rst = rst.encode(sla::PNGRasterEncoder{}); + REQUIRE(Slic3r::png::is_png({enc_rst.data(), enc_rst.size()})); + } + + SECTION("Fake png buffer should be recognized as such.") { + std::vector fake(10, '\0'); + REQUIRE(!Slic3r::png::is_png({fake.data(), fake.size()})); + } + + SECTION("Decoded PNG buffer resolution should match the original") { + auto enc_rst = rst.encode(sla::PNGRasterEncoder{}); + + png::ImageGreyscale img; + png::decode_png({enc_rst.data(), enc_rst.size()}, img); + + REQUIRE(img.rows == rst.resolution().height_px); + REQUIRE(img.cols == rst.resolution().width_px); + + size_t sum = std::accumulate(img.buf.begin(), img.buf.end(), size_t(0)); + + REQUIRE(sum == rstsum); + } +} From 769ee15475a3edc10c8dd29db8e853675091677e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 28 Apr 2020 20:21:29 +0200 Subject: [PATCH 351/826] Move SLA import to libslic3r with png reading using libpng Also fix flipped object issue --- src/libslic3r/Format/SL1.cpp | 306 ++++++++++++++++++++++++++ src/libslic3r/Format/SL1.hpp | 18 ++ src/libslic3r/PNGRead.cpp | 41 ++-- src/libslic3r/PNGRead.hpp | 10 +- src/slic3r/CMakeLists.txt | 2 - src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 3 +- src/slic3r/Utils/SLAImport.cpp | 317 --------------------------- src/slic3r/Utils/SLAImport.hpp | 35 --- 9 files changed, 359 insertions(+), 375 deletions(-) delete mode 100644 src/slic3r/Utils/SLAImport.cpp delete mode 100644 src/slic3r/Utils/SLAImport.hpp diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 5c402ef5bf..3d672eccb4 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -8,8 +8,314 @@ #include "libslic3r/Zipper.hpp" #include "libslic3r/SLAPrint.hpp" +#include + +#include "libslic3r/SlicesToTriangleMesh.hpp" +#include "libslic3r/MarchingSquares.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SLA/RasterBase.hpp" +#include "libslic3r/miniz_extension.hpp" +#include "libslic3r/PNGRead.hpp" + +#include +#include +#include + +namespace marchsq { + +template<> struct _RasterTraits { + using Rst = Slic3r::png::ImageGreyscale; + + // The type of pixel cell in the raster + using ValueType = uint8_t; + + // Value at a given position + static uint8_t get(const Rst &rst, size_t row, size_t col) + { + return rst.get(row, col); + } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.rows; } + static size_t cols(const Rst &rst) { return rst.cols; } +}; + +} // namespace marchsq + namespace Slic3r { +namespace { + +struct PNGBuffer { std::vector buf; std::string fname; }; +struct ArchiveData { + boost::property_tree::ptree profile, config; + std::vector images; +}; + +static const constexpr char *CONFIG_FNAME = "config.ini"; +static const constexpr char *PROFILE_FNAME = "prusaslicer.ini"; + +boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, + MZ_Archive & zip) +{ + std::string buf(size_t(entry.m_uncomp_size), '\0'); + + if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, + buf.data(), buf.size(), 0)) + throw std::runtime_error(zip.get_errorstr()); + + boost::property_tree::ptree tree; + std::stringstream ss(buf); + boost::property_tree::read_ini(ss, tree); + return tree; +} + +PNGBuffer read_png(const mz_zip_archive_file_stat &entry, + MZ_Archive & zip, + const std::string & name) +{ + std::vector buf(entry.m_uncomp_size); + + if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, + buf.data(), buf.size(), 0)) + throw std::runtime_error(zip.get_errorstr()); + + return {std::move(buf), (name.empty() ? entry.m_filename : name)}; +} + +ArchiveData extract_sla_archive(const std::string &zipfname, + const std::string &exclude) +{ + ArchiveData arch; + + // Little RAII + struct Arch: public MZ_Archive { + Arch(const std::string &fname) { + if (!open_zip_reader(&arch, fname)) + throw std::runtime_error(get_errorstr()); + } + + ~Arch() { close_zip_reader(&arch); } + } zip (zipfname); + + mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch); + + for (mz_uint i = 0; i < num_entries; ++i) + { + mz_zip_archive_file_stat entry; + + if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) + { + std::string name = entry.m_filename; + boost::algorithm::to_lower(name); + + if (boost::algorithm::contains(name, exclude)) continue; + + if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip); + if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip); + + if (boost::filesystem::path(name).extension().string() == ".png") { + auto it = std::lower_bound( + arch.images.begin(), arch.images.end(), PNGBuffer{{}, name}, + [](const PNGBuffer &r1, const PNGBuffer &r2) { + return std::less()(r1.fname, r2.fname); + }); + + arch.images.insert(it, read_png(entry, zip, name)); + } + } + } + + return arch; +} + +ExPolygons rings_to_expolygons(const std::vector &rings, + double px_w, double px_h) +{ + ExPolygons polys; polys.reserve(rings.size()); + + for (const marchsq::Ring &ring : rings) { + Polygon poly; Points &pts = poly.points; + pts.reserve(ring.size()); + + for (const marchsq::Coord &crd : ring) + pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); + + polys.emplace_back(poly); + } + + // reverse the raster transformations + return union_ex(polys); +} + +template void foreach_vertex(ExPolygon &poly, Fn &&fn) +{ + for (auto &p : poly.contour.points) fn(p); + for (auto &h : poly.holes) + for (auto &p : h.points) fn(p); +} + +void invert_raster_trafo(ExPolygons & expolys, + const sla::RasterBase::Trafo &trafo, + coord_t width, + coord_t height) +{ + for (auto &expoly : expolys) { + if (trafo.mirror_y) + foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); + + if (trafo.mirror_x) + foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); + + expoly.translate(-trafo.center_x, -trafo.center_y); + + if (trafo.flipXY) + foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); + + if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) { + expoly.contour.reverse(); + for (auto &h : expoly.holes) h.reverse(); + } + } +} + +struct RasterParams { + sla::RasterBase::Trafo trafo; // Raster transformations + coord_t width, height; // scaled raster dimensions (not resolution) + double px_h, px_w; // pixel dimesions + marchsq::Coord win; // marching squares window size +}; + +RasterParams get_raster_params(const DynamicPrintConfig &cfg) +{ + auto *opt_disp_cols = cfg.option("display_pixels_x"); + auto *opt_disp_rows = cfg.option("display_pixels_y"); + auto *opt_disp_w = cfg.option("display_width"); + auto *opt_disp_h = cfg.option("display_height"); + auto *opt_mirror_x = cfg.option("display_mirror_x"); + auto *opt_mirror_y = cfg.option("display_mirror_y"); + auto *opt_orient = cfg.option>("display_orientation"); + + if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || + !opt_mirror_x || !opt_mirror_y || !opt_orient) + throw std::runtime_error("Invalid SL1 file"); + + RasterParams rstp; + + rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1); + rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1); + + rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ? + sla::RasterBase::roLandscape : + sla::RasterBase::roPortrait, + {opt_mirror_x->value, opt_mirror_y->value}}; + + rstp.height = scaled(opt_disp_h->value); + rstp.width = scaled(opt_disp_w->value); + + return rstp; +} + +struct SliceParams { double layerh = 0., initial_layerh = 0.; }; + +SliceParams get_slice_params(const DynamicPrintConfig &cfg) +{ + auto *opt_layerh = cfg.option("layer_height"); + auto *opt_init_layerh = cfg.option("initial_layer_height"); + + if (!opt_layerh || !opt_init_layerh) + throw std::runtime_error("Invalid SL1 file"); + + return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; +} + +std::vector extract_slices_from_sla_archive( + ArchiveData & arch, + const RasterParams & rstp, + std::function progr) +{ + auto jobdir = arch.config.get("jobDir"); + for (auto &c : jobdir) c = std::tolower(c); + + std::vector slices(arch.images.size()); + + struct Status + { + double incr, val, prev; + bool stop = false; + tbb::spin_mutex mutex; + } st {100. / slices.size(), 0., 0.}; + + tbb::parallel_for(size_t(0), arch.images.size(), + [&arch, &slices, &st, &rstp, progr](size_t i) { + // Status indication guarded with the spinlock + { + std::lock_guard lck(st.mutex); + if (st.stop) return; + + st.val += st.incr; + double curr = std::round(st.val); + if (curr > st.prev) { + st.prev = curr; + st.stop = !progr(int(curr)); + } + } + + png::ImageGreyscale img; + png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()}; + if (!png::decode_png(rb, img)) return; + + auto rings = marchsq::execute(img, 128, rstp.win); + ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h); + + // Invert the raster transformations indicated in + // the profile metadata + invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height); + + slices[i] = std::move(expolys); + }); + + if (st.stop) slices = {}; + + return slices; +} + +} // namespace + +void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) +{ + ArchiveData arch = extract_sla_archive(zipfname, "png"); + out.load(arch.profile); +} + +void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + DynamicPrintConfig & profile, + std::function progr) +{ + // Ensure minimum window size for marching squares + windowsize.x() = std::max(2, windowsize.x()); + windowsize.y() = std::max(2, windowsize.y()); + + ArchiveData arch = extract_sla_archive(zipfname, "thumbnail"); + profile.load(arch.profile); + + RasterParams rstp = get_raster_params(profile); + rstp.win = {windowsize.y(), windowsize.x()}; + + SliceParams slicp = get_slice_params(profile); + + std::vector slices = + extract_slices_from_sla_archive(arch, rstp, progr); + + if (!slices.empty()) + out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); +} + using ConfMap = std::map; namespace { diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index fbb6d61604..ab731ff841 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -38,6 +38,24 @@ public: } }; +void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); + +void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + DynamicPrintConfig & profile, + std::function progr = [](int) { return true; }); + +inline void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + std::function progr = [](int) { return true; }) +{ + DynamicPrintConfig profile; + import_sla_archive(zipfname, windowsize, out, profile, progr); +} } // namespace Slic3r::sla diff --git a/src/libslic3r/PNGRead.cpp b/src/libslic3r/PNGRead.cpp index 8bfa3cb951..6eb7d593cc 100644 --- a/src/libslic3r/PNGRead.cpp +++ b/src/libslic3r/PNGRead.cpp @@ -7,11 +7,21 @@ namespace Slic3r { namespace png { -struct png_deleter { void operator()(png_struct *p) { - png_destroy_read_struct( &p, nullptr, nullptr); } -}; +struct PNGDescr { + png_struct *png = nullptr; png_info *info = nullptr; -using png_ptr_t = std::unique_ptr; + PNGDescr() = default; + PNGDescr(const PNGDescr&) = delete; + PNGDescr(PNGDescr&&) = delete; + PNGDescr& operator=(const PNGDescr&) = delete; + PNGDescr& operator=(PNGDescr&&) = delete; + + ~PNGDescr() + { + if (png && info) png_destroy_info_struct(png, &info); + if (png) png_destroy_read_struct( &png, nullptr, nullptr); + } +}; bool is_png(const ReadBuf &rb) { @@ -25,22 +35,23 @@ bool decode_png(const ReadBuf &rb, ImageGreyscale &img) { if (!is_png(rb)) return false; - png_ptr_t png{png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr)}; + PNGDescr dsc; + dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if(!png) return false; + if(!dsc.png) return false; - png_infop info = png_create_info_struct(png.get()); - if(!info) return {}; + dsc.info = png_create_info_struct(dsc.png); + if(!dsc.info) return {}; FILE *io = ::fmemopen(const_cast(rb.buf), rb.sz, "rb"); - png_init_io(png.get(), io); + png_init_io(dsc.png, io); - png_read_info(png.get(), info); + png_read_info(dsc.png, dsc.info); - img.cols = png_get_image_width(png.get(), info); - img.rows = png_get_image_height(png.get(), info); - size_t color_type = png_get_color_type(png.get(), info); - size_t bit_depth = png_get_bit_depth(png.get(), info); + img.cols = png_get_image_width(dsc.png, dsc.info); + img.rows = png_get_image_height(dsc.png, dsc.info); + size_t color_type = png_get_color_type(dsc.png, dsc.info); + size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info); if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8) return false; @@ -49,7 +60,7 @@ bool decode_png(const ReadBuf &rb, ImageGreyscale &img) auto readbuf = static_cast(img.buf.data()); for (size_t r = 0; r < img.rows; ++r) - png_read_row(png.get(), readbuf + r * img.cols, nullptr); + png_read_row(dsc.png, readbuf + r * img.cols, nullptr); fclose(io); diff --git a/src/libslic3r/PNGRead.hpp b/src/libslic3r/PNGRead.hpp index 88a948395b..0e7311f2e5 100644 --- a/src/libslic3r/PNGRead.hpp +++ b/src/libslic3r/PNGRead.hpp @@ -19,12 +19,14 @@ struct RGB { uint8_t r, g, b; }; using ImageRGB = Image; using ImageGreyscale = Image; +bool is_png(const ReadBuf &pngbuf); + +// Only decodes true 8 bit grayscale png images. Returns false for other formats +// TODO: implement transformation of rgb images into grayscale... +bool decode_png(const ReadBuf &pngbuf, ImageGreyscale &img); + // TODO // bool decode_png(Buffer &&pngbuf, ImageRGB &img); -bool is_png(const ReadBuf &pngbuf); - -bool decode_png(const ReadBuf &pngbuf, ImageGreyscale &img); - }} #endif // PNGREAD_HPP diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 8a2672c774..b64cfdf4fc 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -187,8 +187,6 @@ set(SLIC3R_GUI_SOURCES Utils/HexFile.cpp Utils/HexFile.hpp Utils/Thread.hpp - Utils/SLAImport.hpp - Utils/SLAImport.cpp ) if (APPLE) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index aa5264b07d..b59631d674 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -294,7 +294,7 @@ public: void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_shape_object(const std::string &type_name); - void load_mesh_object(const TriangleMesh &mesh, const wxString &name); + void load_mesh_object(const TriangleMesh &mesh, const wxString &name); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 2d5d5b0729..ae92bd6989 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -1,10 +1,11 @@ #include "SLAImportJob.hpp" +#include "libslic3r/Format/SL1.hpp" + #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/Utils/SLAImport.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp deleted file mode 100644 index 13ea603396..0000000000 --- a/src/slic3r/Utils/SLAImport.cpp +++ /dev/null @@ -1,317 +0,0 @@ -#include "SLAImport.hpp" - -#include - -#include "libslic3r/SlicesToTriangleMesh.hpp" -#include "libslic3r/MarchingSquares.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/MTUtils.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/SLA/RasterBase.hpp" -#include "libslic3r/miniz_extension.hpp" -#include "libslic3r/PNGRead.hpp" - -#include -#include -#include - -#include - -namespace marchsq { - -template<> struct _RasterTraits { - using Rst = Slic3r::png::ImageGreyscale; - - // The type of pixel cell in the raster - using ValueType = uint8_t; - - // Value at a given position - static uint8_t get(const Rst &rst, size_t row, size_t col) - { - return rst.get(row, col); - } - - // Number of rows and cols of the raster - static size_t rows(const Rst &rst) { return rst.rows; } - static size_t cols(const Rst &rst) { return rst.cols; } -}; - -} // namespace marchsq - -namespace Slic3r { - -namespace { - -struct PNGBuffer { std::vector buf; std::string fname; }; -struct ArchiveData { - boost::property_tree::ptree profile, config; - std::vector images; -}; - -static const constexpr char *CONFIG_FNAME = "config.ini"; -static const constexpr char *PROFILE_FNAME = "prusaslicer.ini"; - -boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, - MZ_Archive & zip) -{ - std::string buf(size_t(entry.m_uncomp_size), '\0'); - - if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, - buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); - - boost::property_tree::ptree tree; - std::stringstream ss(buf); - boost::property_tree::read_ini(ss, tree); - return tree; -} - -PNGBuffer read_png(const mz_zip_archive_file_stat &entry, - MZ_Archive & zip, - const std::string & name) -{ - std::vector buf(entry.m_uncomp_size); - - if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, - buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); - - return {std::move(buf), (name.empty() ? entry.m_filename : name)}; -} - -ArchiveData extract_sla_archive(const std::string &zipfname, - const std::string &exclude) -{ - ArchiveData arch; - - // Little RAII - struct Arch: public MZ_Archive { - Arch(const std::string &fname) { - if (!open_zip_reader(&arch, fname)) - throw std::runtime_error(get_errorstr()); - } - - ~Arch() { close_zip_reader(&arch); } - } zip (zipfname); - - mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch); - - for (mz_uint i = 0; i < num_entries; ++i) - { - mz_zip_archive_file_stat entry; - - if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) - { - std::string name = entry.m_filename; - boost::algorithm::to_lower(name); - - if (boost::algorithm::contains(name, exclude)) continue; - - if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip); - if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip); - - if (boost::filesystem::path(name).extension().string() == ".png") { - auto it = std::lower_bound( - arch.images.begin(), arch.images.end(), PNGBuffer{{}, name}, - [](const PNGBuffer &r1, const PNGBuffer &r2) { - return std::less()(r1.fname, r2.fname); - }); - - arch.images.insert(it, read_png(entry, zip, name)); - } - } - } - - return arch; -} - -ExPolygons rings_to_expolygons(const std::vector &rings, - double px_w, double px_h) -{ - ExPolygons polys; polys.reserve(rings.size()); - - for (const marchsq::Ring &ring : rings) { - Polygon poly; Points &pts = poly.points; - pts.reserve(ring.size()); - - for (const marchsq::Coord &crd : ring) - pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); - - polys.emplace_back(poly); - } - - // reverse the raster transformations - return union_ex(polys); -} - -template void foreach_vertex(ExPolygon &poly, Fn &&fn) -{ - for (auto &p : poly.contour.points) fn(p); - for (auto &h : poly.holes) - for (auto &p : h.points) fn(p); -} - -void invert_raster_trafo(ExPolygons & expolys, - const sla::RasterBase::Trafo &trafo, - coord_t width, - coord_t height) -{ - for (auto &expoly : expolys) { - if (trafo.mirror_y) - foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); - - if (trafo.mirror_x) - foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); - - expoly.translate(-trafo.center_x, -trafo.center_y); - - if (trafo.flipXY) - foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); - - if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) { - expoly.contour.reverse(); - for (auto &h : expoly.holes) h.reverse(); - } - } -} - -struct RasterParams { - sla::RasterBase::Trafo trafo; // Raster transformations - coord_t width, height; // scaled raster dimensions (not resolution) - double px_h, px_w; // pixel dimesions - marchsq::Coord win; // marching squares window size -}; - -RasterParams get_raster_params(const DynamicPrintConfig &cfg) -{ - auto *opt_disp_cols = cfg.option("display_pixels_x"); - auto *opt_disp_rows = cfg.option("display_pixels_y"); - auto *opt_disp_w = cfg.option("display_width"); - auto *opt_disp_h = cfg.option("display_height"); - auto *opt_mirror_x = cfg.option("display_mirror_x"); - auto *opt_mirror_y = cfg.option("display_mirror_y"); - auto *opt_orient = cfg.option>("display_orientation"); - - if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || - !opt_mirror_x || !opt_mirror_y || !opt_orient) - throw std::runtime_error("Invalid SL1 file"); - - RasterParams rstp; - - rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1); - rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1); - - sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ? - sla::RasterBase::roLandscape : - sla::RasterBase::roPortrait, - {opt_mirror_x->value, opt_mirror_y->value}}; - - rstp.height = scaled(opt_disp_h->value); - rstp.width = scaled(opt_disp_w->value); - - return rstp; -} - -struct SliceParams { double layerh = 0., initial_layerh = 0.; }; - -SliceParams get_slice_params(const DynamicPrintConfig &cfg) -{ - auto *opt_layerh = cfg.option("layer_height"); - auto *opt_init_layerh = cfg.option("initial_layer_height"); - - if (!opt_layerh || !opt_init_layerh) - throw std::runtime_error("Invalid SL1 file"); - - return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; -} - -std::vector extract_slices_from_sla_archive( - ArchiveData & arch, - const RasterParams & rstp, - std::function progr) -{ - auto jobdir = arch.config.get("jobDir"); - for (auto &c : jobdir) c = std::tolower(c); - - std::vector slices(arch.images.size()); - - struct Status - { - double incr, val, prev; - bool stop = false; - tbb::spin_mutex mutex; - } st {100. / slices.size(), 0., 0.}; - - tbb::parallel_for(size_t(0), arch.images.size(), - [&arch, &slices, &st, &rstp, progr](size_t i) { - // Status indication guarded with the spinlock - { - std::lock_guard lck(st.mutex); - if (st.stop) return; - - st.val += st.incr; - double curr = std::round(st.val); - if (curr > st.prev) { - st.prev = curr; - st.stop = !progr(int(curr)); - } - } - -// PNGBuffer &png = arch.images[i]; -// wxMemoryInputStream stream{png.buf.data(), png.buf.size()}; -// wxImage img{stream}; - - png::ImageGreyscale img; - png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()}; - png::decode_png(rb, img); - - auto rings = marchsq::execute(img, 128, rstp.win); - ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h); - - // Invert the raster transformations indicated in - // the profile metadata - invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height); - - slices[i] = std::move(expolys); - }); - - if (st.stop) slices = {}; - - return slices; -} - -} // namespace - -void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) -{ - ArchiveData arch = extract_sla_archive(zipfname, "png"); - out.load(arch.profile); -} - -void import_sla_archive( - const std::string & zipfname, - Vec2i windowsize, - TriangleMesh & out, - DynamicPrintConfig & profile, - std::function progr) -{ - // Ensure minimum window size for marching squares - windowsize.x() = std::max(2, windowsize.x()); - windowsize.y() = std::max(2, windowsize.y()); - - ArchiveData arch = extract_sla_archive(zipfname, "thumbnail"); - profile.load(arch.profile); - - RasterParams rstp = get_raster_params(profile); - rstp.win = {windowsize.y(), windowsize.x()}; - - SliceParams slicp = get_slice_params(profile); - - std::vector slices = - extract_slices_from_sla_archive(arch, rstp, progr); - - if (!slices.empty()) - out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); -} - -} // namespace Slic3r diff --git a/src/slic3r/Utils/SLAImport.hpp b/src/slic3r/Utils/SLAImport.hpp deleted file mode 100644 index 73995014f4..0000000000 --- a/src/slic3r/Utils/SLAImport.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef SLAIMPORT_HPP -#define SLAIMPORT_HPP - -#include - -#include -#include - -namespace Slic3r { - -class TriangleMesh; -class DynamicPrintConfig; - -void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); - -void import_sla_archive( - const std::string & zipfname, - Vec2i windowsize, - TriangleMesh & out, - DynamicPrintConfig & profile, - std::function progr = [](int) { return true; }); - -inline void import_sla_archive( - const std::string & zipfname, - Vec2i windowsize, - TriangleMesh & out, - std::function progr = [](int) { return true; }) -{ - DynamicPrintConfig profile; - import_sla_archive(zipfname, windowsize, out, profile, progr); -} - -} - -#endif // SLAIMPORT_HPP From 8541ce406069e2b710b22bec937d3405ed0e8316 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 29 Apr 2020 16:26:39 +0200 Subject: [PATCH 352/826] SLA archive import will now recover the model's original position. --- CMakeLists.txt | 2 +- src/libslic3r/Format/SL1.cpp | 2 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 19 ++++++++++++------- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 6 ++++-- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48ad5033e0..a4be182e7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -386,7 +386,7 @@ if (NOT EXPAT_FOUND) set(EXPAT_LIBRARIES expat) endif () -find_package(PNG) +find_package(PNG REQUIRED) find_package(OpenGL REQUIRED) diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 3d672eccb4..ff1af5d8b1 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -162,6 +162,8 @@ void invert_raster_trafo(ExPolygons & expolys, coord_t width, coord_t height) { + if (trafo.flipXY) std::swap(height, width); + for (auto &expoly : expolys) { if (trafo.mirror_y) foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a326eea7b3..44b2b6186d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2209,7 +2209,7 @@ void ObjectList::load_shape_object(const std::string& type_name) load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name)); } -void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name) +void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center) { // Add mesh to model as a new object Model& model = wxGetApp().plater()->model(); @@ -2219,6 +2219,7 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name #endif /* _DEBUG */ std::vector object_idxs; + auto bb = mesh.bounding_box(); ModelObject* new_object = model.add_object(); new_object->name = into_u8(name); new_object->add_instance(); // each object should have at list one instance @@ -2228,13 +2229,17 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_object->invalidate_bounding_box(); - - new_object->center_around_origin(); + new_object->translate(-bb.center()); + + if (center) { + const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); + new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -new_object->origin_translation(2))); + } else { + new_object->instances[0]->set_offset(bb.center()); + } + new_object->ensure_on_bed(); - - const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); - new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -new_object->origin_translation(2))); - + object_idxs.push_back(model.objects.size() - 1); #ifdef _DEBUG check_model_ids_validity(model); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b59631d674..9f7dcd2475 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -294,7 +294,7 @@ public: void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_shape_object(const std::string &type_name); - void load_mesh_object(const TriangleMesh &mesh, const wxString &name); + void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index ae92bd6989..adecae6ac0 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -219,8 +219,10 @@ void SLAImportJob::finalize() wxGetApp().load_current_presets(); } - if (!p->mesh.empty()) - p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name); + if (!p->mesh.empty()) { + bool is_centered = false; + p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name, is_centered); + } reset(); } From 1560e15ed90adfea2d28077f3b4b4a57ab293409 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 3 Aug 2020 15:27:58 +0200 Subject: [PATCH 353/826] Add missing includes for win --- tests/libslic3r/test_png_io.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp index 3378d0062e..51f94be326 100644 --- a/tests/libslic3r/test_png_io.cpp +++ b/tests/libslic3r/test_png_io.cpp @@ -1,8 +1,11 @@ #define NOMINMAX #include +#include + #include "libslic3r/PNGRead.hpp" #include "libslic3r/SLA/AGGRaster.hpp" +#include "libslic3r/BoundingBox.hpp" using namespace Slic3r; From 3f41e4023d040777bab5b754c5423769efd52c57 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 3 Aug 2020 15:50:05 +0200 Subject: [PATCH 354/826] Try to override mac library search order to find static dep libs --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a4be182e7e..1d4576c37d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,11 @@ option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") +if (APPLE) + set(CMAKE_FIND_FRAMEWORK LAST) + set(CMAKE_FIND_APPBUNDLE LAST) +endif () + # Proposal for C++ unit tests and sandboxes option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF) option(SLIC3R_BUILD_TESTS "Build unit tests" ON) From b09552e56faced8580fde7c4648a5116ae578d61 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 3 Aug 2020 18:00:32 +0200 Subject: [PATCH 355/826] Don't use fmemopen, its not standard. --- src/libslic3r/PNGRead.cpp | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PNGRead.cpp b/src/libslic3r/PNGRead.cpp index 6eb7d593cc..695ceba3dc 100644 --- a/src/libslic3r/PNGRead.cpp +++ b/src/libslic3r/PNGRead.cpp @@ -31,20 +31,46 @@ bool is_png(const ReadBuf &rb) !png_sig_cmp(static_cast(rb.buf), 0, PNG_SIG_BYTES); } +// A wrapper around ReadBuf to be read repeatedly like a stream. libpng needs +// this form for its buffer read callback. +struct ReadBufReader { + const ReadBuf &rdbuf; size_t pos; + ReadBufReader(const ReadBuf &rd): rdbuf{rd}, pos{0} {} +}; + +// Buffer read callback for libpng. It provides an allocated output buffer and +// the amount of data it desires to read from the input. +void png_read_callback(png_struct *png_ptr, + png_bytep outBytes, + png_size_t byteCountToRead) +{ + // Retrieve our input buffer through the png_ptr + auto reader = static_cast(png_get_io_ptr(png_ptr)); + + if (!reader || byteCountToRead > reader->rdbuf.sz - reader->pos) return; + + auto buf = static_cast(reader->rdbuf.buf); + size_t pos = reader->pos; + + std::copy(buf + pos, buf + (pos + byteCountToRead), outBytes); + reader->pos += byteCountToRead; +} + bool decode_png(const ReadBuf &rb, ImageGreyscale &img) { if (!is_png(rb)) return false; PNGDescr dsc; - dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); if(!dsc.png) return false; dsc.info = png_create_info_struct(dsc.png); if(!dsc.info) return {}; - FILE *io = ::fmemopen(const_cast(rb.buf), rb.sz, "rb"); - png_init_io(dsc.png, io); + ReadBufReader reader {rb}; + png_set_read_fn(dsc.png, static_cast(&reader), png_read_callback); png_read_info(dsc.png, dsc.info); @@ -62,9 +88,7 @@ bool decode_png(const ReadBuf &rb, ImageGreyscale &img) for (size_t r = 0; r < img.rows; ++r) png_read_row(dsc.png, readbuf + r * img.cols, nullptr); - fclose(io); - return true; } -}} +}} // namespace Slic3r::png From ad0df8fd098cfead66f9ede7456cd783a88ba049 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 3 Aug 2020 18:50:43 +0200 Subject: [PATCH 356/826] Be compatible with earlier libpng versions. --- src/libslic3r/PNGRead.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PNGRead.cpp b/src/libslic3r/PNGRead.cpp index 695ceba3dc..5b5b9ffecb 100644 --- a/src/libslic3r/PNGRead.cpp +++ b/src/libslic3r/PNGRead.cpp @@ -27,8 +27,18 @@ bool is_png(const ReadBuf &rb) { static const constexpr int PNG_SIG_BYTES = 8; - return rb.sz >= PNG_SIG_BYTES && - !png_sig_cmp(static_cast(rb.buf), 0, PNG_SIG_BYTES); +#if PNG_LIBPNG_VER_MINOR <= 2 + // Earlier libpng versions had png_sig_cmp(png_bytep, ...) which is not + // a const pointer. It is not possible to cast away the const qualifier from + // the input buffer so... yes... life is challenging... + png_byte buf[PNG_SIG_BYTES]; + auto inbuf = static_cast(rb.buf); + std::copy(inbuf, inbuf + PNG_SIG_BYTES, buf); +#else + auto buf = static_cast(rb.buf); +#endif + + return rb.sz >= PNG_SIG_BYTES && !png_sig_cmp(buf, 0, PNG_SIG_BYTES); } // A wrapper around ReadBuf to be read repeatedly like a stream. libpng needs From 79567a1958f334e9c43208aa336e668e7da1a311 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 4 Aug 2020 10:13:01 +0200 Subject: [PATCH 357/826] Add some comments for png read interface --- src/libslic3r/PNGRead.cpp | 46 ++++++++++++++----------------- src/libslic3r/PNGRead.hpp | 58 ++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/PNGRead.cpp b/src/libslic3r/PNGRead.cpp index 5b5b9ffecb..e66143b845 100644 --- a/src/libslic3r/PNGRead.cpp +++ b/src/libslic3r/PNGRead.cpp @@ -41,13 +41,6 @@ bool is_png(const ReadBuf &rb) return rb.sz >= PNG_SIG_BYTES && !png_sig_cmp(buf, 0, PNG_SIG_BYTES); } -// A wrapper around ReadBuf to be read repeatedly like a stream. libpng needs -// this form for its buffer read callback. -struct ReadBufReader { - const ReadBuf &rdbuf; size_t pos; - ReadBufReader(const ReadBuf &rd): rdbuf{rd}, pos{0} {} -}; - // Buffer read callback for libpng. It provides an allocated output buffer and // the amount of data it desires to read from the input. void png_read_callback(png_struct *png_ptr, @@ -55,20 +48,21 @@ void png_read_callback(png_struct *png_ptr, png_size_t byteCountToRead) { // Retrieve our input buffer through the png_ptr - auto reader = static_cast(png_get_io_ptr(png_ptr)); + auto reader = static_cast(png_get_io_ptr(png_ptr)); - if (!reader || byteCountToRead > reader->rdbuf.sz - reader->pos) return; + if (!reader || !reader->is_ok()) return; - auto buf = static_cast(reader->rdbuf.buf); - size_t pos = reader->pos; - - std::copy(buf + pos, buf + (pos + byteCountToRead), outBytes); - reader->pos += byteCountToRead; + reader->read(static_cast(outBytes), byteCountToRead); } -bool decode_png(const ReadBuf &rb, ImageGreyscale &img) +bool decode_png(IStream &in_buf, ImageGreyscale &out_img) { - if (!is_png(rb)) return false; + static const constexpr int PNG_SIG_BYTES = 8; + + std::vector sig(PNG_SIG_BYTES, 0); + in_buf.read(sig.data(), PNG_SIG_BYTES); + if (!png_check_sig(sig.data(), PNG_SIG_BYTES)) + return false; PNGDescr dsc; dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, @@ -77,26 +71,28 @@ bool decode_png(const ReadBuf &rb, ImageGreyscale &img) if(!dsc.png) return false; dsc.info = png_create_info_struct(dsc.png); - if(!dsc.info) return {}; + if(!dsc.info) return false; - ReadBufReader reader {rb}; - png_set_read_fn(dsc.png, static_cast(&reader), png_read_callback); + png_set_read_fn(dsc.png, static_cast(&in_buf), png_read_callback); + + // Tell that we have already read the first bytes to check the signature + png_set_sig_bytes(dsc.png, PNG_SIG_BYTES); png_read_info(dsc.png, dsc.info); - img.cols = png_get_image_width(dsc.png, dsc.info); - img.rows = png_get_image_height(dsc.png, dsc.info); + out_img.cols = png_get_image_width(dsc.png, dsc.info); + out_img.rows = png_get_image_height(dsc.png, dsc.info); size_t color_type = png_get_color_type(dsc.png, dsc.info); size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info); if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8) return false; - img.buf.resize(img.rows * img.cols); + out_img.buf.resize(out_img.rows * out_img.cols); - auto readbuf = static_cast(img.buf.data()); - for (size_t r = 0; r < img.rows; ++r) - png_read_row(dsc.png, readbuf + r * img.cols, nullptr); + auto readbuf = static_cast(out_img.buf.data()); + for (size_t r = 0; r < out_img.rows; ++r) + png_read_row(dsc.png, readbuf + r * out_img.cols, nullptr); return true; } diff --git a/src/libslic3r/PNGRead.hpp b/src/libslic3r/PNGRead.hpp index 0e7311f2e5..082edd5691 100644 --- a/src/libslic3r/PNGRead.hpp +++ b/src/libslic3r/PNGRead.hpp @@ -3,30 +3,68 @@ #include #include +#include namespace Slic3r { namespace png { -struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; }; +// Interface for an input stream of encoded png image data. +struct IStream { + virtual ~IStream() = default; + virtual size_t read(std::uint8_t *outp, size_t amount) = 0; + virtual bool is_ok() const = 0; +}; +// The output format of decode_png: a 2D pixel matrix stored continuously row +// after row (row major layout). template struct Image { std::vector buf; size_t rows, cols; PxT get(size_t row, size_t col) const { return buf[row * cols + col]; } }; -struct RGB { uint8_t r, g, b; }; - -using ImageRGB = Image; using ImageGreyscale = Image; +// Only decodes true 8 bit grayscale png images. Returns false for other formats +// TODO (if needed): implement transformation of rgb images into grayscale... +bool decode_png(IStream &stream, ImageGreyscale &out_img); + +// TODO (if needed) +// struct RGB { uint8_t r, g, b; }; +// using ImageRGB = Image; +// bool decode_png(IStream &stream, ImageRGB &img); + + +// Encoded png data buffer: a simple read-only buffer and its size. +struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; }; + bool is_png(const ReadBuf &pngbuf); -// Only decodes true 8 bit grayscale png images. Returns false for other formats -// TODO: implement transformation of rgb images into grayscale... -bool decode_png(const ReadBuf &pngbuf, ImageGreyscale &img); +template bool decode_png(const ReadBuf &in_buf, Img &out_img) +{ + struct ReadBufStream: public IStream { + const ReadBuf &rbuf_ref; size_t pos = 0; -// TODO -// bool decode_png(Buffer &&pngbuf, ImageRGB &img); + explicit ReadBufStream(const ReadBuf &buf): rbuf_ref{buf} {} + + size_t read(std::uint8_t *outp, size_t amount) override + { + if (amount > rbuf_ref.sz - pos) return 0; + + auto buf = static_cast(rbuf_ref.buf); + std::copy(buf + pos, buf + (pos + amount), outp); + pos += amount; + + return amount; + } + + bool is_ok() const override { return pos < rbuf_ref.sz; } + } stream{in_buf}; + + return decode_png(stream, out_img); +} + +// TODO: std::istream of FILE* could be similarly adapted in case its needed... + +}} // namespace Slic3r::png -}} #endif // PNGREAD_HPP From 93921dc7c8d975f2155bfc9d60a7c78bf8bf2745 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 28 Aug 2020 08:54:58 +0200 Subject: [PATCH 358/826] ENABLE_GCODE_VIEWER -> Experimental taskbar icon --- src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 38 ++++++++++++++++++++++++++++++++++ src/slic3r/GUI/MainFrame.hpp | 11 ++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index af08d16401..8f5ec121aa 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,6 +58,7 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_TASKBAR_ICON (1 && ENABLE_GCODE_VIEWER) #define TIME_ESTIMATE_NONE 0 #define TIME_ESTIMATE_DEFAULT 1 diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index e46bd03fc2..aadaeece66 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -93,6 +93,28 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // Font is already set in DPIFrame constructor */ +#if ENABLE_GCODE_VIEWER_TASKBAR_ICON + if (wxTaskBarIcon::IsAvailable()) { +#if defined(__WXOSX__) && wxOSX_USE_COCOA + m_taskbar_icon = new wxTaskBarIcon(wxTBI_DOCK); +#else + m_taskbar_icon = new wxTaskBarIcon(); +#endif + m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer"); + + m_taskbar_icon->Bind(wxEVT_TASKBAR_CLICK, [this](wxTaskBarIconEvent& evt) { + wxString msg = _L("You pressed the icon in taskbar for ") + "\n"; + if (m_mode == EMode::Editor) + msg += wxString(SLIC3R_APP_NAME); + else + msg += wxString(SLIC3R_APP_NAME) + "-GCode viewer"; + + wxMessageDialog dialog(nullptr, msg, _("Taskbar icon clicked"), wxOK); + dialog.ShowModal(); + }); + } +#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON + SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); // // Load the icon either from the exe, or from the ico file. //#if _WIN32 @@ -255,6 +277,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S } } +#if ENABLE_GCODE_VIEWER_TASKBAR_ICON +MainFrame::~MainFrame() +{ + if (m_taskbar_icon != nullptr) + delete m_taskbar_icon; +} +#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON + void MainFrame::update_layout() { auto restore_to_creation = [this]() { @@ -1388,6 +1418,10 @@ void MainFrame::set_mode(EMode mode) m_plater->Thaw(); SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#if ENABLE_GCODE_VIEWER_TASKBAR_ICON + if (m_taskbar_icon != nullptr) + m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer"); +#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON break; } @@ -1435,6 +1469,10 @@ void MainFrame::set_mode(EMode mode) m_plater->Thaw(); SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG)); +#if ENABLE_GCODE_VIEWER_TASKBAR_ICON + if (m_taskbar_icon != nullptr) + m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); +#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON break; } diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 53d8488768..7777a053d2 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -7,6 +7,9 @@ #include #include #include +#if ENABLE_GCODE_VIEWER_TASKBAR_ICON +#include +#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON #include #include @@ -160,7 +163,11 @@ protected: public: MainFrame(); +#if ENABLE_GCODE_VIEWER_TASKBAR_ICON + ~MainFrame(); +#else ~MainFrame() = default; +#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON void update_layout(); @@ -219,6 +226,10 @@ public: wxProgressDialog* m_progress_dialog { nullptr }; std::shared_ptr m_statusbar; +#if ENABLE_GCODE_VIEWER_TASKBAR_ICON + wxTaskBarIcon* m_taskbar_icon{ nullptr }; +#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON + #ifdef _WIN32 void* m_hDeviceNotify { nullptr }; uint32_t m_ulSHChangeNotifyRegister { 0 }; From d07d5e36de2e496edfc11200a355bf793365beee Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 28 Aug 2020 11:20:18 +0200 Subject: [PATCH 359/826] Follow-up of 93921dc7c8d975f2155bfc9d60a7c78bf8bf2745 -> Remove taskbar icon before to change it --- src/slic3r/GUI/MainFrame.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index aadaeece66..886e96e1a0 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -280,8 +280,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #if ENABLE_GCODE_VIEWER_TASKBAR_ICON MainFrame::~MainFrame() { - if (m_taskbar_icon != nullptr) - delete m_taskbar_icon; + delete m_taskbar_icon; } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON @@ -1419,8 +1418,10 @@ void MainFrame::set_mode(EMode mode) SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); #if ENABLE_GCODE_VIEWER_TASKBAR_ICON - if (m_taskbar_icon != nullptr) + if (m_taskbar_icon != nullptr) { + m_taskbar_icon->RemoveIcon(); m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer"); + } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON break; @@ -1470,8 +1471,10 @@ void MainFrame::set_mode(EMode mode) SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG)); #if ENABLE_GCODE_VIEWER_TASKBAR_ICON - if (m_taskbar_icon != nullptr) + if (m_taskbar_icon != nullptr) { + m_taskbar_icon->RemoveIcon(); m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); + } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON break; From 1c2ef87cfa088adc37119118f9014d6174bc355f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 28 Aug 2020 12:28:21 +0200 Subject: [PATCH 360/826] GCodeViewer -> Reduced vertices count when generating solid toolpaths --- src/slic3r/GUI/GCodeViewer.cpp | 86 +++++++++++++++++++++++----------- src/slic3r/GUI/GCodeViewer.hpp | 2 + 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 8cb18f4e18..c624fb338e 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -994,37 +994,69 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Vec3f prev_pos = prev.position - half_height * up; Vec3f curr_pos = curr.position - half_height * up; - // vertices 1st endpoint - store_vertex(buffer_vertices, prev_pos + half_height * up, up); // top - store_vertex(buffer_vertices, prev_pos + half_width * right, right); // right - store_vertex(buffer_vertices, prev_pos - half_height * up, -up); // bottom - store_vertex(buffer_vertices, prev_pos - half_width * right, -right); // left + Path& last_path = buffer.paths.back(); + if (last_path.vertices_count() == 1) { + // vertices 1st endpoint + store_vertex(buffer_vertices, prev_pos + half_height * up, up); // top + store_vertex(buffer_vertices, prev_pos + half_width * right, right); // right + store_vertex(buffer_vertices, prev_pos - half_height * up, -up); // bottom + store_vertex(buffer_vertices, prev_pos - half_width * right, -right); // left - // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); // top - store_vertex(buffer_vertices, curr_pos + half_width * right, right); // right - store_vertex(buffer_vertices, curr_pos - half_height * up, -up); // bottom - store_vertex(buffer_vertices, curr_pos - half_width * right, -right); // left + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); // top + store_vertex(buffer_vertices, curr_pos + half_width * right, right); // right + store_vertex(buffer_vertices, curr_pos - half_height * up, -up); // bottom + store_vertex(buffer_vertices, curr_pos - half_width * right, -right); // left - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - // triangles sides - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); + // triangles sides + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); + } + else { + // vertices 1st endpoint + store_vertex(buffer_vertices, prev_pos + half_width * right, right); // right + store_vertex(buffer_vertices, prev_pos - half_width * right, -right); // left - buffer.paths.back().last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); // top + store_vertex(buffer_vertices, curr_pos + half_width * right, right); // right + store_vertex(buffer_vertices, curr_pos - half_height * up, -up); // bottom + store_vertex(buffer_vertices, curr_pos - half_width * right, -right); // left + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); + } + + last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; }; // toolpaths data -> extract from result @@ -1299,7 +1331,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool for (const TBuffer& buffer : m_buffers) { // searches the path containing the current position for (const Path& path : buffer.paths) { - if (path.first.s_id <= m_sequential_view.current.last && m_sequential_view.current.last <= path.last.s_id) { + if (path.contains(m_sequential_view.current.last)) { unsigned int offset = m_sequential_view.current.last - path.first.s_id; if (offset > 0) { if (buffer.primitive_type == TBuffer::EPrimitiveType::Line) diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 55c8ea1993..808c1a8ee0 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -133,6 +133,8 @@ class GCodeViewer unsigned char cp_color_id{ 0 }; bool matches(const GCodeProcessor::MoveVertex& move) const; + size_t vertices_count() const { return last.s_id - first.s_id + 1; } + bool contains(unsigned int id) const { return first.s_id <= id && id <= last.s_id; } }; // Used to batch the indices needed to render paths From 8e6760e03332290b76abc5a0fa38f3f3d64ff016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 30 Aug 2020 20:38:07 +0200 Subject: [PATCH 361/826] Fix crash on inconsistent input --- src/libslic3r/Fill/FillAdaptive.cpp | 2 +- src/libslic3r/PrintObject.cpp | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index a3068989ef..0563b612ab 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -149,7 +149,7 @@ std::unique_ptr FillAdaptive::build_octree( { using namespace FillAdaptive_Internal; - if(line_spacing <= 0) + if(line_spacing <= 0 || std::isnan(line_spacing)) { return nullptr; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5752452ad7..b1b5d3a7b7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,8 +434,16 @@ void PrintObject::generate_support_material() void PrintObject::prepare_adaptive_infill_data() { - float fill_density = this->print()->full_print_config().opt_float("fill_density"); - float infill_extrusion_width = this->print()->full_print_config().opt_float("infill_extrusion_width"); + const ConfigOptionFloatOrPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); + const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); + + if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) + { + return; + } + + float fill_density = opt_fill_density->value; + float infill_extrusion_width = opt_infill_extrusion_width->value; coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); From 423d1f2f4013e7f01cec40c23b1e182b678e2fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 31 Aug 2020 08:49:17 +0200 Subject: [PATCH 362/826] Fix wrong data type --- src/libslic3r/PrintObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b1b5d3a7b7..1ab5664a0f 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,7 +434,7 @@ void PrintObject::generate_support_material() void PrintObject::prepare_adaptive_infill_data() { - const ConfigOptionFloatOrPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); + const ConfigOptionPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) From bf7b952eff82a0fe3805cf3bb84d3b9bc5c636e3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 1 Sep 2020 08:29:06 +0200 Subject: [PATCH 363/826] GCodeViewer -> Smoothed solid toolpaths corners --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 214 +++++++++++++++++++++++++-------- src/slic3r/GUI/GCodeViewer.hpp | 13 +- 3 files changed, 175 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 8f5ec121aa..834cebbe45 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -58,7 +58,7 @@ #define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_TASKBAR_ICON (1 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER) #define TIME_ESTIMATE_NONE 0 #define TIME_ESTIMATE_DEFAULT 1 diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index c624fb338e..8853d7a754 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -297,19 +297,19 @@ bool GCodeViewer::init() case EMoveType::Retract: case EMoveType::Unretract: { - buffer.primitive_type = TBuffer::EPrimitiveType::Point; + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; buffer.vertices.format = VBuffer::EFormat::Position; break; } case EMoveType::Extrude: { - buffer.primitive_type = TBuffer::EPrimitiveType::Triangle; + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; break; } case EMoveType::Travel: { - buffer.primitive_type = TBuffer::EPrimitiveType::Line; + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; buffer.vertices.format = VBuffer::EFormat::PositionNormal1; break; } @@ -397,6 +397,8 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: if (m_vertices_count == 0) return; + wxBusyCursor busy; + if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty()) // update tool colors from config stored in the gcode m_tool_colors = decode_colors(gcode_result.extruder_colors); @@ -961,6 +963,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // format data into the buffers to be rendered as solid auto add_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { + static Vec3f prev_dir; + static Vec3f prev_up; + static float prev_length; auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { // append position for (int j = 0; j < 3; ++j) { @@ -976,6 +981,18 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer_indices.push_back(i2); buffer_indices.push_back(i3); }; + auto extract_position_at = [](const std::vector& vertices, size_t id) { + return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); + }; + auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { + vertices[id + 0] = position[0]; + vertices[id + 1] = position[1]; + vertices[id + 2] = position[2]; + }; + auto append_dummy_cap = [store_triangle](std::vector& buffer_indices, unsigned int id) { + store_triangle(buffer_indices, id, id, id); + store_triangle(buffer_indices, id, id, id); + }; if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id - 1)); @@ -986,32 +1003,41 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Vec3f dir = (curr.position - prev.position).normalized(); Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f left = -right; Vec3f up = right.cross(dir); + Vec3f bottom = -up; - float half_width = 0.5f * round_to_nearest(curr.width, 2); - float half_height = 0.5f * round_to_nearest(curr.height, 2); + Path& last_path = buffer.paths.back(); + + float half_width = 0.5f * last_path.width; + float half_height = 0.5f * last_path.height; Vec3f prev_pos = prev.position - half_height * up; Vec3f curr_pos = curr.position - half_height * up; - Path& last_path = buffer.paths.back(); + float length = (curr_pos - prev_pos).norm(); if (last_path.vertices_count() == 1) { + // 1st segment + // vertices 1st endpoint - store_vertex(buffer_vertices, prev_pos + half_height * up, up); // top - store_vertex(buffer_vertices, prev_pos + half_width * right, right); // right - store_vertex(buffer_vertices, prev_pos - half_height * up, -up); // bottom - store_vertex(buffer_vertices, prev_pos - half_width * right, -right); // left + store_vertex(buffer_vertices, prev_pos + half_height * up, up); + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); // top - store_vertex(buffer_vertices, curr_pos + half_width * right, right); // right - store_vertex(buffer_vertices, curr_pos - half_height * up, -up); // bottom - store_vertex(buffer_vertices, curr_pos - half_width * right, -right); // left + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); // triangles starting cap store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + // dummy triangles outer corner cap + append_dummy_cap(buffer_indices, starting_vertices_size); + // triangles sides store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); @@ -1027,20 +1053,101 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); } else { - // vertices 1st endpoint - store_vertex(buffer_vertices, prev_pos + half_width * right, right); // right - store_vertex(buffer_vertices, prev_pos - half_width * right, -right); // left + // any other segment + Vec3f med_dir = (prev_dir + dir).normalized(); + float displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + Vec3f displacement_vec = displacement * prev_dir; + bool can_displace = displacement < prev_length && displacement < length; + + size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); + size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); + Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id); + Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id); + + bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = prev_dir.dot(dir) < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible + if (can_displace) { + if (is_right_turn) { + prev_right_pos -= displacement_vec; + update_position_at(buffer_vertices, prev_right_id, prev_right_pos); + right_displaced = true; + } + else { + prev_left_pos -= displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } + } + + if (!is_sharp) { + // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible + if (can_displace) { + if (is_right_turn) { + prev_left_pos += displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } + else { + prev_right_pos += displacement_vec; + update_position_at(buffer_vertices, prev_right_id, prev_right_pos); + right_displaced = true; + } + } + + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // vertices position matches that of the previous segment 2nd endpoint, if displaced + store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); + store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); + } + else { + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced + if (is_right_turn) { + store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); + } + else { + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); + } + } // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); // top - store_vertex(buffer_vertices, curr_pos + half_width * right, right); // right - store_vertex(buffer_vertices, curr_pos - half_height * up, -up); // bottom - store_vertex(buffer_vertices, curr_pos - half_width * right, -right); // left + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); // triangles starting cap store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); + // triangles outer corner cap + if (is_right_turn) { + if (left_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1); + } + } + else { + if (right_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0); + } + } + // triangles sides store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); @@ -1057,6 +1164,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + prev_dir = dir; + prev_up = up; + prev_length = length; }; // toolpaths data -> extract from result @@ -1137,20 +1247,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); - switch (m_buffers[travel_buffer_id].primitive_type) - { - case TBuffer::EPrimitiveType::Line: { m_statistics.travel_segments_count = indices[travel_buffer_id].size() / 2; break; } - case TBuffer::EPrimitiveType::Triangle: { m_statistics.travel_segments_count = indices[travel_buffer_id].size() / 36; break; } - default: { break; } - } - + m_statistics.travel_segments_count = indices[travel_buffer_id].size() / m_buffers[travel_buffer_id].indices_per_segment(); unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); - switch (m_buffers[extrude_buffer_id].primitive_type) - { - case TBuffer::EPrimitiveType::Line: { m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / 2; break; } - case TBuffer::EPrimitiveType::Triangle: { m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / 36; break; } - default: { break; } - } + m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / m_buffers[extrude_buffer_id].indices_per_segment(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // layers zs / roles / extruder ids / cp color ids -> extract from result @@ -1334,10 +1433,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (path.contains(m_sequential_view.current.last)) { unsigned int offset = m_sequential_view.current.last - path.first.s_id; if (offset > 0) { - if (buffer.primitive_type == TBuffer::EPrimitiveType::Line) + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) offset = 2 * offset - 1; - else if (buffer.primitive_type == TBuffer::EPrimitiveType::Triangle) - offset = 36 * (offset - 1) + 30; + else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + unsigned int indices_count = buffer.indices_per_segment(); + offset = indices_count * (offset - 1) + (indices_count - 6); + } } offset += path.first.i_id; @@ -1382,11 +1483,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool unsigned int size_in_vertices = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; unsigned int size_in_indices = 0; - switch (buffer->primitive_type) + switch (buffer->render_primitive_type) { - case TBuffer::EPrimitiveType::Point: { size_in_indices = size_in_vertices; break; } - case TBuffer::EPrimitiveType::Line: { size_in_indices = 2 * (size_in_vertices - 1); break; } - case TBuffer::EPrimitiveType::Triangle: { size_in_indices = 36 * (size_in_vertices - 1); break; } + case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = size_in_vertices; break; } + case TBuffer::ERenderPrimitiveType::Line: + case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (size_in_vertices - 1); break; } } it->sizes.push_back(size_in_indices); @@ -1394,8 +1495,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= path.last.s_id) delta_1st = m_sequential_view.current.first - path.first.s_id; - if (buffer->primitive_type == TBuffer::EPrimitiveType::Triangle) - delta_1st *= 36; + if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) + delta_1st *= buffer->indices_per_segment(); it->offsets.push_back(static_cast((path.first.i_id + delta_1st) * sizeof(unsigned int))); } @@ -1502,9 +1603,9 @@ void GCodeViewer::render_toolpaths() const glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - switch (buffer.primitive_type) + switch (buffer.render_primitive_type) { - case TBuffer::EPrimitiveType::Point: + case TBuffer::ERenderPrimitiveType::Point: { EOptionsColors color; switch (buffer_type(i)) @@ -1519,12 +1620,12 @@ void GCodeViewer::render_toolpaths() const render_as_points(buffer, color, *shader); break; } - case TBuffer::EPrimitiveType::Line: + case TBuffer::ERenderPrimitiveType::Line: { render_as_lines(buffer, *shader); break; } - case TBuffer::EPrimitiveType::Triangle: + case TBuffer::ERenderPrimitiveType::Triangle: { render_as_triangles(buffer, *shader); break; @@ -1819,7 +1920,19 @@ void GCodeViewer::render_legend() const if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { ImGui::AlignTextToFramePadding(); - imgui.text(_u8L("Estimated printing time") + ":"); + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); + break; + } + } ImGui::SameLine(); imgui.text(short_time(get_time_dhms(time_mode.time))); @@ -1833,7 +1946,6 @@ void GCodeViewer::render_legend() const } } if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { - ImGui::SameLine(0.0f, 10.0f); if (imgui.button(label)) { m_time_estimate_mode = mode; wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); @@ -1846,12 +1958,12 @@ void GCodeViewer::render_legend() const { case PrintEstimatedTimeStatistics::ETimeMode::Normal: { - show_mode_button(_u8L("Stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); break; } case PrintEstimatedTimeStatistics::ETimeMode::Stealth: { - show_mode_button(_u8L("Normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); break; } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 808c1a8ee0..e49a1f08bf 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -149,14 +149,14 @@ class GCodeViewer // buffer containing data for rendering a specific toolpath type struct TBuffer { - enum class EPrimitiveType : unsigned char + enum class ERenderPrimitiveType : unsigned char { Point, Line, Triangle }; - EPrimitiveType primitive_type; + ERenderPrimitiveType render_primitive_type; VBuffer vertices; IBuffer indices; @@ -167,6 +167,15 @@ class GCodeViewer void reset(); void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); + unsigned int indices_per_segment() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Point: { return 1; } + case ERenderPrimitiveType::Line: { return 2; } + case ERenderPrimitiveType::Triangle: { return 42; } // 3 indices x 14 triangles + default: { return 0; } + } + } }; // helper to render shells From e32930aa6c5008a86323a6d2c9a7d6a4a67be930 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 1 Sep 2020 09:28:02 +0200 Subject: [PATCH 364/826] Code cleanup --- src/libslic3r/Technologies.hpp | 6 - src/slic3r/GUI/GCodeViewer.cpp | 439 +-------------------------- src/slic3r/GUI/GCodeViewer.hpp | 18 -- src/slic3r/GUI/GLCanvas3D.cpp | 12 - src/slic3r/GUI/GLCanvas3D.hpp | 3 - src/slic3r/GUI/GUI_Preview.cpp | 14 +- src/slic3r/GUI/GUI_Preview.hpp | 5 +- src/slic3r/GUI/KBShortcutsDialog.cpp | 9 - 8 files changed, 3 insertions(+), 503 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 834cebbe45..a0484b259c 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -60,10 +60,4 @@ #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER) -#define TIME_ESTIMATE_NONE 0 -#define TIME_ESTIMATE_DEFAULT 1 -#define TIME_ESTIMATE_MODAL 2 -#define TIME_ESTIMATE_LEGEND 3 -#define GCODE_VIEWER_TIME_ESTIMATE TIME_ESTIMATE_LEGEND - #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 8853d7a754..24ed5be9b4 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -383,9 +383,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty()); } -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE m_time_statistics = gcode_result.time_statistics; -#endif // GCODE_VIEWER_TIME_ESTIMATE } void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) @@ -461,12 +459,8 @@ void GCodeViewer::reset() m_layers_zs = std::vector(); m_layers_z_range = { 0.0, 0.0 }; m_roles = std::vector(); -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE m_time_statistics.reset(); -#endif // GCODE_VIEWER_TIME_ESTIMATE -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal; -#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); @@ -479,15 +473,8 @@ void GCodeViewer::render() const m_statistics.reset_opengl(); #endif // ENABLE_GCODE_VIEWER_STATISTICS -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - if (m_roles.empty()) { - m_time_estimate_frames_count = 0; - return; - } -#else if (m_roles.empty()) return; -#endif // GCODE_VIEWER_TIME_ESTIMATE glsafe(::glEnable(GL_DEPTH_TEST)); render_toolpaths(); @@ -495,9 +482,6 @@ void GCodeViewer::render() const m_sequential_view.marker.render(); render_shells(); render_legend(); -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE - render_time_estimate(); -#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -533,9 +517,6 @@ unsigned int GCodeViewer::get_options_visibility_flags() const flags = set_flag(flags, static_cast(Preview::OptionType::Shells), m_shells.visible); flags = set_flag(flags, static_cast(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible()); flags = set_flag(flags, static_cast(Preview::OptionType::Legend), is_legend_enabled()); -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT - flags = set_flag(flags, static_cast(Preview::OptionType::TimeEstimate), is_time_estimate_enabled()); -#endif // GCODE_VIEWER_TIME_ESTIMATE return flags; } @@ -555,9 +536,6 @@ void GCodeViewer::set_options_visibility_from_flags(unsigned int flags) m_shells.visible = is_flag_set(static_cast(Preview::OptionType::Shells)); m_sequential_view.marker.set_visible(is_flag_set(static_cast(Preview::OptionType::ToolMarker))); enable_legend(is_flag_set(static_cast(Preview::OptionType::Legend))); -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT - enable_time_estimate(is_flag_set(static_cast(Preview::OptionType::TimeEstimate))); -#endif // GCODE_VIEWER_TIME_ESTIMATE } void GCodeViewer::set_layers_z_range(const std::array& layers_z_range) @@ -569,16 +547,6 @@ void GCodeViewer::set_layers_z_range(const std::array& layers_z_range wxGetApp().plater()->update_preview_moves_slider(); } -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE -void GCodeViewer::enable_time_estimate(bool enable) -{ - m_time_estimate_enabled = enable; - wxGetApp().update_ui_from_settings(); - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); -} -#endif // GCODE_VIEWER_TIME_ESTIMATE - void GCodeViewer::export_toolpaths_to_obj(const char* filename) const { if (filename == nullptr) @@ -1685,7 +1653,6 @@ void GCodeViewer::render_legend() const Line }; -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast(m_time_estimate_mode)]; float icon_size = ImGui::GetTextLineHeight(); @@ -1696,10 +1663,6 @@ void GCodeViewer::render_legend() const std::function callback = nullptr) { if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); -#else - auto append_item = [this, draw_list, &imgui](EItemType type, const Color& color, const std::string& label, std::function callback = nullptr) { - float icon_size = ImGui::GetTextLineHeight(); -#endif // GCODE_VIEWER_TIME_ESTIMATE ImVec2 pos = ImGui::GetCursorScreenPos(); switch (type) { @@ -1745,7 +1708,6 @@ void GCodeViewer::render_legend() const if (callback != nullptr) { if (ImGui::MenuItem(label.c_str())) callback(); -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND else { // show tooltip if (ImGui::IsItemHovered()) { @@ -1779,15 +1741,12 @@ void GCodeViewer::render_legend() const ::sprintf(buf, "%.1f%%", 100.0f * percent); ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); } -#endif // GCODE_VIEWER_TIME_ESTIMATE } else imgui.text(label); -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND if (!visible) ImGui::PopStyleVar(); -#endif // GCODE_VIEWER_TIME_ESTIMATE }; auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -1812,7 +1771,6 @@ void GCodeViewer::render_legend() const } }; -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { imgui.text(texts[0]); ImGui::SameLine(offsets[0]); @@ -1838,7 +1796,6 @@ void GCodeViewer::render_legend() const ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x; return ret; }; -#endif // GCODE_VIEWER_TIME_ESTIMATE auto color_print_ranges = [this](unsigned char extruder_id, const std::vector& custom_gcode_per_print_z) { std::vector>> ret; @@ -1887,7 +1844,6 @@ void GCodeViewer::render_legend() const return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND auto role_time_and_percent = [this, time_mode](ExtrusionRole role) { auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); @@ -1969,20 +1925,15 @@ void GCodeViewer::render_legend() const } ImGui::Spacing(); } -#endif // GCODE_VIEWER_TIME_ESTIMATE // extrusion paths section -> title switch (m_view_type) { -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND case EViewType::FeatureType: { append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets); break; } -#else - case EViewType::FeatureType: { imgui.title(_u8L("Feature type")); break; } -#endif // GCODE_VIEWER_TIME_ESTIMATE case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } @@ -2003,28 +1954,15 @@ void GCodeViewer::render_legend() const if (role >= erCount) continue; bool visible = is_visible(role); -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() { -#else - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], _u8L(ExtrusionEntity::role_to_string(role)), - [this, role, visible]() { -#endif // GCODE_VIEWER_TIME_ESTIMATE - m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); + m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->update_preview_bottom_toolbar(); } ); - -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_LEGEND - if (!visible) - ImGui::PopStyleVar(); -#endif // GCODE_VIEWER_TIME_ESTIMATE } break; } @@ -2102,7 +2040,6 @@ void GCodeViewer::render_legend() const default: { break; } } -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND // partial estimated printing time section if (m_view_type == EViewType::ColorPrint) { using Times = std::pair; @@ -2185,11 +2122,6 @@ void GCodeViewer::render_legend() const draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f })); -// ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); -// draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6); -// center.x += icon_size; -// draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6); - ImGui::SameLine(offsets[0]); imgui.text(short_time(get_time_dhms(times.second - times.first))); }; @@ -2240,7 +2172,6 @@ void GCodeViewer::render_legend() const } } } -#endif // GCODE_VIEWER_TIME_ESTIMATE // travel paths section if (m_buffers[buffer_id(EMoveType::Travel)].visible) { @@ -2307,374 +2238,6 @@ void GCodeViewer::render_legend() const ImGui::PopStyleVar(); } -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE -void GCodeViewer::render_time_estimate() const -{ - if (!m_time_estimate_enabled) { -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - m_time_estimate_frames_count = 0; -#endif // GCODE_VIEWER_TIME_ESTIMATE - return; - } - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - // esc - if (ImGui::GetIO().KeysDown[27]) { - m_time_estimate_enabled = false; - return; - } -#endif // GCODE_VIEWER_TIME_ESTIMATE - - using Times = std::pair; - using TimesList = std::vector>; - using Headers = std::vector; - using ColumnOffsets = std::array; - - // helper structure containig the data needed to render the time items - struct PartialTime - { - enum class EType : unsigned char - { - Print, - ColorChange, - Pause - }; - EType type; - int extruder_id; - Color color1; - Color color2; - Times times; - }; - using PartialTimes = std::vector; - - auto append_headers = [&imgui](const Headers& headers, const ColumnOffsets& offsets) { - imgui.text(headers[0]); - ImGui::SameLine(offsets[0]); - imgui.text(headers[1]); - ImGui::SameLine(offsets[1]); - imgui.text(headers[2]); - ImGui::Separator(); - }; - - auto append_mode = [this, &imgui, append_headers](float total_time, const PartialTimes& items, - const Headers& partial_times_headers, - const std::vector>& moves_time, - const Headers& moves_headers, - const std::vector>& roles_time, - const Headers& roles_headers) { - auto append_partial_times = [this, &imgui, append_headers](const PartialTimes& items, const Headers& headers) { - auto calc_offsets = [this, &headers](const PartialTimes& items) { - ColumnOffsets ret = { ImGui::CalcTextSize(headers[0].c_str()).x, ImGui::CalcTextSize(headers[1].c_str()).x }; - for (const PartialTime& item : items) { - std::string label; - switch (item.type) - { - case PartialTime::EType::Print: { label = _u8L("Print"); break; } - case PartialTime::EType::Pause: { label = _u8L("Pause"); break; } - case PartialTime::EType::ColorChange: { label = _u8L("Color change"); break; } - } - - ret[0] = std::max(ret[0], ImGui::CalcTextSize(label.c_str()).x); - ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(item.times.second)).c_str()).x); - } - - const ImGuiStyle& style = ImGui::GetStyle(); - ret[0] += 2.0f * (ImGui::GetTextLineHeight() + style.ItemSpacing.x); - ret[1] += ret[0] + style.ItemSpacing.x; - return ret; - }; - auto append_color = [this, &imgui](const Color& color1, const Color& color2, ColumnOffsets& offsets, const Times& times) { - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Color change")); - ImGui::SameLine(); - - float icon_size = ImGui::GetTextLineHeight(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 pos = ImGui::GetCursorScreenPos(); - pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x; - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color1[0], color1[1], color1[2], 1.0f }), 6); - center.x += icon_size; - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color2[0], color2[1], color2[2], 1.0f }), 6); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(times.second - times.first))); - }; - - if (items.empty()) - return; - - ColumnOffsets offsets = calc_offsets(items); - - ImGui::Spacing(); - append_headers(headers, offsets); - - for (const PartialTime& item : items) { - switch (item.type) - { - case PartialTime::EType::Print: - { - imgui.text(_u8L("Print")); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.times.second))); - ImGui::SameLine(offsets[1]); - imgui.text(short_time(get_time_dhms(item.times.first))); - break; - } - case PartialTime::EType::Pause: - { - imgui.text(_u8L("Pause")); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(item.times.second - item.times.first))); - break; - } - case PartialTime::EType::ColorChange: - { - append_color(item.color1, item.color2, offsets, item.times); - break; - } - } - } - }; - - auto move_type_label = [](EMoveType type) { - switch (type) - { - case EMoveType::Noop: { return _u8L("Noop"); } - case EMoveType::Retract: { return _u8L("Retraction"); } - case EMoveType::Unretract: { return _u8L("Unretraction"); } - case EMoveType::Tool_change: { return _u8L("Tool change"); } - case EMoveType::Color_change: { return _u8L("Color change"); } - case EMoveType::Pause_Print: { return _u8L("Pause print"); } - case EMoveType::Custom_GCode: { return _u8L("Custom GCode"); } - case EMoveType::Travel: { return _u8L("Travel"); } - case EMoveType::Extrude: { return _u8L("Extrusion"); } - default: { return _u8L("Unknown"); } - } - }; - - auto append_time_item = [&imgui] (const std::string& label, float time, float percentage, const ImVec4& color, const ColumnOffsets& offsets) { - imgui.text(label); - ImGui::SameLine(offsets[0]); - imgui.text(short_time(get_time_dhms(time))); - ImGui::SameLine(offsets[1]); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percentage); - ImGuiWindow* window = ImGui::GetCurrentWindow(); - ImRect frame_bb; - frame_bb.Min = { ImGui::GetCursorScreenPos().x, window->DC.CursorPos.y }; - frame_bb.Max = { frame_bb.Min.x + percentage * (window->WorkRect.Max.x - frame_bb.Min.x), window->DC.CursorPos.y + ImGui::CalcTextSize(buf, nullptr, false).y }; - frame_bb.Min.x -= IM_FLOOR(window->WindowPadding.x * 0.5f - 1.0f); - frame_bb.Max.x += IM_FLOOR(window->WindowPadding.x * 0.5f); - window->DrawList->AddRectFilled(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32({ color.x, color.y, color.z, 1.0f }), 0.0f, 0); - ImGui::TextUnformatted(buf); - }; - - auto append_move_times = [this, &imgui, move_type_label, append_headers, append_time_item](float total_time, - const std::vector>& moves_time, - const Headers& headers, const ColumnOffsets& offsets) { - - if (moves_time.empty()) - return; - - if (!ImGui::CollapsingHeader(_u8L("Moves Time").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) - return; - - append_headers(headers, offsets); - - std::vector> sorted_moves_time(moves_time); - std::sort(sorted_moves_time.begin(), sorted_moves_time.end(), [](const auto& p1, const auto& p2) { return p2.second < p1.second; }); - - for (const auto& [type, time] : sorted_moves_time) { - append_time_item(move_type_label(type), time, time / total_time, ImGuiWrapper::COL_ORANGE_LIGHT, offsets); - } - }; - - auto append_role_times = [this, &imgui, append_headers, append_time_item](float total_time, - const std::vector>& roles_time, - const Headers& headers, const ColumnOffsets& offsets) { - - if (roles_time.empty()) - return; - - if (!ImGui::CollapsingHeader(_u8L("Features Time").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) - return; - - append_headers(headers, offsets); - - std::vector> sorted_roles_time(roles_time); - std::sort(sorted_roles_time.begin(), sorted_roles_time.end(), [](const auto& p1, const auto& p2) { return p2.second < p1.second; }); - - for (const auto& [role, time] : sorted_roles_time) { - Color color = Extrusion_Role_Colors[static_cast(role)]; - append_time_item(_u8L(ExtrusionEntity::role_to_string(role)), time, time / total_time, { 0.666f * color[0], 0.666f * color[1], 0.666f * color[2], 1.0f}, offsets); - } - }; - - auto calc_common_offsets = [move_type_label]( - const std::vector>& moves_time, const Headers& moves_headers, - const std::vector>& roles_time, const Headers& roles_headers) { - ColumnOffsets ret = { std::max(ImGui::CalcTextSize(moves_headers[0].c_str()).x, ImGui::CalcTextSize(roles_headers[0].c_str()).x), - std::max(ImGui::CalcTextSize(moves_headers[1].c_str()).x, ImGui::CalcTextSize(roles_headers[1].c_str()).x) }; - - for (const auto& [type, time] : moves_time) { - ret[0] = std::max(ret[0], ImGui::CalcTextSize(move_type_label(type).c_str()).x); - ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time)).c_str()).x); - } - - for (const auto& [role, time] : roles_time) { - ret[0] = std::max(ret[0], ImGui::CalcTextSize(_u8L(ExtrusionEntity::role_to_string(role)).c_str()).x); - ret[1] = std::max(ret[1], ImGui::CalcTextSize(short_time(get_time_dhms(time)).c_str()).x); - } - - const ImGuiStyle& style = ImGui::GetStyle(); - ret[0] += 2.0f * style.ItemSpacing.x; - ret[1] += ret[0] + style.ItemSpacing.x; - return ret; - }; - - imgui.text(_u8L("Time") + ":"); - ImGui::SameLine(); - imgui.text(short_time(get_time_dhms(total_time))); - append_partial_times(items, partial_times_headers); - ColumnOffsets common_offsets = calc_common_offsets(moves_time, moves_headers, roles_time, roles_headers); - append_move_times(total_time, moves_time, moves_headers, common_offsets); - append_role_times(total_time, roles_time, roles_headers, common_offsets); - }; - - auto generate_partial_times = [this](const TimesList& times) { - PartialTimes items; - - std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; - int extruders_count = wxGetApp().extruders_edited_cnt(); - std::vector last_color(extruders_count); - for (int i = 0; i < extruders_count; ++i) { - last_color[i] = m_tool_colors[i]; - } - int last_extruder_id = 1; - for (const auto& time_rec : times) { - switch (time_rec.first) - { - case CustomGCode::PausePrint: - { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second }); - items.push_back({ PartialTime::EType::Pause, it->extruder, Color(), Color(), time_rec.second }); - custom_gcode_per_print_z.erase(it); - } - break; - } - case CustomGCode::ColorChange: - { - auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); - if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, Color(), Color(), time_rec.second }); - items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); - last_color[it->extruder - 1] = decode_color(it->color); - last_extruder_id = it->extruder; - custom_gcode_per_print_z.erase(it); - } - else - items.push_back({ PartialTime::EType::Print, last_extruder_id, Color(), Color(), time_rec.second }); - - break; - } - default: { break; } - } - } - - return items; - }; - - const Headers partial_times_headers = { - _u8L("Event"), - _u8L("Remaining"), - _u8L("Duration") - }; - const Headers moves_headers = { - _u8L("Type"), - _u8L("Time"), - _u8L("Percentage") - }; - const Headers roles_headers = { - _u8L("Feature"), - _u8L("Time"), - _u8L("Percentage") - }; - - Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - std::string title = _u8L("Estimated printing time"); - ImGui::OpenPopup(title.c_str()); - - imgui.set_next_window_pos(0.5f * static_cast(cnv_size.get_width()), 0.5f * static_cast(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 0.5f); - ImGui::SetNextWindowSize({ -1.0f, 0.666f * static_cast(cnv_size.get_height()) }); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.6f); - if (ImGui::BeginPopupModal(title.c_str(), &m_time_estimate_enabled, ImGuiWindowFlags_AlwaysAutoResize)) { - if (m_time_estimate_enabled) { - // imgui takes several frames to grayout the content of the canvas - if (m_time_estimate_frames_count < 10) { - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - ++m_time_estimate_frames_count; - } -#else - imgui.set_next_window_pos(static_cast(cnv_size.get_width()), static_cast(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f); - ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, 0.5f * static_cast(cnv_size.get_height()) }); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - ImGui::SetNextWindowBgAlpha(0.6f); - imgui.begin(std::string("Time_estimate"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); - - // title - imgui.title(_u8L("Estimated printing time")); -#endif // GCODE_VIEWER_TIME_ESTIMATE - - // mode tabs - ImGui::BeginTabBar("mode_tabs"); - const PrintEstimatedTimeStatistics::Mode& normal_mode = m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)]; - if (normal_mode.time > 0.0f) { - if (ImGui::BeginTabItem(_u8L("Normal").c_str())) { - append_mode(normal_mode.time, - generate_partial_times(normal_mode.custom_gcode_times), partial_times_headers, - normal_mode.moves_times, moves_headers, - normal_mode.roles_times, roles_headers); - ImGui::EndTabItem(); - } - } - const PrintEstimatedTimeStatistics::Mode& stealth_mode = m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)]; - if (stealth_mode.time > 0.0f) { - if (ImGui::BeginTabItem(_u8L("Stealth").c_str())) { - append_mode(stealth_mode.time, - generate_partial_times(stealth_mode.custom_gcode_times), partial_times_headers, - stealth_mode.moves_times, moves_headers, - stealth_mode.roles_times, roles_headers); - ImGui::EndTabItem(); - } - } - ImGui::EndTabBar(); - -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - // this is ugly, but it is the only way to ensure that the dialog is large - // enough to show enterely the title - // see: https://github.com/ocornut/imgui/issues/3239 - float width = std::max(ImGui::CalcTextSize(title.c_str()).x + 2.0f * ImGui::GetStyle().WindowPadding.x, 300.0f); - ImGui::SetCursorPosX(width); - ImGui::SetCursorPosX(0.0f); - } - else - m_time_estimate_enabled = false; - - ImGui::EndPopup(); - } -#else - imgui.end(); -#endif // GCODE_VIEWER_TIME_ESTIMATE - ImGui::PopStyleVar(); -} -#endif // GCODE_VIEWER_TIME_ESTIMATE - #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const { diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index e49a1f08bf..302296c412 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -369,18 +369,8 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE PrintEstimatedTimeStatistics m_time_statistics; -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - mutable bool m_time_estimate_enabled{ false }; - mutable unsigned int m_time_estimate_frames_count{ 0 }; -#else - bool m_time_estimate_enabled{ false }; -#endif // GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL -#endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND mutable PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal }; -#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS mutable Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -433,11 +423,6 @@ public: bool is_legend_enabled() const { return m_legend_enabled; } void enable_legend(bool enable) { m_legend_enabled = enable; } -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE - bool is_time_estimate_enabled() const { return m_time_estimate_enabled; } - void enable_time_estimate(bool enable); -#endif // GCODE_VIEWER_TIME_ESTIMATE - void export_toolpaths_to_obj(const char* filename) const; private: @@ -448,9 +433,6 @@ private: void render_toolpaths() const; void render_shells() const; void render_legend() const; -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE - void render_time_estimate() const; -#endif // GCODE_VIEWER_TIME_ESTIMATE #if ENABLE_GCODE_VIEWER_STATISTICS void render_statistics() const; #endif // ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c0da110d92..7ae6f42945 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3109,18 +3109,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) break; } #endif // ENABLE_RENDER_PICKING_PASS -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT || GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - case 'T': - case 't': - { - if (!m_main_toolbar.is_enabled()) { - m_gcode_viewer.enable_time_estimate(!m_gcode_viewer.is_time_estimate_enabled()); - m_dirty = true; - wxGetApp().plater()->update_preview_bottom_toolbar(); - } - break; - } -#endif // GCODE_VIEWER_TIME_ESTIMATE case 'Z': #if ENABLE_GCODE_VIEWER case 'z': diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ee1d32abb8..03d42089b8 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -558,9 +558,6 @@ public: void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); } -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE - bool is_time_estimate_enabled() const { return m_gcode_viewer.is_time_estimate_enabled(); } -#endif // GCODE_VIEWER_TIME_ESTIMATE #endif // ENABLE_GCODE_VIEWER void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 57b1158f65..5dcd26a877 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -331,13 +331,8 @@ bool Preview::init(wxWindow* parent, Model* model) get_option_type_string(OptionType::CustomGCodes) + "|0|" + get_option_type_string(OptionType::Shells) + "|0|" + get_option_type_string(OptionType::ToolMarker) + "|0|" + -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT - get_option_type_string(OptionType::Legend) + "|1|" + - get_option_type_string(OptionType::TimeEstimate) + "|1" -#else get_option_type_string(OptionType::Legend) + "|1" -#endif // GCODE_VIEWER_TIME_ESTIMATE - ); +); Slic3r::GUI::create_combochecklist(m_combochecklist_options, GUI::into_u8(_L("Options")), options_items); #else m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel"))); @@ -1472,14 +1467,7 @@ wxString Preview::get_option_type_string(OptionType type) const case OptionType::CustomGCodes: { return _L("Custom GCodes"); } case OptionType::Shells: { return _L("Shells"); } case OptionType::ToolMarker: { return _L("Tool marker"); } -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND case OptionType::Legend: { return _L("Legend/Estimated printing time"); } -#else - case OptionType::Legend: { return _L("Legend"); } -#endif // GCODE_VIEWER_TIME_ESTIMATE -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE - case OptionType::TimeEstimate: { return _L("Estimated printing time"); } -#endif // GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE default: { return ""; } } } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index ddb7af86fb..d9ce44bd62 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -150,10 +150,7 @@ public: CustomGCodes, Shells, ToolMarker, - Legend, -#if GCODE_VIEWER_TIME_ESTIMATE != TIME_ESTIMATE_NONE - TimeEstimate -#endif // GCODE_VIEWER_TIME_ESTIMATE + Legend }; Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process, diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 1a551216e7..1eceea22e4 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -206,16 +206,7 @@ void KBShortcutsDialog::fill_shortcuts() { L("Arrow Down"), L("Lower Layer") }, { "U", L("Upper Layer") }, { "D", L("Lower Layer") }, -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_LEGEND { "L", L("Show/Hide Legend/Estimated printing time") }, -#else - { "L", L("Show/Hide Legend") }, -#endif // GCODE_VIEWER_TIME_ESTIMATE -#if GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_DEFAULT - { "T", L("Show/Hide Estimated printing time") } -#elif GCODE_VIEWER_TIME_ESTIMATE == TIME_ESTIMATE_MODAL - { "T", L("Show Estimated printing time") } -#endif // GCODE_VIEWER_TIME_ESTIMATE }; m_full_shortcuts.push_back(std::make_pair(_L("Preview"), preview_shortcuts)); From 683af51685ec21d09be4c0ef9ac8228b38bcb56d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 1 Sep 2020 14:15:19 +0200 Subject: [PATCH 365/826] Replaced boost::filesystem::canonical() with boost::filesystem::absolute(), as canonical() is broken on Windows (reparse points aka symbolic links are not processed correctly). Fixes https://github.com/prusa3d/PrusaSlicer/issues/732 https://github.com/prusa3d/PrusaSlicer/issues/3956 https://github.com/prusa3d/PrusaSlicer/issues/4557 --- src/libslic3r/Preset.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index a5160d2db3..7aaa96c8cf 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -634,7 +634,9 @@ void PresetCollection::add_default_preset(const std::vector &keys, // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) { - boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); + // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, + // see https://github.com/prusa3d/PrusaSlicer/issues/732 + boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred(); m_dir_path = dir.string(); std::string errors_cummulative; // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. @@ -1518,7 +1520,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector Date: Tue, 1 Sep 2020 14:35:42 +0200 Subject: [PATCH 366/826] Fixed export of toolpaths to obj files --- src/slic3r/GUI/GCodeViewer.cpp | 25 +++++++++++++++++-------- src/slic3r/GUI/GCodeViewer.hpp | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 24ed5be9b4..772b290ea8 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -608,9 +608,15 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer.vertices.data_size_bytes(), vertices.data())); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - auto get_vertex = [&vertices, floats_per_vertex](size_t id) { + // get indices data from index buffer on gpu + std::vector indices = std::vector(buffer.indices.count); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.size() * sizeof(unsigned int)), indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + auto get_vertex = [&vertices, floats_per_vertex](unsigned int id) { // extract vertex from vector of floats - size_t base_id = id * floats_per_vertex; + unsigned int base_id = id * floats_per_vertex; return Vec3f(vertices[base_id + 0], vertices[base_id + 1], vertices[base_id + 2]); }; @@ -626,7 +632,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const float length; }; - auto generate_segment = [get_vertex](size_t start_id, float half_width, float half_height) { + auto generate_segment = [get_vertex](unsigned int start_id, unsigned int end_id, float half_width, float half_height) { auto local_basis = [](const Vec3f& dir) { // calculate local basis (dir, right, up) on given segment std::array ret; @@ -650,13 +656,16 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const }; Vec3f v1 = get_vertex(start_id) - half_height * Vec3f::UnitZ(); - Vec3f v2 = get_vertex(start_id + 1) - half_height * Vec3f::UnitZ(); + Vec3f v2 = get_vertex(end_id) - half_height * Vec3f::UnitZ(); float length = (v2 - v1).norm(); const auto&& [dir, right, up] = local_basis(v2 - v1); return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length }); }; size_t out_vertices_count = 0; + unsigned int indices_per_segment = buffer.indices_per_segment(); + unsigned int start_vertex_offset = buffer.start_segment_vertex_offset(); + unsigned int end_vertex_offset = buffer.end_segment_vertex_offset(); for (size_t i = 0; i < buffer.render_paths.size(); ++i) { // get paths segments from buffer paths @@ -675,9 +684,8 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const unsigned int start = static_cast(render_path.offsets[j] / sizeof(unsigned int)); unsigned int end = start + render_path.sizes[j]; - for (size_t k = start; k < end; k += 2) { - Segment curr = generate_segment(k, half_width, half_height); - + for (size_t k = start; k < end; k += static_cast(indices_per_segment)) { + Segment curr = generate_segment(indices[k + start_vertex_offset], indices[k + end_vertex_offset], half_width, half_height); if (k == start) { // starting endpoint vertices/normals out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right @@ -699,7 +707,8 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left out_vertices_count += 2; - Segment prev = generate_segment(k - 2, half_width, half_height); + size_t first_vertex_id = k - static_cast(indices_per_segment); + Segment prev = generate_segment(indices[first_vertex_id + start_vertex_offset], indices[first_vertex_id + end_vertex_offset], half_width, half_height); Vec3f med_dir = (prev.dir + curr.dir).normalized(); float disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); Vec3f disp_vec = disp * prev.dir; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 302296c412..68fed6f334 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -176,6 +176,24 @@ class GCodeViewer default: { return 0; } } } + unsigned int start_segment_vertex_offset() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Point: + case ERenderPrimitiveType::Line: + case ERenderPrimitiveType::Triangle: + default: { return 0; } + } + } + unsigned int end_segment_vertex_offset() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Point: { return 0; } + case ERenderPrimitiveType::Line: { return 1; } + case ERenderPrimitiveType::Triangle: { return 36; } // 1 vertex of 13th triangle + default: { return 0; } + } + } }; // helper to render shells From 2455df4017e4000347dc77b543c8674bd052b145 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 24 Aug 2020 13:53:18 +0200 Subject: [PATCH 367/826] notifiactions: new icons + deleting old warnings&errors --- resources/icons/notification_close.svg | 18 +++++ resources/icons/notification_close_hover.svg | 66 +++++++++++++++++ resources/icons/notification_error.svg | 71 +++++++++++++++++++ resources/icons/notification_minimalize.svg | 14 ++++ .../icons/notification_minimalize_hover.svg | 58 +++++++++++++++ resources/icons/notification_warning.svg | 70 ++++++++++++++++++ src/imgui/imconfig.h | 24 ++++--- src/slic3r/GUI/ImGuiWrapper.cpp | 24 ++++--- src/slic3r/GUI/NotificationManager.cpp | 4 +- src/slic3r/GUI/Plater.cpp | 4 ++ 10 files changed, 329 insertions(+), 24 deletions(-) create mode 100644 resources/icons/notification_close.svg create mode 100644 resources/icons/notification_close_hover.svg create mode 100644 resources/icons/notification_error.svg create mode 100644 resources/icons/notification_minimalize.svg create mode 100644 resources/icons/notification_minimalize_hover.svg create mode 100644 resources/icons/notification_warning.svg diff --git a/resources/icons/notification_close.svg b/resources/icons/notification_close.svg new file mode 100644 index 0000000000..708d8bfef1 --- /dev/null +++ b/resources/icons/notification_close.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/resources/icons/notification_close_hover.svg b/resources/icons/notification_close_hover.svg new file mode 100644 index 0000000000..a04dce21ad --- /dev/null +++ b/resources/icons/notification_close_hover.svg @@ -0,0 +1,66 @@ + +image/svg+xml + + + + + + + + + + diff --git a/resources/icons/notification_error.svg b/resources/icons/notification_error.svg new file mode 100644 index 0000000000..5356e7af6e --- /dev/null +++ b/resources/icons/notification_error.svg @@ -0,0 +1,71 @@ + +image/svg+xml + + + + + + + + + + diff --git a/resources/icons/notification_minimalize.svg b/resources/icons/notification_minimalize.svg new file mode 100644 index 0000000000..bb3ae9b7a1 --- /dev/null +++ b/resources/icons/notification_minimalize.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/resources/icons/notification_minimalize_hover.svg b/resources/icons/notification_minimalize_hover.svg new file mode 100644 index 0000000000..bc5bc6cca1 --- /dev/null +++ b/resources/icons/notification_minimalize_hover.svg @@ -0,0 +1,58 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_warning.svg b/resources/icons/notification_warning.svg new file mode 100644 index 0000000000..6ba7a046d8 --- /dev/null +++ b/resources/icons/notification_warning.svg @@ -0,0 +1,70 @@ + +image/svg+xml + + + + + + + + + + diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index feda857ae2..4a1d1faa0c 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -108,17 +108,19 @@ namespace ImGui const char ColorMarkerEnd = 0x3; // ETX // Special ASCII characters are used here as an ikons markers - const char PrintIconMarker = 0x4; - const char PrinterIconMarker = 0x5; - const char PrinterSlaIconMarker = 0x6; - const char FilamentIconMarker = 0x7; - const char MaterialIconMarker = 0x8; - const char CloseIconMarker = 0xB; - const char CloseIconHoverMarker = 0xC; - const char TimerDotMarker = 0xE; - const char TimerDotEmptyMarker = 0xF; - const char WarningMarker = 0x10; - const char ErrorMarker = 0x11; + const char PrintIconMarker = 0x4; + const char PrinterIconMarker = 0x5; + const char PrinterSlaIconMarker = 0x6; + const char FilamentIconMarker = 0x7; + const char MaterialIconMarker = 0x8; + const char CloseIconMarker = 0xB; + const char CloseIconHoverMarker = 0xC; +// const char TimerDotMarker = 0xE; +// const char TimerDotEmptyMarker = 0xF; + const char MinimalizeMarker = 0xE; + const char MinimalizeHoverMarker = 0xF; + const char WarningMarker = 0x10; + const char ErrorMarker = 0x11; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7c27545026..e839fdf9b2 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -37,17 +37,19 @@ namespace GUI { static const std::map font_icons = { - {ImGui::PrintIconMarker , "cog" }, - {ImGui::PrinterIconMarker , "printer" }, - {ImGui::PrinterSlaIconMarker, "sla_printer" }, - {ImGui::FilamentIconMarker , "spool" }, - {ImGui::MaterialIconMarker , "resin" }, - {ImGui::CloseIconMarker , "cross" }, - {ImGui::CloseIconHoverMarker, "cross_focus_large" }, - {ImGui::TimerDotMarker , "timer_dot" }, - {ImGui::TimerDotEmptyMarker , "timer_dot_empty" }, - {ImGui::WarningMarker , "flag_green" }, - {ImGui::ErrorMarker , "flag_red" } + {ImGui::PrintIconMarker , "cog" }, + {ImGui::PrinterIconMarker , "printer" }, + {ImGui::PrinterSlaIconMarker , "sla_printer" }, + {ImGui::FilamentIconMarker , "spool" }, + {ImGui::MaterialIconMarker , "resin" }, + {ImGui::CloseIconMarker , "notification_close" }, + {ImGui::CloseIconHoverMarker , "notification_close_hover" }, + //{ImGui::TimerDotMarker , "timer_dot" }, + //{ImGui::TimerDotEmptyMarker , "timer_dot_empty" }, + {ImGui::MinimalizeMarker , "notification_minimalize" }, + {ImGui::MinimalizeHoverMarker , "notification_minimalize_hover" }, + {ImGui::WarningMarker , "notification_warning" }, + {ImGui::ErrorMarker , "notification_error" } }; const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f }; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b7301f3d82..e90557a65e 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -492,12 +492,12 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& //button - if part if treggered std::string button_text; - button_text = ImGui::CloseIconMarker; + button_text = ImGui::MinimalizeMarker; if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1), ImVec2(win_pos_x, win_pos_y + m_window_height), true)) { - button_text = ImGui::CloseIconHoverMarker; + button_text = ImGui::MinimalizeHoverMarker; } ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1fe32fd2dc..1e4b344899 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3481,6 +3481,10 @@ void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, s } void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid) { + if (model.objects.size() == 0) { + clear_warnings(); + return; + } std::vector living_oids; living_oids.push_back(model.id().id); living_oids.push_back(print_oid); From 25fb569017741eb89422dcb25316af96012409c0 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 25 Aug 2020 09:55:29 +0200 Subject: [PATCH 368/826] notifications: plater warning not visible in preview --- src/slic3r/GUI/NotificationManager.cpp | 8 ++++++++ src/slic3r/GUI/NotificationManager.hpp | 3 +++ src/slic3r/GUI/Plater.cpp | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index e90557a65e..387a4d5deb 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -909,6 +909,14 @@ bool NotificationManager::find_older(NotificationManager::PopNotification* notif return false; } +void NotificationManager::set_in_preview(bool preview) +{ + m_in_preview = preview; + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PlaterWarning) + notification->hide(preview); + } +} void NotificationManager::dpi_changed() { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index d7037c53e4..2bd0ae86d0 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -94,6 +94,7 @@ public: void set_gray(bool g) { m_is_gray = g; } void set_paused(bool p) { m_paused = p; } bool compare_text(const std::string& text); + void hide(bool h) { m_hidden = h; } protected: // Call after every size change void init(); @@ -230,6 +231,7 @@ public: // finds and closes all notifications of given type void close_notification_of_type(const NotificationType type); void dpi_changed(); + void set_in_preview(bool preview); private: //pushes notification into the queue of notifications that are rendered //can be used to create custom notification @@ -246,6 +248,7 @@ private: bool m_hovered { false }; //timestamps used for slining finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; + bool m_in_preview; //prepared (basic) notifications const std::vector basic_notifications = { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1e4b344899..45a1f6ea82 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1512,7 +1512,7 @@ struct Plater::priv GLToolbar view_toolbar; GLToolbar collapse_toolbar; Preview *preview; - NotificationManager* notification_manager; + NotificationManager* notification_manager { nullptr }; BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; @@ -3304,6 +3304,8 @@ void Plater::priv::set_current_panel(wxPanel* panel) // sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably) view3D->set_as_dirty(); view_toolbar.select_item("3D"); + if(notification_manager != nullptr) + notification_manager->set_in_preview(false); } else if (current_panel == preview) { @@ -3318,6 +3320,8 @@ void Plater::priv::set_current_panel(wxPanel* panel) preview->set_as_dirty(); view_toolbar.select_item("Preview"); + if (notification_manager != nullptr) + notification_manager->set_in_preview(true); } current_panel->SetFocusFromKbd(); From bca60821d8246a5bcf1e129c6599579347dc823e Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 25 Aug 2020 16:28:03 +0200 Subject: [PATCH 369/826] notifications: plater warning refactor --- src/slic3r/GUI/GLCanvas3D.cpp | 66 +++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7ae6f42945..81f63cb4d4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -620,53 +620,57 @@ GLCanvas3D::WarningTexture::WarningTexture() void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas) { + // Since we have NotificationsManager.hpp the warning textures are no loger needed. + // However i have left the infrastructure here and only commented the rendering. + // The plater warning / error notifications are added and closed from here. + + std::string text; + bool error = false; + switch (warning) { + case ObjectOutside: text = L("An object outside the print area was detected."); break; + case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; + case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; + case SomethingNotShown: text = L("Some objects are not visible."); break; + case ObjectClashed: + text = L( "An object outside the print area was detected.\n" + "Resolve the current problem to continue slicing."); + error = true; + break; + } + if(state) { + if(error) + wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(text,*(wxGetApp().plater()->get_current_canvas3D())); + else + wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D())); + } else { + if (error) + wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); + else + wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text); + } + + /* auto it = std::find(m_warnings.begin(), m_warnings.end(), warning); if (state) { if (it != m_warnings.end()) // this warning is already set to be shown return; - m_warnings.emplace_back(warning); + m_warnings.push_back(warning); std::sort(m_warnings.begin(), m_warnings.end()); - - std::string text; - switch (warning) { - case ObjectOutside: text = L("An object outside the print area was detected."); break; - case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; - case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; - case SomethingNotShown: text = L("Some objects are not visible."); break; - case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n" - "Resolve the current problem to continue slicing."), - *(wxGetApp().plater()->get_current_canvas3D())); - break; - } - if (!text.empty()) - wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D())); } else { if (it == m_warnings.end()) // deactivating something that is not active is an easy task return; m_warnings.erase(it); - - std::string text; - switch (warning) { - case ObjectOutside: text = L("An object outside the print area was detected."); break; - case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; - case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; - case SomethingNotShown: text = L("Some objects are not visibl.e"); break; - case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break; - } - if (!text.empty()) - wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text); - - /*if (m_warnings.empty()) { // nothing remains to be shown + if (m_warnings.empty()) { // nothing remains to be shown reset(); m_msg_text = "";// save information for rescaling return; - }*/ + } } - /* + // Look at the end of our vector and generate proper texture. std::string text; bool red_colored = false; @@ -674,7 +678,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool case ObjectOutside : text = L("An object outside the print area was detected"); break; case ToolpathOutside : text = L("A toolpath outside the print area was detected"); break; case SlaSupportsOutside : text = L("SLA supports outside the print area were detected"); break; - case SomethingNotShown : text = L("Some objects are not visible"); break; + case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break; case ObjectClashed: { text = L("An object outside the print area was detected\n" "Resolve the current problem to continue slicing"); From a81afce1b83681f14995f20f1ef9ef684139039f Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 25 Aug 2020 17:59:51 +0200 Subject: [PATCH 370/826] notifications not showing slicing finished when error --- src/slic3r/GUI/NotificationManager.cpp | 17 +++++++++++++---- src/slic3r/GUI/NotificationManager.hpp | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 387a4d5deb..e27a4215cd 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -730,16 +730,16 @@ void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, { std::string hypertext; int time = 10; - if(large) - { + if (has_error_notification()) + return; + if (large) { hypertext = _u8L("Export G-Code."); time = 0; } NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext }; NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large); - if (push_notification_data(notification, canvas, timestamp)) { - } else { + if (!push_notification_data(notification, canvas, timestamp)) { delete notification; } } @@ -917,6 +917,15 @@ void NotificationManager::set_in_preview(bool preview) notification->hide(preview); } } +bool NotificationManager::has_error_notification() +{ + for (PopNotification* notification : m_pop_notifications) { + if (notification->get_data().level == NotificationLevel::ErrorNotification) + return true; + } + return false; +} + void NotificationManager::dpi_changed() { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 2bd0ae86d0..0b066a3a04 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -240,6 +240,7 @@ private: //finds older notification of same type and moves it to the end of queue. returns true if found bool find_older(NotificationManager::PopNotification* notification); void sort_notifications(); + bool has_error_notification(); wxEvtHandler* m_evt_handler; std::deque m_pop_notifications; From 3984326ee335da301f6d1c2440a2f176ba57a65b Mon Sep 17 00:00:00 2001 From: David Kocik Date: Wed, 26 Aug 2020 10:49:42 +0200 Subject: [PATCH 371/826] notification init() at first render, not notification creation. Hopefully a fix of issue #4647. --- src/slic3r/GUI/NotificationManager.cpp | 7 ++++++- src/slic3r/GUI/NotificationManager.hpp | 11 ++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index e27a4215cd..47962f4b2a 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -49,13 +49,17 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, , m_text2 (n.text2) , m_evt_handler (evt_handler) { - init(); + //init(); } NotificationManager::PopNotification::~PopNotification() { } NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y) { + if (!m_initialized) + { + init(); + } if (m_finished) return RenderResult::Finished; if (m_close_pending) { @@ -228,6 +232,7 @@ void NotificationManager::PopNotification::init() } m_lines_count++; } + m_initialized = true; } void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 0b066a3a04..a11d08394c 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -121,6 +121,7 @@ public: const NotificationData m_data; int m_id; + bool m_initialized { false }; // Main text std::string m_text1; // Clickable text @@ -131,12 +132,12 @@ public: long m_remaining_time; bool m_counting_down; long m_last_remaining_time; - bool m_paused{ false }; - int m_countdown_frame{ 0 }; - bool m_fading_out{ false }; + bool m_paused { false }; + int m_countdown_frame { 0 }; + bool m_fading_out { false }; // total time left when fading beggins - float m_fading_time{ 0.0f }; - float m_current_fade_opacity{ 1.f }; + float m_fading_time { 0.0f }; + float m_current_fade_opacity { 1.f }; // If hidden the notif is alive but not visible to user bool m_hidden { false }; // m_finished = true - does not render, marked to delete From ac9e1e8e4aa8661ca79013650637cdcc42b79d13 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 1 Sep 2020 16:14:18 +0200 Subject: [PATCH 372/826] Extract app icon from exe on Windows --- src/slic3r/GUI/MainFrame.cpp | 59 +++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 886e96e1a0..bab5d7502e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -115,18 +115,18 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON - SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -// // Load the icon either from the exe, or from the ico file. -//#if _WIN32 -// { -// -// TCHAR szExeFileName[MAX_PATH]; -// GetModuleFileName(nullptr, szExeFileName, MAX_PATH); -// SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); -// } -//#else // SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -//#endif // _WIN32 + // Load the icon either from the exe, or from the ico file. +#if _WIN32 + { + + TCHAR szExeFileName[MAX_PATH]; + GetModuleFileName(nullptr, szExeFileName, MAX_PATH); + SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); + } +#else + SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#endif // _WIN32 // initialize status bar m_statusbar = std::make_shared(this); @@ -1416,7 +1416,18 @@ void MainFrame::set_mode(EMode mode) m_plater->Thaw(); +// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); + // Load the icon either from the exe, or from the ico file. +#if _WIN32 + { + + TCHAR szExeFileName[MAX_PATH]; + GetModuleFileName(nullptr, szExeFileName, MAX_PATH); + SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); + } +#else SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#endif // _WIN32 #if ENABLE_GCODE_VIEWER_TASKBAR_ICON if (m_taskbar_icon != nullptr) { m_taskbar_icon->RemoveIcon(); @@ -1473,7 +1484,7 @@ void MainFrame::set_mode(EMode mode) #if ENABLE_GCODE_VIEWER_TASKBAR_ICON if (m_taskbar_icon != nullptr) { m_taskbar_icon->RemoveIcon(); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); + m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON @@ -1981,18 +1992,18 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -// // Load the icon either from the exe, or from the ico file. -//#if _WIN32 -// { -// -// TCHAR szExeFileName[MAX_PATH]; -// GetModuleFileName(nullptr, szExeFileName, MAX_PATH); -// SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); -// } -//#else -// SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -//#endif // _WIN32 +// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); + // Load the icon either from the exe, or from the ico file. +#if _WIN32 + { + + TCHAR szExeFileName[MAX_PATH]; + GetModuleFileName(nullptr, szExeFileName, MAX_PATH); + SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); + } +#else + SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#endif // _WIN32 this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) { From 08580a9b1800c30f0673bacc7c8049278dace7ff Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 1 Sep 2020 16:56:12 +0200 Subject: [PATCH 373/826] WIP: prusa-gcodeviewer command line wrapper to start the PrusaSlicer in standalone G-code viewer mode. Linux and OSX stuff will follow. --- resources/icons/PrusaSlicer-gcodeviewer.ico | Bin 0 -> 113976 bytes src/CMakeLists.txt | 13 ++++++++- src/PrusaSlicer.cpp | 4 +++ src/PrusaSlicer_app_msvc.cpp | 5 ++++ src/libslic3r/PrintConfig.cpp | 6 +++++ .../msw/PrusaSlicer-gcodeviewer.rc.in | 25 ++++++++++++++++++ 6 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 resources/icons/PrusaSlicer-gcodeviewer.ico create mode 100644 src/platform/msw/PrusaSlicer-gcodeviewer.rc.in diff --git a/resources/icons/PrusaSlicer-gcodeviewer.ico b/resources/icons/PrusaSlicer-gcodeviewer.ico new file mode 100644 index 0000000000000000000000000000000000000000..2c9272369fd5617beefe7847ab2264a40f033ab4 GIT binary patch literal 113976 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:/DEBUG>") + target_link_options(PrusaSlicer_app_console PUBLIC "$<$:/DEBUG>") endif () target_compile_definitions(PrusaSlicer_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE) add_dependencies(PrusaSlicer_app_console PrusaSlicer) set_target_properties(PrusaSlicer_app_console PROPERTIES OUTPUT_NAME "prusa-slicer-console") target_link_libraries(PrusaSlicer_app_console PRIVATE boost_headeronly) + + add_executable(PrusaSlicer_app_gcodeviewer WIN32 PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer-gcodeviewer.rc) + # Generate debug symbols even in release mode. + if (MSVC) + target_link_options(PrusaSlicer_app_gcodeviewer PUBLIC "$<$:/DEBUG>") + endif () + target_compile_definitions(PrusaSlicer_app_gcodeviewer PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE -DSLIC3R_WRAPPER_GCODEVIEWER) + add_dependencies(PrusaSlicer_app_gcodeviewer PrusaSlicer) + set_target_properties(PrusaSlicer_app_gcodeviewer PROPERTIES OUTPUT_NAME "prusa-gcodeviewer") + target_link_libraries(PrusaSlicer_app_gcodeviewer PRIVATE boost_headeronly) endif () # Link the resources dir to where Slic3r GUI expects it diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index aaf3db9158..2962f0cdfe 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -101,6 +101,7 @@ int CLI::run(int argc, char **argv) std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); + bool start_as_gcodeviewer = false; const std::vector &load_configs = m_config.option("load", true)->values; @@ -521,6 +522,9 @@ int CLI::run(int argc, char **argv) << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; */ } + } else if (opt_key == "gcodeviewer") { + start_gui = true; + start_as_gcodeviewer = true; } else { boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; return 1; diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 712cff687d..5f12c91479 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -221,6 +221,11 @@ int wmain(int argc, wchar_t **argv) std::vector argv_extended; argv_extended.emplace_back(argv[0]); +#ifdef SLIC3R_WRAPPER_GCODEVIEWER + wchar_t gcodeviewer_param[] = L"--gcodeviewer"; + argv_extended.emplace_back(gcodeviewer_param); +#endif /* SLIC3R_WRAPPER_GCODEVIEWER */ + #ifdef SLIC3R_GUI // Here one may push some additional parameters based on the wrapper type. bool force_mesa = false; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3401dcc020..770983ad59 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3530,6 +3530,12 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->cli = "export-gcode|gcode|g"; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("gcodeviewer", coBool); + def->label = L("G-code viewer"); + def->tooltip = L("Visualize an already sliced and saved G-code"); + def->cli = "gcodeviewer"; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("slice", coBool); def->label = L("Slice"); def->tooltip = L("Slice the model as FFF or SLA based on the printer_technology configuration value."); diff --git a/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in b/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in new file mode 100644 index 0000000000..7f4e5a15c3 --- /dev/null +++ b/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in @@ -0,0 +1,25 @@ +1 VERSIONINFO +FILEVERSION @SLIC3R_RC_VERSION@ +PRODUCTVERSION @SLIC3R_RC_VERSION@ +{ + BLOCK "StringFileInfo" + { + BLOCK "040904E4" + { + VALUE "CompanyName", "Prusa Research" + VALUE "FileDescription", "@SLIC3R_APP_NAME@ G-code Viewer" + VALUE "FileVersion", "@SLIC3R_BUILD_ID@" + VALUE "ProductName", "@SLIC3R_APP_NAME@ G-code Viewer" + VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" + VALUE "InternalName", "@SLIC3R_APP_NAME@ G-code Viewer" + VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranelucci" + VALUE "OriginalFilename", "prusa-gcodeviewer.exe" + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x409, 1252 + } +} +2 ICON "@SLIC3R_RESOURCES_DIR@/icons/PrusaSlicer-gcodeviewer.ico" +1 24 "PrusaSlicer.manifest" From 6033e6990671d2ccc535058b5e0a8f8da862f832 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Tue, 1 Sep 2020 17:56:19 +0200 Subject: [PATCH 374/826] filaments selecting: sorting via printer, showing printers for filament --- src/slic3r/GUI/ConfigWizard.cpp | 484 ++++++++++++++++++------ src/slic3r/GUI/ConfigWizard_private.hpp | 150 ++++++-- 2 files changed, 476 insertions(+), 158 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 60323976c8..f8abfb1788 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -561,30 +561,37 @@ const std::string PageMaterials::EMPTY; PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) : ConfigWizardPage(parent, std::move(title), std::move(shortname)) , materials(materials) - , list_l1(new StringList(this)) - , list_l2(new StringList(this)) - , list_l3(new PresetList(this)) + , list_printer(new StringList(this, wxLB_MULTIPLE)) + , list_type(new StringList(this)) + , list_vendor(new StringList(this)) + , list_profile(new PresetList(this)) + , compatible_printers(new wxStaticText(this, wxID_ANY, _(L("")))) { append_spacer(VERTICAL_SPACING); const int em = parent->em_unit(); const int list_h = 30*em; - list_l1->SetMinSize(wxSize(8*em, list_h)); - list_l2->SetMinSize(wxSize(13*em, list_h)); - list_l3->SetMinSize(wxSize(25*em, list_h)); + list_printer->SetWindowStyle(wxLB_EXTENDED); - auto *grid = new wxFlexGridSizer(3, em/2, em); - grid->AddGrowableCol(2, 1); + list_printer->SetMinSize(wxSize(23*em, list_h)); + list_type->SetMinSize(wxSize(8*em, list_h)); + list_vendor->SetMinSize(wxSize(13*em, list_h)); + list_profile->SetMinSize(wxSize(23*em, list_h)); + + grid = new wxFlexGridSizer(4, em/2, em); + grid->AddGrowableCol(3, 1); grid->AddGrowableRow(1, 1); + grid->Add(new wxStaticText(this, wxID_ANY, _(L("Printer:")))); grid->Add(new wxStaticText(this, wxID_ANY, list1name)); grid->Add(new wxStaticText(this, wxID_ANY, _(L("Vendor:")))); grid->Add(new wxStaticText(this, wxID_ANY, _(L("Profile:")))); - grid->Add(list_l1, 0, wxEXPAND); - grid->Add(list_l2, 0, wxEXPAND); - grid->Add(list_l3, 1, wxEXPAND); + grid->Add(list_printer, 0, wxEXPAND); + grid->Add(list_type, 0, wxEXPAND); + grid->Add(list_vendor, 0, wxEXPAND); + grid->Add(list_profile, 1, wxEXPAND); auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); auto *sel_all = new wxButton(this, wxID_ANY, _(L("All"))); @@ -592,121 +599,342 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); btn_sizer->Add(sel_none); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); grid->Add(new wxBoxSizer(wxHORIZONTAL)); grid->Add(btn_sizer, 0, wxALIGN_RIGHT); + + auto* notes_sizer = new wxBoxSizer(wxHORIZONTAL); + notes_sizer->Add(compatible_printers); + grid->Add(notes_sizer); + append(grid, 1, wxEXPAND); - list_l1->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_l1->GetSelection(), list_l2->GetSelection()); + list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { + update_lists(evt.GetInt(), list_type->GetSelection(), list_vendor->GetSelection()); + }); + list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_printer->GetSelection(), list_type->GetSelection(), list_vendor->GetSelection()); }); - list_l2->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_l1->GetSelection(), list_l2->GetSelection()); + list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_printer->GetSelection(), list_type->GetSelection(), list_vendor->GetSelection()); }); - list_l3->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); + list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); + list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); + Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); + + list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); + list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); + list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); + reload_presets(); } - +void PageMaterials::on_paint() +{ + if (first_paint) { + first_paint = false; + prepare_compatible_printers_label(); + } +} +void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) +{ + const wxClientDC dc(list_profile); + const wxPoint pos = evt.GetLogicalPosition(dc); + int item = list_profile->HitTest(pos); + BOOST_LOG_TRIVIAL(error) << "hit test: " << item; + on_material_hovered(item); +} +void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) +{} +void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) +{ + on_material_hovered(-1); +} void PageMaterials::reload_presets() { clear(); - list_l1->append(_(L("(All)")), &EMPTY); + list_printer->append(_(L("(All)")), &EMPTY); + list_printer->SetLabelMarkup("bald"); + for (const Preset* printer : materials->printers) { + list_printer->append(printer->name, &printer->name); + } - for (const std::string &type : materials->types) { - list_l1->append(type, &type); - } - - if (list_l1->GetCount() > 0) { - list_l1->SetSelection(0); - sel1_prev = wxNOT_FOUND; - sel2_prev = wxNOT_FOUND; - update_lists(0, 0); + if (list_printer->GetCount() > 0) { + list_printer->SetSelection(0); + sel_printer_prev = wxNOT_FOUND; + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; + update_lists(0, 0, 0); } presets_loaded = true; } -void PageMaterials::update_lists(int sel1, int sel2) +void PageMaterials::prepare_compatible_printers_label() { - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - if (sel1 != sel1_prev) { - // Refresh the second list - - // XXX: The vendor list is created with quadratic complexity here, - // but the number of vendors is going to be very small this shouldn't be a problem. - - list_l2->Clear(); - list_l2->append(_(L("(All)")), &EMPTY); - if (sel1 != wxNOT_FOUND) { - const std::string &type = list_l1->get_data(sel1); - - materials->filter_presets(type, EMPTY, [this](const Preset *p) { - const std::string &vendor = this->materials->get_vendor(p); - - if (list_l2->find(vendor) == wxNOT_FOUND) { - list_l2->append(vendor, &vendor); - } - }); - } - - sel1_prev = sel1; - sel2 = 0; - sel2_prev = wxNOT_FOUND; - list_l2->SetSelection(sel2); - list_l3->Clear(); + assert(grid->GetColWidths().size() == 4); + compatible_printers_width = grid->GetColWidths()[3]; + empty_printers_label = "Compatible printers:"; + for (const Preset* printer : materials->printers) { + empty_printers_label += "\n"; } + clear_compatible_printers_label(); +} - if (sel2 != sel2_prev) { - // Refresh the third list +void PageMaterials::clear_compatible_printers_label() +{ + compatible_printers->SetLabel(boost::nowide::widen(empty_printers_label)); + compatible_printers->Wrap(compatible_printers_width); + Layout(); +} - list_l3->Clear(); - if (sel1 != wxNOT_FOUND && sel2 != wxNOT_FOUND) { - const std::string &type = list_l1->get_data(sel1); - const std::string &vendor = list_l2->get_data(sel2); - - materials->filter_presets(type, vendor, [this](const Preset *p) { - bool was_checked = false; - - int cur_i = list_l3->find(p->alias); - if (cur_i == wxNOT_FOUND) - cur_i = list_l3->append(p->alias, &p->alias); +void PageMaterials::on_material_hovered(int sel_material) +{ + if ( sel_material == last_hovered_item) + return; + if (sel_material == -1) { + clear_compatible_printers_label(); + return; + } + last_hovered_item = sel_material; + std::string compatible_printers_label = "compatible printers:\n"; + //selected material string + std::string material_name = list_profile->get_data(sel_material); + // get material preset + const std::vector matching_materials = materials->get_presets_by_alias(material_name); + if (matching_materials.empty()) + { + clear_compatible_printers_label(); + return; + } + //find matching printers + bool first = true; + for (const Preset* printer : materials->printers) { + bool compatible = false; + for (const Preset* material : matching_materials) { + if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { + if (first) + first = false; else - was_checked = list_l3->IsChecked(cur_i); - - const std::string& section = materials->appconfig_section(); - - const bool checked = wizard_p()->appconfig_new.has(section, p->name); - list_l3->Check(cur_i, checked | was_checked); - - /* Update preset selection in config. - * If one preset from aliases bundle is selected, - * than mark all presets with this aliases as selected - * */ - if (checked && !was_checked) - wizard_p()->update_presets_in_config(section, p->alias, true); - else if (!checked && was_checked) - wizard_p()->appconfig_new.set(section, p->name, "1"); - } ); + compatible_printers_label += "\n";//", "; + compatible_printers_label += printer->name; + compatible = true; + break; + } } - - sel2_prev = sel2; } + this->compatible_printers->SetLabel(boost::nowide::widen(compatible_printers_label)); + this->compatible_printers->Wrap(compatible_printers_width); +} + +void PageMaterials::on_material_highlighted(int sel_material) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + //std::string compatible_printers_label = "compatible printers:\n"; + //std::string empty_suplement = std::string(); + //unselect all printers + list_printer->SetSelection(wxNOT_FOUND); + //selected material string + std::string material_name = list_profile->get_data(sel_material); + // get material preset + const std::vector matching_materials = materials->get_presets_by_alias(material_name); + if (matching_materials.empty()) + return; + //find matching printers + //bool first = true; + for (const Preset* printer : materials->printers) { + bool compatible = false; + for (const Preset* material : matching_materials) { + if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { + //select printer + int index = list_printer->find(printer->name); + list_printer->SetSelection(index); + /*if (first) + first = false; + else + compatible_printers_label += "\n";//", "; + compatible_printers_label += printer->name; + compatible = true; + break;*/ + } + } + //if(!compatible) + // empty_suplement += std::string(printer->name.length() + 2, ' '); + } + // fill rest of label with blanks so it maintains legth + //compatible_printers_label += empty_suplement; + + update_lists(0,0,0); + list_profile->SetSelection(list_profile->find(material_name)); + + //this->compatible_printers->SetLabel(boost::nowide::widen(compatible_printers_label)); + //this->compatible_printers->Wrap(compatible_printers_width); + //Refresh(); +} + +void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + wxArrayInt sel_printers; + int sel_printers_count = list_printer->GetSelections(sel_printers); + + if (sel_printers_count != sel_printer_prev) { + // Refresh type list + list_type->Clear(); + list_type->append(_(L("(All)")), &EMPTY); + if (sel_printers_count > 0) { + // If all is selected with other printers + // unselect "all" or all printers depending on last value + if (sel_printers[0] == 0 && sel_printers_count > 1) { + if (sel_printer == 0) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + } else { + list_printer->SetSelection(0, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + if (sel_printers[0] != 0) { + for (size_t i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + } else { + //clear selection except "ALL" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + + materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + + } + + sel_printer_prev = sel_printers_count; + sel_type = 0; + sel_type_prev = wxNOT_FOUND; + list_type->SetSelection(sel_type); + list_profile->Clear(); + } + + if (sel_type != sel_type_prev) { + // Refresh vendor list + + // XXX: The vendor list is created with quadratic complexity here, + // but the number of vendors is going to be very small this shouldn't be a problem. + + list_vendor->Clear(); + list_vendor->append(_(L("(All)")), &EMPTY); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + // find printer preset + for (size_t i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { + const std::string& vendor = this->materials->get_vendor(p); + if (list_vendor->find(vendor) == wxNOT_FOUND) { + list_vendor->append(vendor, &vendor); + } + }); + } + } + + sel_type_prev = sel_type; + sel_vendor = 0; + sel_vendor_prev = wxNOT_FOUND; + list_vendor->SetSelection(sel_vendor); + list_profile->Clear(); + } + + if (sel_vendor != sel_vendor_prev) { + // Refresh material list + + list_profile->Clear(); + clear_compatible_printers_label(); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + const std::string& vendor = list_vendor->get_data(sel_vendor); + // finst printer preset + for (size_t i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + + materials->filter_presets(printer, type, vendor, [this](const Preset* p) { + bool was_checked = false; + //size_t printer_counter = materials->get_printer_counter(p); + int cur_i = list_profile->find(p->alias); + if (cur_i == wxNOT_FOUND) + //cur_i = list_profile->append(p->alias + " " + std::to_string(printer_counter)/*+ (omnipresent ? "" : " ONLY SOME PRINTERS")*/, &p->alias); + cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); + else + was_checked = list_profile->IsChecked(cur_i); + + const std::string& section = materials->appconfig_section(); + + const bool checked = wizard_p()->appconfig_new.has(section, p->name); + list_profile->Check(cur_i, checked | was_checked); + + /* Update preset selection in config. + * If one preset from aliases bundle is selected, + * than mark all presets with this aliases as selected + * */ + if (checked && !was_checked) + wizard_p()->update_presets_in_config(section, p->alias, true); + else if (!checked && was_checked) + wizard_p()->appconfig_new.set(section, p->name, "1"); + }); + } + } + + sel_vendor_prev = sel_vendor; + } } void PageMaterials::select_material(int i) { - const bool checked = list_l3->IsChecked(i); + const bool checked = list_profile->IsChecked(i); - const std::string& alias_key = list_l3->get_data(i); + const std::string& alias_key = list_profile->get_data(i); wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); } @@ -715,10 +943,10 @@ void PageMaterials::select_all(bool select) wxWindowUpdateLocker freeze_guard(this); (void)freeze_guard; - for (unsigned i = 0; i < list_l3->GetCount(); i++) { - const bool current = list_l3->IsChecked(i); + for (unsigned i = 0; i < list_profile->GetCount(); i++) { + const bool current = list_profile->IsChecked(i); if (current != select) { - list_l3->Check(i, select); + list_profile->Check(i, select); select_material(i); } } @@ -726,11 +954,13 @@ void PageMaterials::select_all(bool select) void PageMaterials::clear() { - list_l1->Clear(); - list_l2->Clear(); - list_l3->Clear(); - sel1_prev = wxNOT_FOUND; - sel2_prev = wxNOT_FOUND; + list_printer->Clear(); + list_type->Clear(); + list_vendor->Clear(); + list_profile->Clear(); + sel_printer_prev = wxNOT_FOUND; + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; presets_loaded = false; } @@ -740,6 +970,7 @@ void PageMaterials::on_activate() wizard_p()->update_materials(materials->technology); reload_presets(); } + first_paint = true; } @@ -1314,16 +1545,22 @@ const std::string Materials::UNKNOWN = "(Unknown)"; void Materials::push(const Preset *preset) { - presets.push_back(preset); + presets.emplace_back(preset, 0); types.insert(technology & T_FFF ? Materials::get_filament_type(preset) : Materials::get_material_type(preset)); } +void Materials::add_printer(const Preset* preset) +{ + printers.insert(preset); +} + void Materials::clear() { presets.clear(); types.clear(); + printers.clear(); } const std::string& Materials::appconfig_section() const @@ -1373,7 +1610,6 @@ const std::string& Materials::get_material_vendor(const Preset *preset) return opt != nullptr ? opt->value : UNKNOWN; } - // priv static const std::unordered_map> legacy_preset_map {{ @@ -1601,26 +1837,28 @@ void ConfigWizard::priv::update_materials(Technology technology) if (any_fff_selected && (technology & T_FFF)) { filaments.clear(); aliases_fff.clear(); - // Iterate filaments in all bundles for (const auto &pair : bundles) { for (const auto &filament : pair.second.preset_bundle->filaments) { // Check if filament is already added - if (filaments.containts(&filament)) - continue; + if (filaments.containts(&filament)) + continue; // Iterate printers in all bundles - // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. -// for (const auto &pair : bundles) - for (const auto &printer : pair.second.preset_bundle->printers) - // Filter out inapplicable printers - if (printer.is_visible && printer.printer_technology() == ptFFF && - is_compatible_with_printer(PresetWithVendorProfile(filament, nullptr), PresetWithVendorProfile(printer, nullptr)) && - // Check if filament is already added - ! filaments.containts(&filament)) { - filaments.push(&filament); - if (!filament.alias.empty()) - aliases_fff[filament.alias].insert(filament.name); - } + for (const auto &printer : pair.second.preset_bundle->printers) { + if (!printer.is_visible || printer.printer_technology() != ptFFF) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + filaments.add_printer_counter(&filament); + filaments.add_printer(&printer); + } + } + } } } @@ -1637,17 +1875,21 @@ void ConfigWizard::priv::update_materials(Technology technology) continue; // Iterate printers in all bundles // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. -// for (const auto &pair : bundles) - for (const auto &printer : pair.second.preset_bundle->printers) - // Filter out inapplicable printers - if (printer.is_visible && printer.printer_technology() == ptSLA && - is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr)) && - // Check if material is already added - ! sla_materials.containts(&material)) { + for (const auto& printer : pair.second.preset_bundle->printers) { + if(!printer.is_visible || printer.printer_technology() != ptSLA) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { + // Check if material is already added + if(!sla_materials.containts(&material)) { sla_materials.push(&material); if (!material.alias.empty()) aliases_sla[material.alias].insert(material.name); } + sla_materials.add_printer_counter(&material); + sla_materials.add_printer(&printer); + } + } } } } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 9921552a73..f7987a8908 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -57,32 +57,98 @@ enum Technology { T_ANY = ~0, }; +struct Bundle +{ + std::unique_ptr preset_bundle; + VendorProfile* vendor_profile{ nullptr }; + bool is_in_resources{ false }; + bool is_prusa_bundle{ false }; + + Bundle() = default; + Bundle(Bundle&& other); + + // Returns false if not loaded. Reason for that is logged as boost::log error. + bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false); + + const std::string& vendor_id() const { return vendor_profile->id; } +}; + +struct BundleMap : std::unordered_map +{ + static BundleMap load(); + + Bundle& prusa_bundle(); + const Bundle& prusa_bundle() const; +}; + struct Materials { Technology technology; // use vector for the presets to purpose of save of presets sorting in the bundle - std::vector presets; + // bool is true if material is present in all printers (omnipresent) + // size_t is counter of printers compatible with material + std::vector> presets; std::set types; + std::set printers; Materials(Technology technology) : technology(technology) {} void push(const Preset *preset); + void add_printer(const Preset* preset); void clear(); bool containts(const Preset *preset) const { - return std::find(presets.begin(), presets.end(), preset) != presets.end(); + //return std::find(presets.begin(), presets.end(), preset) != presets.end(); + return std::find_if(presets.begin(), presets.end(), + [preset](const std::pair& element) { return element.first == preset; }) != presets.end(); + } + + bool get_omnipresent(const Preset* preset) { + return get_printer_counter(preset) == printers.size(); + } + + const std::vector get_presets_by_alias(const std::string name) { + std::vector ret_vec; + for (auto it = presets.begin(); it != presets.end(); ++it) { + if ((*it).first->alias == name) + ret_vec.push_back((*it).first); + } + return ret_vec; + } + + void add_printer_counter(const Preset* preset) { + for (auto it = presets.begin(); it != presets.end(); ++it) { + if ((*it).first->alias == preset->alias) + (*it).second += 1; + } + } + + size_t get_printer_counter(const Preset* preset) { + size_t highest = 0; + for (auto it : presets) { + if (it.first->alias == preset->alias && it.second > highest) + highest = it.second; + } + return highest; + } const std::string& appconfig_section() const; const std::string& get_type(const Preset *preset) const; const std::string& get_vendor(const Preset *preset) const; + - template void filter_presets(const std::string &type, const std::string &vendor, F cb) { - for (const Preset *preset : presets) { - if ((type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor)) { - cb(preset); - } - } - } + template void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) { + for (auto preset : presets) { + const Preset& prst = *(preset.first); + const Preset& prntr = *printer; + if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) && + (type.empty() || get_type(preset.first) == type) && + (vendor.empty() || get_vendor(preset.first) == vendor)) { + + cb(preset.first); + } + } + } static const std::string UNKNOWN; static const std::string& get_filament_type(const Preset *preset); @@ -91,33 +157,9 @@ struct Materials static const std::string& get_material_vendor(const Preset *preset); }; -struct Bundle -{ - std::unique_ptr preset_bundle; - VendorProfile *vendor_profile { nullptr }; - bool is_in_resources { false }; - bool is_prusa_bundle { false }; - - Bundle() = default; - Bundle(Bundle &&other); - - // Returns false if not loaded. Reason for that is logged as boost::log error. - bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false); - - const std::string& vendor_id() const { return vendor_profile->id; } -}; - -struct BundleMap: std::unordered_map -{ - static BundleMap load(); - - Bundle& prusa_bundle(); - const Bundle& prusa_bundle() const; -}; struct PrinterPickerEvent; - // GUI elements typedef std::function ModelFilter; @@ -225,6 +267,7 @@ struct PagePrinters: ConfigWizardPage template struct DataList : public T { DataList(wxWindow *parent) : T(parent, wxID_ANY) {} + DataList(wxWindow* parent, int style) : T(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, style) {} // Note: We're _not_ using wxLB_SORT here because it doesn't do the right thing, // eg. "ABS" is sorted before "(All)" @@ -252,6 +295,25 @@ template struct DataList : public T } int size() { return this->GetCount(); } + + void on_mouse_move(const wxPoint& position) { + int item = T::HitTest(position); + + if(item == wxHitTest::wxHT_WINDOW_INSIDE) + BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_INSIDE"; + else if (item == wxHitTest::wxHT_WINDOW_OUTSIDE) + BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_OUTSIDE"; + else if(item == wxHitTest::wxHT_WINDOW_CORNER) + BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_CORNER"; + else if (item == wxHitTest::wxHT_WINDOW_VERT_SCROLLBAR) + BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_VERT_SCROLLBAR"; + else if (item == wxHitTest::wxHT_NOWHERE) + BOOST_LOG_TRIVIAL(error) << "hit test wxHT_NOWHERE"; + else if (item == wxHitTest::wxHT_MAX) + BOOST_LOG_TRIVIAL(error) << "hit test wxHT_MAX"; + else + BOOST_LOG_TRIVIAL(error) << "hit test: " << item; + } }; typedef DataList StringList; @@ -260,21 +322,35 @@ typedef DataList PresetList; struct PageMaterials: ConfigWizardPage { Materials *materials; - StringList *list_l1, *list_l2; - PresetList *list_l3; - int sel1_prev, sel2_prev; + StringList *list_printer, *list_type, *list_vendor; + PresetList *list_profile; + int sel_printer_prev, sel_type_prev, sel_vendor_prev; bool presets_loaded; + wxFlexGridSizer *grid; + wxStaticText *compatible_printers; + int compatible_printers_width = { 100 }; + std::string empty_printers_label; + bool first_paint = { false }; static const std::string EMPTY; + int last_hovered_item = { -1 } ; PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name); void reload_presets(); - void update_lists(int sel1, int sel2); + void update_lists(int sel1, int sel2, int sel3); + void on_material_highlighted(int sel_material); + void on_material_hovered(int sel_material); void select_material(int i); void select_all(bool select); void clear(); + void prepare_compatible_printers_label(); + void clear_compatible_printers_label(); + void on_paint(); + void on_mouse_move_on_profiles(wxMouseEvent& evt); + void on_mouse_enter_profiles(wxMouseEvent& evt); + void on_mouse_leave_profiles(wxMouseEvent& evt); virtual void on_activate() override; }; From 1eef1d32a08bf12c99d088d2783fcfe53d8a70bc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 1 Sep 2020 18:04:56 +0200 Subject: [PATCH 375/826] Added two missing includes to fix build on gcc --- src/libslic3r/GCode/GCodeProcessor.cpp | 1 + src/slic3r/GUI/GLShader.hpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 54addbd979..13b1ed1a8d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index a1160f8e98..84fdf5ebad 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -4,6 +4,8 @@ #include #include +#include "libslic3r/Point.hpp" + namespace Slic3r { class GLShaderProgram From 761e71eb634e3af52da6b5ee3cb0f06e96d4892a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Aug 2020 13:45:18 +0200 Subject: [PATCH 376/826] Fix build on msvc --- src/libslic3r/Point.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 84010c7eb1..30a1a4942c 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -88,7 +88,7 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + std: std::vector transform(const std::vector& points, const Transform3f& t); Pointf3s transform(const Pointf3s& points, const Transform3d& t); -template using Vec = Eigen::Matrix; +template using Vec = Eigen::Matrix; class Point : public Vec2crd { From 255469347f92a8342974fe4a61ee6cd04c6af3bd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 19 Aug 2020 17:15:01 +0200 Subject: [PATCH 377/826] Fixed several indentation-related warnings --- src/libslic3r/PrintBase.hpp | 12 ++++++------ src/slic3r/GUI/Plater.cpp | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 5e94e011a7..647c24c1ce 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -507,9 +507,9 @@ protected: bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } PrintStateBase::TimeStamp set_done(PrintStepEnum step) { std::pair status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); - if (status.second) - this->status_update_warnings(this->id(), static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); - return status.first; + if (status.second) + this->status_update_warnings(this->id(), static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; } bool invalidate_step(PrintStepEnum step) { return m_state.invalidate(step, this->cancel_callback()); } @@ -556,9 +556,9 @@ protected: { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) { std::pair status = m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); - if (status.second) - this->status_update_warnings(m_print, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); - return status.first; + if (status.second) + this->status_update_warnings(m_print, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; } bool invalidate_step(PrintObjectStepEnum step) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2c330b60e6..027611750a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2841,7 +2841,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; - show_warning_dialog = true; + show_warning_dialog = true; if (! output_path.empty()) { background_process.schedule_export(output_path.string(), output_path_on_removable_media); } else { @@ -4697,8 +4697,8 @@ void Plater::export_gcode(bool prefer_removable) if (p->model.objects.empty()) return; - if (p->process_completed_with_error)//here - return; + if (p->process_completed_with_error)//here + return; // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. From d3e7684a5a47e34cb2cf94f194b75158bfe07068 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 18 Aug 2020 15:17:26 +0200 Subject: [PATCH 378/826] Forbid translation of objects when SLA/Hollow/FDM gizmos are active --- src/slic3r/GUI/GLCanvas3D.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9bed5fde7c..94f6f6ef32 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3658,6 +3658,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { m_mouse.dragging = true; + // Translation of objects is forbidden when SLA supports/hollowing/fdm + // supports gizmo is active. + if (m_gizmos.get_current_type() == GLGizmosManager::SlaSupports + || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports + || m_gizmos.get_current_type() == GLGizmosManager::Hollow) + return; + Vec3d cur_pos = m_mouse.drag.start_position_3D; // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag if (m_selection.contains_volume(get_first_hover_volume_idx())) From 223eb6933c602a1c01fc2c14f3092504b05858f8 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 18 Aug 2020 15:18:00 +0200 Subject: [PATCH 379/826] TriangleSelector paints continuously when dragging fast Previously there would be distinct circles with gaps in between --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 195 +++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 1 + 2 files changed, 110 insertions(+), 86 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index f3b6db4f26..62854ab465 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -314,106 +314,128 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; const Transform3d& instance_trafo = mi->get_transformation().get_matrix(); - std::vector>> hit_positions_and_facet_ids; - bool clipped_mesh_was_hit = false; + // List of mouse positions that will be used as seeds for painting. + std::vector mouse_positions{mouse_position}; - Vec3f normal = Vec3f::Zero(); - Vec3f hit = Vec3f::Zero(); - size_t facet = 0; - Vec3f closest_hit = Vec3f::Zero(); - double closest_hit_squared_distance = std::numeric_limits::max(); - size_t closest_facet = 0; - int closest_hit_mesh_id = -1; + // In case current mouse position is far from the last one, + // add several positions from between into the list, so there + // are no gaps in the painted region. + { + if (m_last_mouse_position == Vec2d::Zero()) + m_last_mouse_position = mouse_position; + // resolution describes minimal distance limit using circle radius + // as a unit (e.g., 2 would mean the patches will be touching). + double resolution = 0.7; + double diameter_px = resolution * m_cursor_radius * camera.get_zoom(); + int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px); + if (patches_in_between > 0) { + Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1); + for (int i=1; i<=patches_in_between; ++i) + mouse_positions.emplace_back(m_last_mouse_position + i*diff); + } + } + m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved - // Transformations of individual meshes - std::vector trafo_matrices; + // Now "click" into all the prepared points and spill paint around them. + for (const Vec2d& mp : mouse_positions) { + std::vector>> hit_positions_and_facet_ids; + bool clipped_mesh_was_hit = false; - int mesh_id = -1; - // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + size_t closest_facet = 0; + int closest_hit_mesh_id = -1; - ++mesh_id; + // Transformations of individual meshes + std::vector trafo_matrices; - trafo_matrices.push_back(instance_trafo * mv->get_matrix()); - hit_positions_and_facet_ids.push_back(std::vector>()); - - if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( - mouse_position, - trafo_matrices[mesh_id], - camera, - hit, - normal, - m_clipping_plane.get(), - &facet)) - { - // In case this hit is clipped, skip it. - if (is_mesh_point_clipped(hit.cast())) { - clipped_mesh_was_hit = true; + int mesh_id = -1; + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) continue; - } - // Is this hit the closest to the camera so far? - double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); - if (hit_squared_distance < closest_hit_squared_distance) { - closest_hit_squared_distance = hit_squared_distance; - closest_facet = facet; - closest_hit_mesh_id = mesh_id; - closest_hit = hit; + ++mesh_id; + + trafo_matrices.push_back(instance_trafo * mv->get_matrix()); + hit_positions_and_facet_ids.push_back(std::vector>()); + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( + mp, + trafo_matrices[mesh_id], + camera, + hit, + normal, + m_clipping_plane.get(), + &facet)) + { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast())) { + clipped_mesh_was_hit = true; + continue; + } + + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_facet = facet; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + } } } - } - bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); + bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); - // The mouse button click detection is enabled when there is a valid hit - // or when the user clicks the clipping plane. Missing the object entirely - // shall not capture the mouse. - if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { - if (m_button_down == Button::None) - m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); - } - - if (closest_hit_mesh_id == -1) { - // In case we have no valid hit, we can return. The event will - // be stopped in following two cases: - // 1. clicking the clipping plane - // 2. dragging while painting (to prevent scene rotations and moving the object) - return clipped_mesh_was_hit - || dragging_while_painting; - } - - // Find respective mesh id. - // FIXME We need a separate TriangleSelector for each volume mesh. - mesh_id = -1; - //const TriangleMesh* mesh = nullptr; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++mesh_id; - if (mesh_id == closest_hit_mesh_id) { - //mesh = &mv->mesh(); - break; + // The mouse button click detection is enabled when there is a valid hit + // or when the user clicks the clipping plane. Missing the object entirely + // shall not capture the mouse. + if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { + if (m_button_down == Button::None) + m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); } + + if (closest_hit_mesh_id == -1) { + // In case we have no valid hit, we can return. The event will + // be stopped in following two cases: + // 1. clicking the clipping plane + // 2. dragging while painting (to prevent scene rotations and moving the object) + return clipped_mesh_was_hit + || dragging_while_painting; + } + + // Find respective mesh id. + mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++mesh_id; + if (mesh_id == closest_hit_mesh_id) + break; + } + + const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; + + // Calculate how far can a point be from the line (in mesh coords). + // FIXME: The scaling of the mesh can be non-uniform. + const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); + const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; + const float limit = m_cursor_radius/avg_scaling; + + // Calculate direction from camera to the hit (in mesh coords): + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + Vec3f dir = (closest_hit - camera_pos).normalized(); + + assert(mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, + dir, limit, new_state); + m_last_mouse_position = mouse_position; } - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; - - // Calculate how far can a point be from the line (in mesh coords). - // FIXME: The scaling of the mesh can be non-uniform. - const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); - const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = m_cursor_radius/avg_scaling; - - // Calculate direction from camera to the hit (in mesh coords): - Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); - Vec3f dir = (closest_hit - camera_pos).normalized(); - - assert(mesh_id < int(m_triangle_selectors.size())); - m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, - dir, limit, new_state); - return true; } @@ -430,6 +452,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous update_model_object(); m_button_down = Button::None; + m_last_mouse_position = Vec2d::Zero(); return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index e1dee373f2..c3f920e2f0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -92,6 +92,7 @@ private: bool m_setting_angle = false; bool m_internal_stack_active = false; bool m_schedule_update = false; + Vec2d m_last_mouse_position = Vec2d::Zero(); // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. From 7a6531ede7bd7d2b60e777a76b93a529a3079d27 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 20 Aug 2020 16:35:56 +0200 Subject: [PATCH 380/826] Started work on separating FDM gizmo into base and child classes --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 883 ------------------- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 115 +-- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 883 +++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 127 +++ 5 files changed, 1018 insertions(+), 992 deletions(-) create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f8598cea08..f1089ae935 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -51,6 +51,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoCut.hpp GUI/Gizmos/GLGizmoHollow.cpp GUI/Gizmos/GLGizmoHollow.hpp + GUI/Gizmos/GLGizmoPainterBase.cpp + GUI/Gizmos/GLGizmoPainterBase.hpp GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.hpp GUI/GLTexture.hpp diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 62854ab465..e69de29bb2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -1,883 +0,0 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoFdmSupports.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Model.hpp" - - - -namespace Slic3r { -namespace GUI { - - -GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) - , m_quadric(nullptr) -{ - m_clipping_plane.reset(new ClippingPlane()); - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - // using GLU_FILL does not work when the instance's transformation - // contains mirroring (normals are reverted) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoFdmSupports::~GLGizmoFdmSupports() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -bool GLGizmoFdmSupports::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - - m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size"] = _L("Cursor size") + ": "; - m_desc["enforce_caption"] = _L("Left mouse button") + ": "; - m_desc["enforce"] = _L("Enforce supports"); - m_desc["block_caption"] = _L("Right mouse button") + " "; - m_desc["block"] = _L("Block supports"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all selection"); - - return true; -} - - -void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate) -{ - if (activate && ! m_internal_stack_active) { - Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); - wxGetApp().plater()->enter_gizmos_stack(); - m_internal_stack_active = true; - } - if (! activate && m_internal_stack_active) { - wxGetApp().plater()->leave_gizmos_stack(); - Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); - m_internal_stack_active = false; - } -} - -void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection) -{ - if (m_state != On) - return; - - const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; - - if (mo && selection.is_from_single_instance() - && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) - { - update_from_model_object(); - m_old_mo_id = mo->id(); - m_old_volumes_size = mo->volumes.size(); - m_schedule_update = false; - } -} - - - -void GLGizmoFdmSupports::on_render() const -{ - const Selection& selection = m_parent.get_selection(); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - render_triangles(selection); - - m_c->object_clipper()->render_cut(); - render_cursor_circle(); - - glsafe(::glDisable(GL_BLEND)); -} - -void GLGizmoFdmSupports::render_triangles(const Selection& selection) const -{ - if (m_setting_angle) - return; - - const ModelObject* mo = m_c->selection_info()->model_object(); - - glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); - ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } ); - glsafe(::glPolygonOffset(-1.0, 1.0)); - - // Take care of the clipping plane. The normal of the clipping plane is - // saved with opposite sign than we need to pass to OpenGL (FIXME) - bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; - if (clipping_plane_active) { - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - double clp_data[4]; - memcpy(clp_data, clp->get_data(), 4 * sizeof(double)); - for (int i=0; i<3; ++i) - clp_data[i] = -1. * clp_data[i]; - - glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data)); - glsafe(::glEnable(GL_CLIP_PLANE0)); - } - - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - const Transform3d trafo_matrix = - mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * - mv->get_matrix(); - - bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; - if (is_left_handed) - glsafe(::glFrontFace(GL_CW)); - - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); - - if (! m_setting_angle) - m_triangle_selectors[mesh_id]->render(m_imgui); - - glsafe(::glPopMatrix()); - if (is_left_handed) - glsafe(::glFrontFace(GL_CCW)); - } - if (clipping_plane_active) - glsafe(::glDisable(GL_CLIP_PLANE0)); -} - - -void GLGizmoFdmSupports::render_cursor_circle() const -{ - const Camera& camera = wxGetApp().plater()->get_camera(); - float zoom = (float)camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - - Size cnv_size = m_parent.get_canvas_size(); - float cnv_half_width = 0.5f * (float)cnv_size.get_width(); - float cnv_half_height = 0.5f * (float)cnv_size.get_height(); - if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) - return; - Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1)); - Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1)); - center = center * inv_zoom; - - glsafe(::glLineWidth(1.5f)); - float color[3]; - color[0] = 0.f; - color[1] = 1.f; - color[2] = 0.3f; - glsafe(::glColor3fv(color)); - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the circle is renderered inside the frustrum - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); - // ensure that the overlay fits the frustrum near z plane - double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); - - glsafe(::glPushAttrib(GL_ENABLE_BIT)); - glsafe(::glLineStipple(4, 0xAAAA)); - glsafe(::glEnable(GL_LINE_STIPPLE)); - - ::glBegin(GL_LINE_LOOP); - for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) - ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); - glsafe(::glEnd()); - - glsafe(::glPopAttrib()); - glsafe(::glPopMatrix()); -} - - -void GLGizmoFdmSupports::update_model_object() const -{ - bool updated = false; - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++idx; - updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); - } - - if (updated) - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); -} - - -void GLGizmoFdmSupports::update_from_model_object() -{ - wxBusyCursor wait; - - const ModelObject* mo = m_c->selection_info()->model_object(); - m_triangle_selectors.clear(); - - int volume_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++volume_id; - - // This mesh does not account for the possible Z up SLA offset. - const TriangleMesh* mesh = &mv->mesh(); - - m_triangle_selectors.emplace_back(std::make_unique(*mesh)); - m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); - } -} - - - -bool GLGizmoFdmSupports::is_mesh_point_clipped(const Vec3d& point) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix(); - - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - if (action == SLAGizmoEventType::MouseWheelUp - || action == SLAGizmoEventType::MouseWheelDown) { - if (control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = action == SLAGizmoEventType::MouseWheelDown - ? std::max(0., pos - 0.01) - : std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - else if (alt_down) { - m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown - ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin) - : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax); - m_parent.set_as_dirty(); - return true; - } - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - if (action == SLAGizmoEventType::LeftDown - || action == SLAGizmoEventType::RightDown - || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { - - if (m_triangle_selectors.empty()) - return false; - - EnforcerBlockerType new_state = EnforcerBlockerType::NONE; - if (! shift_down) { - if (action == SLAGizmoEventType::Dragging) - new_state = m_button_down == Button::Left - ? EnforcerBlockerType::ENFORCER - : EnforcerBlockerType::BLOCKER; - else - new_state = action == SLAGizmoEventType::LeftDown - ? EnforcerBlockerType::ENFORCER - : EnforcerBlockerType::BLOCKER; - } - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - const Transform3d& instance_trafo = mi->get_transformation().get_matrix(); - - // List of mouse positions that will be used as seeds for painting. - std::vector mouse_positions{mouse_position}; - - // In case current mouse position is far from the last one, - // add several positions from between into the list, so there - // are no gaps in the painted region. - { - if (m_last_mouse_position == Vec2d::Zero()) - m_last_mouse_position = mouse_position; - // resolution describes minimal distance limit using circle radius - // as a unit (e.g., 2 would mean the patches will be touching). - double resolution = 0.7; - double diameter_px = resolution * m_cursor_radius * camera.get_zoom(); - int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px); - if (patches_in_between > 0) { - Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1); - for (int i=1; i<=patches_in_between; ++i) - mouse_positions.emplace_back(m_last_mouse_position + i*diff); - } - } - m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved - - // Now "click" into all the prepared points and spill paint around them. - for (const Vec2d& mp : mouse_positions) { - std::vector>> hit_positions_and_facet_ids; - bool clipped_mesh_was_hit = false; - - Vec3f normal = Vec3f::Zero(); - Vec3f hit = Vec3f::Zero(); - size_t facet = 0; - Vec3f closest_hit = Vec3f::Zero(); - double closest_hit_squared_distance = std::numeric_limits::max(); - size_t closest_facet = 0; - int closest_hit_mesh_id = -1; - - // Transformations of individual meshes - std::vector trafo_matrices; - - int mesh_id = -1; - // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - trafo_matrices.push_back(instance_trafo * mv->get_matrix()); - hit_positions_and_facet_ids.push_back(std::vector>()); - - if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( - mp, - trafo_matrices[mesh_id], - camera, - hit, - normal, - m_clipping_plane.get(), - &facet)) - { - // In case this hit is clipped, skip it. - if (is_mesh_point_clipped(hit.cast())) { - clipped_mesh_was_hit = true; - continue; - } - - // Is this hit the closest to the camera so far? - double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); - if (hit_squared_distance < closest_hit_squared_distance) { - closest_hit_squared_distance = hit_squared_distance; - closest_facet = facet; - closest_hit_mesh_id = mesh_id; - closest_hit = hit; - } - } - } - - bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); - - // The mouse button click detection is enabled when there is a valid hit - // or when the user clicks the clipping plane. Missing the object entirely - // shall not capture the mouse. - if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { - if (m_button_down == Button::None) - m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); - } - - if (closest_hit_mesh_id == -1) { - // In case we have no valid hit, we can return. The event will - // be stopped in following two cases: - // 1. clicking the clipping plane - // 2. dragging while painting (to prevent scene rotations and moving the object) - return clipped_mesh_was_hit - || dragging_while_painting; - } - - // Find respective mesh id. - mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++mesh_id; - if (mesh_id == closest_hit_mesh_id) - break; - } - - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; - - // Calculate how far can a point be from the line (in mesh coords). - // FIXME: The scaling of the mesh can be non-uniform. - const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); - const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = m_cursor_radius/avg_scaling; - - // Calculate direction from camera to the hit (in mesh coords): - Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); - Vec3f dir = (closest_hit - camera_pos).normalized(); - - assert(mesh_id < int(m_triangle_selectors.size())); - m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, - dir, limit, new_state); - m_last_mouse_position = mouse_position; - } - - return true; - } - - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) - && m_button_down != Button::None) { - // Take snapshot and update ModelVolume data. - wxString action_name = shift_down - ? _L("Remove selection") - : (m_button_down == Button::Left - ? _L("Add supports") - : _L("Block supports")); - activate_internal_undo_redo_stack(true); - Plater::TakeSnapshot(wxGetApp().plater(), action_name); - update_model_object(); - - m_button_down = Button::None; - m_last_mouse_position = Vec2d::Zero(); - return true; - } - - return false; -} - - - -void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) -{ - float threshold = (M_PI/180.)*threshold_deg; - const Selection& selection = m_parent.get_selection(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); - Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); - Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); - - float dot_limit = limit.dot(down); - - // Now calculate dot product of vert_direction and facets' normals. - int idx = -1; - for (const stl_facet& facet : mv->mesh().stl.facet_start) { - ++idx; - if (facet.normal.dot(down) > dot_limit) - m_triangle_selectors[mesh_id]->set_facet(idx, - block - ? EnforcerBlockerType::BLOCKER - : EnforcerBlockerType::ENFORCER); - } - } - - activate_internal_undo_redo_stack(true); - - Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") - : _L("Add supports by angle")); - update_model_object(); - m_parent.set_as_dirty(); - m_setting_angle = false; -} - - -void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) -{ - if (! m_c->selection_info()->model_object()) - return; - - const float approx_height = m_imgui->scaled(18.0f); - y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - - if (! m_setting_angle) { - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); - const float minimal_slider_width = m_imgui->scaled(4.f); - - float caption_max = 0.f; - float total_text_max = 0.; - for (const std::string& t : {"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x); - total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); - } - caption_max += m_imgui->scaled(1.f); - total_text_max += m_imgui->scaled(1.f); - - float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); - window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); - - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - m_imgui->text_colored(ORANGE, caption); - ImGui::SameLine(caption_max); - m_imgui->text(text); - }; - - for (const std::string& t : {"enforce", "block", "remove"}) - draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); - - m_imgui->text(""); - - if (m_imgui->button("Autoset by angle...")) { - m_setting_angle = true; - } - - ImGui::SameLine(); - - if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) { - ++idx; - m_triangle_selectors[idx]->reset(); - } - } - update_model_object(); - m_parent.set_as_dirty(); - } - - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; - - m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); - ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) - m_imgui->text(m_desc.at("clipping_of_view")); - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); - float clp_dist = m_c->object_clipper()->get_position(); - if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - m_imgui->end(); - if (m_setting_angle) { - m_parent.show_slope(false); - m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - m_parent.use_slope(true); - m_parent.set_as_dirty(); - } - } - else { - std::string name = "Autoset custom supports"; - m_imgui->begin(wxString(name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->text("Threshold:"); - ImGui::SameLine(); - if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) - m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - if (m_imgui->button("Enforce")) - select_facets_by_angle(m_angle_threshold_deg, false); - ImGui::SameLine(); - if (m_imgui->button("Block")) - select_facets_by_angle(m_angle_threshold_deg, true); - ImGui::SameLine(); - if (m_imgui->button("Cancel")) - m_setting_angle = false; - m_imgui->end(); - if (! m_setting_angle) { - m_parent.use_slope(false); - m_parent.set_as_dirty(); - } - } -} - -bool GLGizmoFdmSupports::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF - || !selection.is_single_full_instance()) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside) - return false; - - return true; -} - -bool GLGizmoFdmSupports::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF - && wxGetApp().get_mode() != comSimple ); -} - -std::string GLGizmoFdmSupports::on_get_name() const -{ - return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data(); -} - - -CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::ObjectClipper)); -} - - -void GLGizmoFdmSupports::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - if (! m_parent.get_gizmos_manager().is_serializing()) { - wxGetApp().CallAfter([this]() { - activate_internal_undo_redo_stack(true); - }); - } - } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - // we are actually shutting down - if (m_setting_angle) { - m_setting_angle = false; - m_parent.use_slope(false); - } - activate_internal_undo_redo_stack(false); - m_old_mo_id = -1; - //m_iva.release_geometry(); - m_triangle_selectors.clear(); - } - m_old_state = m_state; -} - - - -void GLGizmoFdmSupports::on_start_dragging() -{ - -} - - -void GLGizmoFdmSupports::on_stop_dragging() -{ - -} - - - -void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive&) -{ - // We should update the gizmo from current ModelObject, but it is not - // possible at this point. That would require having updated selection and - // common gizmos data, which is not done at this point. Instead, save - // a flag to do the update in set_fdm_support_data, which will be called - // soon after. - m_schedule_update = true; -} - - - -void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const -{ - -} - - -void TriangleSelectorGUI::render(ImGuiWrapper* imgui) -{ - int enf_cnt = 0; - int blc_cnt = 0; - - m_iva_enforcers.release_geometry(); - m_iva_blockers.release_geometry(); - - for (const Triangle& tr : m_triangles) { - if (! tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE) - continue; - - GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER - ? m_iva_enforcers - : m_iva_blockers; - int& cnt = tr.get_state() == EnforcerBlockerType::ENFORCER - ? enf_cnt - : blc_cnt; - - for (int i=0; i<3; ++i) - va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), - double(m_vertices[tr.verts_idxs[i]].v[1]), - double(m_vertices[tr.verts_idxs[i]].v[2]), - 0., 0., 1.); - va.push_triangle(cnt, - cnt+1, - cnt+2); - cnt += 3; - } - - m_iva_enforcers.finalize_geometry(true); - m_iva_blockers.finalize_geometry(true); - - if (m_iva_enforcers.has_VBOs()) { - ::glColor4f(0.f, 0.f, 1.f, 0.2f); - m_iva_enforcers.render(); - } - - - if (m_iva_blockers.has_VBOs()) { - ::glColor4f(1.f, 0.f, 0.f, 0.2f); - m_iva_blockers.render(); - } - - -#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG - if (imgui) - render_debug(imgui); - else - assert(false); // If you want debug output, pass ptr to ImGuiWrapper. -#endif -} - - - -#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) -{ - imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - static float edge_limit = 1.f; - imgui->text("Edge limit (mm): "); - imgui->slider_float("", &edge_limit, 0.1f, 8.f); - set_edge_limit(edge_limit); - imgui->checkbox("Show split triangles: ", m_show_triangles); - imgui->checkbox("Show invalid triangles: ", m_show_invalid); - - int valid_triangles = m_triangles.size() - m_invalid_triangles; - imgui->text("Valid triangles: " + std::to_string(valid_triangles) + - "/" + std::to_string(m_triangles.size())); - imgui->text("Vertices: " + std::to_string(m_vertices.size())); - if (imgui->button("Force garbage collection")) - garbage_collect(); - - if (imgui->button("Serialize - deserialize")) { - auto map = serialize(); - deserialize(map); - } - - imgui->end(); - - if (! m_show_triangles) - return; - - enum vtype { - ORIGINAL = 0, - SPLIT, - INVALID - }; - - for (auto& va : m_varrays) - va.release_geometry(); - - std::array cnts; - - ::glScalef(1.01f, 1.01f, 1.01f); - - for (int tr_id=0; tr_idpush_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), - double(m_vertices[tr.verts_idxs[i]].v[1]), - double(m_vertices[tr.verts_idxs[i]].v[2]), - 0., 0., 1.); - va->push_triangle(*cnt, - *cnt+1, - *cnt+2); - *cnt += 3; - } - - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - for (vtype i : {ORIGINAL, SPLIT, INVALID}) { - GLIndexedVertexArray& va = m_varrays[i]; - va.finalize_geometry(true); - if (va.has_VBOs()) { - switch (i) { - case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; - case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; - case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; - } - va.render(); - } - } - ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); -} -#endif - - - -} // namespace GUI -} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index c3f920e2f0..196a21bc03 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -1,127 +1,24 @@ #ifndef slic3r_GLGizmoFdmSupports_hpp_ #define slic3r_GLGizmoFdmSupports_hpp_ -#include "GLGizmoBase.hpp" - -#include "slic3r/GUI/3DScene.hpp" - -#include "libslic3r/ObjectID.hpp" -#include "libslic3r/TriangleSelector.hpp" - -#include - - - +#include "GLGizmoPainterBase.hpp" namespace Slic3r { -enum class EnforcerBlockerType : int8_t; - namespace GUI { -enum class SLAGizmoEventType : unsigned char; -class ClippingPlane; - - - -class TriangleSelectorGUI : public TriangleSelector { -public: - explicit TriangleSelectorGUI(const TriangleMesh& mesh) - : TriangleSelector(mesh) {} - - // Render current selection. Transformation matrices are supposed - // to be already set. - void render(ImGuiWrapper* imgui = nullptr); - -#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG - void render_debug(ImGuiWrapper* imgui); - bool m_show_triangles{false}; - bool m_show_invalid{false}; -#endif - -private: - GLIndexedVertexArray m_iva_enforcers; - GLIndexedVertexArray m_iva_blockers; - std::array m_varrays; -}; - - - -class GLGizmoFdmSupports : public GLGizmoBase +class GLGizmoFdmSupports : public GLGizmoPainterBase { -private: - ObjectID m_old_mo_id; - size_t m_old_volumes_size = 0; - - GLUquadricObj* m_quadric; - - float m_cursor_radius = 2.f; - static constexpr float CursorRadiusMin = 0.4f; // cannot be zero - static constexpr float CursorRadiusMax = 8.f; - static constexpr float CursorRadiusStep = 0.2f; - - // For each model-part volume, store status and division of the triangles. - std::vector> m_triangle_selectors; - public: - GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - ~GLGizmoFdmSupports() override; - void set_fdm_support_data(ModelObject* model_object, const Selection& selection); - bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} - -private: - bool on_init() override; - void on_render() const override; - void on_render_for_picking() const override {} - - void render_triangles(const Selection& selection) const; - void render_cursor_circle() const; - - void update_model_object() const; - void update_from_model_object(); - void activate_internal_undo_redo_stack(bool activate); - - void select_facets_by_angle(float threshold, bool block); - float m_angle_threshold_deg = 45.f; - - bool is_mesh_point_clipped(const Vec3d& point) const; - - float m_clipping_plane_distance = 0.f; - std::unique_ptr m_clipping_plane; - bool m_setting_angle = false; - bool m_internal_stack_active = false; - bool m_schedule_update = false; - Vec2d m_last_mouse_position = Vec2d::Zero(); - - // This map holds all translated description texts, so they can be easily referenced during layout calculations - // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. - std::map m_desc; - - enum class Button { - None, - Left, - Right - }; - - Button m_button_down = Button::None; - EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - -protected: - void on_set_state() override; - void on_start_dragging() override; - void on_stop_dragging() override; - void on_render_input_window(float x, float y, float bottom_limit) override; - std::string on_get_name() const override; - bool on_is_activable() const override; - bool on_is_selectable() const override; - void on_load(cereal::BinaryInputArchive& ar) override; - void on_save(cereal::BinaryOutputArchive& ar) const override; - CommonGizmosDataID on_get_requirements() const override; }; + } // namespace GUI } // namespace Slic3r + #endif // slic3r_GLGizmoFdmSupports_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp new file mode 100644 index 0000000000..37792a48e0 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -0,0 +1,883 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoPainterBase.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Model.hpp" + + + +namespace Slic3r { +namespace GUI { + + +GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) + , m_quadric(nullptr) +{ + m_clipping_plane.reset(new ClippingPlane()); + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + // using GLU_FILL does not work when the instance's transformation + // contains mirroring (normals are reverted) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoPainterBase::~GLGizmoPainterBase() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +bool GLGizmoPainterBase::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + + m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["cursor_size"] = _L("Cursor size") + ": "; + m_desc["enforce_caption"] = _L("Left mouse button") + ": "; + m_desc["enforce"] = _L("Enforce supports"); + m_desc["block_caption"] = _L("Right mouse button") + " "; + m_desc["block"] = _L("Block supports"); + m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _L("Remove selection"); + m_desc["remove_all"] = _L("Remove all selection"); + + return true; +} + + +void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) +{ + if (activate && ! m_internal_stack_active) { + Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); + wxGetApp().plater()->enter_gizmos_stack(); + m_internal_stack_active = true; + } + if (! activate && m_internal_stack_active) { + wxGetApp().plater()->leave_gizmos_stack(); + Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); + m_internal_stack_active = false; + } +} + +void GLGizmoPainterBase::set_fdm_support_data(ModelObject* model_object, const Selection& selection) +{ + if (m_state != On) + return; + + const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; + + if (mo && selection.is_from_single_instance() + && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) + { + update_from_model_object(); + m_old_mo_id = mo->id(); + m_old_volumes_size = mo->volumes.size(); + m_schedule_update = false; + } +} + + + +void GLGizmoPainterBase::on_render() const +{ + const Selection& selection = m_parent.get_selection(); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + render_triangles(selection); + + m_c->object_clipper()->render_cut(); + render_cursor_circle(); + + glsafe(::glDisable(GL_BLEND)); +} + +void GLGizmoPainterBase::render_triangles(const Selection& selection) const +{ + if (m_setting_angle) + return; + + const ModelObject* mo = m_c->selection_info()->model_object(); + + glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); + ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } ); + glsafe(::glPolygonOffset(-1.0, 1.0)); + + // Take care of the clipping plane. The normal of the clipping plane is + // saved with opposite sign than we need to pass to OpenGL (FIXME) + bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; + if (clipping_plane_active) { + const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); + double clp_data[4]; + memcpy(clp_data, clp->get_data(), 4 * sizeof(double)); + for (int i=0; i<3; ++i) + clp_data[i] = -1. * clp_data[i]; + + glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data)); + glsafe(::glEnable(GL_CLIP_PLANE0)); + } + + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = + mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * + mv->get_matrix(); + + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); + + if (! m_setting_angle) + m_triangle_selectors[mesh_id]->render(m_imgui); + + glsafe(::glPopMatrix()); + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + } + if (clipping_plane_active) + glsafe(::glDisable(GL_CLIP_PLANE0)); +} + + +void GLGizmoPainterBase::render_cursor_circle() const +{ + const Camera& camera = wxGetApp().plater()->get_camera(); + float zoom = (float)camera.get_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + Size cnv_size = m_parent.get_canvas_size(); + float cnv_half_width = 0.5f * (float)cnv_size.get_width(); + float cnv_half_height = 0.5f * (float)cnv_size.get_height(); + if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f)) + return; + Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1)); + Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1)); + center = center * inv_zoom; + + glsafe(::glLineWidth(1.5f)); + float color[3]; + color[0] = 0.f; + color[1] = 1.f; + color[2] = 0.3f; + glsafe(::glColor3fv(color)); + glsafe(::glDisable(GL_DEPTH_TEST)); + + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + // ensure that the circle is renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); + + glsafe(::glPushAttrib(GL_ENABLE_BIT)); + glsafe(::glLineStipple(4, 0xAAAA)); + glsafe(::glEnable(GL_LINE_STIPPLE)); + + ::glBegin(GL_LINE_LOOP); + for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) + ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); + glsafe(::glEnd()); + + glsafe(::glPopAttrib()); + glsafe(::glPopMatrix()); +} + + +void GLGizmoPainterBase::update_model_object() const +{ + bool updated = false; + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++idx; + updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + } + + if (updated) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + + +void GLGizmoPainterBase::update_from_model_object() +{ + wxBusyCursor wait; + + const ModelObject* mo = m_c->selection_info()->model_object(); + m_triangle_selectors.clear(); + + int volume_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++volume_id; + + // This mesh does not account for the possible Z up SLA offset. + const TriangleMesh* mesh = &mv->mesh(); + + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); + } +} + + + +bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + int active_inst = m_c->selection_info()->get_active_instance(); + const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; + const Transform3d& trafo = mi->get_transformation().get_matrix(); + + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (action == SLAGizmoEventType::MouseWheelUp + || action == SLAGizmoEventType::MouseWheelDown) { + if (control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = action == SLAGizmoEventType::MouseWheelDown + ? std::max(0., pos - 0.01) + : std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + else if (alt_down) { + m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown + ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin) + : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax); + m_parent.set_as_dirty(); + return true; + } + } + + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + if (action == SLAGizmoEventType::LeftDown + || action == SLAGizmoEventType::RightDown + || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { + + if (m_triangle_selectors.empty()) + return false; + + EnforcerBlockerType new_state = EnforcerBlockerType::NONE; + if (! shift_down) { + if (action == SLAGizmoEventType::Dragging) + new_state = m_button_down == Button::Left + ? EnforcerBlockerType::ENFORCER + : EnforcerBlockerType::BLOCKER; + else + new_state = action == SLAGizmoEventType::LeftDown + ? EnforcerBlockerType::ENFORCER + : EnforcerBlockerType::BLOCKER; + } + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Selection& selection = m_parent.get_selection(); + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + const Transform3d& instance_trafo = mi->get_transformation().get_matrix(); + + // List of mouse positions that will be used as seeds for painting. + std::vector mouse_positions{mouse_position}; + + // In case current mouse position is far from the last one, + // add several positions from between into the list, so there + // are no gaps in the painted region. + { + if (m_last_mouse_position == Vec2d::Zero()) + m_last_mouse_position = mouse_position; + // resolution describes minimal distance limit using circle radius + // as a unit (e.g., 2 would mean the patches will be touching). + double resolution = 0.7; + double diameter_px = resolution * m_cursor_radius * camera.get_zoom(); + int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px); + if (patches_in_between > 0) { + Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1); + for (int i=1; i<=patches_in_between; ++i) + mouse_positions.emplace_back(m_last_mouse_position + i*diff); + } + } + m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved + + // Now "click" into all the prepared points and spill paint around them. + for (const Vec2d& mp : mouse_positions) { + std::vector>> hit_positions_and_facet_ids; + bool clipped_mesh_was_hit = false; + + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + size_t closest_facet = 0; + int closest_hit_mesh_id = -1; + + // Transformations of individual meshes + std::vector trafo_matrices; + + int mesh_id = -1; + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + + trafo_matrices.push_back(instance_trafo * mv->get_matrix()); + hit_positions_and_facet_ids.push_back(std::vector>()); + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( + mp, + trafo_matrices[mesh_id], + camera, + hit, + normal, + m_clipping_plane.get(), + &facet)) + { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast())) { + clipped_mesh_was_hit = true; + continue; + } + + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_facet = facet; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + } + } + } + + bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); + + // The mouse button click detection is enabled when there is a valid hit + // or when the user clicks the clipping plane. Missing the object entirely + // shall not capture the mouse. + if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { + if (m_button_down == Button::None) + m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); + } + + if (closest_hit_mesh_id == -1) { + // In case we have no valid hit, we can return. The event will + // be stopped in following two cases: + // 1. clicking the clipping plane + // 2. dragging while painting (to prevent scene rotations and moving the object) + return clipped_mesh_was_hit + || dragging_while_painting; + } + + // Find respective mesh id. + mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++mesh_id; + if (mesh_id == closest_hit_mesh_id) + break; + } + + const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; + + // Calculate how far can a point be from the line (in mesh coords). + // FIXME: The scaling of the mesh can be non-uniform. + const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); + const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; + const float limit = m_cursor_radius/avg_scaling; + + // Calculate direction from camera to the hit (in mesh coords): + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + Vec3f dir = (closest_hit - camera_pos).normalized(); + + assert(mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, + dir, limit, new_state); + m_last_mouse_position = mouse_position; + } + + return true; + } + + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) + && m_button_down != Button::None) { + // Take snapshot and update ModelVolume data. + wxString action_name = shift_down + ? _L("Remove selection") + : (m_button_down == Button::Left + ? _L("Add supports") + : _L("Block supports")); + activate_internal_undo_redo_stack(true); + Plater::TakeSnapshot(wxGetApp().plater(), action_name); + update_model_object(); + + m_button_down = Button::None; + m_last_mouse_position = Vec2d::Zero(); + return true; + } + + return false; +} + + + +void GLGizmoPainterBase::select_facets_by_angle(float threshold_deg, bool block) +{ + float threshold = (M_PI/180.)*threshold_deg; + const Selection& selection = m_parent.get_selection(); + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); + Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); + Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); + + float dot_limit = limit.dot(down); + + // Now calculate dot product of vert_direction and facets' normals. + int idx = -1; + for (const stl_facet& facet : mv->mesh().stl.facet_start) { + ++idx; + if (facet.normal.dot(down) > dot_limit) + m_triangle_selectors[mesh_id]->set_facet(idx, + block + ? EnforcerBlockerType::BLOCKER + : EnforcerBlockerType::ENFORCER); + } + } + + activate_internal_undo_redo_stack(true); + + Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") + : _L("Add supports by angle")); + update_model_object(); + m_parent.set_as_dirty(); + m_setting_angle = false; +} + + +void GLGizmoPainterBase::on_render_input_window(float x, float y, float bottom_limit) +{ + if (! m_c->selection_info()->model_object()) + return; + + const float approx_height = m_imgui->scaled(18.0f); + y = std::min(y, bottom_limit - approx_height); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + if (! m_setting_angle) { + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float minimal_slider_width = m_imgui->scaled(4.f); + + float caption_max = 0.f; + float total_text_max = 0.; + for (const std::string& t : {"enforce", "block", "remove"}) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x); + total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); + } + caption_max += m_imgui->scaled(1.f); + total_text_max += m_imgui->scaled(1.f); + + float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + + auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { + static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); + m_imgui->text_colored(ORANGE, caption); + ImGui::SameLine(caption_max); + m_imgui->text(text); + }; + + for (const std::string& t : {"enforce", "block", "remove"}) + draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + + m_imgui->text(""); + + if (m_imgui->button("Autoset by angle...")) { + m_setting_angle = true; + } + + ImGui::SameLine(); + + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + } + } + update_model_object(); + m_parent.set_as_dirty(); + } + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + + m_imgui->text(m_desc.at("cursor_size")); + ImGui::SameLine(clipping_slider_left); + ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) + m_imgui->text(m_desc.at("clipping_of_view")); + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + ImGui::SameLine(clipping_slider_left); + ImGui::PushItemWidth(window_width - clipping_slider_left); + float clp_dist = m_c->object_clipper()->get_position(); + if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f")) + m_c->object_clipper()->set_position(clp_dist, true); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + m_imgui->end(); + if (m_setting_angle) { + m_parent.show_slope(false); + m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); + m_parent.use_slope(true); + m_parent.set_as_dirty(); + } + } + else { + std::string name = "Autoset custom supports"; + m_imgui->begin(wxString(name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->text("Threshold:"); + ImGui::SameLine(); + if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) + m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); + if (m_imgui->button("Enforce")) + select_facets_by_angle(m_angle_threshold_deg, false); + ImGui::SameLine(); + if (m_imgui->button("Block")) + select_facets_by_angle(m_angle_threshold_deg, true); + ImGui::SameLine(); + if (m_imgui->button("Cancel")) + m_setting_angle = false; + m_imgui->end(); + if (! m_setting_angle) { + m_parent.use_slope(false); + m_parent.set_as_dirty(); + } + } +} + +bool GLGizmoPainterBase::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF + || !selection.is_single_full_instance()) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside) + return false; + + return true; +} + +bool GLGizmoPainterBase::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF + && wxGetApp().get_mode() != comSimple ); +} + +std::string GLGizmoPainterBase::on_get_name() const +{ + return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data(); +} + + +CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::ObjectClipper)); +} + + +void GLGizmoPainterBase::on_set_state() +{ + if (m_state == m_old_state) + return; + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + if (! m_parent.get_gizmos_manager().is_serializing()) { + wxGetApp().CallAfter([this]() { + activate_internal_undo_redo_stack(true); + }); + } + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + // we are actually shutting down + if (m_setting_angle) { + m_setting_angle = false; + m_parent.use_slope(false); + } + activate_internal_undo_redo_stack(false); + m_old_mo_id = -1; + //m_iva.release_geometry(); + m_triangle_selectors.clear(); + } + m_old_state = m_state; +} + + + +void GLGizmoPainterBase::on_start_dragging() +{ + +} + + +void GLGizmoPainterBase::on_stop_dragging() +{ + +} + + + +void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) +{ + // We should update the gizmo from current ModelObject, but it is not + // possible at this point. That would require having updated selection and + // common gizmos data, which is not done at this point. Instead, save + // a flag to do the update in set_fdm_support_data, which will be called + // soon after. + m_schedule_update = true; +} + + + +void GLGizmoPainterBase::on_save(cereal::BinaryOutputArchive&) const +{ + +} + + +void TriangleSelectorGUI::render(ImGuiWrapper* imgui) +{ + int enf_cnt = 0; + int blc_cnt = 0; + + m_iva_enforcers.release_geometry(); + m_iva_blockers.release_geometry(); + + for (const Triangle& tr : m_triangles) { + if (! tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE) + continue; + + GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER + ? m_iva_enforcers + : m_iva_blockers; + int& cnt = tr.get_state() == EnforcerBlockerType::ENFORCER + ? enf_cnt + : blc_cnt; + + for (int i=0; i<3; ++i) + va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va.push_triangle(cnt, + cnt+1, + cnt+2); + cnt += 3; + } + + m_iva_enforcers.finalize_geometry(true); + m_iva_blockers.finalize_geometry(true); + + if (m_iva_enforcers.has_VBOs()) { + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + m_iva_enforcers.render(); + } + + + if (m_iva_blockers.has_VBOs()) { + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + m_iva_blockers.render(); + } + + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + if (imgui) + render_debug(imgui); + else + assert(false); // If you want debug output, pass ptr to ImGuiWrapper. +#endif +} + + + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) +{ + imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + imgui->text("Edge limit (mm): "); + imgui->slider_float("", &edge_limit, 0.1f, 8.f); + set_edge_limit(edge_limit); + imgui->checkbox("Show split triangles: ", m_show_triangles); + imgui->checkbox("Show invalid triangles: ", m_show_invalid); + + int valid_triangles = m_triangles.size() - m_invalid_triangles; + imgui->text("Valid triangles: " + std::to_string(valid_triangles) + + "/" + std::to_string(m_triangles.size())); + imgui->text("Vertices: " + std::to_string(m_vertices.size())); + if (imgui->button("Force garbage collection")) + garbage_collect(); + + if (imgui->button("Serialize - deserialize")) { + auto map = serialize(); + deserialize(map); + } + + imgui->end(); + + if (! m_show_triangles) + return; + + enum vtype { + ORIGINAL = 0, + SPLIT, + INVALID + }; + + for (auto& va : m_varrays) + va.release_geometry(); + + std::array cnts; + + ::glScalef(1.01f, 1.01f, 1.01f); + + for (int tr_id=0; tr_idpush_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va->push_triangle(*cnt, + *cnt+1, + *cnt+2); + *cnt += 3; + } + + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + for (vtype i : {ORIGINAL, SPLIT, INVALID}) { + GLIndexedVertexArray& va = m_varrays[i]; + va.finalize_geometry(true); + if (va.has_VBOs()) { + switch (i) { + case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; + case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; + case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; + } + va.render(); + } + } + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); +} +#endif + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp new file mode 100644 index 0000000000..1770c96a7d --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -0,0 +1,127 @@ +#ifndef slic3r_GLGizmoPainterBase_hpp_ +#define slic3r_GLGizmoPainterBase_hpp_ + +#include "GLGizmoBase.hpp" + +#include "slic3r/GUI/3DScene.hpp" + +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/TriangleSelector.hpp" + +#include + + + + +namespace Slic3r { + +enum class EnforcerBlockerType : int8_t; + +namespace GUI { + +enum class SLAGizmoEventType : unsigned char; +class ClippingPlane; + + + +class TriangleSelectorGUI : public TriangleSelector { +public: + explicit TriangleSelectorGUI(const TriangleMesh& mesh) + : TriangleSelector(mesh) {} + + // Render current selection. Transformation matrices are supposed + // to be already set. + void render(ImGuiWrapper* imgui = nullptr); + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + void render_debug(ImGuiWrapper* imgui); + bool m_show_triangles{false}; + bool m_show_invalid{false}; +#endif + +private: + GLIndexedVertexArray m_iva_enforcers; + GLIndexedVertexArray m_iva_blockers; + std::array m_varrays; +}; + + + +class GLGizmoPainterBase : public GLGizmoBase +{ +private: + ObjectID m_old_mo_id; + size_t m_old_volumes_size = 0; + + GLUquadricObj* m_quadric; + + float m_cursor_radius = 2.f; + static constexpr float CursorRadiusMin = 0.4f; // cannot be zero + static constexpr float CursorRadiusMax = 8.f; + static constexpr float CursorRadiusStep = 0.2f; + + // For each model-part volume, store status and division of the triangles. + std::vector> m_triangle_selectors; + +public: + GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + ~GLGizmoPainterBase() override; + void set_fdm_support_data(ModelObject* model_object, const Selection& selection); + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + + +private: + bool on_init() override; + void on_render() const override; + void on_render_for_picking() const override {} + + void render_triangles(const Selection& selection) const; + void render_cursor_circle() const; + + void update_model_object() const; + void update_from_model_object(); + void activate_internal_undo_redo_stack(bool activate); + + void select_facets_by_angle(float threshold, bool block); + float m_angle_threshold_deg = 45.f; + + bool is_mesh_point_clipped(const Vec3d& point) const; + + float m_clipping_plane_distance = 0.f; + std::unique_ptr m_clipping_plane; + bool m_setting_angle = false; + bool m_internal_stack_active = false; + bool m_schedule_update = false; + Vec2d m_last_mouse_position = Vec2d::Zero(); + + // This map holds all translated description texts, so they can be easily referenced during layout calculations + // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. + std::map m_desc; + + enum class Button { + None, + Left, + Right + }; + + Button m_button_down = Button::None; + EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) + +protected: + void on_set_state() override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_render_input_window(float x, float y, float bottom_limit) override; + std::string on_get_name() const override; + bool on_is_activable() const override; + bool on_is_selectable() const override; + void on_load(cereal::BinaryInputArchive& ar) override; + void on_save(cereal::BinaryOutputArchive& ar) const override; + CommonGizmosDataID on_get_requirements() const override; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoPainterBase_hpp_ From a9435cccb8f9a93c7ab03b40a4ed75232c0af7d6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 26 Aug 2020 11:33:41 +0200 Subject: [PATCH 381/826] Finished separation of FDM gizmo into base and child --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 296 +++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 23 ++ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 290 +----------------- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 54 ++-- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 2 +- 6 files changed, 356 insertions(+), 321 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index e69de29bb2..cc08f86a73 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -0,0 +1,296 @@ +#include "GLGizmoFdmSupports.hpp" + +#include "libslic3r/Model.hpp" + +//#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" +#include "slic3r/GUI/Plater.hpp" + + +#include + + +namespace Slic3r { + +namespace GUI { + + + +void GLGizmoFdmSupports::on_opening() +{ + +} + + + +void GLGizmoFdmSupports::on_shutdown() +{ + if (m_setting_angle) { + m_setting_angle = false; + m_parent.use_slope(false); + } +} + + + +bool GLGizmoFdmSupports::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + + m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["cursor_size"] = _L("Cursor size") + ": "; + m_desc["enforce_caption"] = _L("Left mouse button") + ": "; + m_desc["enforce"] = _L("Enforce supports"); + m_desc["block_caption"] = _L("Right mouse button") + " "; + m_desc["block"] = _L("Block supports"); + m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _L("Remove selection"); + m_desc["remove_all"] = _L("Remove all selection"); + + return true; +} + + + +void GLGizmoFdmSupports::on_render() const +{ + const Selection& selection = m_parent.get_selection(); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + if (! m_setting_angle) + render_triangles(selection); + + m_c->object_clipper()->render_cut(); + render_cursor_circle(); + + glsafe(::glDisable(GL_BLEND)); +} + + + +void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) +{ + if (! m_c->selection_info()->model_object()) + return; + + const float approx_height = m_imgui->scaled(18.0f); + y = std::min(y, bottom_limit - approx_height); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + if (! m_setting_angle) { + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float minimal_slider_width = m_imgui->scaled(4.f); + + float caption_max = 0.f; + float total_text_max = 0.; + for (const std::string& t : {"enforce", "block", "remove"}) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x); + total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); + } + caption_max += m_imgui->scaled(1.f); + total_text_max += m_imgui->scaled(1.f); + + float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + + auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { + static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); + m_imgui->text_colored(ORANGE, caption); + ImGui::SameLine(caption_max); + m_imgui->text(text); + }; + + for (const std::string& t : {"enforce", "block", "remove"}) + draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + + m_imgui->text(""); + + if (m_imgui->button("Autoset by angle...")) { + m_setting_angle = true; + } + + ImGui::SameLine(); + + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + } + } + + update_model_object(); + m_parent.set_as_dirty(); + } + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + + m_imgui->text(m_desc.at("cursor_size")); + ImGui::SameLine(clipping_slider_left); + ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) + m_imgui->text(m_desc.at("clipping_of_view")); + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + ImGui::SameLine(clipping_slider_left); + ImGui::PushItemWidth(window_width - clipping_slider_left); + float clp_dist = m_c->object_clipper()->get_position(); + if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f")) + m_c->object_clipper()->set_position(clp_dist, true); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + m_imgui->end(); + if (m_setting_angle) { + m_parent.show_slope(false); + m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); + m_parent.use_slope(true); + m_parent.set_as_dirty(); + } + } + else { + std::string name = "Autoset custom supports"; + m_imgui->begin(wxString(name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->text("Threshold:"); + ImGui::SameLine(); + if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) + m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); + if (m_imgui->button("Enforce")) + select_facets_by_angle(m_angle_threshold_deg, false); + ImGui::SameLine(); + if (m_imgui->button("Block")) + select_facets_by_angle(m_angle_threshold_deg, true); + ImGui::SameLine(); + if (m_imgui->button("Cancel")) + m_setting_angle = false; + m_imgui->end(); + if (! m_setting_angle) { + m_parent.use_slope(false); + m_parent.set_as_dirty(); + } + } +} + + + +void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) +{ + float threshold = (M_PI/180.)*threshold_deg; + const Selection& selection = m_parent.get_selection(); + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); + Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); + Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); + + float dot_limit = limit.dot(down); + + // Now calculate dot product of vert_direction and facets' normals. + int idx = -1; + for (const stl_facet& facet : mv->mesh().stl.facet_start) { + ++idx; + if (facet.normal.dot(down) > dot_limit) + m_triangle_selectors[mesh_id]->set_facet(idx, + block + ? EnforcerBlockerType::BLOCKER + : EnforcerBlockerType::ENFORCER); + } + } + + activate_internal_undo_redo_stack(true); + + Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") + : _L("Add supports by angle")); + update_model_object(); + m_parent.set_as_dirty(); + m_setting_angle = false; +} + + + +void GLGizmoFdmSupports::update_model_object() const +{ + bool updated = false; + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++idx; + updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + } + + if (updated) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + + + +void GLGizmoFdmSupports::update_from_model_object() +{ + wxBusyCursor wait; + + const ModelObject* mo = m_c->selection_info()->model_object(); + m_triangle_selectors.clear(); + + int volume_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++volume_id; + + // This mesh does not account for the possible Z up SLA offset. + const TriangleMesh* mesh = &mv->mesh(); + + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); + } +} + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 196a21bc03..dc0788c2c8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -13,6 +13,29 @@ public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} +protected: + void on_render_input_window(float x, float y, float bottom_limit) override; + +private: + bool on_init() override; + void on_render() const override; + void on_render_for_picking() const override {} + + void update_model_object() const override; + void update_from_model_object() override; + + void on_opening() override; + void on_shutdown() override; + + void select_facets_by_angle(float threshold, bool block); + float m_angle_threshold_deg = 45.f; + bool m_setting_angle = false; + + + + // This map holds all translated description texts, so they can be easily referenced during layout calculations + // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. + std::map m_desc; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 37792a48e0..365d71316c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -19,39 +19,10 @@ namespace GUI { GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) - , m_quadric(nullptr) { m_clipping_plane.reset(new ClippingPlane()); - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - // using GLU_FILL does not work when the instance's transformation - // contains mirroring (normals are reverted) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); } -GLGizmoPainterBase::~GLGizmoPainterBase() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -bool GLGizmoPainterBase::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - - m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size"] = _L("Cursor size") + ": "; - m_desc["enforce_caption"] = _L("Left mouse button") + ": "; - m_desc["enforce"] = _L("Enforce supports"); - m_desc["block_caption"] = _L("Right mouse button") + " "; - m_desc["block"] = _L("Block supports"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all selection"); - - return true; -} void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) @@ -68,7 +39,9 @@ void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) } } -void GLGizmoPainterBase::set_fdm_support_data(ModelObject* model_object, const Selection& selection) + + +void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection) { if (m_state != On) return; @@ -87,26 +60,8 @@ void GLGizmoPainterBase::set_fdm_support_data(ModelObject* model_object, const S -void GLGizmoPainterBase::on_render() const -{ - const Selection& selection = m_parent.get_selection(); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - render_triangles(selection); - - m_c->object_clipper()->render_cut(); - render_cursor_circle(); - - glsafe(::glDisable(GL_BLEND)); -} - void GLGizmoPainterBase::render_triangles(const Selection& selection) const { - if (m_setting_angle) - return; - const ModelObject* mo = m_c->selection_info()->model_object(); glsafe(::glEnable(GL_POLYGON_OFFSET_FILL)); @@ -145,8 +100,7 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); - if (! m_setting_angle) - m_triangle_selectors[mesh_id]->render(m_imgui); + m_triangle_selectors[mesh_id]->render(m_imgui); glsafe(::glPopMatrix()); if (is_left_handed) @@ -202,46 +156,6 @@ void GLGizmoPainterBase::render_cursor_circle() const } -void GLGizmoPainterBase::update_model_object() const -{ - bool updated = false; - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++idx; - updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); - } - - if (updated) - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); -} - - -void GLGizmoPainterBase::update_from_model_object() -{ - wxBusyCursor wait; - - const ModelObject* mo = m_c->selection_info()->model_object(); - m_triangle_selectors.clear(); - - int volume_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++volume_id; - - // This mesh does not account for the possible Z up SLA offset. - const TriangleMesh* mesh = &mv->mesh(); - - m_triangle_selectors.emplace_back(std::make_unique(*mesh)); - m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); - } -} - - bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const { @@ -461,179 +375,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous -void GLGizmoPainterBase::select_facets_by_angle(float threshold_deg, bool block) -{ - float threshold = (M_PI/180.)*threshold_deg; - const Selection& selection = m_parent.get_selection(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); - Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); - Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); - - float dot_limit = limit.dot(down); - - // Now calculate dot product of vert_direction and facets' normals. - int idx = -1; - for (const stl_facet& facet : mv->mesh().stl.facet_start) { - ++idx; - if (facet.normal.dot(down) > dot_limit) - m_triangle_selectors[mesh_id]->set_facet(idx, - block - ? EnforcerBlockerType::BLOCKER - : EnforcerBlockerType::ENFORCER); - } - } - - activate_internal_undo_redo_stack(true); - - Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") - : _L("Add supports by angle")); - update_model_object(); - m_parent.set_as_dirty(); - m_setting_angle = false; -} -void GLGizmoPainterBase::on_render_input_window(float x, float y, float bottom_limit) -{ - if (! m_c->selection_info()->model_object()) - return; - const float approx_height = m_imgui->scaled(18.0f); - y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - if (! m_setting_angle) { - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); - const float minimal_slider_width = m_imgui->scaled(4.f); - - float caption_max = 0.f; - float total_text_max = 0.; - for (const std::string& t : {"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x); - total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); - } - caption_max += m_imgui->scaled(1.f); - total_text_max += m_imgui->scaled(1.f); - - float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); - window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); - - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { - static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); - m_imgui->text_colored(ORANGE, caption); - ImGui::SameLine(caption_max); - m_imgui->text(text); - }; - - for (const std::string& t : {"enforce", "block", "remove"}) - draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); - - m_imgui->text(""); - - if (m_imgui->button("Autoset by angle...")) { - m_setting_angle = true; - } - - ImGui::SameLine(); - - if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) { - ++idx; - m_triangle_selectors[idx]->reset(); - } - } - update_model_object(); - m_parent.set_as_dirty(); - } - - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; - - m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); - ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) - m_imgui->text(m_desc.at("clipping_of_view")); - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); - float clp_dist = m_c->object_clipper()->get_position(); - if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - m_imgui->end(); - if (m_setting_angle) { - m_parent.show_slope(false); - m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - m_parent.use_slope(true); - m_parent.set_as_dirty(); - } - } - else { - std::string name = "Autoset custom supports"; - m_imgui->begin(wxString(name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->text("Threshold:"); - ImGui::SameLine(); - if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) - m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - if (m_imgui->button("Enforce")) - select_facets_by_angle(m_angle_threshold_deg, false); - ImGui::SameLine(); - if (m_imgui->button("Block")) - select_facets_by_angle(m_angle_threshold_deg, true); - ImGui::SameLine(); - if (m_imgui->button("Cancel")) - m_setting_angle = false; - m_imgui->end(); - if (! m_setting_angle) { - m_parent.use_slope(false); - m_parent.set_as_dirty(); - } - } -} bool GLGizmoPainterBase::on_is_activable() const { @@ -680,6 +425,7 @@ void GLGizmoPainterBase::on_set_state() return; if (m_state == On && m_old_state != On) { // the gizmo was just turned on + on_opening(); if (! m_parent.get_gizmos_manager().is_serializing()) { wxGetApp().CallAfter([this]() { activate_internal_undo_redo_stack(true); @@ -688,10 +434,7 @@ void GLGizmoPainterBase::on_set_state() } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off // we are actually shutting down - if (m_setting_angle) { - m_setting_angle = false; - m_parent.use_slope(false); - } + on_shutdown(); activate_internal_undo_redo_stack(false); m_old_mo_id = -1; //m_iva.release_geometry(); @@ -702,37 +445,18 @@ void GLGizmoPainterBase::on_set_state() -void GLGizmoPainterBase::on_start_dragging() -{ - -} - - -void GLGizmoPainterBase::on_stop_dragging() -{ - -} - - - void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) { // We should update the gizmo from current ModelObject, but it is not // possible at this point. That would require having updated selection and // common gizmos data, which is not done at this point. Instead, save - // a flag to do the update in set_fdm_support_data, which will be called + // a flag to do the update in set_painter_gizmo_data, which will be called // soon after. m_schedule_update = true; } -void GLGizmoPainterBase::on_save(cereal::BinaryOutputArchive&) const -{ - -} - - void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { int enf_cnt = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 1770c96a7d..886807b6a5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -46,14 +46,27 @@ private: }; - +// Following class is a base class for a gizmo with ability to paint on mesh +// using circular blush (such as FDM supports gizmo and seam painting gizmo). +// The purpose is not to duplicate code related to mesh painting. class GLGizmoPainterBase : public GLGizmoBase { private: ObjectID m_old_mo_id; size_t m_old_volumes_size = 0; - GLUquadricObj* m_quadric; +public: + GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + ~GLGizmoPainterBase() override {} + void set_painter_gizmo_data(const Selection& selection); + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + +protected: + void render_triangles(const Selection& selection) const; + void render_cursor_circle() const; + virtual void update_model_object() const = 0; + virtual void update_from_model_object() = 0; + void activate_internal_undo_redo_stack(bool activate); float m_cursor_radius = 2.f; static constexpr float CursorRadiusMin = 0.4f; // cannot be zero @@ -63,41 +76,17 @@ private: // For each model-part volume, store status and division of the triangles. std::vector> m_triangle_selectors; -public: - GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - ~GLGizmoPainterBase() override; - void set_fdm_support_data(ModelObject* model_object, const Selection& selection); - bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); - private: - bool on_init() override; - void on_render() const override; - void on_render_for_picking() const override {} - - void render_triangles(const Selection& selection) const; - void render_cursor_circle() const; - - void update_model_object() const; - void update_from_model_object(); - void activate_internal_undo_redo_stack(bool activate); - - void select_facets_by_angle(float threshold, bool block); - float m_angle_threshold_deg = 45.f; - bool is_mesh_point_clipped(const Vec3d& point) const; float m_clipping_plane_distance = 0.f; std::unique_ptr m_clipping_plane; - bool m_setting_angle = false; + bool m_internal_stack_active = false; bool m_schedule_update = false; Vec2d m_last_mouse_position = Vec2d::Zero(); - // This map holds all translated description texts, so they can be easily referenced during layout calculations - // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. - std::map m_desc; - enum class Button { None, Left, @@ -109,14 +98,17 @@ private: protected: void on_set_state() override; - void on_start_dragging() override; - void on_stop_dragging() override; - void on_render_input_window(float x, float y, float bottom_limit) override; + void on_start_dragging() override {} + void on_stop_dragging() override {} + + virtual void on_opening() = 0; + virtual void on_shutdown() = 0; + std::string on_get_name() const override; bool on_is_activable() const override; bool on_is_selectable() const override; void on_load(cereal::BinaryInputArchive& ar) override; - void on_save(cereal::BinaryOutputArchive& ar) const override; + void on_save(cereal::BinaryOutputArchive& ar) const override {} CommonGizmosDataID on_get_requirements() const override; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 78998b92d9..089e2c6ffc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -221,7 +221,7 @@ void GLGizmosManager::update_data() ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; set_flattening_data(model_object); set_sla_support_data(model_object); - set_fdm_support_data(model_object); + set_painter_gizmo_data(); } else if (selection.is_single_volume() || selection.is_single_modifier()) { @@ -230,7 +230,7 @@ void GLGizmosManager::update_data() set_rotation(Vec3d::Zero()); set_flattening_data(nullptr); set_sla_support_data(nullptr); - set_fdm_support_data(nullptr); + set_painter_gizmo_data(); } else if (is_wipe_tower) { @@ -239,7 +239,7 @@ void GLGizmosManager::update_data() set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); set_flattening_data(nullptr); set_sla_support_data(nullptr); - set_fdm_support_data(nullptr); + set_painter_gizmo_data(); } else { @@ -247,7 +247,7 @@ void GLGizmosManager::update_data() set_rotation(Vec3d::Zero()); set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); - set_fdm_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); + set_painter_gizmo_data(); } } @@ -382,12 +382,12 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object) gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection()); } -void GLGizmosManager::set_fdm_support_data(ModelObject* model_object) +void GLGizmosManager::set_painter_gizmo_data() { if (!m_enabled || m_gizmos.empty()) return; - dynamic_cast(m_gizmos[FdmSupports].get())->set_fdm_support_data(model_object, m_parent.get_selection()); + dynamic_cast(m_gizmos[FdmSupports].get())->set_painter_gizmo_data(m_parent.get_selection()); } // Returns true if the gizmo used the event to do something, false otherwise. diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 4ad46a2a92..b8b78eceb0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -203,7 +203,7 @@ public: void set_sla_support_data(ModelObject* model_object); - void set_fdm_support_data(ModelObject* model_object); + void set_painter_gizmo_data(); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); ClippingPlane get_clipping_plane() const; From db7559157ca6b4e06481dfbf4eb3d8f823977da7 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 26 Aug 2020 13:15:15 +0200 Subject: [PATCH 382/826] Revert "Forbid translation of objects when SLA/Hollow/FDM gizmos are active" This reverts commit c29171790930a1a9f9b0374b6a5ab8ccec1e88a9. Translation of object when those gizmos are active should already be supressed by previous commit (ba97ebb0). The FDM gizmo was erroneously not blocking the translation, the commit that is reverted is therefore needless after this was fixed the way it should have been fixed in the first place. --- src/slic3r/GUI/GLCanvas3D.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 94f6f6ef32..04ce89a80e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3657,14 +3657,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (!m_mouse.drag.move_requires_threshold) { m_mouse.dragging = true; - - // Translation of objects is forbidden when SLA supports/hollowing/fdm - // supports gizmo is active. - if (m_gizmos.get_current_type() == GLGizmosManager::SlaSupports - || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports - || m_gizmos.get_current_type() == GLGizmosManager::Hollow) - return; - Vec3d cur_pos = m_mouse.drag.start_position_3D; // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag if (m_selection.contains_volume(get_first_hover_volume_idx())) From 01b59ff57b7fdb85a25b623a0ebf6cb7dc02a1c7 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 31 Aug 2020 07:25:12 +0200 Subject: [PATCH 383/826] Seam gizmo created on frontend --- resources/icons/seam.svg | 42 ++++ src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 1 - src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 20 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 10 - src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 1 - src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 208 +++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp | 42 ++++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 24 ++- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 + 12 files changed, 323 insertions(+), 37 deletions(-) create mode 100644 resources/icons/seam.svg create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp diff --git a/resources/icons/seam.svg b/resources/icons/seam.svg new file mode 100644 index 0000000000..119fb6afcc --- /dev/null +++ b/resources/icons/seam.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f1089ae935..33994fe8ec 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -53,6 +53,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoHollow.hpp GUI/Gizmos/GLGizmoPainterBase.cpp GUI/Gizmos/GLGizmoPainterBase.hpp + GUI/Gizmos/GLGizmoSeam.cpp + GUI/Gizmos/GLGizmoSeam.hpp GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.hpp GUI/GLTexture.hpp diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 04ce89a80e..6646a12579 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3582,7 +3582,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports - && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports) + && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports + && m_gizmos.get_current_type() != GLGizmosManager::Seam) { m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); m_dirty = true; @@ -5317,7 +5318,8 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes) const bool show_texture = ! bottom || (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports); + && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports + && m_gizmos.get_current_type() != GLGizmosManager::Seam); wxGetApp().plater()->get_bed().render(const_cast(*this), bottom, scale_factor, show_axes, show_texture); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 3ab58c2585..44f0a69729 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -124,7 +124,6 @@ public: void set_state(EState state) { m_state = state; on_set_state(); } int get_shortcut_key() const { return m_shortcut_key; } - void set_shortcut_key(int key) { m_shortcut_key = key; } const std::string& get_icon_filename() const { return m_icon_filename; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index cc08f86a73..a34eca1a66 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -18,13 +18,6 @@ namespace GUI { -void GLGizmoFdmSupports::on_opening() -{ - -} - - - void GLGizmoFdmSupports::on_shutdown() { if (m_setting_angle) { @@ -35,6 +28,13 @@ void GLGizmoFdmSupports::on_shutdown() +std::string GLGizmoFdmSupports::on_get_name() const +{ + return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data(); +} + + + bool GLGizmoFdmSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; @@ -176,12 +176,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } m_imgui->end(); - if (m_setting_angle) { - m_parent.show_slope(false); - m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - m_parent.use_slope(true); - m_parent.set_as_dirty(); - } } else { std::string name = "Autoset custom supports"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index dc0788c2c8..7100d611e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -15,6 +15,7 @@ public: protected: void on_render_input_window(float x, float y, float bottom_limit) override; + std::string on_get_name() const override; private: bool on_init() override; @@ -24,7 +25,7 @@ private: void update_model_object() const override; void update_from_model_object() override; - void on_opening() override; + void on_opening() override {} void on_shutdown() override; void select_facets_by_angle(float threshold, bool block); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 365d71316c..1809b417cc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -375,11 +375,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous - - - - - bool GLGizmoPainterBase::on_is_activable() const { const Selection& selection = m_parent.get_selection(); @@ -403,11 +398,6 @@ bool GLGizmoPainterBase::on_is_selectable() const && wxGetApp().get_mode() != comSimple ); } -std::string GLGizmoPainterBase::on_get_name() const -{ - return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data(); -} - CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 886807b6a5..da9b378957 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -104,7 +104,6 @@ protected: virtual void on_opening() = 0; virtual void on_shutdown() = 0; - std::string on_get_name() const override; bool on_is_activable() const override; bool on_is_selectable() const override; void on_load(cereal::BinaryInputArchive& ar) override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp new file mode 100644 index 0000000000..8a08f5ebe7 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -0,0 +1,208 @@ +#include "GLGizmoSeam.hpp" + +#include "libslic3r/Model.hpp" + +//#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" +#include "slic3r/GUI/Plater.hpp" + + +#include + + +namespace Slic3r { + +namespace GUI { + + + +bool GLGizmoSeam::on_init() +{ + m_shortcut_key = WXK_CONTROL_P; + + m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["cursor_size"] = _L("Cursor size") + ": "; + m_desc["enforce_caption"] = _L("Left mouse button") + ": "; + m_desc["enforce"] = _L("Enforce seam"); + m_desc["block_caption"] = _L("Right mouse button") + " "; + m_desc["block"] = _L("Block seam"); + m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _L("Remove selection"); + m_desc["remove_all"] = _L("Remove all selection"); + + return true; +} + + + +std::string GLGizmoSeam::on_get_name() const +{ + return (_(L("Seam Editing")) + " [P]").ToUTF8().data(); +} + + + +void GLGizmoSeam::on_render() const +{ + const Selection& selection = m_parent.get_selection(); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + render_triangles(selection); + + m_c->object_clipper()->render_cut(); + render_cursor_circle(); + + glsafe(::glDisable(GL_BLEND)); +} + + + +void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) +{ + if (! m_c->selection_info()->model_object()) + return; + + const float approx_height = m_imgui->scaled(18.0f); + y = std::min(y, bottom_limit - approx_height); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float minimal_slider_width = m_imgui->scaled(4.f); + + float caption_max = 0.f; + float total_text_max = 0.; + for (const std::string& t : {"enforce", "block", "remove"}) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x); + total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x); + } + caption_max += m_imgui->scaled(1.f); + total_text_max += m_imgui->scaled(1.f); + + float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + + auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { + static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); + m_imgui->text_colored(ORANGE, caption); + ImGui::SameLine(caption_max); + m_imgui->text(text); + }; + + for (const std::string& t : {"enforce", "block", "remove"}) + draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + + m_imgui->text(""); + + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + } + } + + update_model_object(); + m_parent.set_as_dirty(); + } + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + + m_imgui->text(m_desc.at("cursor_size")); + ImGui::SameLine(clipping_slider_left); + ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) + m_imgui->text(m_desc.at("clipping_of_view")); + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + ImGui::SameLine(clipping_slider_left); + ImGui::PushItemWidth(window_width - clipping_slider_left); + float clp_dist = m_c->object_clipper()->get_position(); + if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f")) + m_c->object_clipper()->set_position(clp_dist, true); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + m_imgui->end(); +} + + + +void GLGizmoSeam::update_model_object() const +{ + bool updated = false; + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++idx; + updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + } + + if (updated) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + + + +void GLGizmoSeam::update_from_model_object() +{ + wxBusyCursor wait; + + const ModelObject* mo = m_c->selection_info()->model_object(); + m_triangle_selectors.clear(); + + int volume_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++volume_id; + + // This mesh does not account for the possible Z up SLA offset. + const TriangleMesh* mesh = &mv->mesh(); + + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); + } +} + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp new file mode 100644 index 0000000000..469ec9180c --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -0,0 +1,42 @@ +#ifndef slic3r_GLGizmoSeam_hpp_ +#define slic3r_GLGizmoSeam_hpp_ + +#include "GLGizmoPainterBase.hpp" + +namespace Slic3r { + +namespace GUI { + +class GLGizmoSeam : public GLGizmoPainterBase +{ +public: + GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoPainterBase(parent, icon_filename, sprite_id) {} + +protected: + void on_render_input_window(float x, float y, float bottom_limit) override; + std::string on_get_name() const override; + +private: + bool on_init() override; + void on_render() const override; + void on_render_for_picking() const override {} + + void update_model_object() const override; + void update_from_model_object() override; + + void on_opening() override {} + void on_shutdown() override {} + + // This map holds all translated description texts, so they can be easily referenced during layout calculations + // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. + std::map m_desc; +}; + + + +} // namespace GUI +} // namespace Slic3r + + +#endif // slic3r_GLGizmoSeam_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 089e2c6ffc..1087c64d5a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -16,6 +16,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" #include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -104,6 +105,7 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5)); m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6)); m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "sla_supports.svg", 7)); + m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8)); m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent)); @@ -388,6 +390,7 @@ void GLGizmosManager::set_painter_gizmo_data() return; dynamic_cast(m_gizmos[FdmSupports].get())->set_painter_gizmo_data(m_parent.get_selection()); + dynamic_cast(m_gizmos[Seam].get())->set_painter_gizmo_data(m_parent.get_selection()); } // Returns true if the gizmo used the event to do something, false otherwise. @@ -402,6 +405,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == FdmSupports) return dynamic_cast(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == Seam) + return dynamic_cast(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else return false; } @@ -465,7 +470,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) { bool processed = false; - if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) { + if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) { float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) processed = true; @@ -607,7 +612,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) if (evt.LeftDown()) { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) + if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ||m_current == Seam) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) // the gizmo got the event and took some action, there is no need to do anything more processed = true; @@ -634,23 +639,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // event was taken care of by the SlaSupports gizmo processed = true; } - else if (evt.RightDown() && (selected_object_idx != -1) && m_current == FdmSupports + else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == FdmSupports || m_current == Seam) && gizmo_event(SLAGizmoEventType::RightDown, mouse_pos)) { - // event was taken care of by the FdmSupports gizmo + // event was taken care of by the FdmSupports / Seam gizmo processed = true; } - else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow)) + else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) + && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam)) // don't allow dragging objects with the Sla gizmo on processed = true; - else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ) + else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam ) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) { // the gizmo got the event and took some action, no need to do anything more here m_parent.set_as_dirty(); processed = true; } - else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && !m_parent.is_mouse_dragging()) + else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && !m_parent.is_mouse_dragging()) { // in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither // object moving or selecting is suppressed in that case @@ -662,7 +668,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active processed = true; } - else if (evt.RightUp() && m_current == FdmSupports && !m_parent.is_mouse_dragging()) + else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam) && !m_parent.is_mouse_dragging()) { gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); processed = true; @@ -752,7 +758,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) case 'r' : case 'R' : { - if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane)) + if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && gizmo_event(SLAGizmoEventType::ResetClippingPlane)) processed = true; break; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index b8b78eceb0..6b965525d5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -67,6 +67,7 @@ public: Hollow, SlaSupports, FdmSupports, + Seam, Undefined }; From d904862bc708ee3571480cc97b84c2def389c84e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 1 Sep 2020 19:04:22 +0200 Subject: [PATCH 384/826] Build libpng as part of deps on Linux - We've found counter case where the system provided one is missing or is too old. --- deps/deps-linux.cmake | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/deps/deps-linux.cmake b/deps/deps-linux.cmake index 3ad3cca64f..ae972327f8 100644 --- a/deps/deps-linux.cmake +++ b/deps/deps-linux.cmake @@ -3,10 +3,11 @@ set(DEP_CMAKE_OPTS "-DCMAKE_POSITION_INDEPENDENT_CODE=ON") include("deps-unix-common.cmake") -find_package(PNG QUIET) -if (NOT PNG_FOUND) - message(WARNING "No PNG dev package found in system, building static library. You should install the system package.") -endif () +# Some Linuxes may have very old libpng, so it's best to bundle it instead of relying on the system version. +# find_package(PNG QUIET) +# if (NOT PNG_FOUND) +# message(WARNING "No PNG dev package found in system, building static library. You should install the system package.") +# endif () #TODO UDEV From 9c59b4f9305bab09af75eb1b2d61efff177efeab Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 31 Aug 2020 07:25:43 +0200 Subject: [PATCH 385/826] Custom seam: Model integration, backend invalidation, 3MF loading/saving --- src/libslic3r/Format/3mf.cpp | 14 +++++++++++++- src/libslic3r/Model.cpp | 12 ++++++++++++ src/libslic3r/Model.hpp | 14 +++++++++++--- src/libslic3r/Print.cpp | 4 ++++ src/libslic3r/Print.hpp | 6 ++---- src/libslic3r/PrintObject.cpp | 12 +++++++----- src/libslic3r/SupportMaterial.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 4 ++-- 8 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 59dc85a0ae..92119f91c0 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -87,6 +87,7 @@ const char* TRANSFORM_ATTR = "transform"; const char* PRINTABLE_ATTR = "printable"; const char* INSTANCESCOUNT_ATTR = "instances_count"; const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; +const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; const char* KEY_ATTR = "key"; const char* VALUE_ATTR = "value"; @@ -285,6 +286,7 @@ namespace Slic3r { std::vector vertices; std::vector triangles; std::vector custom_supports; + std::vector custom_seam; bool empty() { @@ -296,6 +298,7 @@ namespace Slic3r { vertices.clear(); triangles.clear(); custom_supports.clear(); + custom_seam.clear(); } }; @@ -1544,6 +1547,7 @@ namespace Slic3r { m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); + m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); return true; } @@ -1877,14 +1881,18 @@ namespace Slic3r { volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->calculate_convex_hull(); - // recreate custom supports from previously loaded attribute + // recreate custom supports and seam from previously loaded attribute for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); + if (! geometry.custom_seam[index].empty()) + volume->m_seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); } + // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { @@ -2401,6 +2409,10 @@ namespace Slic3r { if (! custom_supports_data_string.empty()) stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" "; + std::string custom_seam_data_string = volume->m_seam_facets.get_triangle_as_string(i); + if (! custom_seam_data_string.empty()) + stream << CUSTOM_SEAM_ATTR << "=\"" << custom_seam_data_string << "\" "; + stream << "/>\n"; } } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 196e9c213b..d12dc7a0f4 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1007,6 +1007,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial for (ModelVolume* volume : volumes) { volume->m_supported_facets.clear(); + volume->m_seam_facets.clear(); if (!volume->mesh().empty()) { TriangleMesh mesh(volume->mesh()); mesh.require_shared_vertices(); @@ -1112,6 +1113,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b const auto volume_matrix = volume->get_matrix(); volume->m_supported_facets.clear(); + volume->m_seam_facets.clear(); if (! volume->is_model_part()) { // Modifiers are not cut, but we still need to add the instance transformation @@ -1993,6 +1995,16 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject return false; } +bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new) { + assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART)); + assert(mo.volumes.size() == mo_new.volumes.size()); + for (size_t i=0; im_seam_facets.is_same_as(mo.volumes[i]->m_seam_facets)) + return true; + } + return false; +} + extern bool model_has_multi_part_objects(const Model &model) { for (const ModelObject *model_object : model.objects) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 608ce670f6..a623f5cca0 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -464,6 +464,9 @@ public: // List of mesh facets to be supported/unsupported. FacetsAnnotation m_supported_facets; + // List of seam enforcers/blockers. + FacetsAnnotation m_seam_facets; + // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; } ModelVolumeType type() const { return m_type; } @@ -593,7 +596,7 @@ private: ObjectBase(other), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - m_supported_facets(other.m_supported_facets) + m_supported_facets(other.m_supported_facets), m_seam_facets(other.m_seam_facets) { assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); assert(this->id() == other.id() && this->config.id() == other.config.id()); @@ -612,6 +615,7 @@ private: assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id()); m_supported_facets.clear(); + m_seam_facets.clear(); } ModelVolume& operator=(ModelVolume &rhs) = delete; @@ -625,7 +629,7 @@ private: template void load(Archive &ar) { bool has_convex_hull; ar(name, source, m_mesh, m_type, m_material_id, m_transformation, - m_is_splittable, has_convex_hull, m_supported_facets); + m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets); cereal::load_by_value(ar, config); assert(m_mesh); if (has_convex_hull) { @@ -639,7 +643,7 @@ private: template void save(Archive &ar) const { bool has_convex_hull = m_convex_hull.get() != nullptr; ar(name, source, m_mesh, m_type, m_material_id, m_transformation, - m_is_splittable, has_convex_hull, m_supported_facets); + m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets); cereal::save_by_value(ar, config); if (has_convex_hull) cereal::save_optional(ar, m_convex_hull); @@ -904,6 +908,10 @@ extern bool model_volume_list_changed(const ModelObject &model_object_old, const // The function assumes that volumes list is synchronized. extern bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new); +// Test whether the now ModelObject has newer custom seam data than the old one. +// The function assumes that volumes list is synchronized. +extern bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new); + // If the model has multi-part objects, then it is currently not supported by the SLA mode. // Either the model cannot be loaded, or a SLA printer has to be activated. extern bool model_has_multi_part_objects(const Model &model); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 0c8a11fcf0..37c0a7d154 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -404,6 +404,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, mv_dst.name = mv_src.name; static_cast(mv_dst.config) = static_cast(mv_src.config); mv_dst.m_supported_facets = mv_src.m_supported_facets; + mv_dst.m_seam_facets = mv_src.m_seam_facets; //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -867,6 +868,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_volume_list_update_supports(model_object, model_object_new); } } + if (model_custom_seam_data_changed(model_object, model_object_new)) { + update_apply_status(this->invalidate_step(psGCodeExport)); + } if (! model_parts_differ && ! modifiers_differ) { // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 08acb7a105..6cb80c1f44 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -186,10 +186,8 @@ public: std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } - // Helpers to project custom supports on slices - void project_and_append_custom_supports(EnforcerBlockerType type, std::vector& expolys) const; - void project_and_append_custom_enforcers(std::vector& enforcers) const { project_and_append_custom_supports(EnforcerBlockerType::ENFORCER, enforcers); } - void project_and_append_custom_blockers(std::vector& blockers) const { project_and_append_custom_supports(EnforcerBlockerType::BLOCKER, blockers); } + // Helpers to project custom facets on slices + void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; private: // to be called from Print only. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ddeee1e778..aecf907710 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2669,12 +2669,14 @@ void PrintObject::_generate_support_material() } -void PrintObject::project_and_append_custom_supports( - EnforcerBlockerType type, std::vector& expolys) const +void PrintObject::project_and_append_custom_facets( + bool seam, EnforcerBlockerType type, std::vector& expolys) const { for (const ModelVolume* mv : this->model_object()->volumes) { - const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type); - if (custom_facets.indices.empty()) + const indexed_triangle_set custom_facets = seam + ? mv->m_seam_facets.get_facets(*mv, type) + : mv->m_supported_facets.get_facets(*mv, type); + if (! mv->is_model_part() || custom_facets.indices.empty()) continue; const Transform3f& tr1 = mv->get_matrix().cast(); @@ -2721,7 +2723,7 @@ void PrintObject::project_and_append_custom_supports( // Ignore triangles with upward-pointing normal. Don't forget about mirroring. float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); - if (tr_det_sign * z_comp > 0.) + if (! seam && tr_det_sign * z_comp > 0.) continue; // Sort the three vertices according to z-coordinate. diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 95b4c334b1..1669f60d21 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -972,8 +972,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ std::vector blockers = object.slice_support_blockers(); // Append custom supports. - object.project_and_append_custom_enforcers(enforcers); - object.project_and_append_custom_blockers(blockers); + object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers); + object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers); // Output layers, sorted by top Z. MyLayersPtr contact_out; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 8a08f5ebe7..3c7d180a7b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -172,7 +172,7 @@ void GLGizmoSeam::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->m_seam_facets.set(*m_triangle_selectors[idx].get()); } if (updated) @@ -199,7 +199,7 @@ void GLGizmoSeam::update_from_model_object() const TriangleMesh* mesh = &mv->mesh(); m_triangle_selectors.emplace_back(std::make_unique(*mesh)); - m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); + m_triangle_selectors.back()->deserialize(mv->m_seam_facets.get_data()); } } From 46eb96e84fac3e38734c9b0aefaf829a3e028538 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 1 Sep 2020 23:26:08 +0200 Subject: [PATCH 386/826] Added two missing icons to fix build on Linux --- src/slic3r/GUI/ConfigWizard.cpp | 1 + src/slic3r/GUI/ConfigWizard_private.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index f8abfb1788..2cedbfdf78 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index f7987a8908..260eeb22cb 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include From 60cf002e948d5a1a56a69b267189b8611957e2c6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 1 Sep 2020 18:33:56 +0200 Subject: [PATCH 387/826] Fixed merge conflicts (whitespace only) --- src/libslic3r/GCode.cpp | 590 ++++++++++++++++++++-------------------- src/libslic3r/GCode.hpp | 1 + 2 files changed, 297 insertions(+), 294 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 135389eb3e..2049fd76bc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -662,7 +662,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec return layers_to_print; } -// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z +// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z // will be printed for all objects at once. // Return a list of items. std::vector>> GCode::collect_layers_to_print(const Print& print) @@ -820,7 +820,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ "Is " + path_tmp + " locked?" + '\n'); BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); - print->set_done(psGCodeExport); + print->set_done(psGCodeExport); // Write the profiler measurements to file PROFILE_UPDATE(); @@ -983,7 +983,8 @@ namespace DoExport { return volumetric_speed; } - static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) + + static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) { // Calculate wiping points if needed if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) { @@ -1123,26 +1124,26 @@ namespace DoExport { } filament_stats_string_out += out_filament_used_mm.first; filament_stats_string_out += "\n" + out_filament_used_cm3.first; - if (out_filament_used_g.second) + if (out_filament_used_g.second) filament_stats_string_out += "\n" + out_filament_used_g.first; - if (out_filament_cost.second) + if (out_filament_cost.second) filament_stats_string_out += "\n" + out_filament_cost.first; - } - return filament_stats_string_out; - } + } + return filament_stats_string_out; + } } // Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints. static inline std::vector sort_object_instances_by_max_z(const Print &print) { std::vector objects(print.objects().begin(), print.objects().end()); - std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); }); - std::vector instances; - instances.reserve(objects.size()); - for (const PrintObject *object : objects) - for (size_t i = 0; i < object->instances().size(); ++ i) - instances.emplace_back(&object->instances()[i]); - return instances; + std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); }); + std::vector instances; + instances.reserve(objects.size()); + for (const PrintObject *object : objects) + for (size_t i = 0; i < object->instances().size(); ++ i) + instances.emplace_back(&object->instances()[i]); + return instances; } // Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model(). @@ -1246,8 +1247,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Write information on the generator. _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, - [this, file](const char* sz) { this->_write(file, sz); }, + DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, + [this, file](const char* sz) { this->_write(file, sz); }, [&print]() { print.throw_if_canceled(); }); // Write notes (content of the Print Settings tab -> Notes) @@ -1282,7 +1283,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write_format(file, "\n"); } print.throw_if_canceled(); - + // adds tags for time estimators #if ENABLE_GCODE_VIEWER if (print.config().remaining_times.value) @@ -1321,12 +1322,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode. // Use the extruder IDs collected from Regions. - this->set_extruders(print.extruders()); + this->set_extruders(print.extruders()); } else { - // Find tool ordering for all the objects at once, and the initial extruder ID. + // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. - tool_ordering = print.tool_ordering(); - tool_ordering.assign_custom_gcodes(print); + tool_ordering = print.tool_ordering(); + tool_ordering.assign_custom_gcodes(print); has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ? // The priming towers will be skipped. @@ -1335,7 +1336,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu tool_ordering.first_extruder(); // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z. // Therefore initialize the printing extruders from there. - this->set_extruders(tool_ordering.all_extruders()); + this->set_extruders(tool_ordering.all_extruders()); // Order object instances using a nearest neighbor search. print_object_instances_ordering = chain_print_object_instances(print); } @@ -1435,7 +1436,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Calculate wiping points if needed DoExport::init_ooze_prevention(print, m_ooze_prevention); print.throw_if_canceled(); - + if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. @@ -1510,7 +1511,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); if (print.config().single_extruder_multi_material_priming) { - _write(file, m_wipe_tower->prime(*this)); + _write(file, m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. BoundingBoxf bbox_print(get_print_extrusions_extents(print)); coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; @@ -1577,7 +1578,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config)); } else { for (const std::string &end_gcode : print.config().end_filament_gcode.values) { - int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()); + int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); _writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config)); } @@ -1651,8 +1652,8 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std m_placeholder_parser_failed_templates.insert(name); // Insert the macro error message into the G-code. return - std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + - err.what() + + std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + + err.what() + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; } } @@ -1678,12 +1679,12 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc int mcode = int(strtol(ptr, &endptr, 10)); if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) { // M104/M109 or M140/M190 found. - ptr = endptr; + ptr = endptr; // Let the caller know that the custom G-code sets the temperature. temp_set_by_gcode = true; // Now try to parse the temperature value. - // While not at the end of the line: - while (strchr(";\r\n\0", *ptr) == nullptr) { + // While not at the end of the line: + while (strchr(";\r\n\0", *ptr) == nullptr) { // Skip whitespaces. for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); if (*ptr == 'S') { @@ -1692,22 +1693,22 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Parse an int. endptr = nullptr; long temp_parsed = strtol(ptr, &endptr, 10); - if (endptr > ptr) { - ptr = endptr; - temp_out = temp_parsed; - } + if (endptr > ptr) { + ptr = endptr; + temp_out = temp_parsed; + } } else { // Skip this word. - for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); } } } } // Skip the rest of the line. for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); - // Skip the end of line indicators. + // Skip the end of line indicators. for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); - } + } return temp_set_by_gcode; } @@ -1796,9 +1797,9 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c } inline GCode::ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, size_t num_objects) { std::vector &objects_by_extruder = by_extruder[extruder_id]; @@ -1808,9 +1809,9 @@ inline GCode::ObjectByExtruder& object_by_extruder( } inline std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, size_t num_objects, size_t num_islands) { @@ -1821,82 +1822,82 @@ inline std::vector& object_islands_by_extruder( } std::vector GCode::sort_print_object_instances( - std::vector &objects_by_extruder, - const std::vector &layers, - // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, - // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx) + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx) { std::vector out; if (ordering == nullptr) { - // Sequential print, single object is being printed. - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); - } + // Sequential print, single object is being printed. + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); + } } else { - // Create mapping from PrintObject* to ObjectByExtruder*. - std::vector> sorted; - sorted.reserve(objects_by_extruder.size()); - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - sorted.emplace_back(print_object, &object_by_extruder); - } - std::sort(sorted.begin(), sorted.end()); + // Create mapping from PrintObject* to ObjectByExtruder*. + std::vector> sorted; + sorted.reserve(objects_by_extruder.size()); + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + sorted.emplace_back(print_object, &object_by_extruder); + } + std::sort(sorted.begin(), sorted.end()); - if (! sorted.empty()) { - out.reserve(sorted.size()); - for (const PrintInstance *instance : *ordering) { - const PrintObject &print_object = *instance->print_object; - std::pair key(&print_object, nullptr); - auto it = std::lower_bound(sorted.begin(), sorted.end(), key); - if (it != sorted.end() && it->first == &print_object) - // ObjectByExtruder for this PrintObject was found. - out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data()); - } - } - } - return out; + if (! sorted.empty()) { + out.reserve(sorted.size()); + for (const PrintInstance *instance : *ordering) { + const PrintObject &print_object = *instance->print_object; + std::pair key(&print_object, nullptr); + auto it = std::lower_bound(sorted.begin(), sorted.end(), key); + if (it != sorted.end() && it->first == &print_object) + // ObjectByExtruder for this PrintObject was found. + out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data()); + } + } + } + return out; } namespace ProcessLayer { static std::string emit_custom_gcode_per_print_z( - const CustomGCode::Item *custom_gcode, + const CustomGCode::Item *custom_gcode, // ID of the first extruder printing this layer. unsigned int first_extruder_id, const PrintConfig &config) - { + { std::string gcode; bool single_extruder_printer = config.nozzle_diameter.size() == 1; - + if (custom_gcode != nullptr) { - // Extruder switches are processed by LayerTools, they should be filtered out. - assert(custom_gcode->type != CustomGCode::ToolChange); + // Extruder switches are processed by LayerTools, they should be filtered out. + assert(custom_gcode->type != CustomGCode::ToolChange); CustomGCode::Type gcode_type = custom_gcode->type; bool color_change = gcode_type == CustomGCode::ColorChange; bool tool_change = gcode_type == CustomGCode::ToolChange; - // Tool Change is applied as Color Change for a single extruder printer only. - assert(! tool_change || single_extruder_printer); + // Tool Change is applied as Color Change for a single extruder printer only. + assert(! tool_change || single_extruder_printer); - std::string pause_print_msg; - int m600_extruder_before_layer = -1; - if (color_change && custom_gcode->extruder > 0) - m600_extruder_before_layer = custom_gcode->extruder - 1; - else if (gcode_type == CustomGCode::PausePrint) - pause_print_msg = custom_gcode->extra; + std::string pause_print_msg; + int m600_extruder_before_layer = -1; + if (color_change && custom_gcode->extruder > 0) + m600_extruder_before_layer = custom_gcode->extruder - 1; + else if (gcode_type == CustomGCode::PausePrint) + pause_print_msg = custom_gcode->extra; - // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count - if (color_change || tool_change) - { + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (color_change || tool_change) + { assert(m600_extruder_before_layer >= 0); // Color Change or Tool Change as Color Change. #if ENABLE_GCODE_VIEWER @@ -1910,13 +1911,13 @@ namespace ProcessLayer #endif // ENABLE_GCODE_VIEWER if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer - // && !MMU1 - ) { - //! FIXME_in_fw show message during print pause - gcode += config.pause_print_gcode;// pause print + // && !MMU1 + ) { + //! FIXME_in_fw show message during print pause + gcode += config.pause_print_gcode;// pause print gcode += "\n"; - gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; - } + gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; + } else { gcode += config.color_change_gcode;//ColorChangeCode; gcode += "\n"; @@ -1956,32 +1957,32 @@ namespace ProcessLayer if (gcode_type == CustomGCode::Template) // Template Cistom Gcode gcode += config.template_custom_gcode; else // custom Gcode - gcode += custom_gcode->extra; + gcode += custom_gcode->extra; - } - gcode += "\n"; - } - } + } + gcode += "\n"; + } + } - return gcode; - } + return gcode; + } } // namespace ProcessLayer namespace Skirt { - static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) - { + static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) + { // Prime all extruders printing over the 1st layer over the skirt lines. size_t n_loops = print.skirt().entities.size(); size_t n_tools = layer_tools.extruders.size(); size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools; for (size_t i = 0; i < n_loops; i += lines_per_extruder) skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair(i, std::min(i + lines_per_extruder, n_loops)); - } + } static std::map> make_skirt_loops_per_extruder_1st_layer( const Print &print, - const std::vector & /*layers */, - const LayerTools &layer_tools, + const std::vector & /*layers */, + const LayerTools &layer_tools, // Heights (print_z) at which the skirt has already been extruded. std::vector &skirt_done) { @@ -1989,7 +1990,7 @@ namespace Skirt { // not at the print_z of the interlaced support material layers. std::map> skirt_loops_per_extruder_out; if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty()) { - skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); + skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); skirt_done.emplace_back(layer_tools.print_z); } return skirt_loops_per_extruder_out; @@ -1997,11 +1998,11 @@ namespace Skirt { static std::map> make_skirt_loops_per_extruder_other_layers( const Print &print, - const std::vector &layers, - const LayerTools &layer_tools, - // First non-empty support layer. - const SupportLayer *support_layer, - // Heights (print_z) at which the skirt has already been extruded. + const std::vector &layers, + const LayerTools &layer_tools, + // First non-empty support layer. + const SupportLayer *support_layer, + // Heights (print_z) at which the skirt has already been extruded. std::vector &skirt_done) { // Extrude skirt at the print_z of the raft layers and normal object layers @@ -2019,7 +2020,7 @@ namespace Skirt { // Prime just the first printing extruder. This is original Slic3r's implementation. skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirts.value); #else - // Prime all extruders planned for this layer, see + // Prime all extruders planned for this layer, see // https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619 skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); #endif @@ -2031,7 +2032,7 @@ namespace Skirt { } // namespace Skirt -// In sequential mode, process_layer is called once per each object and its copy, +// In sequential mode, process_layer is called once per each object and its copy, // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths @@ -2043,8 +2044,8 @@ void GCode::process_layer( // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, const LayerTools &layer_tools, - // Pairs of PrintObject index and its instance index. - const std::vector *ordering, + // Pairs of PrintObject index and its instance index. + const std::vector *ordering, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_instance_idx) @@ -2091,7 +2092,7 @@ void GCode::process_layer( } // If we're going to apply spiralvase to this layer, disable loop clipping m_enable_loop_clipping = ! m_spiral_vase || ! m_spiral_vase->enable; - + std::string gcode; #if ENABLE_GCODE_VIEWER @@ -2120,7 +2121,7 @@ void GCode::process_layer( + "\n"; } gcode += this->change_layer(print_z); // this will increase m_layer_index - m_layer = &layer; + m_layer = &layer; if (! print.config().layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); @@ -2212,8 +2213,8 @@ void GCode::process_layer( } if (layer_to_print.object_layer != nullptr) { const Layer &layer = *layer_to_print.object_layer; - // We now define a strategy for building perimeters and fills. The separation - // between regions doesn't matter in terms of printing order, as we follow + // We now define a strategy for building perimeters and fills. The separation + // between regions doesn't matter in terms of printing order, as we follow // another logic instead: // - we group all extrusions by extruder so that we minimize toolchanges // - we start from the last used extruder @@ -2228,13 +2229,13 @@ void GCode::process_layer( std::vector slices_test_order; slices_test_order.reserve(n_slices); for (size_t i = 0; i < n_slices; ++ i) - slices_test_order.emplace_back(i); + slices_test_order.emplace_back(i); std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) { - const Vec2d s1 = layer_surface_bboxes[i].size().cast(); - const Vec2d s2 = layer_surface_bboxes[j].size().cast(); - return s1.x() * s1.y() < s2.x() * s2.y(); + const Vec2d s1 = layer_surface_bboxes[i].size().cast(); + const Vec2d s2 = layer_surface_bboxes[j].size().cast(); + return s1.x() * s1.y() < s2.x() * s2.y(); }); - auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { + auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { const BoundingBox &bbox = layer_surface_bboxes[i]; return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && point(1) >= bbox.min(1) && point(1) < bbox.max(1) && @@ -2265,27 +2266,27 @@ void GCode::process_layer( // Let's recover vector of extruder overrides: const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; if (! layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) correct_extruder_id = layer_tools.extruders.back(); } printing_extruders.clear(); if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size()); - if (entity_overrides == nullptr) { - printing_extruders.emplace_back(correct_extruder_id); - } else { - printing_extruders.reserve(entity_overrides->size()); - for (int extruder : *entity_overrides) - printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) - static_cast(- extruder - 1)); - Slic3r::sort_remove_duplicates(printing_extruders); - } - } else - printing_extruders.emplace_back(correct_extruder_id); + entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size()); + if (entity_overrides == nullptr) { + printing_extruders.emplace_back(correct_extruder_id); + } else { + printing_extruders.reserve(entity_overrides->size()); + for (int extruder : *entity_overrides) + printing_extruders.emplace_back(extruder >= 0 ? + // at least one copy is overridden to use this extruder + extruder : + // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) + static_cast(- extruder - 1)); + Slic3r::sort_remove_duplicates(printing_extruders); + } + } else + printing_extruders.emplace_back(correct_extruder_id); // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: for (unsigned int extruder : printing_extruders) @@ -2296,10 +2297,10 @@ void GCode::process_layer( &layer_to_print - layers.data(), layers.size(), n_slices+1); for (size_t i = 0; i <= n_slices; ++ i) { - bool last = i == n_slices; - size_t island_idx = last ? n_slices : slices_test_order[i]; + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; if (// extrusions->first_point does not fit inside any slice - last || + last || // extrusions->first_point fits inside ith slice point_inside_surface(island_idx, extrusions->first_point())) { if (islands[island_idx].by_region.empty()) @@ -2374,10 +2375,10 @@ void GCode::process_layer( if (objects_by_extruder_it == by_extruder.end()) continue; - std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - std::vector by_region_per_copy_cache; + std::vector by_region_per_copy_cache; for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) gcode+="; PURGING FINISHED\n"; @@ -2406,7 +2407,7 @@ void GCode::process_layer( } for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; - //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. + //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. if (print.config().infill_first) { gcode += this->extrude_infill(print, by_region_specific, false); gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); @@ -2418,13 +2419,13 @@ void GCode::process_layer( gcode += this->extrude_infill(print,by_region_specific, true); } if (this->config().gcode_label_objects) - gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; + gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; } } } // Apply spiral vase post-processing if this layer contains suitable geometry - // (we must feed all the G-code into the post-processor, including the first + // (we must feed all the G-code into the post-processor, including the first // bottom non-spiral layers otherwise it will mess with positions) // we apply spiral vase at this stage because it requires a full layer. // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only. @@ -2450,7 +2451,7 @@ void GCode::process_layer( gcode = m_pressure_equalizer->process(gcode.c_str(), false); // printf("G-code after filter:\n%s\n", out.c_str()); #endif /* HAS_PRESSURE_EQUALIZER */ - + _write(file, gcode); #if !ENABLE_GCODE_VIEWER BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << @@ -2470,19 +2471,19 @@ void GCode::apply_print_config(const PrintConfig &print_config) void GCode::append_full_config(const Print &print, std::string &str) { - const DynamicPrintConfig &cfg = print.full_print_config(); + const DynamicPrintConfig &cfg = print.full_print_config(); // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. - static constexpr auto banned_keys = { - "compatible_printers"sv, - "compatible_prints"sv, - "print_host"sv, - "printhost_apikey"sv, - "printhost_cafile"sv - }; + static constexpr auto banned_keys = { + "compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "printhost_apikey"sv, + "printhost_cafile"sv + }; assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); - auto is_banned = [](const std::string &key) { - return std::binary_search(banned_keys.begin(), banned_keys.end(), key); - }; + auto is_banned = [](const std::string &key) { + return std::binary_search(banned_keys.begin(), banned_keys.end(), key); + }; for (const std::string &key : cfg.keys()) if (! is_banned(key) && ! cfg.option(key)->is_nil()) str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; @@ -2491,7 +2492,7 @@ void GCode::append_full_config(const Print &print, std::string &str) void GCode::set_extruders(const std::vector &extruder_ids) { m_writer.set_extruders(extruder_ids); - + // enable wipe path generation if any extruder has wipe enabled m_wipe.enable = false; for (auto id : extruder_ids) @@ -2502,7 +2503,7 @@ void GCode::set_extruders(const std::vector &extruder_ids) } void GCode::set_origin(const Vec2d &pointf) -{ +{ // if origin increases (goes towards right), last_pos decreases because it goes towards left const Point translate( scale_(m_origin(0) - pointf(0)), @@ -2516,13 +2517,13 @@ void GCode::set_origin(const Vec2d &pointf) std::string GCode::preamble() { std::string gcode = m_writer.preamble(); - + /* Perform a *silent* move to z_offset: we need this to initialize the Z position of our writer object so that any initial lift taking place before the first layer change will raise the extruder from the correct initial Z instead of 0. */ m_writer.travel_to_z(m_config.z_offset.value); - + return gcode; } @@ -2542,10 +2543,10 @@ std::string GCode::change_layer(coordf_t print_z) comment << "move to next layer (" << m_layer_index << ")"; gcode += m_writer.travel_to_z(z, comment.str()); } - + // forget last wiping path as wiping after raising Z is pointless m_wipe.reset_path(); - + return gcode; } @@ -2554,16 +2555,16 @@ std::string GCode::change_layer(coordf_t print_z) static inline float bspline_kernel(float x) { x = std::abs(x); - if (x < 1.f) { - return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; - } - else if (x < 2.f) { - x -= 1.f; - float x2 = x * x; - float x3 = x2 * x; - return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; - } - else + if (x < 1.f) { + return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; + } + else if (x < 2.f) { + x -= 1.f; + float x2 = x * x; + float x3 = x2 * x; + return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; + } + else return 0; } @@ -2636,13 +2637,13 @@ static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polyg pt_min = p1; double linv = double(d_seg) / double(l2_seg); pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5)); - pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); - assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); + pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); + assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); } } } - assert(i_min != size_t(-1)); + assert(i_min != size_t(-1)); if ((pt_min - polygon.points[i_min]).cast().norm() > eps) { // Insert a new point on the segment i_min, i_min+1. return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); @@ -2706,9 +2707,9 @@ std::vector polygon_angles_at_vertices(const Polygon &polygon, const std: const Point &p2 = polygon.points[idx_next]; const Point v1 = p1 - p0; const Point v2 = p2 - p1; - int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1)); - int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0)); - float angle = float(atan2(double(cross), double(dot))); + int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1)); + int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0)); + float angle = float(atan2(double(cross), double(dot))); angles[idx_curr] = angle; } @@ -2740,14 +2741,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou #endif } } - + // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); - + SeamPosition seam_position = m_config.seam_position; - if (loop.loop_role() == elrSkirt) + if (loop.loop_role() == elrSkirt) seam_position = spNearest; - + // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); @@ -2813,8 +2814,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); } // Give a negative penalty for points close to the last point or the prefered seam location. - float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? - std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : + float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? + std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); @@ -2861,6 +2862,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou m_seam_position[m_layer->object()] = polygon.points[idx_min]; } + // Export the contour into a SVG file. #if 0 { @@ -2904,23 +2906,23 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Find the closest point, avoid overhangs. loop.split_at(last_pos, true); } - + // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case - double clip_length = m_enable_loop_clipping ? - scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : + double clip_length = m_enable_loop_clipping ? + scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : 0; // get paths ExtrusionPaths paths; loop.clip_end(clip_length, &paths); if (paths.empty()) return ""; - + // apply the small perimeter speed if (is_perimeter(paths.front().role()) && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1) speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); - + // extrude along the path std::string gcode; for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { @@ -2929,31 +2931,31 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou path->simplify(SCALED_RESOLUTION); gcode += this->_extrude(*path, description, speed); } - + // reset acceleration gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5)); - + if (m_wipe.enable) m_wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path - + // make a little move inwards before leaving loop - if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { + if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { // detect angle between last and first segment // the side depends on the original winding order of the polygon (left for contours, right for holes) - //FIXME improve the algorithm in case the loop is tiny. - //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). + //FIXME improve the algorithm in case the loop is tiny. + //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). Point a = paths.front().polyline.points[1]; // second point Point b = *(paths.back().polyline.points.end()-3); // second to last point if (was_clockwise) { // swap points Point c = a; a = b; b = c; } - + double angle = paths.front().first_point().ccw_angle(a, b) / 3; - + // turn left if contour, turn right if hole if (was_clockwise) angle *= -1; - + // create the destination point along the first segment and rotate it // we make sure we don't exceed the segment length because we don't know // the rotation of the second segment so we might cross the object boundary @@ -2969,7 +2971,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // generate the travel move gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); } - + return gcode; } @@ -3040,23 +3042,23 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorrole() == erIroning) == ironing) - extrusions.emplace_back(ee); - if (! extrusions.empty()) { - m_config.apply(print.regions()[®ion - &by_region.front()]->config()); - chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); - for (const ExtrusionEntity *fill : extrusions) { - auto *eec = dynamic_cast(fill); - if (eec) { - for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) - gcode += this->extrude_entity(*ee, extrusion_name); - } else - gcode += this->extrude_entity(*fill, extrusion_name); - } - } + extrusions.clear(); + extrusions.reserve(region.infills.size()); + for (ExtrusionEntity *ee : region.infills) + if ((ee->role() == erIroning) == ironing) + extrusions.emplace_back(ee); + if (! extrusions.empty()) { + m_config.apply(print.regions()[®ion - &by_region.front()]->config()); + chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); + for (const ExtrusionEntity *fill : extrusions) { + auto *eec = dynamic_cast(fill); + if (eec) { + for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + gcode += this->extrude_entity(*ee, extrusion_name); + } else + gcode += this->extrude_entity(*fill, extrusion_name); + } + } } return gcode; } @@ -3150,10 +3152,10 @@ void GCode::_write_format(FILE* file, const char* format, ...) std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed) { std::string gcode; - + if (is_bridge(path.role())) description += " (bridge)"; - + // go to first point of extrusion path if (!m_last_pos_defined || m_last_pos != path.first_point()) { gcode += this->travel_to( @@ -3162,10 +3164,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, "move to first " + description + " point" ); } - + // compensate retraction gcode += this->unretract(); - + // adjust acceleration { double acceleration; @@ -3182,11 +3184,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } - + // calculate extrusion length per distance unit double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm; if (m_writer.extrusion_axis().empty()) e_per_mm = 0; - + // set speed if (speed == -1) { if (path.role() == erPerimeter) { @@ -3228,7 +3230,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, ); } double F = speed * 60; // convert mm/sec to mm/min - + // extrude arc or line if (m_enable_extrusion_role_markers) { @@ -3336,40 +3338,40 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } if (m_enable_cooling_markers) gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; - + this->set_last_pos(path.last_point()); return gcode; } // This method accepts &point in print coordinates. std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) -{ +{ /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ Polyline travel; travel.append(this->last_pos()); travel.append(point); - + // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); - + // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters && ! m_avoid_crossing_perimeters.disable_once) { travel = m_avoid_crossing_perimeters.travel_to(*this, point); - + // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); //if (needs_retraction && m_layer_index > 1) exit(0); } - + // Re-allow avoid_crossing_perimeters for the next travel moves m_avoid_crossing_perimeters.disable_once = false; m_avoid_crossing_perimeters.use_external_mp_once = false; - + // generate G-code for the travel move std::string gcode; if (needs_retraction) @@ -3377,12 +3379,12 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string else // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); - + // use G1 because we rely on paths being straight (G0 may make round paths) Lines lines = travel.lines(); if (! lines.empty()) { for (const Line &line : lines) - gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); + gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); this->set_last_pos(lines.back().b); } return gcode; @@ -3394,7 +3396,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) // skip retraction if the move is shorter than the configured threshold return false; } - + if (role == erSupportMaterial) { const SupportLayer* support_layer = dynamic_cast(m_layer); //FIXME support_layer->support_islands.contains should use some search structure! @@ -3411,7 +3413,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) // internal infill is enabled (so that stringing is entirely not visible). //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. return false; - + // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply return true; } @@ -3419,26 +3421,26 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) std::string GCode::retract(bool toolchange) { std::string gcode; - + if (m_writer.extruder() == nullptr) return gcode; - + // wipe (if it's enabled for this extruder and we have a stored wipe path) if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path()) { gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true); gcode += m_wipe.wipe(*this, toolchange); } - + /* The parent class will decide whether we need to perform an actual retraction - (the extruder might be already retracted fully or partially). We call these + (the extruder might be already retracted fully or partially). We call these methods even if we performed wipe, since this will ensure the entire retraction length is honored in case wipe path was too short. */ gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); - + gcode += m_writer.reset_e(); if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction) gcode += m_writer.lift(); - + return gcode; } @@ -3446,11 +3448,11 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) { if (!m_writer.need_toolchange(extruder_id)) return ""; - + // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { m_placeholder_parser.set("current_extruder", extruder_id); - + std::string gcode; // Append the filament start G-code. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); @@ -3462,13 +3464,13 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) gcode += m_writer.toolchange(extruder_id); return gcode; } - + // prepend retraction on the current extruder std::string gcode = this->retract(true); // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); - + if (m_writer.extruder() != nullptr) { // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower // so it should not be injected twice. @@ -3480,7 +3482,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) } } - + // If ooze prevention is enabled, park current extruder in the nearest // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) @@ -3529,7 +3531,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) // Set the new extruder to the operating temperature. if (m_ooze_prevention.enable) gcode += m_ooze_prevention.post_toolchange(*this); - + return gcode; } @@ -3556,17 +3558,17 @@ const std::vector& GCode::ObjectByExtru { bool has_overrides = false; for (const auto& reg : by_region) - if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { - has_overrides = true; - break; - } + if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { + has_overrides = true; + break; + } - // Data is cleared, but the memory is not. + // Data is cleared, but the memory is not. by_region_per_copy_cache.clear(); if (! has_overrides) - // Simple case. No need to copy the regions. - return wiping_entities ? by_region_per_copy_cache : this->by_region; + // Simple case. No need to copy the regions. + return wiping_entities ? by_region_per_copy_cache : this->by_region; // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions. // Some of the extrusions of some object instances are printed later - those are the clean print extrusions. @@ -3585,25 +3587,25 @@ const std::vector& GCode::ObjectByExtru // Now the most important thing - which extrusion should we print. // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. if (wiping_entities) { - // Apply overrides for this region. - for (unsigned int i = 0; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which overrides the default one. - if (this_override != nullptr && (*this_override)[copy] == int(extruder)) - target_eec.emplace_back(entities[i]); - } - } else { - // Apply normal extrusions (non-overrides) for this region. - unsigned int i = 0; - for (; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. - if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) - target_eec.emplace_back(entities[i]); - } - for (; i < entities.size(); ++ i) + // Apply overrides for this region. + for (unsigned int i = 0; i < overrides.size(); ++ i) { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which overrides the default one. + if (this_override != nullptr && (*this_override)[copy] == int(extruder)) + target_eec.emplace_back(entities[i]); + } + } else { + // Apply normal extrusions (non-overrides) for this region. + unsigned int i = 0; + for (; i < overrides.size(); ++ i) { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. + if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) + target_eec.emplace_back(entities[i]); + } + for (; i < entities.size(); ++ i) target_eec.emplace_back(entities[i]); - } + } } } return by_region_per_copy_cache; @@ -3623,11 +3625,11 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr perimeters_or_infills_overrides = &perimeters_overrides; break; case INFILL: - perimeters_or_infills = &infills; - perimeters_or_infills_overrides = &infills_overrides; + perimeters_or_infills = &infills; + perimeters_or_infills_overrides = &infills_overrides; break; default: - throw std::invalid_argument("Unknown parameter!"); + throw std::invalid_argument("Unknown parameter!"); } // First we append the entities, there are eec->entities.size() of them: @@ -3635,18 +3637,18 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1); perimeters_or_infills->reserve(new_size); if (eec->can_reverse()) { - for (auto* ee : eec->entities) - perimeters_or_infills->emplace_back(ee); - } else - perimeters_or_infills->emplace_back(const_cast(eec)); + for (auto* ee : eec->entities) + perimeters_or_infills->emplace_back(ee); + } else + perimeters_or_infills->emplace_back(const_cast(eec)); if (copies_extruder != nullptr) { - // Don't reallocate overrides if not needed. - // Missing overrides are implicitely considered non-overridden. + // Don't reallocate overrides if not needed. + // Missing overrides are implicitely considered non-overridden. perimeters_or_infills_overrides->reserve(new_size); perimeters_or_infills_overrides->resize(old_size, nullptr); perimeters_or_infills_overrides->resize(new_size, copies_extruder); - } + } } } // namespace Slic3r diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8bae2ef43d..5923f63e94 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -69,6 +69,7 @@ private: std::unique_ptr m_layer_mp; }; + class OozePrevention { public: bool enable; From 7844ca12fa497863064a20dcda3135e343db62a8 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 2 Sep 2020 00:26:13 +0200 Subject: [PATCH 388/826] First naive prototype of seam painter --- src/libslic3r/GCode.cpp | 65 +++++++++++++++++++++++++++++++++++++++++ src/libslic3r/GCode.hpp | 12 ++++++++ 2 files changed, 77 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2049fd76bc..b4196dc5fc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -175,6 +175,32 @@ namespace Slic3r { return islands; } + + int CustomSeam::get_point_status(const Point& pt, size_t layer_id) const + { + // TEMPORARY - WILL BE IMPROVED + // - quadratic algorithm + // - does not support variable layer height + + if (! enforcers.empty()) { + assert(layer_id < enforcers.size()); + for (const ExPolygon& explg : enforcers[layer_id]) { + if (explg.contains(pt)) + return 1; + } + } + if (! blockers.empty()) { + assert(layer_id < blockers.size()); + for (const ExPolygon& explg : blockers[layer_id]) { + if (explg.contains(pt)) + return -1; + } + } + return 0; + } + + + std::string OozePrevention::pre_toolchange(GCode& gcodegen) { std::string gcode; @@ -984,6 +1010,22 @@ namespace DoExport { } + static void collect_custom_seam(const Print& print, CustomSeam& custom_seam) + { + custom_seam = CustomSeam(); + for (const PrintObject* po : print.objects()) { + po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, custom_seam.enforcers); + po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, custom_seam.blockers); + } + for (ExPolygons& explgs : custom_seam.enforcers) { + explgs = Slic3r::offset_ex(explgs, scale_(0.5)); + } + for (ExPolygons& explgs : custom_seam.blockers) { + explgs = Slic3r::offset_ex(explgs, scale_(0.5)); + } + } + + static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) { // Calculate wiping points if needed @@ -1437,6 +1479,9 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu DoExport::init_ooze_prevention(print, m_ooze_prevention); print.throw_if_canceled(); + // Collect custom seam data from all objects. + DoExport::collect_custom_seam(print, m_custom_seam); + if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. @@ -2841,6 +2886,13 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou } } + // Penalty according to custom seam selection. This one is huge compared to + // the others so that points outside enforcers/inside blockers never win. + for (size_t i = 0; i < polygon.points.size(); ++ i) { + const Point &p = polygon.points[i]; + penalties[i] -= float(100000 * m_custom_seam.get_point_status(p, m_layer->id())); + } + // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); @@ -2862,6 +2914,19 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou m_seam_position[m_layer->object()] = polygon.points[idx_min]; } +////////////////////// +// int layer_id = m_layer->id(); +// std::ostringstream os; +// os << std::setw(3) << std::setfill('0') << layer_id; +// int a = scale_(15.); +// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); +// if (! m_custom_seam.enforcers.empty()) +// svg.draw(m_custom_seam.enforcers[layer_id], "blue"); +// if (! m_custom_seam.blockers.empty()) +// svg.draw(m_custom_seam.blockers[layer_id], "red"); +// svg.draw(polygon.points, "black"); +//////////////////// + // Export the contour into a SVG file. #if 0 diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 5923f63e94..1004a8efcd 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -70,6 +70,15 @@ private: }; +struct CustomSeam { + std::vector enforcers; + std::vector blockers; + + // Finds whether the point is inside an enforcer/blockers. + // Returns +1, 0 or -1. + int get_point_status(const Point& pt, size_t layer_id) const; +}; + class OozePrevention { public: bool enable; @@ -339,6 +348,9 @@ private: std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id, double print_z); + // Cache for custom seam enforcers/blockers for each layer. + CustomSeam m_custom_seam; + /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ From d8487b1458eb768439a16bb255d82b71064a5f96 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 2 Sep 2020 09:06:42 +0200 Subject: [PATCH 389/826] Unsaved Changes: bug fix and improvements - changed width of the "Save dialog" - SavePresetDialog: added info for Print/Filament user presets incompatible with selected printer_technology - fixed missed "modified" suffix when options are moved to the another preset - "move selected options" button is added for dependent presets --- src/slic3r/GUI/PresetComboBoxes.cpp | 8 ++-- src/slic3r/GUI/Tab.cpp | 51 +++++++++++++++++-------- src/slic3r/GUI/Tab.hpp | 5 ++- src/slic3r/GUI/UnsavedChangesDialog.cpp | 22 ++++++----- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 9b0c9d0c86..7300a2887f 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1059,7 +1059,7 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); - m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)); + m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name), wxDefaultPosition, wxSize(35 * wxGetApp().em_unit(), -1)); for (const std::string& value : values) m_combo->Append(from_u8(value)); @@ -1131,8 +1131,10 @@ void SavePresetDialog::Item::update() if (m_valid_type == Valid && existing && m_preset_name != m_presets->get_selected_preset_name()) { - info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()) + "\n" + - _L("Note: This preset will be replaced after saving"); + info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()); + if (!existing->is_compatible) + info_line += "\n" + _L("And selected preset is imcopatible with selected printer."); + info_line += "\n" + _L("Note: This preset will be replaced after saving"); m_valid_type = Warning; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 898890f6e5..b95227dad9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1103,6 +1103,21 @@ void Tab::apply_searcher() wxGetApp().sidebar().get_searcher().apply(m_config, m_type, m_mode); } +void Tab::cache_config_diff(const std::vector& selected_options) +{ + m_cache_config.apply_only(m_presets->get_edited_preset().config, selected_options); +} + +void Tab::apply_config_from_cache() +{ + if (!m_cache_config.empty()) { + m_presets->get_edited_preset().config.apply(m_cache_config); + m_cache_config.clear(); + + update_dirty(); + } +} + // Call a callback to update the selection of presets on the plater: // To update the content of the selection boxes, @@ -1122,9 +1137,12 @@ void Tab::on_presets_changed() // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for (auto t: m_dependent_tabs) { + Tab* tab = wxGetApp().get_tab(t); // If the printer tells us that the print or filament/sla_material preset has been switched or invalidated, // refresh the print or filament/sla_material tab page. - wxGetApp().get_tab(t)->load_current_preset(); + // But if there are options, moved from the previously selected preset, update them to edited preset + tab->apply_config_from_cache(); + tab->load_current_preset(); } // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function @@ -3136,10 +3154,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, static_cast(this)->apply_extruder_cnt_from_cache(); // check if there is something in the cache to move to the new selected preset - if (!m_cache_config.empty()) { - m_presets->get_edited_preset().config.apply(m_cache_config); - m_cache_config.clear(); - } + apply_config_from_cache(); load_current_preset(); } @@ -3189,17 +3204,23 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr else if (dlg.move_preset()) // move selected changes { std::vector selected_options = dlg.get_selected_options(); - auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); - if (it != selected_options.end()) { - // erase "extruders_count" option from the list - selected_options.erase(it); - // cache the extruders count - if (m_type == Preset::TYPE_PRINTER) - static_cast(this)->cache_extruder_cnt(); - } + if (m_type == presets->type()) // move changes for the current preset from this tab + { + if (m_type == Preset::TYPE_PRINTER) { + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + static_cast(this)->cache_extruder_cnt(); + } + } - // copy selected options to the cache from edited preset - m_cache_config.apply_only(*m_config, selected_options); + // copy selected options to the cache from edited preset + cache_config_diff(selected_options); + } + else + wxGetApp().get_tab(presets->type())->cache_config_diff(selected_options); } return true; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 9bddebeab1..f0b2e97b3d 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -231,12 +231,13 @@ protected: } m_highlighter; + DynamicPrintConfig m_cache_config; + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; PresetCollection* m_presets; DynamicPrintConfig* m_config; - DynamicPrintConfig m_cache_config; ogStaticText* m_parent_preset_description_line; ScalableButton* m_detach_preset_btn = nullptr; @@ -330,6 +331,8 @@ public: void update_wiping_button_visibility(); void activate_option(const std::string& opt_key, const wxString& category); void apply_searcher(); + void cache_config_diff(const std::vector& selected_options); + void apply_config_from_cache(); protected: void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, widget_t widget); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index c147d3e2c2..f30e719ce0 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -590,8 +590,8 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ int btn_idx = 0; add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, btn_idx++); - if (type != Preset::TYPE_INVALID && type == dependent_presets->type() && - dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology()) + if (dependent_presets && (type != dependent_presets->type() ? true : + dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology())) add_btn(&m_move_btn, m_move_btn_id, "paste_menu", Action::Move, btn_idx++); add_btn(&m_continue_btn, m_continue_btn_id, "cross", Action::Continue, btn_idx, false); @@ -666,12 +666,11 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name else if (action == Action::Continue) text = _L("All changed options will be reverted."); else { - if (action == Action::Save && preset_name.empty()) - text = _L("Press to save the selected options"); - else { - std::string act_string = action == Action::Save ? _u8L("saved") : _u8L("moved"); + std::string act_string = action == Action::Save ? _u8L("save") : _u8L("move"); + if (preset_name.empty()) + text = from_u8((boost::format("Press to %1% selected options.") % act_string).str()); + else text = from_u8((boost::format("Press to %1% selected options to the preset \"%2%\".") % act_string % preset_name).str()); - } text += "\n" + _L("Unselected options will be reverted."); } m_info_line->SetLabel(text); @@ -856,8 +855,10 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent // activate buttons and labels m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets ? presets->get_selected_preset().name : ""); e.Skip(); }); - if (m_move_btn) - m_move_btn ->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset] (wxMouseEvent& e) { show_info_line(Action::Move, new_selected_preset); e.Skip(); }); + if (m_move_btn) { + bool is_empty_name = type != dependent_presets->type(); + m_move_btn ->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset, is_empty_name] (wxMouseEvent& e) { show_info_line(Action::Move, is_empty_name ? "" : new_selected_preset); e.Skip(); }); + } m_continue_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Continue); e.Skip(); }); m_continue_btn->SetLabel(_L("Continue without changes")); @@ -879,6 +880,9 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent _L("is not compatible with print profile"); action_msg += " \"" + from_u8(new_selected_preset) + "\"\n"; action_msg += _L("and it has the following unsaved changes:"); + + if (m_move_btn) + m_move_btn->SetLabel(_L("Move selected to the first compatible preset")); } m_action_line->SetLabel(from_u8((boost::format(_utf8(L("Preset \"%1%\" %2%"))) % _utf8(presets->get_edited_preset().name) % action_msg).str())); m_save_btn->SetLabel(from_u8((boost::format(_u8L("Save selected to preset: %1%")) % ("\"" + presets->get_selected_preset().name + "\"")).str())); From 0cfa64e24568188516506f94d310d9c46688e053 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 2 Sep 2020 14:24:32 +0200 Subject: [PATCH 390/826] GCodeViewer -> Fixed bug in generating solid toolpaths and export of toolpaths to obj file --- src/slic3r/GUI/GCodeViewer.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 772b290ea8..bc424466bf 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -709,12 +709,18 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const size_t first_vertex_id = k - static_cast(indices_per_segment); Segment prev = generate_segment(indices[first_vertex_id + start_vertex_offset], indices[first_vertex_id + end_vertex_offset], half_width, half_height); - Vec3f med_dir = (prev.dir + curr.dir).normalized(); - float disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); + float disp = 0.0f; + float cos_dir = prev.dir.dot(curr.dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev.dir + curr.dir).normalized(); + disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); + } + Vec3f disp_vec = disp * prev.dir; bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f; - if (prev.dir.dot(curr.dir) < 0.7071068f) { + if (cos_dir < 0.7071068f) { // if the angle between two consecutive segments is greater than 45 degrees // we add a cap in the outside corner // and displace the vertices in the inside corner to the same position, if possible @@ -725,7 +731,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 }); // update right vertices - if (disp < prev.length && disp < curr.length) { + if (disp > 0.0f && disp < prev.length && disp < curr.length) { base_id = out_vertices.size() - 6; out_vertices[base_id + 0] -= disp_vec; out_vertices[base_id + 4] = out_vertices[base_id + 0]; @@ -738,7 +744,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 }); // update left vertices - if (disp < prev.length && disp < curr.length) { + if (disp > 0.0f && disp < prev.length && disp < curr.length) { base_id = out_vertices.size() - 6; out_vertices[base_id + 2] -= disp_vec; out_vertices[base_id + 5] = out_vertices[base_id + 2]; @@ -1031,10 +1037,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } else { // any other segment - Vec3f med_dir = (prev_dir + dir).normalized(); - float displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + } + Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement < prev_length && displacement < length; + bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); @@ -1043,7 +1055,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = prev_dir.dot(dir) < 0.7071068f; + bool is_sharp = cos_dir < 0.7071068f; bool right_displaced = false; bool left_displaced = false; From 5997f2759cfb1d041c47ee4e03d6cc7c03a02ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 2 Sep 2020 22:53:10 +0200 Subject: [PATCH 391/826] Change in passing octree struct --- src/libslic3r/Fill/Fill.cpp | 4 ++-- src/libslic3r/Layer.hpp | 6 +++++- src/libslic3r/Print.hpp | 9 ++++----- src/libslic3r/PrintObject.cpp | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index c948df400e..9d468a6aa9 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills() +void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -345,7 +345,7 @@ void Layer::make_fills() f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; - f->adapt_fill_octree = this->object()->adaptiveInfillOctree(); + f->adapt_fill_octree = adaptive_fill_octree; // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index c104d46da1..4c824a1093 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -13,6 +13,10 @@ class Layer; class PrintRegion; class PrintObject; +namespace FillAdaptive_Internal { + struct Octree; +}; + class LayerRegion { public: @@ -134,7 +138,7 @@ public: return false; } void make_perimeters(); - void make_fills(); + void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree); void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 2e2746a345..9b5d9d4c1a 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -11,7 +11,6 @@ #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "GCode/ThumbnailData.hpp" -#include "Fill/FillAdaptive.hpp" #include "libslic3r.h" @@ -26,6 +25,9 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; +namespace FillAdaptive_Internal { + struct Octree; +}; // Print step IDs for keeping track of the print state. enum PrintStep { @@ -193,7 +195,6 @@ public: void project_and_append_custom_enforcers(std::vector& enforcers) const { project_and_append_custom_supports(FacetSupportType::ENFORCER, enforcers); } void project_and_append_custom_blockers(std::vector& blockers) const { project_and_append_custom_supports(FacetSupportType::BLOCKER, blockers); } - FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree.get(); } private: // to be called from Print only. friend class Print; @@ -235,7 +236,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - void prepare_adaptive_infill_data(); + std::unique_ptr prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; @@ -256,8 +257,6 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - std::unique_ptr m_adapt_fill_octree = nullptr; - std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const; std::vector slice_modifiers(size_t region_id, const std::vector &z) const; std::vector slice_volumes(const std::vector &z, SlicingMode mode, const std::vector &volumes) const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1ab5664a0f..25b20ea9af 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -362,8 +362,6 @@ void PrintObject::prepare_infill() } // for each layer #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - this->prepare_adaptive_infill_data(); - this->set_done(posPrepareInfill); } @@ -373,13 +371,15 @@ void PrintObject::infill() this->prepare_infill(); if (this->set_started(posInfill)) { + std::unique_ptr octree = this->prepare_adaptive_infill_data(); + BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { + [this, &octree](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(); + m_layers[layer_idx]->make_fills(octree.get()); } } ); @@ -432,14 +432,14 @@ void PrintObject::generate_support_material() } } -void PrintObject::prepare_adaptive_infill_data() +std::unique_ptr PrintObject::prepare_adaptive_infill_data() { const ConfigOptionPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) { - return; + return std::unique_ptr{}; } float fill_density = opt_fill_density->value; @@ -448,15 +448,15 @@ void PrintObject::prepare_adaptive_infill_data() coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); BoundingBoxf bed_shape(this->print()->config().bed_shape.values); - BoundingBoxf3 printer_volume(Vec3d(bed_shape.min(0), bed_shape.min(1), 0), - Vec3d(bed_shape.max(0), bed_shape.max(1), this->print()->config().max_print_height)); + BoundingBoxf3 printer_volume(Vec3d(bed_shape.min.x(), bed_shape.min.y(), 0), + Vec3d(bed_shape.max.x(), bed_shape.max.y(), this->print()->config().max_print_height)); Vec3d model_center = this->model_object()->bounding_box().center(); - model_center(2) = 0.0f; // Set position in Z axis to 0 + model_center.z() = 0.0f; // Set position in Z axis to 0 // Center of the first cube in octree TriangleMesh mesh = this->model_object()->mesh(); - this->m_adapt_fill_octree = FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); + return FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); } void PrintObject::clear_layers() From 71237cf11ff21abc649666043b6279e5dc945fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 07:52:53 +0200 Subject: [PATCH 392/826] Fix tests which expect make_fills without arguments --- src/libslic3r/Layer.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 4c824a1093..014d2623af 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -138,6 +138,7 @@ public: return false; } void make_perimeters(); + void make_fills() { this->make_fills(nullptr); }; void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree); void make_ironing(); From fd3a31651c2e7c5855944813ea09e8cbfdf17cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 08:04:05 +0200 Subject: [PATCH 393/826] Octree's first cube depends on model size. --- src/libslic3r/Fill/FillAdaptive.cpp | 21 +++++++++++---------- src/libslic3r/Fill/FillAdaptive.hpp | 3 +-- src/libslic3r/PrintObject.cpp | 9 ++------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 0563b612ab..62c4a3af7b 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -142,9 +142,8 @@ void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) std::unique_ptr FillAdaptive::build_octree( - TriangleMesh &triangleMesh, + TriangleMesh &triangle_mesh, coordf_t line_spacing, - const BoundingBoxf3 &printer_volume, const Vec3d &cube_center) { using namespace FillAdaptive_Internal; @@ -154,10 +153,11 @@ std::unique_ptr FillAdaptive::build_octree( return nullptr; } - // The furthest point from center of bed. - double furthest_point = std::sqrt(((printer_volume.size()[0] * printer_volume.size()[0]) / 4.0) + - ((printer_volume.size()[1] * printer_volume.size()[1]) / 4.0) + - (printer_volume.size()[2] * printer_volume.size()[2])); + Vec3d bb_size = triangle_mesh.bounding_box().size(); + // The furthest point from the center of the bottom of the mesh bounding box. + double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + + ((bb_size.y() * bb_size.y()) / 4.0) + + (bb_size.z() * bb_size.z())); double max_cube_edge_length = furthest_point * 2; std::vector cubes_properties; @@ -172,19 +172,20 @@ std::unique_ptr FillAdaptive::build_octree( cubes_properties.push_back(props); } - if (triangleMesh.its.vertices.empty()) + if (triangle_mesh.its.vertices.empty()) { - triangleMesh.require_shared_vertices(); + triangle_mesh.require_shared_vertices(); } Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.264), Geometry::deg2rad(30.0)); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); - AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); + AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + triangle_mesh.its.vertices, triangle_mesh.its.indices); std::unique_ptr octree = std::unique_ptr( new Octree{std::unique_ptr(new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}), cube_center}); - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangleMesh); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh); return octree; } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index fb1f2da8e3..c7539df5a7 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -60,9 +60,8 @@ protected: public: static std::unique_ptr build_octree( - TriangleMesh &triangleMesh, + TriangleMesh &triangle_mesh, coordf_t line_spacing, - const BoundingBoxf3 &printer_volume, const Vec3d &cube_center); static void expand_cube( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 25b20ea9af..f6823baae7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -447,16 +447,11 @@ std::unique_ptr PrintObject::prepare_adaptive_inf coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); - BoundingBoxf bed_shape(this->print()->config().bed_shape.values); - BoundingBoxf3 printer_volume(Vec3d(bed_shape.min.x(), bed_shape.min.y(), 0), - Vec3d(bed_shape.max.x(), bed_shape.max.y(), this->print()->config().max_print_height)); - - Vec3d model_center = this->model_object()->bounding_box().center(); - model_center.z() = 0.0f; // Set position in Z axis to 0 // Center of the first cube in octree + Vec3d model_center = this->model_object()->bounding_box().center(); TriangleMesh mesh = this->model_object()->mesh(); - return FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); + return FillAdaptive::build_octree(mesh, line_spacing, model_center); } void PrintObject::clear_layers() From 573194e059836916b6f216dc068c27a89ea7b843 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 3 Sep 2020 08:32:06 +0200 Subject: [PATCH 394/826] GCodeProcessor -> Added cancel callback --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/GCode/GCodeProcessor.cpp | 18 +++++++++++++----- src/libslic3r/GCode/GCodeProcessor.hpp | 3 ++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 135389eb3e..0ee9ec0143 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -787,7 +787,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ } #if ENABLE_GCODE_VIEWER - m_processor.process_file(path_tmp); + m_processor.process_file(path_tmp, [print]() { print->throw_if_canceled(); }); DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); if (result != nullptr) *result = std::move(m_processor.extract_result()); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 13b1ed1a8d..cd42dc2e6c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -11,10 +11,7 @@ #include #if ENABLE_GCODE_VIEWER - -#if ENABLE_GCODE_VIEWER_STATISTICS #include -#endif // ENABLE_GCODE_VIEWER_STATISTICS static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; @@ -730,8 +727,10 @@ void GCodeProcessor::reset() #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } -void GCodeProcessor::process_file(const std::string& filename) +void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) { + auto last_cancel_callback_time = std::chrono::high_resolution_clock::now(); + #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -758,9 +757,18 @@ void GCodeProcessor::process_file(const std::string& filename) } } + // process gcode m_result.id = ++s_result_id; m_result.moves.emplace_back(MoveVertex()); - m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); + m_parser.parse_file(filename, [this, cancel_callback, &last_cancel_callback_time](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + auto curr_time = std::chrono::high_resolution_clock::now(); + // call the cancel callback every 100 ms + if (std::chrono::duration_cast(curr_time - last_cancel_callback_time).count() > 100) { + cancel_callback(); + last_cancel_callback_time = curr_time; + } + process_gcode_line(line); + }); // process the time blocks for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 22aeed7620..42772d12bc 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -419,7 +419,8 @@ namespace Slic3r { Result&& extract_result() { return std::move(m_result); } // Process the gcode contained in the file with the given filename - void process_file(const std::string& filename); + // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). + void process_file(const std::string& filename, std::function cancel_callback = std::function()); float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const; From cbe93815b2ce8c59217aae8b25fed06fab2c9019 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 Sep 2020 09:27:53 +0200 Subject: [PATCH 395/826] Fixed layout after switching mode of settings layout --- src/slic3r/GUI/GUI_App.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e675a9292a..d444017f40 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1124,10 +1124,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu) app_layout_changed = dlg.settings_layout_changed(); } if (app_layout_changed) { - mainframe->GetSizer()->Hide((size_t)0); + // hide full main_sizer for mainFrame + mainframe->GetSizer()->Show(false); mainframe->update_layout(); mainframe->select_tab(0); - mainframe->GetSizer()->Show((size_t)0); } break; } From 0f0c9a0726cc7a6cb1e180df5d1e730a73ea6489 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 Sep 2020 10:44:54 +0200 Subject: [PATCH 396/826] OSX specific: UnsavedChangesDialog: Fixed strange ellipsis for items in DataViewCtrl --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index f30e719ce0..5a0d23a209 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -531,7 +531,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SetBackgroundColour(bgr_clr); -#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT From a3a1c2017224221ff2a61eb4901e7a0c4be458aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 11:56:41 +0200 Subject: [PATCH 397/826] Code cleanup --- src/libslic3r/Fill/FillAdaptive.cpp | 56 +++++++++++++++-------------- src/libslic3r/Fill/FillAdaptive.hpp | 10 ++++-- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 62c4a3af7b..91da86b69e 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -15,15 +15,20 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - std::vector infill_polylines(3); - this->generate_polylines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_polylines); + std::vector infill_lines_dir(3); + this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_lines_dir); - for (Polylines &infill_polyline : infill_polylines) { - // Crop all polylines - infill_polyline = intersection_pl(infill_polyline, to_polygons(expolygon)); - polylines_out.insert(polylines_out.end(), infill_polyline.begin(), infill_polyline.end()); + for (Lines &infill_lines : infill_lines_dir) + { + for (const Line &line : infill_lines) + { + polylines_out.emplace_back(line.a, line.b); + } } + // Crop all polylines + polylines_out = intersection_pl(polylines_out, to_polygons(expolygon)); + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRuna = 0; @@ -58,11 +63,11 @@ void FillAdaptive::_fill_surface_single( #endif /* SLIC3R_DEBUG */ } -void FillAdaptive::generate_polylines( +void FillAdaptive::generate_infill_lines( FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, - std::vector &polylines_out) + std::vector &dir_lines_out) { using namespace FillAdaptive_Internal; @@ -86,9 +91,8 @@ void FillAdaptive::generate_polylines( Point to(-from.x(), from.y()); // Relative to cube center - float rotation_angle = Geometry::deg2rad(120.0); - - for (int i = 0; i < polylines_out.size(); i++) + float rotation_angle = (2.0 * M_PI) / 3.0; + for (Lines &lines : dir_lines_out) { Vec3d offset = cube->center - origin; Point from_abs(from), to_abs(to); @@ -98,8 +102,8 @@ void FillAdaptive::generate_polylines( to_abs.x() += scale_(offset.x()); to_abs.y() += scale_(offset.y()); -// polylines_out[i].push_back(Polyline(from_abs, to_abs)); - this->merge_polylines(polylines_out[i], Line(from_abs, to_abs)); +// lines.emplace_back(from_abs, to_abs); + this->connect_lines(lines, Line(from_abs, to_abs)); from.rotate(rotation_angle); to.rotate(rotation_angle); @@ -108,35 +112,35 @@ void FillAdaptive::generate_polylines( for(const std::unique_ptr &child : cube->children) { - generate_polylines(child.get(), z_position, origin, polylines_out); + generate_infill_lines(child.get(), z_position, origin, dir_lines_out); } } -void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) +void FillAdaptive::connect_lines(Lines &lines, const Line &new_line) { int eps = scale_(0.10); bool modified = false; - for (Polyline &polyline : polylines) + for (Line &line : lines) { - if (std::abs(new_line.a.x() - polyline.points[1].x()) < eps && std::abs(new_line.a.y() - polyline.points[1].y()) < eps) + if (std::abs(new_line.a.x() - line.b.x()) < eps && std::abs(new_line.a.y() - line.b.y()) < eps) { - polyline.points[1].x() = new_line.b.x(); - polyline.points[1].y() = new_line.b.y(); + line.b.x() = new_line.b.x(); + line.b.y() = new_line.b.y(); modified = true; } - if (std::abs(new_line.b.x() - polyline.points[0].x()) < eps && std::abs(new_line.b.y() - polyline.points[0].y()) < eps) + if (std::abs(new_line.b.x() - line.a.x()) < eps && std::abs(new_line.b.y() - line.a.y()) < eps) { - polyline.points[0].x() = new_line.a.x(); - polyline.points[0].y() = new_line.a.y(); + line.a.x() = new_line.a.x(); + line.a.y() = new_line.a.y(); modified = true; } } if(!modified) { - polylines.emplace_back(Polyline(new_line.a, new_line.b)); + lines.push_back(new_line); } } @@ -182,8 +186,8 @@ std::unique_ptr FillAdaptive::build_octree( AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( triangle_mesh.its.vertices, triangle_mesh.its.indices); - std::unique_ptr octree = std::unique_ptr( - new Octree{std::unique_ptr(new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}), cube_center}); + auto octree = std::make_unique( + std::make_unique(cube_center, cubes_properties.size() - 1, cubes_properties.back()), cube_center); FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh); @@ -215,7 +219,7 @@ void FillAdaptive::expand_cube( Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { - cube->children.push_back(std::unique_ptr(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]})); + cube->children.emplace_back(std::make_unique(child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1])); FillAdaptive::expand_cube(cube->children.back().get(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index c7539df5a7..570318aa40 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -24,12 +24,18 @@ namespace FillAdaptive_Internal size_t depth; CubeProperties properties; std::vector> children; + + Cube(const Vec3d ¢er, size_t depth, const CubeProperties &properties) + : center(center), depth(depth), properties(properties) {} }; struct Octree { std::unique_ptr root_cube; Vec3d origin; + + Octree(std::unique_ptr rootCube, const Vec3d &origin) + : root_cube(std::move(rootCube)), origin(origin) {} }; }; // namespace FillAdaptive_Internal @@ -54,9 +60,9 @@ protected: virtual bool no_sort() const { return true; } - void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &polylines_out); + void generate_infill_lines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &dir_lines_out); - void merge_polylines(Polylines &polylines, const Line &new_line); + void connect_lines(Lines &lines, const Line &new_line); public: static std::unique_ptr build_octree( From 353c65fa4cb5e25c4e74be49ed87882d1ed40e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 13:05:28 +0200 Subject: [PATCH 398/826] Connect infill to perimeters --- src/libslic3r/Fill/FillAdaptive.cpp | 35 ++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 91da86b69e..d3246dc18b 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -3,6 +3,7 @@ #include "../Surface.hpp" #include "../Geometry.hpp" #include "../AABBTreeIndirect.hpp" +#include "../ShortestPath.hpp" #include "FillAdaptive.hpp" @@ -15,19 +16,47 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { + // Store grouped lines by its direction (multiple of 120°) std::vector infill_lines_dir(3); this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_lines_dir); + Polylines all_polylines; + all_polylines.reserve(infill_lines_dir[0].size() * 3); for (Lines &infill_lines : infill_lines_dir) { for (const Line &line : infill_lines) { - polylines_out.emplace_back(line.a, line.b); + all_polylines.emplace_back(line.a, line.b); } } - // Crop all polylines - polylines_out = intersection_pl(polylines_out, to_polygons(expolygon)); + if (params.dont_connect) + { + // Crop all polylines + polylines_out = intersection_pl(all_polylines, to_polygons(expolygon)); + } + else + { + // Crop all polylines + all_polylines = intersection_pl(all_polylines, to_polygons(expolygon)); + + Polylines boundary_polylines; + Polylines non_boundary_polylines; + for (const Polyline &polyline : all_polylines) + { + // connect_infill required all polylines to touch the boundary. + if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) + { + boundary_polylines.push_back(polyline); + } else { + non_boundary_polylines.push_back(polyline); + } + } + + boundary_polylines = chain_polylines(boundary_polylines); + FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end()); + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { From 184cb7afd9d2def98a08fde50534e61c70d2611d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 14:28:25 +0200 Subject: [PATCH 399/826] Fix bug in lines merging --- src/libslic3r/Fill/FillAdaptive.cpp | 30 +++++++++++++---------------- src/libslic3r/Fill/FillAdaptive.hpp | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index d3246dc18b..030debad62 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -145,35 +145,31 @@ void FillAdaptive::generate_infill_lines( } } -void FillAdaptive::connect_lines(Lines &lines, const Line &new_line) +void FillAdaptive::connect_lines(Lines &lines, Line new_line) { int eps = scale_(0.10); - bool modified = false; - - for (Line &line : lines) + for (size_t i = 0; i < lines.size(); ++i) { - if (std::abs(new_line.a.x() - line.b.x()) < eps && std::abs(new_line.a.y() - line.b.y()) < eps) + if (std::abs(new_line.a.x() - lines[i].b.x()) < eps && std::abs(new_line.a.y() - lines[i].b.y()) < eps) { - line.b.x() = new_line.b.x(); - line.b.y() = new_line.b.y(); - modified = true; + new_line.a = lines[i].a; + lines.erase(lines.begin() + i); + --i; + continue; } - if (std::abs(new_line.b.x() - line.a.x()) < eps && std::abs(new_line.b.y() - line.a.y()) < eps) + if (std::abs(new_line.b.x() - lines[i].a.x()) < eps && std::abs(new_line.b.y() - lines[i].a.y()) < eps) { - line.a.x() = new_line.a.x(); - line.a.y() = new_line.a.y(); - modified = true; + new_line.b = lines[i].b; + lines.erase(lines.begin() + i); + --i; + continue; } } - if(!modified) - { - lines.push_back(new_line); - } + lines.emplace_back(new_line.a, new_line.b); } - std::unique_ptr FillAdaptive::build_octree( TriangleMesh &triangle_mesh, coordf_t line_spacing, diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 570318aa40..44a2536f00 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -62,7 +62,7 @@ protected: void generate_infill_lines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &dir_lines_out); - void connect_lines(Lines &lines, const Line &new_line); + static void connect_lines(Lines &lines, Line new_line); public: static std::unique_ptr build_octree( From c49221c6217b787785807284bb6a7164395abe62 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 Sep 2020 15:40:14 +0200 Subject: [PATCH 400/826] Fix of Settings scaling when they are placed in non-modal Dialog --- src/slic3r/GUI/GUI_Utils.hpp | 9 ++++++++- src/slic3r/GUI/MainFrame.cpp | 10 +++++++++- src/slic3r/GUI/PresetComboBoxes.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 96b24524c2..749a556b84 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -218,7 +218,7 @@ private: void rescale(const wxRect &suggested_rect) { this->Freeze(); - +/* #if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) if (m_force_rescale) { #endif // wxVERSION_EQUAL_OR_GREATER_THAN @@ -230,6 +230,13 @@ private: m_force_rescale = false; } #endif // wxVERSION_EQUAL_OR_GREATER_THAN +*/ +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + // rescale fonts of all controls + scale_controls_fonts(this, m_new_font_point_size); + // rescale current window font + scale_win_font(this, m_new_font_point_size); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN // set normal application font as a current window font m_normal_font = this->GetFont(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index bab5d7502e..191e6c4553 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -787,9 +787,10 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) this->SetSize(sz); this->Maximize(is_maximized); - +/* if (m_layout == ESettingsLayout::Dlg) rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); + */ } void MainFrame::on_sys_color_changed() @@ -1988,7 +1989,14 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX, "settings_dialog"), m_main_frame(mainframe) { +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) + // ys_FIXME! temporary workaround for correct font scaling + // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, + // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT + this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#else this->SetFont(wxGetApp().normal_font()); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 7300a2887f..8bc9393873 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1194,7 +1194,7 @@ SavePresetDialog::~SavePresetDialog() void SavePresetDialog::build(std::vector types, std::string suffix) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b95227dad9..29c9e33022 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -102,7 +102,7 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : wxGetApp().tabs_list.push_back(this); - m_em_unit = wxGetApp().em_unit(); + m_em_unit = em_unit(m_parent); //wxGetApp().em_unit(); m_config_manipulation = get_config_manipulation(); From c2af265df81a600908689ae7732c69d4b256e4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 16:08:40 +0200 Subject: [PATCH 401/826] Change to using raw_mesh instead of mesh --- src/libslic3r/PrintObject.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f6823baae7..5a486776cc 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -447,11 +447,14 @@ std::unique_ptr PrintObject::prepare_adaptive_inf coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); - // Center of the first cube in octree - Vec3d model_center = this->model_object()->bounding_box().center(); + TriangleMesh mesh = this->model_object()->raw_mesh(); + mesh.transform(m_trafo, true); + // Apply XY shift + mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); - TriangleMesh mesh = this->model_object()->mesh(); - return FillAdaptive::build_octree(mesh, line_spacing, model_center); + // Center of the first cube in octree + Vec3d mesh_origin = mesh.bounding_box().center(); + return FillAdaptive::build_octree(mesh, line_spacing, mesh_origin); } void PrintObject::clear_layers() From ce18b824ada00c8eb67c13fe5b89a2b03e2a32f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 19:21:55 +0200 Subject: [PATCH 402/826] Octree representation rework --- src/libslic3r/Fill/FillAdaptive.cpp | 51 ++++++++++++++++++----------- src/libslic3r/Fill/FillAdaptive.hpp | 44 ++++++++++++++----------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 030debad62..577ba7e610 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -18,7 +18,10 @@ void FillAdaptive::_fill_surface_single( { // Store grouped lines by its direction (multiple of 120°) std::vector infill_lines_dir(3); - this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_lines_dir); + this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), + this->z, this->adapt_fill_octree->origin,infill_lines_dir, + this->adapt_fill_octree->cubes_properties, + this->adapt_fill_octree->cubes_properties.size() - 1); Polylines all_polylines; all_polylines.reserve(infill_lines_dir[0].size() * 3); @@ -96,7 +99,9 @@ void FillAdaptive::generate_infill_lines( FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, - std::vector &dir_lines_out) + std::vector &dir_lines_out, + const std::vector &cubes_properties, + int depth) { using namespace FillAdaptive_Internal; @@ -107,16 +112,16 @@ void FillAdaptive::generate_infill_lines( double z_diff = std::abs(z_position - cube->center.z()); - if (z_diff > cube->properties.height / 2) + if (z_diff > cubes_properties[depth].height / 2) { return; } - if (z_diff < cube->properties.line_z_distance) + if (z_diff < cubes_properties[depth].line_z_distance) { Point from( - scale_((cube->properties.diagonal_length / 2) * (cube->properties.line_z_distance - z_diff) / cube->properties.line_z_distance), - scale_(cube->properties.line_xy_distance - ((z_position - (cube->center.z() - cube->properties.line_z_distance)) / sqrt(2)))); + scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance), + scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube->center.z() - cubes_properties[depth].line_z_distance)) / sqrt(2)))); Point to(-from.x(), from.y()); // Relative to cube center @@ -141,7 +146,10 @@ void FillAdaptive::generate_infill_lines( for(const std::unique_ptr &child : cube->children) { - generate_infill_lines(child.get(), z_position, origin, dir_lines_out); + if(child != nullptr) + { + generate_infill_lines(child.get(), z_position, origin, dir_lines_out, cubes_properties, depth - 1); + } } } @@ -206,15 +214,14 @@ std::unique_ptr FillAdaptive::build_octree( triangle_mesh.require_shared_vertices(); } - Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.264), Geometry::deg2rad(30.0)); + Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( triangle_mesh.its.vertices, triangle_mesh.its.indices); - auto octree = std::make_unique( - std::make_unique(cube_center, cubes_properties.size() - 1, cubes_properties.back()), cube_center); + auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh, cubes_properties.size() - 1); return octree; } @@ -223,12 +230,12 @@ void FillAdaptive::expand_cube( FillAdaptive_Internal::Cube *cube, const std::vector &cubes_properties, const Transform3d &rotation_matrix, - const AABBTreeIndirect::Tree3f &distanceTree, - const TriangleMesh &triangleMesh) + const AABBTreeIndirect::Tree3f &distance_tree, + const TriangleMesh &triangle_mesh, int depth) { using namespace FillAdaptive_Internal; - if (cube == nullptr || cube->depth == 0) + if (cube == nullptr || depth == 0) { return; } @@ -238,14 +245,18 @@ void FillAdaptive::expand_cube( Vec3d( 1, 1, 1), Vec3d(-1, 1, 1), Vec3d( 1, -1, 1), Vec3d( 1, 1, -1) }; - double cube_radius_squared = (cube->properties.height * cube->properties.height) / 16; + double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16; - for (const Vec3d &child_center : child_centers) { - Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); + for (size_t i = 0; i < 8; ++i) + { + const Vec3d &child_center = child_centers[i]; + Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cubes_properties[depth].edge_length / 4)); - if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { - cube->children.emplace_back(std::make_unique(child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1])); - FillAdaptive::expand_cube(cube->children.back().get(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); + if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices, + distance_tree, child_center_transformed, cube_radius_squared)) + { + cube->children[i] = std::make_unique(child_center_transformed); + FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, rotation_matrix, distance_tree, triangle_mesh, depth - 1); } } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 44a2536f00..14694b766c 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -21,21 +21,18 @@ namespace FillAdaptive_Internal struct Cube { Vec3d center; - size_t depth; - CubeProperties properties; - std::vector> children; - - Cube(const Vec3d ¢er, size_t depth, const CubeProperties &properties) - : center(center), depth(depth), properties(properties) {} + std::unique_ptr children[8] = {}; + Cube(const Vec3d ¢er) : center(center) {} }; struct Octree { std::unique_ptr root_cube; Vec3d origin; + std::vector cubes_properties; - Octree(std::unique_ptr rootCube, const Vec3d &origin) - : root_cube(std::move(rootCube)), origin(origin) {} + Octree(std::unique_ptr rootCube, const Vec3d &origin, const std::vector &cubes_properties) + : root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {} }; }; // namespace FillAdaptive_Internal @@ -52,30 +49,37 @@ public: protected: virtual Fill* clone() const { return new FillAdaptive(*this); }; virtual void _fill_surface_single( - const FillParams ¶ms, + const FillParams ¶ms, unsigned int thickness_layers, - const std::pair &direction, - ExPolygon &expolygon, + const std::pair &direction, + ExPolygon &expolygon, Polylines &polylines_out); virtual bool no_sort() const { return true; } - void generate_infill_lines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &dir_lines_out); + void generate_infill_lines( + FillAdaptive_Internal::Cube *cube, + double z_position, + const Vec3d & origin, + std::vector & dir_lines_out, + const std::vector &cubes_properties, + int depth); static void connect_lines(Lines &lines, Line new_line); public: static std::unique_ptr build_octree( - TriangleMesh &triangle_mesh, - coordf_t line_spacing, - const Vec3d &cube_center); + TriangleMesh &triangle_mesh, + coordf_t line_spacing, + const Vec3d & cube_center); static void expand_cube( - FillAdaptive_Internal::Cube *cube, - const std::vector &cubes_properties, - const Transform3d &rotation_matrix, - const AABBTreeIndirect::Tree3f &distanceTree, - const TriangleMesh &triangleMesh); + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d & rotation_matrix, + const AABBTreeIndirect::Tree3f &distance_tree, + const TriangleMesh & triangle_mesh, + int depth); }; } // namespace Slic3r From 6c01d537e4d7a31107eb07d82a5ddcde03cc9271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 23:15:46 +0200 Subject: [PATCH 403/826] Enable changing adaptive infill density for different objects --- src/libslic3r/PrintObject.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5a486776cc..087d3fe3c5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,17 +434,34 @@ void PrintObject::generate_support_material() std::unique_ptr PrintObject::prepare_adaptive_infill_data() { - const ConfigOptionPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); - const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); + float fill_density = 0; + float infill_extrusion_width = 0; - if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) + // Compute the average of above parameters over all layers + for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) + { + for (size_t region_id = 0; region_id < this->m_layers[layer_idx]->m_regions.size(); ++region_id) + { + LayerRegion *layerm = this->m_layers[layer_idx]->m_regions[region_id]; + + // Check if region_id is used for this layer + if(!layerm->fill_surfaces.surfaces.empty()) { + const PrintRegionConfig ®ion_config = layerm->region()->config(); + + fill_density += region_config.fill_density; + infill_extrusion_width += region_config.infill_extrusion_width; + } + } + } + + fill_density /= this->m_layers.size(); + infill_extrusion_width /= this->m_layers.size(); + + if(fill_density <= 0 || infill_extrusion_width <= 0) { return std::unique_ptr{}; } - float fill_density = opt_fill_density->value; - float infill_extrusion_width = opt_infill_extrusion_width->value; - coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); TriangleMesh mesh = this->model_object()->raw_mesh(); From ba87a4fd9a607fd0ed7f60241a25884ff0d6d61b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Sep 2020 10:08:54 +0200 Subject: [PATCH 404/826] Fixed rescale of the MainFrame/SettingsDialog after switching between settings layouts on the 2 monitors with different DPI --- src/slic3r/GUI/GUI_Utils.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 40 +++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 749a556b84..6a93d4156e 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -231,6 +231,7 @@ private: } #endif // wxVERSION_EQUAL_OR_GREATER_THAN */ + m_force_rescale = false; #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) // rescale fonts of all controls scale_controls_fonts(this, m_new_font_point_size); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 191e6c4553..ac6f541e76 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -310,8 +310,10 @@ void MainFrame::update_layout() m_plater_page = nullptr; } + /* if (m_layout == ESettingsLayout::Dlg) rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::Mainframe); + */ clean_sizer(m_main_sizer); clean_sizer(m_settings_dialog.GetSizer()); @@ -347,6 +349,14 @@ void MainFrame::update_layout() if (m_layout != ESettingsLayout::Unknown) restore_to_creation(); + enum class State { + noUpdate, + fromDlg, + toDlg + }; + State update_scaling_state = m_layout == ESettingsLayout::Dlg ? State::fromDlg : + layout == ESettingsLayout::Dlg ? State::toDlg : State::noUpdate; + m_layout = layout; // From the very beginning the Print settings should be selected @@ -384,7 +394,7 @@ void MainFrame::update_layout() m_tabpanel->Reparent(&m_settings_dialog); m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); - rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); +// rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); m_tabpanel->Show(); m_plater->Show(); @@ -400,6 +410,34 @@ void MainFrame::update_layout() #endif // ENABLE_GCODE_VIEWER } + if (update_scaling_state != State::noUpdate) + { + int mainframe_dpi = get_dpi_for_window(this); + int dialog_dpi = get_dpi_for_window(&m_settings_dialog); + if (mainframe_dpi != dialog_dpi) { + wxSize oldDPI = update_scaling_state == State::fromDlg ? wxSize(dialog_dpi, dialog_dpi) : wxSize(mainframe_dpi, mainframe_dpi); + wxSize newDPI = update_scaling_state == State::toDlg ? wxSize(dialog_dpi, dialog_dpi) : wxSize(mainframe_dpi, mainframe_dpi); + + if (update_scaling_state == State::fromDlg) + this->enable_force_rescale(); + else + (&m_settings_dialog)->enable_force_rescale(); + + wxWindow* win { nullptr }; + if (update_scaling_state == State::fromDlg) + win = this; + else + win = &m_settings_dialog; + +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + m_tabpanel->MSWUpdateOnDPIChange(oldDPI, newDPI); + win->GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(oldDPI, newDPI)); +#else + win->GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, newDPI, win->GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } + } + //#ifdef __APPLE__ // // Using SetMinSize() on Mac messes up the window position in some cases // // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 From 436e12e99f9d516896f156176d99b6a8a3ad246e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 4 Sep 2020 12:46:34 +0200 Subject: [PATCH 405/826] Seam gizmo: fixed action names in undo/redo stack --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 7 ++++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 37 ++++++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 5 +++ src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 7 ++++ src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp | 1 + 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index af1517637f..6b3456b60e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -285,5 +285,12 @@ void GLGizmoFdmSupports::update_from_model_object() } + +PainterGizmoType GLGizmoFdmSupports::get_painter_type() const +{ + return PainterGizmoType::FDM_SUPPORTS; +} + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 913133617c..0c39992f0b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -27,6 +27,7 @@ private: void on_opening() override {} void on_shutdown() override; + PainterGizmoType get_painter_type() const override; void select_facets_by_angle(float threshold, bool block); float m_angle_threshold_deg = 45.f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 1809b417cc..ed98bf71d5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -28,13 +28,19 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) { if (activate && ! m_internal_stack_active) { - Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); + wxString str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS + ? _L("Supports gizmo turned on") + : _L("Seam gizmo turned on"); + Plater::TakeSnapshot(wxGetApp().plater(), str); wxGetApp().plater()->enter_gizmos_stack(); m_internal_stack_active = true; } if (! activate && m_internal_stack_active) { + wxString str = get_painter_type() == PainterGizmoType::SEAM + ? _L("Seam gizmo turned off") + : _L("Supports gizmo turned off"); wxGetApp().plater()->leave_gizmos_stack(); - Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); + Plater::TakeSnapshot(wxGetApp().plater(), str); m_internal_stack_active = false; } } @@ -356,11 +362,28 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) && m_button_down != Button::None) { // Take snapshot and update ModelVolume data. - wxString action_name = shift_down - ? _L("Remove selection") - : (m_button_down == Button::Left - ? _L("Add supports") - : _L("Block supports")); + wxString action_name; + if (get_painter_type() == PainterGizmoType::FDM_SUPPORTS) { + if (shift_down) + action_name = _L("Remove selection"); + else { + if (m_button_down == Button::Left) + action_name = _L("Add supports"); + else + action_name = _L("Block supports"); + } + } + if (get_painter_type() == PainterGizmoType::SEAM) { + if (shift_down) + action_name = _L("Remove selection"); + else { + if (m_button_down == Button::Left) + action_name = _L("Enforce seam"); + else + action_name = _L("Block seam"); + } + } + activate_internal_undo_redo_stack(true); Plater::TakeSnapshot(wxGetApp().plater(), action_name); update_model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index da9b378957..b3e2b65f1f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -22,6 +22,10 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class ClippingPlane; +enum class PainterGizmoType { + FDM_SUPPORTS, + SEAM +}; class TriangleSelectorGUI : public TriangleSelector { @@ -103,6 +107,7 @@ protected: virtual void on_opening() = 0; virtual void on_shutdown() = 0; + virtual PainterGizmoType get_painter_type() const = 0; bool on_is_activable() const override; bool on_is_selectable() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 3c7d180a7b..d0edfba131 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -204,5 +204,12 @@ void GLGizmoSeam::update_from_model_object() } +PainterGizmoType GLGizmoSeam::get_painter_type() const +{ + return PainterGizmoType::SEAM; +} + + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 469ec9180c..c3eb98c808 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -16,6 +16,7 @@ public: protected: void on_render_input_window(float x, float y, float bottom_limit) override; std::string on_get_name() const override; + PainterGizmoType get_painter_type() const override; private: bool on_init() override; From c8133b91b74774ce9ec015986d744991d93a3219 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Sep 2020 13:00:33 +0200 Subject: [PATCH 406/826] Code cleaning. + Use default DPIfont for wxHtmlWindows --- src/slic3r/GUI/AboutDialog.cpp | 4 +-- src/slic3r/GUI/ConfigSnapshotDialog.cpp | 4 +-- src/slic3r/GUI/GUI_Utils.hpp | 14 +--------- src/slic3r/GUI/MainFrame.cpp | 35 ------------------------- src/slic3r/GUI/SysInfoDialog.cpp | 4 +-- 5 files changed, 7 insertions(+), 54 deletions(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 92f8d1fdbd..f95b8d93ba 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -52,7 +52,7 @@ CopyrightsDialog::CopyrightsDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * em_unit(), 20 * em_unit()), wxHW_SCROLLBAR_AUTO); - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const int fs = font.GetPointSize(); const int fs2 = static_cast(1.2f*fs); int size[] = { fs, fs, fs, fs, fs2, fs2, fs2 }; @@ -249,7 +249,7 @@ AboutDialog::AboutDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/); { m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 6a44b96dc6..4855bea81e 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -114,7 +114,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db // text html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); { - wxFont font = wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); #ifdef __WXMSW__ const int fs = font.GetPointSize(); const int fs1 = static_cast(0.8f*fs); @@ -140,7 +140,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db void ConfigSnapshotDialog::on_dpi_changed(const wxRect &suggested_rect) { - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const int fs = font.GetPointSize(); const int fs1 = static_cast(0.8f*fs); const int fs2 = static_cast(1.1f*fs); diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 6a93d4156e..f29e0cd848 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -218,19 +218,7 @@ private: void rescale(const wxRect &suggested_rect) { this->Freeze(); -/* -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - if (m_force_rescale) { -#endif // wxVERSION_EQUAL_OR_GREATER_THAN - // rescale fonts of all controls - scale_controls_fonts(this, m_new_font_point_size); - // rescale current window font - scale_win_font(this, m_new_font_point_size); -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - m_force_rescale = false; - } -#endif // wxVERSION_EQUAL_OR_GREATER_THAN -*/ + m_force_rescale = false; #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) // rescale fonts of all controls diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ac6f541e76..b342ebc724 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -55,29 +55,6 @@ enum class ERescaleTarget SettingsDialog }; -static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog& dialog, ERescaleTarget target) -{ - int mainframe_dpi = get_dpi_for_window(&mainframe); - int dialog_dpi = get_dpi_for_window(&dialog); - if (mainframe_dpi != dialog_dpi) { - if (target == ERescaleTarget::SettingsDialog) { - dialog.enable_force_rescale(); -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - dialog.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(mainframe_dpi, mainframe_dpi), wxSize(dialog_dpi, dialog_dpi))); -#else - dialog.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, dialog_dpi, dialog.GetRect())); -#endif // wxVERSION_EQUAL_OR_GREATER_THAN - } else { -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - mainframe.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(dialog_dpi, dialog_dpi), wxSize(mainframe_dpi, mainframe_dpi))); -#else - mainframe.enable_force_rescale(); - mainframe.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, mainframe_dpi, mainframe.GetRect())); -#endif // wxVERSION_EQUAL_OR_GREATER_THAN - } - } -} - MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) @@ -310,11 +287,6 @@ void MainFrame::update_layout() m_plater_page = nullptr; } - /* - if (m_layout == ESettingsLayout::Dlg) - rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::Mainframe); - */ - clean_sizer(m_main_sizer); clean_sizer(m_settings_dialog.GetSizer()); @@ -393,9 +365,6 @@ void MainFrame::update_layout() m_main_sizer->Add(m_plater, 1, wxEXPAND); m_tabpanel->Reparent(&m_settings_dialog); m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); - -// rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); - m_tabpanel->Show(); m_plater->Show(); break; @@ -825,10 +794,6 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) this->SetSize(sz); this->Maximize(is_maximized); -/* - if (m_layout == ESettingsLayout::Dlg) - rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); - */ } void MainFrame::on_sys_color_changed() diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 3bd0fcf9f7..7a41aca1c3 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -109,7 +109,7 @@ SysInfoDialog::SysInfoDialog() } // main_info_text - wxFont font = wxGetApp().normal_font(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// wxGetApp().normal_font(); const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); @@ -175,7 +175,7 @@ void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect) m_logo_bmp.msw_rescale(); m_logo->SetBitmap(m_logo_bmp.bmp()); - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const int fs = font.GetPointSize() - 1; int font_size[] = { static_cast(fs*1.5), static_cast(fs*1.4), static_cast(fs*1.3), fs, fs, fs, fs }; From 486c07702c35d43bc49e2afaf53e7baa7b30845a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Sep 2020 13:42:44 +0200 Subject: [PATCH 407/826] Added SplashScreen --- src/slic3r/GUI/GUI_App.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d444017f40..7b4c275c1d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -30,6 +30,7 @@ #include #include +#include #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" @@ -436,6 +437,10 @@ bool GUI_App::on_init_inner() // Let the libslic3r know the callback, which will translate messages on demand. Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); + wxBitmap bitmap = create_scaled_bitmap("wrench", nullptr, 400); + wxSplashScreen* scrn = new wxSplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 2500, NULL, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFRAME_NO_TASKBAR | wxSIMPLE_BORDER | wxSTAY_ON_TOP); + wxYield(); + // application frame if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler()); From 9d786b5f889f4d126ce33d85c0d6a61b87aad21b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Sep 2020 16:21:36 +0200 Subject: [PATCH 408/826] Fixed non-MSW builds --- src/slic3r/GUI/MainFrame.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b342ebc724..f6fd939e25 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -321,6 +321,7 @@ void MainFrame::update_layout() if (m_layout != ESettingsLayout::Unknown) restore_to_creation(); +#ifdef __WXMSW__ enum class State { noUpdate, fromDlg, @@ -328,6 +329,7 @@ void MainFrame::update_layout() }; State update_scaling_state = m_layout == ESettingsLayout::Dlg ? State::fromDlg : layout == ESettingsLayout::Dlg ? State::toDlg : State::noUpdate; +#endif //__WXMSW__ m_layout = layout; @@ -379,6 +381,7 @@ void MainFrame::update_layout() #endif // ENABLE_GCODE_VIEWER } +#ifdef __WXMSW__ if (update_scaling_state != State::noUpdate) { int mainframe_dpi = get_dpi_for_window(this); @@ -406,6 +409,7 @@ void MainFrame::update_layout() #endif // wxVERSION_EQUAL_OR_GREATER_THAN } } +#endif //__WXMSW__ //#ifdef __APPLE__ // // Using SetMinSize() on Mac messes up the window position in some cases From 902de849c0d3dcb7ff153268c0e0c028180000f9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Sep 2020 20:25:27 +0200 Subject: [PATCH 409/826] Implemented class SplashScreen for using of text --- resources/icons/prusa_slicer_logo.svg | 5 +++ src/slic3r/GUI/GUI_App.cpp | 50 ++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 resources/icons/prusa_slicer_logo.svg diff --git a/resources/icons/prusa_slicer_logo.svg b/resources/icons/prusa_slicer_logo.svg new file mode 100644 index 0000000000..927c3e70ba --- /dev/null +++ b/resources/icons/prusa_slicer_logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 7b4c275c1d..08219ed865 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -77,6 +77,46 @@ namespace GUI { class MainFrame; +class SplashScreen : public wxSplashScreen +{ +public: + SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxWindow* parent) + : wxSplashScreen(bitmap, splashStyle, milliseconds, parent, wxID_ANY) + { + wxASSERT(bitmap.IsOk()); + m_main_bitmap = bitmap; + } + + void SetText(const wxString& text) + { + SetBmp(m_main_bitmap); + if (!text.empty()) { + wxBitmap bitmap(m_main_bitmap); + wxMemoryDC memDC; + + memDC.SelectObject(bitmap); + + memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + memDC.SetTextForeground(wxColour(237, 107, 33)); + memDC.DrawText(text, 120, 120); + + memDC.SelectObject(wxNullBitmap); + SetBmp(bitmap); + } + wxYield(); + } + + void SetBmp(wxBitmap& bmp) + { + m_window->SetBitmap(bmp); + m_window->Refresh(); + m_window->Update(); + } + +private: + wxBitmap m_main_bitmap; +}; + wxString file_wildcards(FileType file_type, const std::string &custom_extension) { static const std::string defaults[FT_SIZE] = { @@ -390,6 +430,10 @@ bool GUI_App::on_init_inner() app_config->set("version", SLIC3R_VERSION); app_config->save(); + + wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); + SplashScreen* scrn = new SplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, nullptr); + scrn->SetText(_L("Loading configuration...")); preset_bundle = new PresetBundle(); @@ -437,13 +481,11 @@ bool GUI_App::on_init_inner() // Let the libslic3r know the callback, which will translate messages on demand. Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); - wxBitmap bitmap = create_scaled_bitmap("wrench", nullptr, 400); - wxSplashScreen* scrn = new wxSplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 2500, NULL, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFRAME_NO_TASKBAR | wxSIMPLE_BORDER | wxSTAY_ON_TOP); - wxYield(); - // application frame if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler()); + scrn->SetText(_L("Creating settings tabs...")); + mainframe = new MainFrame(); // hide settings tabs after first Layout mainframe->select_tab(0); From e10d1eba54068659a9c23df419d83f0efa70f049 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 7 Sep 2020 08:35:34 +0200 Subject: [PATCH 410/826] GCodeProcessor -> Use decorations to detect toolpaths height for gcode files generated by PrusaSlicer --- src/libslic3r/GCode/GCodeProcessor.cpp | 49 +++++++++++++++----------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index cd42dc2e6c..13764f11e7 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -944,6 +944,20 @@ void GCodeProcessor::process_tags(const std::string& comment) return; } + if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) { + // height tag + pos = comment.find(Height_Tag); + if (pos != comment.npos) { + try { + m_height = std::stof(comment.substr(pos + Height_Tag.length())); + } + catch (...) { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + } + return; + } + } + #if ENABLE_GCODE_VIEWER_DATA_CHECKING // width tag pos = comment.find(Width_Tag); @@ -956,18 +970,6 @@ void GCodeProcessor::process_tags(const std::string& comment) } return; } - - // height tag - pos = comment.find(Height_Tag); - if (pos != comment.npos) { - try { - m_height_compare.last_tag_value = std::stof(comment.substr(pos + Height_Tag.length())); - } - catch (...) { - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - } - return; - } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING // color change tag @@ -1416,12 +1418,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) type = EMoveType::Travel; if (type == EMoveType::Extrude) { - float d_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); float filament_diameter = (static_cast(m_extruder_id) < m_filament_diameters.size()) ? m_filament_diameters[m_extruder_id] : m_filament_diameters.back(); float filament_radius = 0.5f * filament_diameter; float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; - float area_toolpath_cross_section = volume_extruded_filament / d_xyz; + float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; // volume extruded filament / tool displacement = area toolpath cross section m_mm3_per_mm = area_toolpath_cross_section; @@ -1429,23 +1431,28 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (m_end_position[Z] > m_extruded_last_z + EPSILON) { - m_height = m_end_position[Z] - m_extruded_last_z; + if (m_producers_enabled && m_producer != EProducer::PrusaSlicer) { + if (m_end_position[Z] > m_extruded_last_z + EPSILON) { + m_height = m_end_position[Z] - m_extruded_last_z; #if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.update(m_height, m_extrusion_role); + m_height_compare.update(m_height, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - m_extruded_last_z = m_end_position[Z]; + m_extruded_last_z = m_end_position[Z]; + } } if (m_extrusion_role == erExternalPerimeter) // cross section: rectangle - m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height); + m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (delta_xyz * m_height); else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) // cross section: circle - m_width = static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz); + m_width = static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); else // cross section: rectangle + 2 semicircles - m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; + m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; + + // clamp width to avoid artifacts which may arise from wrong values of m_height + m_width = std::min(m_width, 4.0f * m_height); #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_width_compare.update(m_width, m_extrusion_role); From 97e62be9024b6a98d22bc920165b5ae3888aa22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 7 Sep 2020 09:14:06 +0200 Subject: [PATCH 411/826] Check if exist any boundary polyline --- src/libslic3r/Fill/FillAdaptive.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 577ba7e610..bf9cd7f9d2 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -51,13 +51,19 @@ void FillAdaptive::_fill_surface_single( if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) { boundary_polylines.push_back(polyline); - } else { + } + else + { non_boundary_polylines.push_back(polyline); } } - boundary_polylines = chain_polylines(boundary_polylines); - FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + if(!boundary_polylines.empty()) + { + boundary_polylines = chain_polylines(boundary_polylines); + FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + } + polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end()); } From 8579184d70568d2850125c557a6d35b2551a49e6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 7 Sep 2020 11:30:31 +0200 Subject: [PATCH 412/826] Follow-up of 573194e059836916b6f216dc068c27a89ea7b843 -> Fixed crash when opening a gcode file --- src/libslic3r/GCode/GCodeProcessor.cpp | 13 ++++++++----- src/libslic3r/GCode/GCodeProcessor.hpp | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 13764f11e7..e9264dbd4c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -759,13 +759,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function(curr_time - last_cancel_callback_time).count() > 100) { - cancel_callback(); - last_cancel_callback_time = curr_time; + if (cancel_callback != nullptr) { + // call the cancel callback every 100 ms + auto curr_time = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(curr_time - last_cancel_callback_time).count() > 100) { + cancel_callback(); + last_cancel_callback_time = curr_time; + } } process_gcode_line(line); }); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 42772d12bc..b31591ca86 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -420,7 +420,7 @@ namespace Slic3r { // Process the gcode contained in the file with the given filename // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). - void process_file(const std::string& filename, std::function cancel_callback = std::function()); + void process_file(const std::string& filename, std::function cancel_callback = nullptr); float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const; From fd4c28ed91c3d095c6ee296b55904b22c3e51759 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 7 Sep 2020 15:55:03 +0200 Subject: [PATCH 413/826] WIP: G-code viewer menu, refactoring of starting a background process. --- src/slic3r/CMakeLists.txt | 2 ++ src/slic3r/GUI/MainFrame.cpp | 25 ++++++------------------- src/slic3r/Utils/Thread.hpp | 6 +++--- 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5681ed66db..1c30078102 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -195,6 +195,8 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp + Utils/Process.cpp + Utils/Process.hpp Utils/Profile.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f6fd939e25..f4d7f03eca 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -9,7 +9,6 @@ #include //#include #include -#include #include #include @@ -31,6 +30,7 @@ #include "I18N.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" +#include "../Utils/Process.hpp" #include #include "GUI_App.hpp" @@ -40,12 +40,6 @@ #include #endif // _WIN32 -// For starting another PrusaSlicer instance on OSX. -// Fails to compile on Windows on the build server. -#ifdef __APPLE__ - #include -#endif - namespace Slic3r { namespace GUI { @@ -1054,8 +1048,8 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); -#if ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); +#if ENABLE_GCODE_VIEWER append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { if (m_plater->model().objects.empty() || @@ -1064,6 +1058,8 @@ void MainFrame::init_menubar() set_mode(EMode::GCodeViewer); }, "", nullptr); #endif // ENABLE_GCODE_VIEWER + append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview") + dots, _L("Open G-code viewer"), + [this](wxCommandEvent&) { start_new_gcodeviewer_open_file(this); }, "", nullptr); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); @@ -1180,20 +1176,11 @@ void MainFrame::init_menubar() windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), - [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, [this]() {return true; }, this); windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _(L("Open new instance")) + "\tCtrl+I", _(L("Open a new PrusaSlicer instance")), - [this](wxCommandEvent&) { - wxString path = wxStandardPaths::Get().GetExecutablePath(); -#ifdef __APPLE__ - boost::process::spawn((const char*)path.c_str()); -#else - wxExecute(wxStandardPaths::Get().GetExecutablePath(), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); -#endif - }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr); } // View menu diff --git a/src/slic3r/Utils/Thread.hpp b/src/slic3r/Utils/Thread.hpp index e9c76d2aba..194971c9eb 100644 --- a/src/slic3r/Utils/Thread.hpp +++ b/src/slic3r/Utils/Thread.hpp @@ -1,5 +1,5 @@ -#ifndef THREAD_HPP -#define THREAD_HPP +#ifndef GUI_THREAD_HPP +#define GUI_THREAD_HPP #include #include @@ -25,4 +25,4 @@ template inline boost::thread create_thread(Fn &&fn) } -#endif // THREAD_HPP +#endif // GUI_THREAD_HPP From b0bedf33c0d145f2f6494c6e540668a1d49e5e68 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 7 Sep 2020 15:55:03 +0200 Subject: [PATCH 414/826] WIP: G-code viewer menu, refactoring of starting a background process. --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/MainFrame.cpp | 25 ++------- src/slic3r/Utils/Process.cpp | 105 +++++++++++++++++++++++++++++++++++ src/slic3r/Utils/Process.hpp | 19 +++++++ src/slic3r/Utils/Thread.hpp | 6 +- 5 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 src/slic3r/Utils/Process.cpp create mode 100644 src/slic3r/Utils/Process.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5681ed66db..1c30078102 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -195,6 +195,8 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp + Utils/Process.cpp + Utils/Process.hpp Utils/Profile.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f6fd939e25..f4d7f03eca 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -9,7 +9,6 @@ #include //#include #include -#include #include #include @@ -31,6 +30,7 @@ #include "I18N.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" +#include "../Utils/Process.hpp" #include #include "GUI_App.hpp" @@ -40,12 +40,6 @@ #include #endif // _WIN32 -// For starting another PrusaSlicer instance on OSX. -// Fails to compile on Windows on the build server. -#ifdef __APPLE__ - #include -#endif - namespace Slic3r { namespace GUI { @@ -1054,8 +1048,8 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); -#if ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); +#if ENABLE_GCODE_VIEWER append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { if (m_plater->model().objects.empty() || @@ -1064,6 +1058,8 @@ void MainFrame::init_menubar() set_mode(EMode::GCodeViewer); }, "", nullptr); #endif // ENABLE_GCODE_VIEWER + append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview") + dots, _L("Open G-code viewer"), + [this](wxCommandEvent&) { start_new_gcodeviewer_open_file(this); }, "", nullptr); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); @@ -1180,20 +1176,11 @@ void MainFrame::init_menubar() windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), - [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, [this]() {return true; }, this); windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _(L("Open new instance")) + "\tCtrl+I", _(L("Open a new PrusaSlicer instance")), - [this](wxCommandEvent&) { - wxString path = wxStandardPaths::Get().GetExecutablePath(); -#ifdef __APPLE__ - boost::process::spawn((const char*)path.c_str()); -#else - wxExecute(wxStandardPaths::Get().GetExecutablePath(), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); -#endif - }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr); } // View menu diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp new file mode 100644 index 0000000000..17e3d6fedf --- /dev/null +++ b/src/slic3r/Utils/Process.cpp @@ -0,0 +1,105 @@ +#include "Process.hpp" + +#include + +#include "../GUI/GUI.hpp" +// for file_wildcards() +#include "../GUI/GUI_App.hpp" +// localization +#include "../GUI/I18N.hpp" + +// For starting another PrusaSlicer instance on OSX. +// Fails to compile on Windows on the build server. +#ifdef __APPLE__ + #include +#endif + +#include + +namespace Slic3r { +namespace GUI { + +enum class NewSlicerInstanceType { + Slicer, + GCodeViewer +}; + +// Start a new Slicer process instance either in a Slicer mode or in a G-code mode. +// Optionally load a 3MF, STL or a G-code on start. +static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance_type, const wxString *path_to_open) +{ +#ifdef _WIN32 + wxString path; + wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE); + path += "\\"; + path += (instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer.exe" : "prusa-gcodeviewer.exe"; + std::vector args; + args.reserve(3); + args.emplace_back(path.wc_str()); + if (path_to_open != nullptr) + args.emplace_back(path_to_open->wc_str()); + args.emplace_back(nullptr); + wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); +#else + // Own executable path. + boost::filesystem::path own_path = into_path(wxStandardPaths::Get().GetExecutablePath()); + #if defined(__APPLE__) + { + own_path /= (instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"; + // On Apple the wxExecute fails, thus we use boost::process instead. + path_to_open ? boost::process::spawn(path.string(), into_u8(*path_to_open)) : boost::process::spawn(path.string()); + } + #else // Linux or Unix + { + std::vector args; + args.reserve(3); + #ifdef(__linux) + static const char *gcodeviewer_param = "--gcodeviewer"; + { + // If executed by an AppImage, start the AppImage, not the main process. + // see https://docs.appimage.org/packaging-guide/environment-variables.html#id2 + const char *appimage_binary = std::getenv("APPIMAGE"); + if (appimage_binary) { + args.emplace_back(appimage_binary); + if (instance_type == NewSlicerInstanceType::GCodeViewer) + args.emplace_back(gcodeviewer_param); + if () + } + } + #endif // __linux + std::string to_open; + if (path_to_open) { + to_open = into_u8(*path_to_open); + args.emplace_back(to_open.c_str()); + } + args.emplace_back(nullptr); + wxExecute(const_cast(&args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); + } + #endif // Linux or Unix +#endif // Win32 +} + +void start_new_slicer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, path_to_open); +} + +void start_new_gcodeviewer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::GCodeViewer, path_to_open); +} + +void start_new_gcodeviewer_open_file(wxWindow *parent) +{ + wxFileDialog dialog(parent ? parent : wxGetApp().GetTopWindow(), + _L("Open G-code file:"), + from_u8(wxGetApp().app_config->get_last_dir()), wxString(), + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() == wxID_OK) { + wxString path = dialog.GetPath(); + start_new_gcodeviewer(&path); + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/Utils/Process.hpp b/src/slic3r/Utils/Process.hpp new file mode 100644 index 0000000000..c6acaa643e --- /dev/null +++ b/src/slic3r/Utils/Process.hpp @@ -0,0 +1,19 @@ +#ifndef GUI_PROCESS_HPP +#define GUI_PROCESS_HPP + +class wxWindow; + +namespace Slic3r { +namespace GUI { + +// Start a new slicer instance, optionally with a file to open. +void start_new_slicer(const wxString *path_to_open = nullptr); +// Start a new G-code viewer instance, optionally with a file to open. +void start_new_gcodeviewer(const wxString *path_to_open = nullptr); +// Open a file dialog, ask the user to select a new G-code to open, start a new G-code viewer. +void start_new_gcodeviewer_open_file(wxWindow *parent = nullptr); + +} // namespace GUI +} // namespace Slic3r + +#endif // GUI_PROCESS_HPP diff --git a/src/slic3r/Utils/Thread.hpp b/src/slic3r/Utils/Thread.hpp index e9c76d2aba..194971c9eb 100644 --- a/src/slic3r/Utils/Thread.hpp +++ b/src/slic3r/Utils/Thread.hpp @@ -1,5 +1,5 @@ -#ifndef THREAD_HPP -#define THREAD_HPP +#ifndef GUI_THREAD_HPP +#define GUI_THREAD_HPP #include #include @@ -25,4 +25,4 @@ template inline boost::thread create_thread(Fn &&fn) } -#endif // THREAD_HPP +#endif // GUI_THREAD_HPP From 9473ae8fe2fcac9394f85cb3267d8acf62540906 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Sep 2020 16:56:22 +0200 Subject: [PATCH 415/826] Fix of previous commit, added symlinks to gcodeviewer on Linux & OSX --- src/CMakeLists.txt | 29 ++++++++++++++++++++--------- src/slic3r/Utils/Process.cpp | 11 ++++++++--- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0b0b3c0eef..8b9462cb2a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -209,20 +209,31 @@ if (WIN32) add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer) prusaslicer_copy_dlls(PrusaSlicerDllsCopy) -elseif (XCODE) - # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level - add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources" - COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) else () + if (XCODE) + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer" "${CMAKE_CURRENT_BINARY_DIR}/prusa-slicer" + COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer" "${CMAKE_CURRENT_BINARY_DIR}/prusa-gcodeviewer" + COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer" "${CMAKE_CURRENT_BINARY_DIR}/PrusaGCodeViewer" + COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer" + VERBATIM + ) + # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") + else () + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/prusa-slicer" "${CMAKE_CURRENT_BINARY_DIR}/prusa-gcodeviewer" + COMMENT "Symlinking the G-code viewer to PrusaSlicer" + VERBATIM + ) + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources") + endif () add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources" + COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}" COMMENT "Symlinking the resources directory into the build tree" VERBATIM ) -endif() +endif () # Slic3r binary install target if (WIN32) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 17e3d6fedf..ad3730dd88 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -53,7 +53,7 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance { std::vector args; args.reserve(3); - #ifdef(__linux) + #ifdef __linux static const char *gcodeviewer_param = "--gcodeviewer"; { // If executed by an AppImage, start the AppImage, not the main process. @@ -63,17 +63,22 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance args.emplace_back(appimage_binary); if (instance_type == NewSlicerInstanceType::GCodeViewer) args.emplace_back(gcodeviewer_param); - if () } } #endif // __linux + std::string bin_path; + if (args.empty()) { + // Binary path was not set to the AppImage in the Linux specific block above, call the application directly. + bin_path = (own_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer" : "prusa-gcodeviewer")).string(); + args.emplace_back(bin_path.c_str()); + } std::string to_open; if (path_to_open) { to_open = into_u8(*path_to_open); args.emplace_back(to_open.c_str()); } args.emplace_back(nullptr); - wxExecute(const_cast(&args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); + wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); } #endif // Linux or Unix #endif // Win32 From 1221c67d7f9bdb40de5cee3518036511b9f9e8c4 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Sep 2020 17:09:27 +0200 Subject: [PATCH 416/826] Fix for OSX --- src/slic3r/Utils/Process.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index ad3730dd88..596b73ff81 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -42,12 +42,12 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); #else // Own executable path. - boost::filesystem::path own_path = into_path(wxStandardPaths::Get().GetExecutablePath()); + boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); #if defined(__APPLE__) { - own_path /= (instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"; + bin_path /= (instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"; // On Apple the wxExecute fails, thus we use boost::process instead. - path_to_open ? boost::process::spawn(path.string(), into_u8(*path_to_open)) : boost::process::spawn(path.string()); + path_to_open ? boost::process::spawn(bin_path.string(), into_u8(*path_to_open)) : boost::process::spawn(bin_path.string()); } #else // Linux or Unix { From ae0e576c32b360314a962ddc0eef645c3cc3fe2e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Sep 2020 17:41:16 +0200 Subject: [PATCH 417/826] Fixing Linux build --- src/slic3r/Utils/Process.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 596b73ff81..83438390c0 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -66,11 +66,11 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance } } #endif // __linux - std::string bin_path; + std::string my_path; if (args.empty()) { // Binary path was not set to the AppImage in the Linux specific block above, call the application directly. - bin_path = (own_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer" : "prusa-gcodeviewer")).string(); - args.emplace_back(bin_path.c_str()); + my_path = (bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer" : "prusa-gcodeviewer")).string(); + args.emplace_back(my_path.c_str()); } std::string to_open; if (path_to_open) { From 8622437c12d21ca402cc7cd1cf8fb54a603ab62a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Sep 2020 18:09:51 +0200 Subject: [PATCH 418/826] fixing symlinks --- src/CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b9462cb2a..e80349f844 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -212,9 +212,10 @@ if (WIN32) else () if (XCODE) add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer" "${CMAKE_CURRENT_BINARY_DIR}/prusa-slicer" - COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer" "${CMAKE_CURRENT_BINARY_DIR}/prusa-gcodeviewer" - COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer" "${CMAKE_CURRENT_BINARY_DIR}/PrusaGCodeViewer" + COMMAND ln -sf PrusaSlicer prusa-slicer + COMMAND ln -sf PrusaSlicer prusa-gcodeviewer + COMMAND ln -sf PrusaSlicer PrusaGCodeViewer + WORKING_DIRECTORY "$" COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer" VERBATIM ) @@ -222,7 +223,8 @@ else () set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") else () add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sf "${CMAKE_CURRENT_BINARY_DIR}/prusa-slicer" "${CMAKE_CURRENT_BINARY_DIR}/prusa-gcodeviewer" + COMMAND ln -sf prusa-slicer prusa-gcodeviewer + WORKING_DIRECTORY "$" COMMENT "Symlinking the G-code viewer to PrusaSlicer" VERBATIM ) From c2ba096d062a5978b149c510e7537cf1c596778e Mon Sep 17 00:00:00 2001 From: Giles Bathgate Date: Mon, 7 Sep 2020 19:03:12 +0100 Subject: [PATCH 419/826] Document the additional cmake flag Document the additional cmake flag needed for compilation on ubuntu 20.04 focal --- doc/How to build - Linux et al.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/How to build - Linux et al.md b/doc/How to build - Linux et al.md index 9206ae1ed2..a8f2441be5 100644 --- a/doc/How to build - Linux et al.md +++ b/doc/How to build - Linux et al.md @@ -56,6 +56,10 @@ This is done by passing this option to CMake: Note that PrusaSlicer is tested with wxWidgets 3.0 somewhat sporadically and so there may be bugs in bleeding edge releases. +When building on ubuntu 20.04 focal fossa, the package libwxgtk3.0-gtk3-dev needs to be installed instead of libwxgtk3.0-dev and you should use: + + -DSLIC3R_WX_STABLE=1 -DSLIC3R_GTK=3 + ### Build variant By default PrusaSlicer builds the release variant. From 5618293a28f4ebce3a863338ef3d176ac094cc65 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 7 Sep 2020 21:20:49 +0200 Subject: [PATCH 420/826] Splash screen : implemented smart splash screen --- src/slic3r/GUI/GUI_App.cpp | 126 +++++++++++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 08219ed865..11bcda2c44 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -77,6 +77,109 @@ namespace GUI { class MainFrame; +// ysFIXME +static int get_dpi_for_main_display() +{ + wxFrame fr(nullptr, wxID_ANY, wxEmptyString); + return get_dpi_for_window(&fr); +} + +// scale input bitmap and return scale factor +static float scale_bitmap(wxBitmap& bmp) +{ + int dpi = get_dpi_for_main_display(); + float sf = 1.0; + // scale bitmap if needed + if (dpi != DPI_DEFAULT) { + wxImage image = bmp.ConvertToImage(); + if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) + { + sf = (float)dpi / DPI_DEFAULT; + int width = int(sf * image.GetWidth()); + int height = int(sf * image.GetHeight()); + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + bmp = wxBitmap(std::move(image)); + } + } + return sf; +} + +static void word_wrap_string(wxString& input, int line_len) +{ + int idx = -1; + int cur_len = 0; + for (size_t i = 0; i < input.Len(); i++) + { + cur_len++; + if (input[i] == ' ') + idx = i; + if (input[i] == '\n') + { + idx = -1; + cur_len = 0; + } + if (cur_len >= line_len && idx >= 0) + { + input[idx] = '\n'; + cur_len = static_cast(i) - idx; + } + } +} + +static void DecorateSplashScreen(wxBitmap& bmp) +{ + wxASSERT(bmp.IsOk()); + float scale_factor = scale_bitmap(bmp); + + // use a memory DC to draw directly onto the bitmap + wxMemoryDC memDc(bmp); + + // draw an dark grey box at the left of the splashscreen. + // this box will be 2/9 of the weight of the bitmap, and be at the left. + const wxRect bannerRect(wxPoint(0, (bmp.GetHeight() / 9) * 2 - 2), wxPoint((bmp.GetWidth() / 5) * 2, bmp.GetHeight())); + wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); + wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); + memDc.DrawRectangle(bannerRect); + + // title + wxString title_string = SLIC3R_APP_NAME; + wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + title_font.SetPointSize(24); + + // dynamically get the version to display + wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION); + wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Larger().Larger(); + + // create a copyright notice that uses the year that this file was compiled + wxString year(__DATE__); + wxString cr_symbol = wxString::FromUTF8("\xc2\xa9"); + wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" + "%s 2011-2018 Alessandro Ranellucci.", + cr_symbol, year.Mid(year.Length() - 4), cr_symbol); + wxFont copyright_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Larger(); + + copyright_string += "Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + + _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + + _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others."); + + word_wrap_string(copyright_string, 50); + + wxCoord margin = int(scale_factor * 20); + + // draw the (orange) labels inside of our black box (at the left of the splashscreen) + memDc.SetTextForeground(wxColour(237, 107, 33)); + + memDc.SetFont(title_font); + memDc.DrawLabel(title_string, bannerRect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT); + + memDc.SetFont(version_font); + memDc.DrawLabel(version_string, bannerRect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); + + memDc.SetFont(copyright_font); + memDc.DrawLabel(copyright_string, bannerRect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT); +} + class SplashScreen : public wxSplashScreen { public: @@ -85,6 +188,10 @@ public: { wxASSERT(bitmap.IsOk()); m_main_bitmap = bitmap; + + int dpi = get_dpi_for_main_display(); + if (dpi != DPI_DEFAULT) + m_scale_factor = (float)dpi / DPI_DEFAULT; } void SetText(const wxString& text) @@ -92,13 +199,14 @@ public: SetBmp(m_main_bitmap); if (!text.empty()) { wxBitmap bitmap(m_main_bitmap); - wxMemoryDC memDC; + wxMemoryDC memDC; memDC.SelectObject(bitmap); - memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold().Larger(); + memDC.SetFont(font); memDC.SetTextForeground(wxColour(237, 107, 33)); - memDC.DrawText(text, 120, 120); + memDC.DrawText(text, int(m_scale_factor * 45), int(m_scale_factor * 215)); memDC.SelectObject(wxNullBitmap); SetBmp(bitmap); @@ -114,7 +222,8 @@ public: } private: - wxBitmap m_main_bitmap; + wxBitmap m_main_bitmap; + float m_scale_factor {1.0}; }; wxString file_wildcards(FileType file_type, const std::string &custom_extension) @@ -431,8 +540,15 @@ bool GUI_App::on_init_inner() app_config->set("version", SLIC3R_VERSION); app_config->save(); + if (wxImage::FindHandler(wxBITMAP_TYPE_JPEG) == nullptr) + wxImage::AddHandler(new wxJPEGHandler()); + wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); - SplashScreen* scrn = new SplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, nullptr); + wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); + + DecorateSplashScreen(bmp); + + SplashScreen* scrn = new SplashScreen(bmp.IsOk() ? bmp : bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, nullptr); scrn->SetText(_L("Loading configuration...")); preset_bundle = new PresetBundle(); From 889f05167af523da4f0c8d8028049e970ea91358 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Sep 2020 21:36:51 +0200 Subject: [PATCH 421/826] Changing the binary name on OSX to PrusaSlicer. --- src/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e80349f844..c0137502a4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,9 +106,9 @@ if (MINGW) set_target_properties(PrusaSlicer PROPERTIES PREFIX "") endif (MINGW) -if (NOT WIN32) - # Binary name on unix like systems (OSX, Linux) - set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") +if (NOT WIN32 AND NOT APPLE) + # Binary name on unix like systems (Linux, Unix) + set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () target_link_libraries(PrusaSlicer libslic3r cereal) From 620c85f264f8175552aab041b4ced2d39c132cbb Mon Sep 17 00:00:00 2001 From: test Date: Mon, 7 Sep 2020 22:00:01 +0200 Subject: [PATCH 422/826] Fix on OSX --- src/slic3r/Utils/Process.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 83438390c0..e29160870d 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -45,7 +45,7 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); #if defined(__APPLE__) { - bin_path /= (instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"; + bin_path = bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"); // On Apple the wxExecute fails, thus we use boost::process instead. path_to_open ? boost::process::spawn(bin_path.string(), into_u8(*path_to_open)) : boost::process::spawn(bin_path.string()); } From f237b33515b25833bae40e96c11565564f4ee400 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Sep 2020 22:26:58 +0200 Subject: [PATCH 423/826] Yet another fix on OSX. --- src/CMakeLists.txt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c0137502a4..ca57ca5531 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -210,31 +210,32 @@ if (WIN32) prusaslicer_copy_dlls(PrusaSlicerDllsCopy) else () - if (XCODE) + if (APPLE) + # On OSX, the name of the binary matches the name of the Application. add_custom_command(TARGET PrusaSlicer POST_BUILD COMMAND ln -sf PrusaSlicer prusa-slicer COMMAND ln -sf PrusaSlicer prusa-gcodeviewer COMMAND ln -sf PrusaSlicer PrusaGCodeViewer WORKING_DIRECTORY "$" COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer" - VERBATIM - ) - # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level - set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") + VERBATIM) else () add_custom_command(TARGET PrusaSlicer POST_BUILD COMMAND ln -sf prusa-slicer prusa-gcodeviewer WORKING_DIRECTORY "$" COMMENT "Symlinking the G-code viewer to PrusaSlicer" - VERBATIM - ) + VERBATIM) + endif () + if (XCODE) + # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") + else () set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources") endif () add_custom_command(TARGET PrusaSlicer POST_BUILD COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}" COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) + VERBATIM) endif () # Slic3r binary install target From d830e1c970f8562ca862759cf3bcb6c4f80c9c54 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Sep 2020 22:37:55 +0200 Subject: [PATCH 424/826] Run PrusaSlicer as G-code viewer based on argv[0] name on Unix systems. --- src/PrusaSlicer.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2962f0cdfe..94996dc928 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -101,8 +102,14 @@ int CLI::run(int argc, char **argv) std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); - bool start_as_gcodeviewer = false; - + bool start_as_gcodeviewer = +#ifdef _WIN32 + false; +#else + // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. + boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); +#endif // _WIN32 + const std::vector &load_configs = m_config.option("load", true)->values; // load config files supplied via --load From 6dbc1ccf21a5ee95892b2f8a774c0622f6863ac1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Sep 2020 08:19:06 +0200 Subject: [PATCH 425/826] Added missed image for splash screen --- resources/icons/splashscreen.jpg | Bin 0 -> 133522 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/icons/splashscreen.jpg diff --git a/resources/icons/splashscreen.jpg b/resources/icons/splashscreen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..86c55f30f6af0fb6de922766bccd388f987d2d20 GIT binary patch literal 133522 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 bcUFrAWU5*b{v{~IY9mqVuR~GP|4jZ5b80Hr literal 0 HcmV?d00001 From 1418b71541f66793709d206df2835569926eab95 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Sep 2020 09:12:40 +0200 Subject: [PATCH 426/826] Linux: Try to fix warning "Na handler for image type 15" --- src/slic3r/GUI/GUI_App.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 11bcda2c44..c16aeaada5 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -539,9 +539,13 @@ bool GUI_App::on_init_inner() app_config->set("version", SLIC3R_VERSION); app_config->save(); - +/* if (wxImage::FindHandler(wxBITMAP_TYPE_JPEG) == nullptr) wxImage::AddHandler(new wxJPEGHandler()); + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler()); +*/ + wxInitAllImageHandlers(); wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); @@ -598,8 +602,6 @@ bool GUI_App::on_init_inner() Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); // application frame - if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) - wxImage::AddHandler(new wxPNGHandler()); scrn->SetText(_L("Creating settings tabs...")); mainframe = new MainFrame(); From 663f17a0e3de953dde239ed65dec59eca198a7fc Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 09:57:17 +0200 Subject: [PATCH 427/826] Improved logging of spawning a subprocess. --- src/slic3r/Utils/Process.cpp | 40 ++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index e29160870d..4347f66828 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -8,6 +8,11 @@ // localization #include "../GUI/I18N.hpp" +#include +#include + +#include + // For starting another PrusaSlicer instance on OSX. // Fails to compile on Windows on the build server. #ifdef __APPLE__ @@ -29,17 +34,19 @@ enum class NewSlicerInstanceType { static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance_type, const wxString *path_to_open) { #ifdef _WIN32 - wxString path; - wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE); - path += "\\"; - path += (instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer.exe" : "prusa-gcodeviewer.exe"; - std::vector args; - args.reserve(3); - args.emplace_back(path.wc_str()); - if (path_to_open != nullptr) - args.emplace_back(path_to_open->wc_str()); - args.emplace_back(nullptr); - wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); + wxString path; + wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE); + path += "\\"; + path += (instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer.exe" : "prusa-gcodeviewer.exe"; + std::vector args; + args.reserve(3); + args.emplace_back(path.wc_str()); + if (path_to_open != nullptr) + args.emplace_back(path_to_open->wc_str()); + args.emplace_back(nullptr); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << to_u8(path) << "\""; + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << to_u8(path); #else // Own executable path. boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); @@ -47,7 +54,12 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance { bin_path = bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"); // On Apple the wxExecute fails, thus we use boost::process instead. - path_to_open ? boost::process::spawn(bin_path.string(), into_u8(*path_to_open)) : boost::process::spawn(bin_path.string()); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; + try { + path_to_open ? boost::process::spawn(bin_path.string(), into_u8(*path_to_open)) : boost::process::spawn(bin_path.string()); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); + } } #else // Linux or Unix { @@ -78,7 +90,9 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance args.emplace_back(to_open.c_str()); } args.emplace_back(nullptr); - wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << args[0] << "\""; + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << args[0]; } #endif // Linux or Unix #endif // Win32 From 77ba284a59b782c9898cd6eeb1867ba69cbcf8c3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 11:22:27 +0200 Subject: [PATCH 428/826] Trying to fix spawn on OSX --- src/slic3r/Utils/Process.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 4347f66828..3ee141e804 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -56,7 +56,7 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance // On Apple the wxExecute fails, thus we use boost::process instead. BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; try { - path_to_open ? boost::process::spawn(bin_path.string(), into_u8(*path_to_open)) : boost::process::spawn(bin_path.string()); + path_to_open ? boost::process::spawn(bin_path, into_u8(*path_to_open)) : boost::process::spawn(bin_path, boost::path::args()); } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); } From 3c51581e92f7ec88ac82007d5e6ffd2eca8840e5 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 11:36:00 +0200 Subject: [PATCH 429/826] Another fix --- src/slic3r/Utils/Process.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 3ee141e804..ab5a9b1e9b 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -11,6 +11,7 @@ #include #include +#include #include // For starting another PrusaSlicer instance on OSX. @@ -56,7 +57,7 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance // On Apple the wxExecute fails, thus we use boost::process instead. BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; try { - path_to_open ? boost::process::spawn(bin_path, into_u8(*path_to_open)) : boost::process::spawn(bin_path, boost::path::args()); + path_to_open ? boost::process::spawn(bin_path, into_u8(*path_to_open)) : boost::process::spawn(bin_path, boost::filesystem::path::args()); } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); } From ab556a398b579b65802cfa7fdd39d543ab49fbec Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Sep 2020 11:40:06 +0200 Subject: [PATCH 430/826] GCode viewer using the proper layout when started as a standalone application --- .../icons/PrusaSlicer-gcodeviewer_128px.png | Bin 0 -> 5069 bytes .../icons/PrusaSlicerGCodeViewer_128px.png | Bin 15949 -> 0 bytes src/PrusaSlicer.cpp | 4 + src/libslic3r/AppConfig.cpp | 5 + src/libslic3r/AppConfig.hpp | 11 ++ src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/GCodeViewer.cpp | 8 ++ src/slic3r/GUI/GLCanvas3D.cpp | 16 +++ src/slic3r/GUI/GUI_App.cpp | 93 ++++++++---- src/slic3r/GUI/GUI_App.hpp | 25 ++++ src/slic3r/GUI/GUI_Preview.cpp | 4 + src/slic3r/GUI/GUI_Preview.hpp | 5 + src/slic3r/GUI/KBShortcutsDialog.cpp | 6 + src/slic3r/GUI/MainFrame.cpp | 125 ++++++++++++---- src/slic3r/GUI/MainFrame.hpp | 17 ++- src/slic3r/GUI/Plater.cpp | 136 ++++++++++-------- 16 files changed, 341 insertions(+), 115 deletions(-) create mode 100644 resources/icons/PrusaSlicer-gcodeviewer_128px.png delete mode 100644 resources/icons/PrusaSlicerGCodeViewer_128px.png diff --git a/resources/icons/PrusaSlicer-gcodeviewer_128px.png b/resources/icons/PrusaSlicer-gcodeviewer_128px.png new file mode 100644 index 0000000000000000000000000000000000000000..d8e3e438be6c5fc64f7ebc8744a842bdcbfd6aa5 GIT binary patch literal 5069 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 literal 0 HcmV?d00001 diff --git a/resources/icons/PrusaSlicerGCodeViewer_128px.png b/resources/icons/PrusaSlicerGCodeViewer_128px.png deleted file mode 100644 index 0bf85abbd6ea0585d127686a8200b4a647a030b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15949 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Ʊ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 AMF0Q* diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2962f0cdfe..ea56124e6a 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -534,7 +534,11 @@ int CLI::run(int argc, char **argv) if (start_gui) { #ifdef SLIC3R_GUI // #ifdef USE_WX +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + GUI::GUI_App* gui = new GUI::GUI_App(start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); +#else GUI::GUI_App *gui = new GUI::GUI_App(); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1"; if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) { diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 8b41bd2716..db3bd78ddf 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -179,6 +179,11 @@ std::string AppConfig::load() void AppConfig::save() { +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (!m_save_enabled) + return; +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + // The config is first written to a file with a PID suffix and then moved // to avoid race conditions with multiple instances of Slic3r const auto path = config_path(); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index ffd1b9fdf5..3f4ce20089 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -18,6 +18,9 @@ public: AppConfig() : m_dirty(false), m_orig_version(Semver::invalid()), +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + m_save_enabled(true), +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION m_legacy_datadir(false) { this->reset(); @@ -157,6 +160,10 @@ public: bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const { return get_3dmouse_device_numeric_value(name, "swap_yz", swap); } +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + void enable_save(bool enable) { m_save_enabled = enable; } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + static const std::string SECTION_FILAMENTS; static const std::string SECTION_MATERIALS; @@ -183,6 +190,10 @@ private: bool m_dirty; // Original version found in the ini file before it was overwritten Semver m_orig_version; +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + // Whether or not calls to save() should take effect + bool m_save_enabled; +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // Whether the existing version is before system profiles & configuration updating bool m_legacy_datadir; }; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a0484b259c..2dbad472fe 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,5 +59,6 @@ #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION (1 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index bc424466bf..2b9bf8ca46 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -339,7 +339,11 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& reset(); load_toolpaths(gcode_result); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) +#else if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION load_shells(print, initialized); else { Pointfs bed_shape; @@ -875,7 +879,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (size_t i = 0; i < m_vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_gcode_viewer()) +#else if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // for the gcode viewer we need all moves to correctly size the printbed m_paths_bounding_box.merge(move.position.cast()); else { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e7f0f094db..2f9f9464cd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2732,7 +2732,11 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) +#else if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); } @@ -4302,7 +4306,11 @@ void GLCanvas3D::update_ui_from_settings() #endif // ENABLE_RETINA_GL #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) +#else if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); #else bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; @@ -5405,7 +5413,11 @@ void GLCanvas3D::_render_background() const { #if ENABLE_GCODE_VIEWER bool use_error_color = false; +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) { +#else if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION use_error_color = m_dynamic_background_enabled; if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); @@ -7134,7 +7146,11 @@ void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning if (!m_volumes.empty()) show = _is_any_volume_outside(); else { +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) { +#else if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 08219ed865..65aa026b54 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1,3 +1,4 @@ +#include "libslic3r/Technologies.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" @@ -309,8 +310,15 @@ static void generic_exception_handle() IMPLEMENT_APP(GUI_App) +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +GUI_App::GUI_App(EAppMode mode) +#else GUI_App::GUI_App() +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION : wxApp() +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + , m_app_mode(mode) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION , m_em_unit(10) , m_imgui(new ImGuiWrapper()) , m_wizard(nullptr) @@ -366,6 +374,12 @@ void GUI_App::init_app_config() if (!app_config) app_config = new AppConfig(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (is_gcode_viewer()) + // disable config save to avoid to mess it up for the editor + app_config->enable_save(false); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + // load settings app_conf_exists = app_config->exists(); if (app_conf_exists) { @@ -402,18 +416,18 @@ bool GUI_App::on_init_inner() wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. + // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. // wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible // performance when working on high resolution multi-display setups. // wxSystemOptions::SetOption("msw.notebook.themed-background", 0); // Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; - + std::string msg = Http::tls_global_init(); std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); - + if (!msg.empty() && !ssl_accept) { wxRichMessageDialog dlg(nullptr, @@ -423,38 +437,44 @@ bool GUI_App::on_init_inner() if (dlg.ShowModal() != wxID_YES) return false; app_config->set("tls_cert_store_accepted", - dlg.IsCheckBoxChecked() ? "yes" : "no"); + dlg.IsCheckBoxChecked() ? "yes" : "no"); app_config->set("tls_accepted_cert_store_location", - dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); + dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); } - + app_config->set("version", SLIC3R_VERSION); app_config->save(); wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); SplashScreen* scrn = new SplashScreen(bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, nullptr); scrn->SetText(_L("Loading configuration...")); - + preset_bundle = new PresetBundle(); - + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (is_editor()) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #ifdef __WXMSW__ - associate_3mf_files(); + associate_3mf_files(); #endif // __WXMSW__ - preset_updater = new PresetUpdater(); - Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { - app_config->set("version_online", into_u8(evt.GetString())); - app_config->save(); - if(this->plater_ != nullptr) { - if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) { - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D())); - } - } - }); + preset_updater = new PresetUpdater(); + Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + app_config->set("version_online", into_u8(evt.GetString())); + app_config->save(); + if (this->plater_ != nullptr) { + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D())); + } + } + }); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // initialize label colors and fonts init_label_colours(); @@ -484,7 +504,11 @@ bool GUI_App::on_init_inner() // application frame if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler()); - scrn->SetText(_L("Creating settings tabs...")); + +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (is_editor()) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + scrn->SetText(_L("Creating settings tabs...")); mainframe = new MainFrame(); // hide settings tabs after first Layout @@ -519,13 +543,20 @@ bool GUI_App::on_init_inner() static bool once = true; if (once) { once = false; - check_updates(false); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (preset_updater != nullptr) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + check_updates(false); + + CallAfter([this] { + config_wizard_startup(); + preset_updater->slic3r_update_notify(); + preset_updater->sync(preset_bundle); + }); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - CallAfter([this] { - config_wizard_startup(); - preset_updater->slic3r_update_notify(); - preset_updater->sync(preset_bundle); - }); #ifdef _WIN32 //sets window property to mainframe so other instances can indentify it OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); @@ -533,8 +564,16 @@ bool GUI_App::on_init_inner() } }); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (is_gcode_viewer()) { + mainframe->update_layout(); + if (plater_ != nullptr) + // ensure the selected technology is ptFFF + plater_->set_printer_technology(ptFFF); + } +#else load_current_presets(); - +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION mainframe->Show(true); /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 34114c03cb..d63825de3e 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -94,8 +94,22 @@ static wxString dots("…", wxConvUTF8); class GUI_App : public wxApp { +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +public: + enum class EAppMode : unsigned char + { + Editor, + GCodeViewer + }; + +private: +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + bool m_initialized { false }; bool app_conf_exists{ false }; +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + EAppMode m_app_mode{ EAppMode::Editor }; +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION wxColour m_color_label_modified; wxColour m_color_label_sys; @@ -125,13 +139,24 @@ class GUI_App : public wxApp std::unique_ptr m_single_instance_checker; std::string m_instance_hash_string; size_t m_instance_hash_int; + public: bool OnInit() override; bool initialized() const { return m_initialized; } +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + explicit GUI_App(EAppMode mode = EAppMode::Editor); +#else GUI_App(); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION ~GUI_App() override; +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + EAppMode get_app_mode() const { return m_app_mode; } + bool is_editor() const { return m_app_mode == EAppMode::Editor; } + bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + static std::string get_gl_info(bool format_as_html, bool extensions); wxGLContext* init_glcontext(wxGLCanvas& canvas); bool init_opengl(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 5dcd26a877..530b3358e2 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1234,7 +1234,11 @@ void Preview::load_print_as_fff(bool keep_z_range) } #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor() && !has_layers) +#else if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer && !has_layers) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else if (! has_layers) #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index d9ce44bd62..6297663067 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -194,6 +194,9 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, #if ENABLE_GCODE_VIEWER void update_bottom_toolbar(); void update_moves_slider(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + void hide_layers_slider(); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER private: @@ -203,7 +206,9 @@ private: void unbind_event_handlers(); #if ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION void hide_layers_slider(); +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else void show_hide_ui_elements(const std::string& what); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 1eceea22e4..632bc48ed0 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -95,9 +95,15 @@ void KBShortcutsDialog::fill_shortcuts() const std::string& alt = GUI::shortkey_alt_prefix(); #if ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION bool is_gcode_viewer = wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer; +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) { +#else if (!is_gcode_viewer) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER Shortcuts commands_shortcuts = { // File diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f6fd939e25..853d9a6d75 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -92,7 +92,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON -// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -102,7 +101,24 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); } #else - SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + switch (wxGetApp().get_mode()) + { + default: + case GUI_App::EMode::Editor: + { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + break; + } + case GUI_App::EMode::GCodeViewer: + { + SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG)); + break; + } + } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // _WIN32 // initialize status bar @@ -116,8 +132,15 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // initialize tabpanel and menubar init_tabpanel(); #if ENABLE_GCODE_VIEWER - init_editor_menubar(); - init_gcodeviewer_menubar(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_gcode_viewer()) + init_menubar_as_gcodeviewer(); + else + init_menubar_as_editor(); +#else + init_menubar_as_editor(); + init_menubar_as_gcodeviewer(); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #if _WIN32 // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad @@ -148,7 +171,10 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S sizer->Add(m_main_sizer, 1, wxEXPAND); SetSizer(sizer); // initialize layout from config - update_layout(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + update_layout(); sizer->SetSizeHints(this); Fit(); @@ -300,10 +326,17 @@ void MainFrame::update_layout() }; #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + ESettingsLayout layout = wxGetApp().is_gcode_viewer() ? ESettingsLayout::GCodeViewer : + (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old); +#else ESettingsLayout layout = (m_mode == EMode::GCodeViewer) ? ESettingsLayout::GCodeViewer : (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : @@ -375,6 +408,12 @@ void MainFrame::update_layout() case ESettingsLayout::GCodeViewer: { m_main_sizer->Add(m_plater, 1, wxEXPAND); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); + m_plater->enable_view_toolbar(false); + m_plater->get_collapse_toolbar().set_enabled(false); + m_plater->collapse_sidebar(true); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION m_plater->Show(); break; } @@ -482,6 +521,7 @@ void MainFrame::shutdown() if (m_plater != nullptr) { #if ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // restore sidebar if it was hidden when switching to gcode viewer mode if (m_restore_from_gcode_viewer.collapsed_sidebar) m_plater->collapse_sidebar(false); @@ -489,6 +529,7 @@ void MainFrame::shutdown() // restore sla printer if it was deselected when switching to gcode viewer mode if (m_restore_from_gcode_viewer.sla_technology) m_plater->set_printer_technology(ptSLA); +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). @@ -590,7 +631,10 @@ void MainFrame::init_tabpanel() // or when the preset's "modified" status changes. Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this); // #ys_FIXME_to_delete - create_preset_tabs(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + create_preset_tabs(); if (m_plater) { // load initial config @@ -891,7 +935,7 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); } -void MainFrame::init_editor_menubar() +void MainFrame::init_menubar_as_editor() #else void MainFrame::init_menubar() #endif // ENABLE_GCODE_VIEWER @@ -1055,6 +1099,7 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); #if ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { @@ -1063,6 +1108,7 @@ void MainFrame::init_menubar() wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES) set_mode(EMode::GCodeViewer); }, "", nullptr); +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), @@ -1286,6 +1332,17 @@ void MainFrame::init_menubar() // assign menubar to frame after appending items, otherwise special items // will not be handled correctly #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + m_menubar = new wxMenuBar(); + m_menubar->Append(fileMenu, _L("&File")); + if (editMenu) m_menubar->Append(editMenu, _L("&Edit")); + m_menubar->Append(windowMenu, _L("&Window")); + if (viewMenu) m_menubar->Append(viewMenu, _L("&View")); + // Add additional menus from C++ + wxGetApp().add_config_menu(m_menubar); + m_menubar->Append(helpMenu, _L("&Help")); + SetMenuBar(m_menubar); +#else m_editor_menubar = new wxMenuBar(); m_editor_menubar->Append(fileMenu, _L("&File")); if (editMenu) m_editor_menubar->Append(editMenu, _L("&Edit")); @@ -1295,6 +1352,7 @@ void MainFrame::init_menubar() wxGetApp().add_config_menu(m_editor_menubar); m_editor_menubar->Append(helpMenu, _L("&Help")); SetMenuBar(m_editor_menubar); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else auto menubar = new wxMenuBar(); menubar->Append(fileMenu, _(L("&File"))); @@ -1323,15 +1381,11 @@ void MainFrame::init_menubar() #endif if (plater()->printer_technology() == ptSLA) -#if ENABLE_GCODE_VIEWER - update_editor_menubar(); -#else update_menubar(); -#endif // ENABLE_GCODE_VIEWER } #if ENABLE_GCODE_VIEWER -void MainFrame::init_gcodeviewer_menubar() +void MainFrame::init_menubar_as_gcodeviewer() { wxMenu* fileMenu = new wxMenu; { @@ -1342,9 +1396,11 @@ void MainFrame::init_gcodeviewer_menubar() append_menu_item(fileMenu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"), [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, [this]() {return can_export_toolpaths(); }, this); +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"), [this](wxCommandEvent&) { set_mode(EMode::Editor); }); +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); @@ -1360,13 +1416,22 @@ void MainFrame::init_gcodeviewer_menubar() // helpmenu auto helpMenu = generate_help_menu(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + m_menubar = new wxMenuBar(); + m_menubar->Append(fileMenu, _L("&File")); + if (viewMenu != nullptr) m_menubar->Append(viewMenu, _L("&View")); + m_menubar->Append(helpMenu, _L("&Help")); + SetMenuBar(m_menubar); +#else m_gcodeviewer_menubar = new wxMenuBar(); m_gcodeviewer_menubar->Append(fileMenu, _L("&File")); - if ((viewMenu != nullptr)) + if (viewMenu != nullptr) m_gcodeviewer_menubar->Append(viewMenu, _L("&View")); m_gcodeviewer_menubar->Append(helpMenu, _L("&Help")); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION } +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION void MainFrame::set_mode(EMode mode) { if (m_mode == mode) @@ -1432,7 +1497,7 @@ void MainFrame::set_mode(EMode mode) TCHAR szExeFileName[MAX_PATH]; GetModuleFileName(nullptr, szExeFileName, MAX_PATH); SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); - } + } #else SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); #endif // _WIN32 @@ -1488,11 +1553,11 @@ void MainFrame::set_mode(EMode mode) m_plater->Thaw(); - SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG)); + SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG)); #if ENABLE_GCODE_VIEWER_TASKBAR_ICON if (m_taskbar_icon != nullptr) { m_taskbar_icon->RemoveIcon(); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); + m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON @@ -1500,20 +1565,22 @@ void MainFrame::set_mode(EMode mode) } } } +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER -void MainFrame::update_editor_menubar() -#else void MainFrame::update_menubar() -#endif // ENABLE_GCODE_VIEWER { +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_gcode_viewer()) + return; +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + const bool is_fff = plater()->printer_technology() == ptFFF; - m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _(L("Export &G-code")) : _(L("E&xport")) ) + dots + "\tCtrl+G"); - m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _(L("S&end G-code")) : _(L("S&end to print"))) + dots + "\tCtrl+Shift+G"); + m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _L("Export &G-code") : _L("E&xport")) + dots + "\tCtrl+G"); + m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _L("S&end G-code") : _L("S&end to print")) + dots + "\tCtrl+Shift+G"); - m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab"))) + "\tCtrl+3"); + m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _L("&Filament Settings Tab") : _L("Mate&rial Settings Tab")) + "\tCtrl+3"); m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "spool" : "resin")); m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "printer" : "sla_printer")); @@ -1996,6 +2063,11 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX, "settings_dialog"), m_main_frame(mainframe) { +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_gcode_viewer()) + return; +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, @@ -2006,8 +2078,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - -// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -2070,6 +2140,11 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) { +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_gcode_viewer()) + return; +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + const int& em = em_unit(); const wxSize& size = wxSize(85 * em, 50 * em); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 7777a053d2..867e11e86b 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -57,7 +57,7 @@ class SettingsDialog : public DPIDialog MainFrame* m_main_frame { nullptr }; public: SettingsDialog(MainFrame* mainframe); - ~SettingsDialog() {} + ~SettingsDialog() = default; void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; } protected: @@ -72,6 +72,9 @@ class MainFrame : public DPIFrame wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + wxMenuBar* m_menubar{ nullptr }; +#else wxMenuBar* m_editor_menubar{ nullptr }; wxMenuBar* m_gcodeviewer_menubar{ nullptr }; @@ -83,6 +86,7 @@ class MainFrame : public DPIFrame }; RestoreFromGCodeViewer m_restore_from_gcode_viewer; +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER #if 0 @@ -146,6 +150,7 @@ class MainFrame : public DPIFrame ESettingsLayout m_layout{ ESettingsLayout::Unknown }; #if ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION public: enum class EMode : unsigned char { @@ -155,6 +160,7 @@ public: private: EMode m_mode{ EMode::Editor }; +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER protected: @@ -182,16 +188,17 @@ public: void create_preset_tabs(); void add_created_tab(Tab* panel); #if ENABLE_GCODE_VIEWER - void init_editor_menubar(); - void update_editor_menubar(); - void init_gcodeviewer_menubar(); + void init_menubar_as_editor(); + void init_menubar_as_gcodeviewer(); +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION EMode get_mode() const { return m_mode; } void set_mode(EMode mode); +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else void init_menubar(); - void update_menubar(); #endif // ENABLE_GCODE_VIEWER + void update_menubar(); void update_ui_from_settings(); bool is_loaded() const { return m_loaded; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 45a1f6ea82..09640ebaf4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1369,41 +1369,52 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi this->MSWUpdateDragImageOnLeave(); #endif // WIN32 - // gcode section - for (const auto& filename : filenames) { - fs::path path(into_path(filename)); - if (std::regex_match(path.string(), pattern_gcode_drop)) - paths.push_back(std::move(path)); - } - - if (paths.size() > 1) { - wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."), - wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); - return false; - } - else if (paths.size() == 1) { - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { - plater->load_gcode(from_path(paths.front())); - return true; +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_gcode_viewer()) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + // gcode section + for (const auto& filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_gcode_drop)) + paths.push_back(std::move(path)); } - else { - if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { - if (plater->model().objects.empty() || - wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { - wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer); - plater->load_gcode(from_path(paths.front())); - return true; - } - } + if (paths.size() > 1) { + wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."), + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); return false; } + else if (paths.size() == 1) { +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + plater->load_gcode(from_path(paths.front())); + return true; +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + } + else { + if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { + + if (plater->model().objects.empty() || + wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { + wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer); + plater->load_gcode(from_path(paths.front())); + return true; + } + } + return false; + } +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + } +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + return false; } +#endif //ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER - // model section + // editor section for (const auto &filename : filenames) { fs::path path(into_path(filename)); if (std::regex_match(path.string(), pattern_drop)) @@ -1413,6 +1424,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi } #if ENABLE_GCODE_VIEWER +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { if (wxMessageDialog((wxWindow*)plater, _L("Do you want to exit G-code preview ?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop model file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) @@ -1420,6 +1432,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi else return false; } +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER wxString snapshot_label; @@ -1970,7 +1983,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership q->Layout(); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + set_current_panel(wxGetApp().is_editor() ? (wxPanel*)view3D : (wxPanel*)preview); + if (wxGetApp().is_gcode_viewer()) + preview->hide_layers_slider(); +#else set_current_panel(view3D); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); @@ -1990,33 +2009,38 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) #endif /* _WIN32 */ notification_manager = new NotificationManager(this->q); - this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); - this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); - this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); - - this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { - if (evt.data.second) { - this->show_action_buttons(this->ready_to_slice); - notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); - } else { - notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); - } - }); - this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { - this->show_action_buttons(this->ready_to_slice); - if (!this->sidebar->get_eject_shown()) { - notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished); - } - }); - // Start the background thread and register this window as a target for update events. - wxGetApp().removable_drive_manager()->init(this->q); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (wxGetApp().is_editor()) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); + this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); + this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); + this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { + if (evt.data.second) { + this->show_action_buttons(this->ready_to_slice); + notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); + } else { + notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); + } + }); + this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { + this->show_action_buttons(this->ready_to_slice); + if (!this->sidebar->get_eject_shown()) { + notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished); + } + }); + // Start the background thread and register this window as a target for update events. + wxGetApp().removable_drive_manager()->init(this->q); #ifdef _WIN32 - // Trigger enumeration of removable media on Win32 notification. - this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); - this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); + // Trigger enumeration of removable media on Win32 notification. + this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); + this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); #endif /* _WIN32 */ +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // Initialize the Undo / Redo stack with a first snapshot. this->take_snapshot(_L("New Project")); @@ -5384,7 +5408,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->set_printer_technology(config.opt_enum(opt_key)); // print technology is changed, so we should to update a search list p->sidebar->update_searcher(); - } + } else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { bed_shape_changed = true; update_scheduled = true; @@ -5628,11 +5652,7 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); if (wxGetApp().mainframe != nullptr) -#if ENABLE_GCODE_VIEWER - wxGetApp().mainframe->update_editor_menubar(); -#else wxGetApp().mainframe->update_menubar(); -#endif // ENABLE_GCODE_VIEWER p->update_main_toolbar_tooltips(); From f58d3116bfd31c78b00eef3e597232be1b1e8e2d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Sep 2020 11:43:18 +0200 Subject: [PATCH 431/826] Fixed crash when loading gcode files saved with older version of PrusaSlicer 2.3.0.alpha --- src/libslic3r/GCode/GCodeProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e9264dbd4c..db69f4f0ba 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1434,7 +1434,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (m_producers_enabled && m_producer != EProducer::PrusaSlicer) { + if ((m_producers_enabled && m_producer != EProducer::PrusaSlicer) || m_height == 0.0f) { if (m_end_position[Z] > m_extruded_last_z + EPSILON) { m_height = m_end_position[Z] - m_extruded_last_z; #if ENABLE_GCODE_VIEWER_DATA_CHECKING From 0fde670fd654b794c49b462bd140c48f46af6d58 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 11:49:02 +0200 Subject: [PATCH 432/826] osx fix --- src/slic3r/Utils/Process.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index ab5a9b1e9b..2301cd2504 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -57,7 +57,7 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance // On Apple the wxExecute fails, thus we use boost::process instead. BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; try { - path_to_open ? boost::process::spawn(bin_path, into_u8(*path_to_open)) : boost::process::spawn(bin_path, boost::filesystem::path::args()); + path_to_open ? boost::process::spawn(bin_path, into_u8(*path_to_open)) : boost::process::spawn(bin_path, boost::process::args()); } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); } From 2443b7aaeaa7e1769fed84fe2e5d57572ca85337 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Sep 2020 11:55:21 +0200 Subject: [PATCH 433/826] Splash screen for gcode viewer --- resources/icons/splashscreen-gcodeviewer.jpg | Bin 0 -> 135897 bytes src/slic3r/GUI/GUI_App.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 resources/icons/splashscreen-gcodeviewer.jpg diff --git a/resources/icons/splashscreen-gcodeviewer.jpg b/resources/icons/splashscreen-gcodeviewer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f170f390c2b822410ef2853041ce58563410ed25 GIT binary patch literal 135897 zcmbTdbzB=w+cq3Zixo;K#jQn)yW0gVZY{2%cp*q3Xs`-};suIZ(O`ih!KE#(L4!ll z;Fe%P`qKNk?)$!;{@y>n_d9pb%(>awo#WWqo$Smp`8Djng{*Hg=!@2c8=Q+BGZ~Yzf zGu&+47{R}mU;n%wzhT~#_-hvM6hMH7hmUuM03RQpkdT0g_#p}L-Mhpz56DR$GSD(H zGSJe~KVs$Mc*M-ZLQnrhoRddDP((z8i9=FWLP&;BSVZXeNpJ`W35o9#Q-KE8gC z_aPrb!#;*b#3y`COiE5kP0P*8FDQf-6_-?1*T8G*>Kht=w0CrNb@%l4jgE~^ASb7$ zXO@;%R@c^18=G5)M`+CP$?4fS_BXHJJpXO~L+lS;WH-ES-M)=`8~-;ioLfFOj!Sm? z&SOEm2MRCnt=!3(h29c8RE*21{7%Rstb0IV?J+_`$ttqMcKDmxznJ}hBlhn97qkBm z`!BB<05LAkP37T|0ptN_u9;yOwsPOpDM!Nxnjc?}##4&QrN`2&x+8NA7Gka+9WsP| z4vDQ9pqTQ~-|7f_NDiDhWa;4l8BFSk6?=0hCsCDzdR<}?H`YT3*y2Cx+N0}jB!m0~ zSTc&-;RP<$j0ipI=Xp?CEBo0an0S(UW2A|t*ibK2S3m(%p>&{Kx1tfPz_$x0MAe~_ z#59`T=~}Dy@$H~vRti;QqA>Eb&D?Gb&2p^pk1E`%j~nlPbB;}nZKLMc+*DKFYd1$W z8Om5kJShR>Mm+hC&)0-vM5BY@8kAdV-(Wp2FP^>Q-^aZSFAKh}aN_@QY}`H?!#I;9 zLnfZE%nhFPt;pMP#k~)nX$6irP|jp{IM5k0AzSv)tmgIa1jYiAx14zJ4Tw zog6g9bY7|jvInmfSAbh53{|J_c7gb1^xMfHS>;+*UYx?-8O*7htqk$9ick3D4&ntr zkt);E&X4P>*m0LQljXR~5BmNBgazoyy_eJtRi)nJx<{1#;+kTk7Q27R+W2ttg=-A; zoN`Yej`J1aa>IHC`S9`AW0sG_53|ZPWMp4+)EM`k$0;zWFDO4zz`spDNs>1Q&lqky zctg>eh+K3s#0Gv%GwgM%e{-~W)6C>u~)77j32@kbl zP|CskVyNRH&Cy9Va!>c0N?EMs^~{(-L(3bc(@s0zZA}ZG@3bM$KMgRWm$`(KbW(7R z0kK`pt-|T~jK|X(rmI*mLAsDxt#OOp)KfXIa;h1U~?>kap` zhGl<=nl|z+F?X^xC28kwYF!WfpZB=g#cz2^Zh$Q{q!vcAGIc&_#Cr$Q?AD6od@;n2 zh7bDoy_^9PA6vaJ2EFdKq857dSGQ^V;k{Cd0cpy=Jp5@7IA1swAZj|6AMen$JuNt( zEWH=GlF5$!`IczF#+w7@X|ZDWlq+Hh2tD*sf4Bc)c(20#)sC8Pfo<2`005zRBs^Oo zx+JaPkJIj#xBWeeGvk=_!6)B1J0Ud+*of6#tEv3o+31k)RigW{r+qgIEUk+(@q z+xJ(sPgS&T+cH*EO3IltcNU6g_ooJYhkgNme298)#DQi>?~AzuejAU7C^^ztPw6-1d0*n! zQMXnL|3F)9r13PS+$t2xL8D=?MFa*{U%d-{p&dk69Qn3fxc5g?uHBOHRl=*0wn9AF^oaw?@s>N|#q}ERHt5daQKuc!4-Rqsj-@8>gIG zsd2#=Ap*2Y2G|lGND(7Tx2&1MX=Sr^IHp#@yYqypmT(q=-IqP~~T~ zq8;=`FX`lpy<~T4*VhVc_||FWvy}8NsNOJ3lD?8B1L{>YBy>RXQ2Q2r!G~y*xyb;5 z>#ft&!|IBCSyD&xvj=2vdCTRAEO%pXDJ|e)Ob<=DTFaZ_4@oPhZ*H(*@P~&l{<`yr zKZLuT<7xc@;QRs%e*phwJE>KIE*$)O+sae{WzGf3&{N<0rdTPQ!JMGuZ<2R05@&JM zn&?JMkSmyNDtgyA{uT}1Uzl=eSdDAniw4vW!J33|J@pl+sJ+5z4}+G%JMPpC)s5vl zYHmLo(}fG?k#J7LtChP5UEelZG?m0f}Ib^kEyTW z9__uO;0-Q`qW1e=vYS1rDBb6;lD*oCqKTFmRP^Hg@M>7!N!PpN#-R+}datps{#+&R zd|uOxUNR5Y#*lnXlsWDewM%AUWzPCpC~r`TY$0q@>{jy7o+Y zzdb93gNx4B6wx*Heab|Baq}$i{Cd8jDx3}OT2MZL%5&k--+vzCKRFDshdP!%Et=W< z1=u(hiy5K+vJ?hbDV#$c$#VD5j;ti#6PP`kL#NF)Xc>3P+n=v<%Lb!Uu|1efSDX+NzAR5B>CzEAa7JW8Bbg-b<_%UOg32kNY-B` ztzFMwE#W~&r`cst7!2H?XfPq#c%`G9NK?jI((l51)F>FxT&u0^Se^pIalQ;Fti9y6 zD$psB+=D zhkTW7s4$tJ5gEKSjnU#GxAMZJOz)R(GGC#VJueT|2-8d&rQQoS3pa_BkeCtf(hkIj zLJl_cN~Qa>7gQR;JFPaA^gc+pqG*&J8d)v>-|p)B^WE#SQshx9*&PO2d?Vy~;To9b1@r{c?3uP_Fx*FZW7BNP1#!K>+uX4;8^>vaERUL~HL zlX5C>)4ltU33)nW^H-24tn*kt?7{mMJe=FNY;Xg>h$P?();2W>4&AL%ijI zg(Yfs#2!Lbbqpo!<7_e0WOmLh^!x>SNhe0$Y~h1`6S{7SoFd81J`k5Gwwim*$|HwH z7>n>_vmmK=+=-*dRup9*nULGMfbZ<(W?SMvu+ov-dM%QSujH`=>n)rUg$K>a6M4N* z#oTUxGymeu0$6I=I(G0@P10Cvr~I6?2?T;05$>uBNMC#A-9!D7bSy3XM->elmCpnI zSqBG`zxF(I7@CMynqLH$OoQrPt2};Xs0X)D__igV>8sDWIgu#7eeE!A(*Sw_HSj#O z?6)y8uD?GhY;M}h_IAZSQRP%yvfF&QN#}OASaiKu|$| z!lZLKnS+~@n6qG9N(Pg*JJ!eudWKy-HH`25!d0YdNGyp9r3;36bCy?Lwhf?LBAtfg z+Ypzr#@U_3NVkWS3;I=j4Ct}Cm2Sw-V`a;lws|I>c=Z#8%>adE!KG4=<37w{Ib+F` z;MA%zi$BM5)hl(8VnHTSCD&<_;fiD?$5ncNgK9 zV3{0?23fHvlpK7cas4Zn|Lr$Dw1#Wt2ic*A!RC{+@z9u%t6&ynvEQ0`DR%s5A3l{s z_eGL1w}rcX8>$6u_qTI!4a1G)adR`LMZ*tolanOZzL1o8fJeHWJqqhC+C+b=J(!!H z>5^OKDa+BRC9D83L>O-c#E*@@&lo8QHSS1vVP~sn2Tlmfd2PNniYrpRwdpT|-rXUE zifa5d;%P~~(HoG)^L#Cph$oCo;g}+EX%SyU$^a9$g||*(*mJAAO{i4}mVrjLP<@x9 zsvQ=&9m*ZPE|e`eeSo(EcrEyCWucj#-b$N~EOB~s#Yw&F^PF>R%B$Rn9f@}lPrlsx z!-*hJT2JUr>y$%Oyz~n|3Nk(ZNh_BrJN9@j?sZAvT}FJAk*J8H$hiRykNX?B+?LoS z`RGgjd|$;$jg}PS#SSw_FP*ibnIuV+EedK%UQKICD}VP=c9{fMKVxeKq(FQKpznze z#gA>&DK3YaoVn+2W5;KbzD(-U80dFYxx9FTZ*r6)tFFGYMYz;GEOMFB>s6vmp;AE!c^a>) z5h9zRJgWe}_8WQZ75BW1SE)+wnIiVMYlYgPID?Yy4eoJ8rdKSbZ?cJU3;_~}*YlW) zEv!L)jADHJp>kJ7DE}(Axve5Do{z@`Z|V`6o>BN4#{ue~C^W|_7|a08 zh~tT_c|=WrLAq_Q>%#vyoxG#d*yfQzsSB6t@e>Zg`?ue`3xdxgJa>jDbPT{%m1CCe z$q=WO$&C0xw4g=3tm>9O?zv%DxM!0I+{Y(Ec9gl&x>E;=$)Ef>qpv4^izY7sd&@iF zT(CGmA=u?{b%r#H8Y3ag8wnQqOg~KFbu}fnuuTsMQ$OQ;0?Ygbc$!o{!h4depQ!IO zqd-yZQaIDSOd|iBBYnz@vu>~I>Kio)&Ew-=pVc!8w!*wLAdv=CmX z8fAZSwf%fG$|7H2#uxuAYz!)7c% z!}ONru$=h^f%D4I9E-y>L{Z@q4SWkOG~Xg(@qHjc&A(U2@Ffa8--JD(C$jOCn(@`t zQ~Syo?Aa#19{i)>wXfrF;R=VhTTG{3g-D%gf+z_?QZn$^-9kyM_-+`^qw3?Fun>r$OlLV1`= ze&Qs|WSSCci#Fzrb@l0IF}RE!%aFJcy~K0AH?Oj$qj4!d$5d3|_4O>kiw-PXn@ra| z{6G=4nYIfj4Q*v?8P=6{VJC21iGA{2teSDD1-x(Zplr)E#*R_1S4Q^m!QM{=H<1^Q zc`B1Mi!=}JNiMg39AQ)Q^w$l0ORcw~0D)CCs^|_&xJ9Y7dy0Ami&8uGV(7*c;|4fZ5 ziQHI-ap+Orsf7#1pG*FaRZx6Hcm8s||)cF$z5luG!q1xH6xd22lS&I72TSt_AWP(6nw zq9YNa;(IVG=Vz@TiA^xo%KfahID3It-0_X)F(7my?uU zTyDSBxwk#(?L*~JQ$a7Cq$A|f&3)RG{q(q4<(+Hd?%jU-cWXaz>*Mo_t+HfsE}iz2 zVrogOhg7f4nrLVqx-oC+$khWT`AdDHSsMHY5^f8A8sTz~=pE7~(bKx^>!Vlyp-`5e?yi6-oVw*Zzm_)KqLF* zi=~NZw z%HG!I`vpMv1bOk5HGMAP>!S7LekRir_2?pMeDml09fC)f%#|d)n z7ho-4N$`3TaXpAN7)dOoVA;#?a~62?Ge$*p6XM$B4kg&zeIO=ONbvcASO#N<1Kcl8 zRv9!Z!$eYBoJ*0#nF8G#FD){7^jce6t%u`Ha3-WO5Wx;A4h+yMVl{Hl>!4hlpIzNB zE26MCp4T?ciJF%*Ruz2n#pSX335^Ur*l1QQ^s(41NjP;>Q-9Fm?rq$ry~J;*h7HJa zLugg_;|RlXs|O-w)ygz#(NAC2g3*wR3H5xN2C|TX%G|m-A6*^Uxe8%MvJBK4`HpO4 z-q^R~7axo-L+E3_$OLnBMkP1QHdnoS9q7JL=gI2lrP{TUSr+{C*?h6u`E8bqsS06J z@7j>SgErHlv_0WZ2^DJ=PsYM81_qBi@ii^??3?XORCcl^*{~Vrb>bSC@$WGd1Hf(Z zMzLJ`8Y{ZK`cD);(ZH`*Tibx)ce2|Y`UkK~>>H%JAj9?M)f4P!{Mu(vPCfV29B$$M zjob(fg7rym1mI6c9`qnSx~MR9PhNFZC=)F1M4*H331|gPgny&ExxMfz{<_c%UM!bU z4CoG``WaO4B|rH7W$`!C{p&=H-50^W*UHJepKb<}R^W)fq4+c0i(i0(+x(}9HT~9E z(&EcvSn&M~(lvvg-Ujd*{(c%R^fVS_Qc=u*N{1=ZF2X+$-@F#NN}v*KEYIwfkG48% z)1pm^u?=C8niYDo-QbQU-9FPT|BA0Vi+;!eV)_;=RX^%Dm&`h{mgkK}$D}?py5_bs zLb(h8AE9D0f`zpYNj_VpZS^fEJF=eVDtu~K{RHTtx%OhHNG+sLgMByY+i_sUnUr~% zl~TSj$U8*%A|w5U5pPF0Yh{-?bJH91n1>EDmt4pKs}nV50(#+Frs8(i&4=;L9+KT| z$U42hIH6B{lPL?>UeY-wJyDhQ@ZF3_1nV690%VJ^erA1nIasN(`q!Y_PX1~pMpCwh zg%VcWXWGp?$H{#ZwVEYOyLBn($nC`BINSJeE()9>hR>8FuMS@hzUO%Qz|53)aGb0| zJTz33M)>Ob9wb<*r2tl&?y_o@G^4$2yqfSv{u2d4s`tl>?aDFj_v=wuSJzxldC&2F zg6pLcGAF_e19h`2HQ0J++eT=cwFolG?`1LzbkwZcgEvlq~ zrHN=4e@oLgVN+A6e*ZZ`_XTQGorQN)?9%C>)_GW}*~K_AuIVe3w#^X!&2$|D%oG!om5y{%yE-aDN-# zjV^ZwAMZvmy>SA|G&cMjg0so4e>@|q`%p7>$k}GZ{hU6yMO&H z)ZNFufAb~!$$xYFPedhcEE-5sX2BcP;x1XNAWJTK48JXmRtbVQ!*^1?vb zBWOmAb03>je^}k9XJEy1+cfe)FiHm>pv;c$-QXxF$$wyi^Eh6H&`EI!$BsfsM zRJ~q3Yu+=USfv>!pvn9)#qgzK?b`=j9~0d|L7ap2vEXd<_GS>*N*jzOM3WO_p;?<@ zcFZJ>AyqufE?Yf7^;vbh2)~&mV7GzJ>>Cwz8pmu*n<{EjzIlBDCi{)nN$ zMG;85n_2ys)0)e>DL!t9L94U|$N+_DE=|hvoL*l5?V!9`@bg4kqPqg+KD41c(xpyJ zGkMrU5rS*}B#7i4Y%qS9Pb{#Seyfuzp|oxPTq-%=>h=$2!VpbzUYFZHa_$uB6D4)a zsrQpRrnNn3EnV7`o&l*>6BU6>7c+JO-0Ws#_@{-i7m~6NGgARG!CcRVIr8yX6=C-+V-g1{$6MM2y%g<^?=W2X>Z|ELosx>N|;;T`ix@awT zW=pB#{<*wd1^yIjez~7m`Z^KiKmWF_dFQ-zE@kJd+vIn8@ywk%5`LF433HbOopeKP z>M}c_ChZ`+{)R4qVzJRP4N9m+Ke;ylE%k*AQ`JW6ZT8luHl}L6cNhI!?{1&phd+Nd ziZt7jq9UI<&89mc3Da|93FUR8)=GP2A!kX9Y2#XiWbxo^uq?8_{%)8+({K5iNtT55 zA6#&#uVY|*K|}a@Ak&430&-;z`(_A>Y#@F>Ljn4v+cZIiGC-U^*mQF4C}t@6{?s~9 zkB!G)dc7?d!y!KBb{>G#V2=W2-w7-Dc6^m}K6LE2E5l&+etpZZTFHi>)Z_bS#83`s zdX_hx?el6)Mp9irNE}|PVYV=egtuyFCU-Bg#b|!bm@WSx-Ia~-CdsnQYN6u(ZmE0} z9`ZRtLSJ;&h#ct?%zZrAuTtO_Z3lUI;=Yl|r$URdV3!39H|QK==Pb{#5UFW5*KGJ1Q< zBL?yG*`}-Rr?(;lov)O+Z~qv(WsqMpqpe}K+EVCJs$s+b-m(te8eHWi)7`sYd=_bb zrruSpLQ%Y2-rkF8Nf-#U2|%4dd3M7HE5npG-6h??>w#2xYj3bsZm5rvjKO`W;3-TK z3ux_i3pgOFX45+$$=CTGaWFf&w{(Q<0`>N+D~0Qo3WWU4obf;}D`CZ7a?{mdSkhkWm`T-zO2A zyOj9BKGQ{qPIPF`%Tn@Kc2=eBjteZ3EE_LzWC?uTHY8dS1fJyLHbfZ(F9K1U(oGa*y`Wm|yX#kX^4z zyIgp@0hvKAbR;}92zjTM2ok4&-7V?z+cxIBs~KM1JhKSqjs)3{whUUYN53~p?`SdV z2te(~M6p+O^b&$3wY09<5-=ncDx=S9rTszH!W-L7&GdYypSMiT-`8fV_b_qFEJlyW zGJW0tj2OG*C>=jz3>#+b@4?z2{d#qxR=@q19--AlLsl40Q{SqEoOWI$Ps)*5$9VV@ z&pc;BnUMo`Udt!sRKmTE-m3JY=x9Fv3G^mxFt;O5p8&Q|?hxE~c%3)o)pFd)>lf`6 zGYb`td)M2)KJU_aSjno3oO+WfoVZnLx`I|#PZu8L6f7%*#~9}XULr|gk?N-uktJeU za+VawmgFmP?}x_28_Fh1)@yrhdd(R3Z1AqUxlY;*rz$uYAl(uedYmytF)L}}HMrYe zI%}*cpnYd9sMU@jg+ww?vzy~Xz7S7RZ)NrbI#+0L}C zE?5G5)217B$DcAf!7CHUk|`6~`f>ui)_F=_8ICDJ78_ojv=DY-jU*?qY=gXhF|M0* za$)i$uKWVKu4@9Q_?!7W%~atcT#44~AAjt2b;968opd&d4@_cvA6VvPOwJ=5#bAuC z9H2b7qlehN>^>eT^ZJhYjt3u0$vAaE1{7@iMKBXzo9S~aW9+bt$~%b#kIAkpAMQ1| zBGI6_P{bA8&xIR4I?_xs|6y}8af_gCd>eFD@d**(-ysSfD_4itTKK1=6Z>%#Ywu0HYEC{7p12G%QPo?4SBO_WeWrOg(t9bPl0uwX@`N48Y=Wl9bj;+OnKL9T+IB7a!0L0CJ|Y&@9?2If%KpcQwI(lJ-g;ic^1|S<4wV` z$HArR5Tl&Qb5HP>e^5%4ecE?*E82~^#cppism9bge^7nKX(87ER;Z=ET9Q(zul>>V zF@=_0N*X(guiTB_EEN>UZ8g%a^=^5dc&{)KE$NB_3Ywi3ojW{oI19c z*>B$My?gg%{6vmDw~V=po_s}1@_2MIQ-2L#_LrSjQ1N=K-@z_9bTH3pI3;CT>FVh#)XT~0BXr`P z16;It89iqMQJwJP$NR~4Wp5sN;f}3eKbMqT z%lkcJ@ju3#We{nvudZl)3v}y22$J9}*KOYWw^{$X|BlsFDwE0X;5u>$6I5LX7Oinv zAi2bdX9Zmm2R(fQd9{71qr&5~kr~UN!44zFVyc~V2g_UO0ve~B0~se*gQ`pR4(d;f z&JE@d<_85Ns=fBbPok6cMs^w{&i5UbF6qY3e*totxGszkq$L6TxOD}$Z>A$Y_z2iZ z{13Xn17`O(tZ!~H_q+iB?lUWrMUq6CfybM(gJV|%!@So(;0milKS%~&GJ1d5Xm*hc zy-MLto*qSpm>V$v z%Wi86u)ev={Ota1t~9lqS)=;aFEhFrT9*b-q=WD7&Mq=pM*F*k8|aDyRB(lAZXx1xX=lJ~N6{Uu+Jp)@L(Rv)ysI6uoEQZ${md7UgL)qMR zF@Hy|EFc}Dk|vwnAz5b`U9-8jm7XS$l68<0qWLJizO&9_5ue2c$TF!DK{3!q4`2jk>|asnPS!?tF4o-|KRV}M~EK+7p9M8=N$eo>G^6C&rU zqj%lD_=YE`!_R7Qz9Fb~J$2JGNm6hH z4RQ>WXBd@Y{Ob?MIOI`NkM&Osdmk3D!za*6GTu2KZf&!pr1CcfotZYZnrcOIB?g0dbU+%Xv(``5cGbq zqI`F0Dvzh$17T>!&V5kO}3EmR5{vj z$?0t|xV_)o=T2L244Iz%;k*rwsfeF#%I5n6IN;fe*s~8tH0+Yx{;8?ctxv5RV3&qc zbksoaE^$0*0%V0~PRt2fkeR1}-|$O>KwW3TjJeK>Ug_3gC~CZAu+;7D=O5N3yj&BY z^rz#yAX?x>>FNkpY5e@dJHfh4CTl5IBEzr*4;4|MSRumMFJ1ZmRNTrw+i8?R1Ekvw zWe~_u>k`pr?HorFF1imwAUOCP%6%70Pnm@G(asPb@BnDO;<1KXYFKLwDd_jC|{BAURkzbA-cmqB~+6wE^o_`IP&*U((e1RM*gB7@`SIf+pd`zWDpPSmP-a=G= zNNTM)0-_WK^U^mDs;Vgp5>i4~M4^-;m8)w~nduffU*>hh7Tww*HjI;16EkRZ2?{wZ zw1mbp-J&WRqq%KIcH4gaTIq->$NipR@$`ALrOCJAx|o1ZDXAuci<~G~o!94!@I6MO zG0(uewt@T{-Ij{y7JGpV2u3Xh`gV$M|BJ!z+kN+A&1m0_QQ--!d4C~)AWoExoj4`N z8zDA2G454UlG{}mUuQU)Kr?VwZBfo>S^cfJIwl(Jvrmc%^m#uvExiCFn%AH*GuNOq z;YGMFDoidqUg46~t*mGA7thEB#WsB(uf;T)F~DrvEqJB%XFFC6+EU;v*ud={7j1lQ z*dh1v^(~9+B%_lc>y@0)0Js7fyG*7ID%<7+)g|j~1MOPPs(b&f1^;tFL+7@8to6sK zBfTzc)0;n-;Pe#sl>mij%i*)VHT!G@>u@&@f#$0W?IfVx&?xfj)_7t$PdxHS*N#1P zP8{Q1VX*Ao_H?aG(0K_C2{h}x;tPvAmiPrwTJsRrOgx-VrYswwq^J|@PS5^1Tl;-e zkVaxYnM!3tn@3vyY1mJ#z2FML$@=XbKkFqv)**|H6$#%_ANLVm_s_Cp1O{Rk0H_rv_~j%^nWv$3*L;nfPA zts}Ht-FN4p_x(1Nt}i89T(*B)sRSGWE7H+&(Ul;KQ{Y|n+_}K&`sX-p8-wnIv@si> zL7?OyCM#)i1p-cJn-KS%Q4Vw&n8{h!QTmR`7%G6dImYryHAu9PE*!U!pSWJF)idg! z$^^08^Z`&oFtiK`oT`p27Hu2bat2DzyR?a&TF9e-Q2*7Po#}?Ox-aYJO^cT^+my~q zl{2fI2+_SiY7Y=4+4-PjqMt4p2-$a#v$U@{1ftkHB)Owe&Rfj`mt%O*GL6(%JV&oE z4&5N9Qp3r43zF?&zs8n*s>;*mmKfg5;Owc{OR9+`RP;%ZI<#_k!C~X3I=v3IgHU0O zaE_EX>WyfHz*2wwtee+>n$zICgT{T1;tdPurUfm{Q1!#13URfCiS^69kTbDYx>GZD zKj(tI>dz515ZqpHt>00Q^hH}wRWRsmZQn*4Ryk;-eR+XxFARuni`hCvUo%aveYI}v zt*F8xgdO*<{rv;(!^)O}+_u;Mq{=KP<-5Ua15dEomqM7ysaV$(L#DaGS@o{=NZ7zM zO3KuX$ziBnFQ`3oI+Q0RiZa?oFRysKB{(vMD;QnA-j*{pCBr`+NVg=rA6MIW z{r+b7Ti;WrH4@YIc3}a&Ub9}y?O|%$?`%nq*uT&l@#PXW=c7+2R?~$TDVW zbO!Q|X5nrGy_%f@tfWB09AwyH{1-M6qV_a{cW$- z!ho1{T7$1W&Ne@ee2y9I7sW;S&N}Stpyk4 zzA#(eK<_TtDuIHg%tcH7UROhD+uQc!XUM6w=6#d&)xh`V2Ljl<-q+PP6)8uux|?P9 zWKfJgg=>*6%(|TcRxOh0+KjC~nXxGX2;{Yf!Oa*unuFUD+l zzAVrI2LC+Qtqz;ip5~dk?8ZB(8=65uq&|ZW99LSoHF5~xNzw`e+NhZom+SOS=S+lr zH0l`6B)KKJKn1NEo!y<-KIXBIdyD3WqAQ-R$mHy{y1dI(tEnVfopa!@pNiIazX7qR zt=+RMY(@v=QO%-h%+pfSm(qQ*`4&R1!#bxi5|`WAn-Z}}7u`;1uVbpDWe`)&_2Xrr zR~fhDC3aSMs8&yoc)rxsx}DM`x#L16m;<&IY_r-tLi*Zib+&rBAij-WaPAM1IOK`C zj#|mi7E)g23rIa{DS2t|Y=*07mL`*=e5cr+=e>)^uD zm!(=3O~7DF8Tjg$Y;|#V;iQeT{q@sRVsBURxa+*bOA)Oq7pKBB?~3P(G2p!F5g+b_ z=87@t*rBFYzmqJcUb%`cTjq;(-mKjij8uk&?$p_JSmD57YukKt2i9!xz_)HbNNd&A zu#?-mG~teBN_o{vae&DUQOYhR?EutjXZVL=xTE~`w-Qc!C5#W^b7+_U6nGkcmi}$kSwUwVW$(2toIi0igNngx%MVEP%u1YPfikVsu!qL(#VF>ITm5v_nW>8={ zqBL=4+>)}Qq7t^XYi2bC@66i17t~fUydn2=lcB9||Ga%So1|`TH0Q3!vR+uh#l7na z3Y6z5-f5nk!l~AnQKU&&zr4zMn*b)r-@cZ&e7sdO!hQw980K-)b8+9hw!ybuJ9kUV zHavXT;t*asGnO_&+ZjMrzq*g;pJ^Cf*kk0_k~NE+o$&7l~~z=&OfS zC@x4*T0se6-{J3(QcCpaX*q{~HMo(U+h0!_b)CF{c$^(^1zwS=1HZjvn{M9M zM-y0PkygF7AYOSK+#A$g`Epf&6xa(Bk$k5G{yU^?aot5bb z7{_KK@}`!gOg|qiywvOTb6-G_90gcD9d@aPjNi1=cSo?$iGW{##{|&-3$gHSZpY>8 zr2h1o3#m-?`lE-&^QjpI+k&Q|N-V&iKJ|zpxg5S{kz2<8a-vvhx0iNURzb|M^^wcV zj5R2n6hN4S4Xr=$vI%X@WU`Nq2c>r`U+bM*LHlRdPsdvQ!O3^HN7ZLI zpXECCwd8Sx@j*<=&SXLD7{yl)W+G8=ATa*L5A(t#O9Ud0&4d~fvo+jl=uN9 z(I=@dO`UWyTh&f7#KlX?_KZTYmXhr)4jKgMw}bu-iA<#G#_)=FFW^qNqksQJJ;w9e?j8f%8eE;B2c;oPS$}z0gXJ zsi5g}LzsNfs7vL}V!7*KopE#Xd>iZ1+>o=Q?QRTbsx&Xhh`KwSj#!~{D9^OBSwMC{ z&(<`<306u)9J;})`3oQ@e*EVXMy5imW7j=~ov-U@X??;xYWQJbl0^fdmip>LktI{) zWpEpsaXprI*KbzZD&8r{j1qYS&)Q3(xGQ~{b}aieb(p0;J+s2VSKjgSmhXb>acn$q zw4ge71gvKK_&Su)0kPp7o#|3i69sH%*k%!zI2zH+7<<_pqW=*}j4xc#=QsaxCc%t$ zM5qPI)`aXzWS8e~a$9=sg5lOf$lePUYxW0McN{EoLtD3Y4yXRCkK1$Cj&KUejPK6TJ8k@tX|SjCuVVJJhi3#u=ekf(f1((n=sdQ~QdDJx-C_ z_$o4HO_0o9o%piPV?jU2iAAQ&45#L?MW$2L*r92kjbDrB&Qy?rp7?N3g?u-pZt~C> z1o8j#!V^3d9@v;!I6=ZEs;_P>j_;(?Yox*UP3~U$L_IQDkm-+g=)wBW;?-(~5|1G< zV_G82O5>Wm)RDEa7JCV$l>~KJQs&nQYjWL{8{Gt0hfcAWw(QN81pJHo6?yz^Q(pGj+tJM#-<9r%#kLU`7oP1jvxD2 zd%iE#B{nbS98H#@GsR^gZ7K!C)>ALdSKf~D-lxBrGsAL zTdUn;M<7$%&Q%@JJ-=Y?QRUj^DENb1)szHn(NeB>P1Ujz4N+*I#gCb>fz1Z5Grpi$ z{h$8w5*PBx{@iXFW0`J`-N^;Gd7tM;DlXWMSEl6NHEvNY8zVQVIBl+to<&m=t&Ly% zW3IphkfA~G6H>F}>b3789X%-#Y&8L`$U#KyR}>3JoxhAFF&H8d$ou6Fmv z$lG?7Pxp(?F>$f)w!CCgf1EI$V}hvihFX%G<%I(fZIcJCF9_nt^T-s`#bI}?@|>Qh zJ+GCbDj+PN5Z~2m{RM!w0ojogN2H*zCa06Uy5eyk$%Uj%zcfLoX-+?hHBCKJ?oS_n zI7bP+9{!qfMA-# zwWeOpU7)I$L?+ z!6)+jZwdb*d&C-|oint9jg6gce#g<*62m9WCyS8La>`|SwK;3PH`vfO87(L=ZOtx7 z84=%>wlGuqY!j8nKzyPpix8{5>vkt|^M#z-+Oed45~}0?)ep>qL}2k5b@J$_`FFKE z4wrV$E-i{Hfo6N^7x=QTE%-Rn0#0-OfO|7)1t1^z7V_*~>`iN_>J}De|NUpP-=`$P zkI&u!)Nmf)E2EABb-MR}hO4i&!^Bl*#(Oa$d{tJt5kk{KKlkZOrLIb0Yu(3yEX>o3}r{H!;k z7Fi36b?{p9C$&Dk>2|+6_WTnoZrrLKtPl<|6*6%jZ{g(x$@uXe&iGnbxW^{|Yo{u- zlRnpVI!47j8?Nbe?A;dE@7JxJ6+OA)?%Q5-^wf^a#(%gtob0B(5}Zw~3jaJgI@{b| zYSj8{uUQU}b#W=L?Cvjik!CI~C)c}vahX1To)t%C70aKnw;|qLxVtsStmsCv?|Lk@ zmAh%OCh|jn-R^Ve2=rJoqXMi4znt6;)XPrqTr{~n_!Bq>A*@o5QGG=TX<#N4%+cVGNqU-7KqtJ7vO>}YD%h&GC`e8|wT4p>hzXm&dn0!6f3 z1(-MjdELy+I%}Owfb~1$mHlSs5r%p-TVmg#udn15hqkvF>(+k$0hrJ_kY!z#{?IAE zC+YdKA0SS^vs7|LyWEe36k6Qa*~bR2Qp0ntQ3@v6~gK6|#5q8FOCxM}x@&!?Tmx=USNM&~Xh?PbeiiJ6hfu;?T|Zo8z~X-9{6 zL7#GIw#sSaIe3>XloMt69&Zz#`v)pO65~fSV}wvfymgwRx+3eEX$hI8o}Y#Vuk4*| zLpLk%6LrD+ntIPu%HWE&U5tjCTbm8;!#l4Fd)1#!M57$ciq^+F?DBb4pCOr**;l_t znj!6AXuXHKn!t6KvDp}h!vT|Gw{4WYsTFv%>(E&*9Cq`E)LtUnoLJsO2faa=3UD+i zS;u>J^iN=F7{=0^7D~xee?&cTdI(B#3`9y?ntB} zFYSVF^N0|yHhTu2^~9@GJ2%>aNX3UsJnH0%*7u8aOp1wy*#&`?EwB!MTCqSz{d6hM z-pJXprk#ybB4ti7JTCjG)zMyX-N6NQRa&~#zFsCzmyO(A7|e7;Jk384y3VAM#BV}A zA!4cyg%xxP6sj?+C9y}m_S<|j7&&CAc<}NMbc9x$mV#R&ab}9OSxK;nPAIHUlbMU1 zwI`DtMnK4uNTAR07lk_7bhTqmtsGVz*3aNVBZhG~ZTR}=xyq}B;rYs;VW@a@E|NWU zk~=UG#iTF+$=Fq?=to}6sVC3`ObJOts-B@d>!g#1uC7#>?WuPOYHCt+$4dpm<2)ae z*?DEIbO-$cwEuw%z#4wck#r);d|c`@wQgL~6IIBrXh24zpZ2ZjV;?~hJGtPqjWlak z!rnE}7a{5&>zaM5p*h57ypRAt!ZnbQ(cI|t3L}ENq7UhCKZBlkGtFPu4>nk8Ktnv` zK?kWe1Y_y0X1`ygZVQ*{r@k`efI-<<3pQ<9xPXa@oCnFnGJpMn=%yZL`~8hOa;R&D zLzu(!4Jb3BkRJCnbtpe4RX&J&@Fa_kuy>iGY|cbKm4a7?$y)@TF$KdmbKN4R+uO_1 zJ$r>5P56Oy0hhw95{NLcSB;;EyUb}AT1*BLGKcs74_j{)*VYqt4O5C!+}(mV#e;ir zcL-J-g0#4o;x576-Q5bbK!M;8g405=7I#W1(2wW&eeccxeQ!=~&c&KJJ2QJ`ueJ67 z%j%S7TpJ{njI0pB+E9%^mfC|aYlBX2KG^;T$qQD8B`KwKWNdD2wQ4J4&b*1q7k0J? zpvgQFr+Rv*#8e9wxH>l`M}VY6=x3gBP!Dp+t2f|J5XbdD{q$!$l8JJGhW&LCmCx6(h4cKJ4N%?KLbiGho1fCf zTmOOcKcDJ%(yix66%)3~9%?p|Qy~eM)zDgi7zO|UQZlVS#ugf|Wq>tQ^s@^Wx%UPd z3!~?EmDu`51nj1xt=ExJ;(D>U`n*4nKd3Ei2RzHmO0MQJ<(&2VCgUoXI_0%O=IEst zzim%TEcMrU2N<;*=FSS`>a^-Ev8~gqsr!Ow35ukmKmHe0D*Mt0rFHdKI3bHI)|9#? zD?eL08;hfioHH?TnYSR?ke<2$w+56r-cTi8d|_PjjY4Cv7`h%)YF_8}gzLQVgt&L_ zO!i;1VmDfX2F`}g9Fddz{RI~N$i1I?hsh)-Todg}+l$@VO?*PNY$>6dKsh#j#l_m7 zy(15q|6m~o-%m~jI;Jto#4yM1QN=KOX#o~F)&PL)lnTDr)#?lB%;f5^mFy(O&#OEz zl|^r5c`xT$bky9XGl(clXLx?_!~^tr>f)+y;IS|4O+`*|`OUH1G5!YvCmbXEc~JjIfn|#Ti+w z3xsOwgO_4s(w(5VDmZB-`w|4p+aLb}j5YS!GWRzjoB9RuT1$4NoKCx(J&CcZN$p}n zMRuljMjf!!zzM>ss-{4rz-`E%pUdQ^s;`@#S>9S%kNXdW^QLzr=_l3=1MqwAhmhKU zWW49hIOvRCRXE{W%h^IwL!M>P*+mHOlx;ey!C)fNB*L(j=XFm9ltRGdIJ4Ts)pUKN zj0kc=&wSu6^B-u$U+=kR0!_$C;gHq|7;9`cv+0tq!P|OPV5zBspqgX(Gc$rJ$CX4? z&5&!xAUms+sX%C^k%dz zD{V&u?3)G^r%bve83sLlYASuLm(E^>xyV*W#cGA}c)dsk2MLz{*Myx7_^wsHjb(G1 z&{yMNR*5kImg0=Qtp^fz*fKd*s>b=~rgxUx+jlT=kg;)<3Xni*$}^Q?T}k&-NA|7? zeL0upQ?d+gK0erMJ6cdAb#mogizy0@4IX_=8n(D0BakeSBV08^FGG=D=7*(8wcGD@F zHbr(JM~gNMu4&hUw4%e?+X)XbpE2%$0}sIl$$>P)z9Ep?&k$Ofm4D-#8%WV^SX;r? zr{Nesp)l`d(BX!Y!sY?h5L*M)V@-eX3)mFn`+vE&rqD(X-8m_j;#wT_$FLw;OoN*$62W-ba{of>-w3VV*Z zm_h=ygll?^oh^P2J14Cq0@s!resi~YS9PR}1m$Ud==fvX+C@9-*V2t8ZArC(pd*vShl8-hGzb5l~;V7TCkX_Y@m#}X_5f%Y8iX30jXWSmacH%uZCPEcbH8VfwnsI2I$YF+9ISBIF(v%bFM%W7m{!7Su3Nz6O^Q`dZb zXIl9WrC?rA*u`$HS7Mc!Z8%RL`pw^&gFV5nH@v$O)*YwZw(ipbYwa3NaTU(G6&Y;o zOiYwHwYe9ni{PNEn}PqH+Fk%15FbKTxeAF5mlsT8OK64j=<0?l?3J^>jQU6R8f`6W!VJS3$>_4duTVR%1L+z& z{ZwKa*;T;m5%k1x?3rJweDBcWgP>&N+r(Lbsrb}#K+`y+x6k&yx>&|Vcw)CAx zQ@^rxqaA2G&BJf~DR-0ka^>iI+XNwaJRV5BRzCg86N}lQU5gKLlWSn#@yOUw@usK7FAO0TBjVOaP3`BFY(<+5EutuzN4m0cwtgu6=A!PdLyUef% zZstHkr`UN)<#t4*p89AlZ{fJLO=>EH%QmuxpDlORzlhbIiA2y))uQlq`aX*qIF~Tp zz9u)#*hFGZg9Atzs|?F79e)-l2owIVlif?yXFI!>XwRl$|3{oaL`;H7%0$MDMM%!a zug$^;kYyDRq~Mj2V-pevJ?j>*p7jhb&`}>}4H}uOq*bDnCEif4DU${!($krQ!lMKf>d@($i243Q&ykht_5Ioc;|kk({yx zFovR{1>iUZH9WNcaeA)C{ExK|axgjOb>MRE`*5^kXS8A{F0Wl}^O7L6@7V$=`Fr@S zSP7Gb2g?)HKa|VQrc~dkn=hd}&g1j^&uRQVEPx#4_wD?~lOT^la}!3%r>Tm&X3L8k z(SIn`(t>`ZAo?O_K-cnI5jArK6ZaZma*bVJmfZR8F&FD?Y92PDCC?SU>}YV7sJ0~# zBiwQQQ7lB2VAtA=?YIQ8^d@QLIcF}@E<^ipEDs+0`16AC@xZ8KF&2jk=O0Rx`#%)V zN5OUh57Bf>+^$X)r6Ez*tn<`MowRFAz-a0=DdHarIDBrGB5T&#R=PG{a`*vBAGz>{ zVUTWf$H@|oczAaXCH_XP8A72)y9f*({Sh&HA5LGbbTJClDXHN~g6cJ#Sli^9QS2m7 zpD|^@2Ox1K0sl}G%PezpUYg+BrigMddaN;Rw#y^em<}6IyJcye(Uf%_2AD54l@2k= zv@KaFmlbSC+BIy}^PE2HHVrrWr~FI^4H*&qhtl(u1iNO|4B$Jq$KW($+Y{=j4No&N z$vz}dIt=(Y>iu!{;PLY>-xKeDD9qN^^3kWzMEuMXhkv<+}^j4pPr{)*Q8p_mcR=Q7kNHiSe( z?oSo-NG8YzgJV(}%M@@9U%ziGYo|cIZ!GP$3YJT;GI^NmJNz=7Gm;azZ|F?qp>p`PGox_Tx9x%%)SRV1k2?M|n84 z6W@8(zu|b#YIO zk6O|N&ZV2JQB~Snu-HLbl}hGj&q{)3ojJxm7PW9qvXM|Bnhvyv7aUOGppHM3#(bvF z)h>oa*Z7X6ogPykjdf%fY(YJ0&udzi5=oVOrQT3i8U*@>($#&w8_IOy^Sk3}aT5ku zYRvvoJ;+;f!J1Y3K^C|v%|YsEUV3Leu7C7$J03^9H~Hb}uU6>edW%n+pQ+hD6!u%` zey0R$Ez(*Q(_DMQh^cYI8rOb@aoJ%Z6Z&rjeZI`!EZaDtf{(J&ux!33=Zp^hR#1f6?Qg4b3xLXnD~qnzX;f0#||6zrpy( zgn(GeGq`e2jh#b354#d!K%#`$hY!J&e%_c9@(Lx4jMmB2Y~FZ_0VO?;gV7Hlhf5w9 zT5@yx&%Vj=JDKI7i17K>|4>@YpALdkJ}6s{G!&Uvmw&S_lAY$wN41K$-Zu$5C{Ni` z-&~Xw@>RTd+t%OIGAa#lfub)kYb&YEksy{ry#`zVTw9L|nd|(qZ_y;TT#(9Y637S% zigghU=iNTzQ>;B2p#F#Qh++%ktSg?W`2&uF!TV9{(_}hnZt<=7PO;mrb20``rR^~AST~L z|4*S%j-+d1P#eKh{Ewz*Q6}csOi1o)nrm{uo#eWM=#)zLt}9!A(!+7I7F`h_|(53N>T5ir_DBNI~YOHy$rRvC==Wv$B<1c5| z7#mC5WzAI_qaW3s@1^L(^7GxMyra=Gat$<^^5RUs&MdU}Z+LLKME)KAMWD8~)3|CP zp!Kp=bf-XW;@Li6@vb|9Q;DuQ`y`^|1&0i?i`Anv>ylf|6_wukMc^N}v z<2WbrVRiE|-NKw_8-vsu`Zh!@J zgFii{=$$zR_lXneO*jVko_`b=2RD^@eR(Lwl@bENqr?~gp(w3*Id8uJwl35Je@bw} zkXbAm?i81FAE2(LvJhQ}Gcz#qo#&f)YVoc(E&hD}@-N+`koYy>eJGk#iOhW>npB01 zM8?ZQFM?Wasd`z0I)uy>X{5ah0|dYgQkK|=UOHg(A19PJ4r?p+-;y{V{0XIuf_S=K zDtK(GkfPTupHf*Wy)Sf&A8FE;#>^;B%q?O#PsNxk#o@J=*2dQ}sk3yEb7_;A21t z&)?_Z+E~9VmCRhV3GS4!AfWh%0=-u?n@Fi-5sqGIeP}FQ5i-^A%~Xgc7S^sS8y)XW z#&AWGn0ak)I!omup6~fJ4$=VqU3He!?V@zW<>D#o)KcZ)_}ZIG#1AQizTGjZ+tRwN zD7AmgmcJ-o*-8R!Y@9k1i{thTgg`croYIVs^=%e1B_+Y)BsfvFc+?C>B!5C=0gi%y z4z66OlNcQ_LaSzF7MMo+fMPfuuFW&nzQ*|CEyky-?-jEUcLFu&^xtg zl&Sx9uw}nUQ&M~GI(M9VQp_lJo6jC%ok3`1q=i)FCWzh`N&rqb^8RH`yw#)cNm3Yc zL`>OhT=x^1a;>paGYY<5MHcU*flpg-3W z#)&mnLgZWC$atglG37G@E5$VS zq6!deN(@GNI|1e~IT*#-wg?9w1n3&a$pM0sSogD4S#om|CZ|i>%SC+XzN8^nNVDI_ zU%WEPkfiiqrMPH9UTc;;NC>6bUJeifV|ad4K6bf?F{LeY^g8ZohYbt0Y{IGARouV*gr9IwKyleCB(6}&8JD+*THaHWyQx-6XwdxD>zY3S>|eT$uj z1NH&rxxIlbbtQ&DLr_K6yhkdDZE33;KKV)(s^Q{8*|K&j+EfX&{q&>ewvEY`-JNNo zy}f5CoJ*?Xt>`S&wCs{bTn7h-_MFxBAf~#3#v3=9tw#u&ClklpZi`)tM2z`%M?5B5 zK}ua=HbFfvGPFV}axDBQwZV@;-o7lBND`peUOjMu;(=}huL<#4S5oj~)4bslQG;#5Hlm|DUFd{-w(Qj$sJ zw<>qF4t-1Z9@`RtdRl@r6}kG(qq@{F6LP*Sr_=3z zj89+wT8j2xm_SqJuRn7I=3A|>5Rkw%fVYmH4^XZN1@f;~-Hc^dYL zB8c}{>q07(e{>I(nyFA7NK%NppOR`-jqT;G(!*9ZQc7;`l+^`|R97v8%9!pwi05JB4X4C3dE? zUQPrxH0ZFb0Jv8J|A30Pfmm>A*%W3p3uxJ4c1x_5Fl8)3E3M}6kj0Z!=cw=jOKGp% zJRcs0HK@$3A5ciKW+HZ*BraOMdtvFWKFVgEts1dSE%b_-O4!`EdCj+W2wZuIUwETq zQgPr%CtIm{vb5_vzN4PCtfilj9Ix+{Q-A*lv9;VDjGd*B1FQKUapa&cqnrs;V#r!dL+(7^N-^c7rmi; z)UPO~#&o!Y)n0zNYjE#!%9Xcm^vM(CEQ?!PUI#_wxl;4|fcr=s<|xoekK`+gObBwR zyN(4ohwlP+N$aVAqC6^x9tYV;WH0ezxgd;+srUQJu8ZyyDOE$qWhZHNWD&*_>Uj^4 z^@S=W-!T(DulY?O((h)FHtfcW12z&}mYt*tA~6Th+sL^Yn}!2BTft@ISRK)Kp*qR2 z0yt;EaQ!Oed=>6`PoH`YNnUg<0`YE)->lMrHYN!tZOYmLzS@koA$$)DTy|(q3aYan~I%82q=7cT=U($@kX{YuRIbslih~3QO zHhtXqBKDS8H?9u<7E~(MZl+>Khn_YFfr$aaMCOWc#<&ZlmnykrM^!M1!N!Pok){v| zk)N;NI9|w{N?X?MKwzfrz9ep6ysPO@r->lR_?oKcx5;hVf@B#bL4B4a&4IDmuY-}| z<+;CoK)dh2hruS5GibTx@Z?FnA^TAQ+o^pTxlOM)C9icO&rS8%CHp1Jh^351lOE0P zQq^(4Yik(3c?7EWGJqssiPvR61}I#7FV>lNQ#r;tK8KqWV9r~ABK)bZM^R26F{$F-=ni!O@_S5ia!t^VvQ{iE_omlmFj8d?*CnmRPFj4+{mW73He2nTq5M@WlH^V6 z;c<=MqrffxjBoL6K81=)LO1Ts`lL%$Jh;DR`&}y7LG2wMA*U>HOnT11I#odW*p0%_ zjR9+KDW8+Qhv5jUgyeO?Wbb9o$6aVGjVlwgNe@q!K2T0Kihn=zokNE4u*jvJ4Qi>_ zQGMqAJTjUHfB1-;nt!UaU8l{^Sgf(kxa60KU0;~}>FVe#yFbejM{X5w{ri=^XpXtG zAkOCU;z52yM#|E3`c=kEY^zCx5fu*Y`*PBp^_WEj>OYiSpudo966 zfn5n&yC)=!t@d?>ldIq_$X+Z*o2_%feH<>y*>b(nB-X&aLDxaOfLOk319=O6Xpi;@ z5WC%4i;}oqF6l4vI5dqC`UtVBNcTEi$F+8fWk2qCIO-pHo679Eb$O%3D3ti!sql8h z(?~lo4HT%2tua(A_-QfOX0gVMFkC|)2Hg1 zr#=4S1l-ZtecCNP6v%AZosMwU00zcd&y?rK+I+)!G-s>_uA&EXmOpw81N)KppeX*!2D_O|C6}cT_-0@*Am8bGfY&>@H zi&FVE@(vAJ_n8MEjpfUv53F~wjJv?mV=@4(04Cw}4+>ohXi!aRdzU)|bhj@7@n6;xMrSo1I=Q*C;N&NQ()lBIuGAo&QDV&1?vX zfv3S#(v5d^6Uscy3{h`?IHA--%ME+z@C`ZJ3v|5=oSo_7Rq1+Iz3%=RX7|B2I0l*W ztD51aa%x~x-HudU<#GetTWB%_QiJz~&qUs8?B}Hg$b*b2>HA>kZy^NfKa>wE@K#RW zBRi;_W0xPiV$4|GrwCsWMmoO?tMp(OIn#blz8U$$e!=$y zu~?|ddTphEIED)SSA_xDO@P>kUHia@(=v@h8_&OB+_FR>|I zO?*#cRUIEw-f_vjU7N+^Ws2Rj^AJFG4Lg{FzkrT7QVhM`p=X`dPw!cbPb13#7eTOg zWIPD->1EA(&6Iv*e)DmS38|{NpRxLyb(}(RRizIyet=oK2g8^d0=Ft$l zcvB0Alyx&NkY7icCH4^8lxTKXlJ0PaDw_hS$tMpS@Y_DTiphSUEv%|Q+%D|Iu@(~^ zwy=Lr8kfjWJO9>&^-H#stv( zz&{kmA~-Mb4zm}CT`ABsh^-MqaMQE@NDP4#!PBvB=(z8KLx9O~UYTh$Zjx$13fq-{ z5*}ctN_mLVpUESfnY@^5ez}*asdga`Q@gGi8f~e`;65nCS>I~eG|r<++{9JUGocjcjj89a>e2v8pGsj zE7Bs0lRfTZZAThab(oIj-H9V}UuYW9orrh(WN%dXcICu(pGc8qlKu%+NGZB^S<|wZ zb|YH;R8`q6DKvkO^s@fFk0v7sF~rpULq|S9Vm;#t*KUxZy5UO^CXl^6M9q64?V7babev`UpB9| z-6hqQsxZ05|9qJ?ghV|D&`5)Md;1x#}AN=yYNJzt={j{sn73{dh)M=6P?Zrv^`h~`GmHFxY@7}gu%^|nfX z!E32BlIl@_+fSdMqm^ zyf_d4ntC%#N%sw#je+q$l&_}KwfI5Em7;pBz2aYP%1Z)BoZp1I=Ca*j+Wd&5Kbld* z&e)op2SL|b#`o!?>^YVvmeN34#mAE~a3XyWuoA8sw?4Fyb86De-ctA4WRkWt_i4P!Pc&EWp5$_F z&RYBm9Aps~kq@i_zt-(UVLfpPJ!<+hf$L4xtVldgxB|i}tKiU#1)^2Q6ftm)CDV1q zV|#4GE{Gy>4hzwxX)QJCC51W0tumGvBXTiB^SJ3lpL6lj{s12?I_bNJPeY|G7p@S; zu_s1~eB)tx1P(2|^s{#eTjq#ojUME7dK+I8Jte#E54mw_{I+TB;S684mk0KZ3$p0Jb^?$hD1K%Jw7oeZRjXtH@NiUEb+Ru zRrcYrCRClv9^REE?gOL?;}N5^YoC__o_%l+D;JEeWRafx)rTs+t_1Fez1;6tI7j+#GT>L`;*bp^wyCg+Xfxh@}JBg0E&9Ixq z<+ZWE9QCwT7XxqBY>VEf6kwDK6MhGaVtJ(OE1&Ah_bQ*x;X|=}r(;recM?N7Z6ssA z|G0bnS=EwZ+v4~9Wi|Hj8=DByPx3lQS`A` z^u}!vy31`8h<4o^qF2ujz1+A}$*FjVHHpJqieb7LLTxJ#pCu+M1aM0-`d^@Xqi{>% z%&OD-71!_COLci!8BG)(+Z}$%V(5#dqM@xUj4qoPLH5uw{6pFHhu(*hfzPzpzTt9Z zL2dUwmDf(P58B|@3&(+E`xMF;)W#f(u5;&}f|F#_9vw%mYfe8Q0Ln*4fo6A6tzPF( zo$GckJlt*RZ*1Sdg-Ns35P=Jam(TU_G5K0ZcqpfBn9o6!{^UV0i%vECbgiO(=2F3~ z$DWQ`fn*G0lQ7qCWyy*J;i0uevAJAC-}xuPLdA8WD6X%x6W{0pJ>9PUKE7O1VVEcC zc)TwekAfU;Q9i&lx!bM!9bflzM(cGEAdL}E z&4JJ4A0K>gb9>)M$fqdU!x^L3()qh*q~oBr7l@K=O#!?v>T?C;L;nIieG^eyAjD1WhXy8KhA==6Y~^}EjRGN$FF2r2FGk?1Ag{KZ~`TYF#C)!-qc@N#EC37I%VSD-H zl$^YU{8kjvQs5LB|G?{03+ul%zErlgf!7)e4e~zoRg2;pK!**PU*2P`AOyeN=licu zmVm5Dt8BJcG-7yy=$DM?r-IF-EACBfkf8NvVu#a{eC6a3X5j z^vz9ePpxSV+TnV)8Zp(uC-X`!lE>jvCP?Kc$x>dSktog+D8AD-U`*tD6=p-Z%}=oyEtZ%Z z2q+i~pqZ^t>LQNWw5wJmc!^4E6QSNl%vK8K`}gE^m@_9@zr^EcNq6_ zL)Tw(eH%PH#f2Aa^F4IjqnK;^P2R*m_z|nI#Smk;@?`zt+oBJ5$NMTwgzCrc~PA5j&Az=vNul8G&9 zu1cHMn+d9HxPY@>+dUE zG9{Y@uMRyr-u%I7HJMFrX@mSiJAGdD^<3^b(zX+CNcmLxxjb325tf1@!--+VL-|vC zU;B{OVnJB?_p57ZTW|W4cva+?mW9+wJ5kL7;B#ZbQOj(vso_m$WFK}7wkQ`^f1(IS zbjZD~%pl!2WiQPI#T)G!HJxgp96|!Z%C9@lg#0Ui8leRldq3jfgI5 zx3eE)!hcp;-!DGl$f-bxUe1jcNlH{;fU1f0vAJl}U9hb%Q6fl@DH=%9Mgj>y zJ|Sv|8j2O_R6@zZLY;C%Yr$QmOpP0mFa-@~c_U=~=B&?Etm_SfH`$}}(6Ugw=;2hw z@rOVBn*!-7_AK0Th>7QJ4N;eQQoeX(qXWNFc3RZ{QiwzkzzWkFQI{}uQT%Yh>^XNR zKJN*pz0489$^e*cAvGMP$x1f_@AWCuJYP)(>d=HA1CEz6OoJ?wSk>Q5hMD4YssyViuotz-o3yQq>-^VdFq$^Lvw|?Teo4e z+6S?vecY5n>&DMyY42p9plQ5Q^Jg~IhqJ5xyO`I+Cs{-ojpIzbaG&t@i*gkFk1f?KKETY#p{C-#OUXt1NIIzcB|J5yGN-?Jo*hM zWS!KU%)BSj zZZ;ir&o|C)#DmXoC^4<4u*HP(%j7Y-r0g&{GBt-1p)#Wv-zL@|6Jm%`HiVF@@*l!X z?cT5`ue>8x37`V~AeKwdNJT()NMQ7euGtzBRsD-fvn-V}`L$Kmvp}ak0 zbvi`&387({YMX)NQKBdjuTAe0N%Z%na?~I~eW$IS#7rxbJLjInG(-+(3uO!i$tQe< z{M@RJhU*A2Cy48;6Q0CGyFk*=WPt!;VnJm=H)=KQH?9J@I?*SwEH5GL6@nw}@ zt9Dd8O`~QcPR@3n3&VBr8YH;J%z*!jpeIx#InDNBtBsVk6>XJL4M04O)(lAlh$xe= zM9>Dy{QyiG{LrRlj7cIo7qM*cJlCu0%h&#HFFCv+ISgNWyN>1XUeM7NEP;nH-=)*+ z2;1VcE6r!>i)NO69YeDg<`+W@AAB9ni7z`P>P?=q+SAb57{buvaBLgcj?_~IHEcqx zG+gGkk}V#>G}O~EIBZ2W5`u|L)vgcp4D~OP1~y?EAJXfWmy|Y#8T(-xt?vlZ9v*q+ za%bDoN*Pl=7G<8Grel9`OVX~A?XxM8(Mqy@p3ku5GuFf%zYQS_A)3)gSv)~$2A~(y zCmA<|0nmzD0N+rbu*k$_+6SYymuOkY&?gBY0|7=0X26SxU{D z+|pd&l`zt%NS%|y2v5Dd2RcB`cpb`s6$f{wlY2>8vp_nU^rgOrWkE7cXK0eCV;+jJ zXb`=)7_-hSJYLMM#D^wbe5xE~3&Y{gH$459{+hA(&XZTN`-@Kx}4NsH*_DUOWpIKwuFxi)1{;JgY?&$pB*>_V}8inn~K4hr)-#zf42xsYlPDH1Qgv1PVCiSs}jrLI|(! zioZMEe|c(w-=eey8d-EjZMQN+}QDvkJH1?(^BJU)wfsJA_-4{dr+$6 zP?Osex4_>P@J-p`?Fb0V^%c=?tGF^F9UY$cS#IA=xO|GoOD_*7-U%kJe(2QO@3g~y zd$g5!S2!WElhxnx0z<2*lQniM**>V)V-KS>D_sJp5(@>9)QY)yrDad9;~}mo40HuF z^4N(om&Koolr54;>+K`M=N3ooPmQn4X=;!g0=p&OazdfwEWLu4`{= zZ}Zcrb3}cd3at(m5hgEpqMzR)|Jc6ZPpBC|AX`K5LQx3mHfL(-4(mZ@M6&rgjp zn^A)(QZNHes92A+5uvOrkr}R^C=K~M0xnorlUE@G9Fni%MFepa-5I_DGob+i%8twHO^#xe0E3wUOT|$g)In z@jZ=6XS&nWzV3LkD1kP58j`4b?asg1*2$8!E4nc~_Xwp*i`AAnx0l6$xFJ(5s2Cxz z%n~N9xtMF_+sNv{L&;I%qGg_QlM^GR6um)RL`qGBO@6d*u_%}JMjK56vDb6v#5Y#G z!}nEWiq@O*1S#%~KQB{S?Xxd}>GZRWX^nBrF?6+-5frv|Ta54f12o_iDe(hc)s3q40F83X|9p z{QyHUKc*G)^C}cC$Uq*tA6#fcAWdK4iKfrRR##@=GjU^d82jDe9fMV^u4;?b3lN@& z-EM3dt$akNlJb@;gG)GFFV_2F_jYVxqUo~!FU6E9tf&*Ye8w_Ka%;tsT}s^q>M4;j z2&v*?2g^RPH*R&a9!=D_Q5dxyIrxs+&eyaOYIZ~lGGXdyN%Va zOf7#!yk;m}OZG{q;#}(Ysv!|vO`uj6Ki$B`b!4~t)Q&uS&^QC23BW93+;DL70+abzoZo6F*J*lmx~q^$q5x6C+X-zc12= zhYGac^>Oyg<>IKuyiO4NzIFMsoZev)uKV(E{ynvktEI#mVWSPTi93IMq#|iMqtvAv z>xiC?T!H9|^5;K%#M3in_wPMtgNN2ZK=}8%2)>7-d~k%y$p-K*rWgtSpd5N`1us?a@PJiz-$OZQ5-v zn+>duRQaKkWnF>l1SXtFZsG|3bs(G z363xuxq`3|P3dLV1@h@7?Bc61~WCfCdKin{hF+glN5%!Jp${9B$>p3Zls z9%0+YYEk*J=>)~b-tcbBPBygCS5^Q80utBq3{`5WX>7U@BG&Q>(^Pxa^DOnbE?hZj zY<|AVvSW#C9vDNS2FqHpLa_>QD7(Y+7R1ry{T- z3tfLDyw1E9GaM@c5h+f{dmV#Z2OSjGWo}`obBHD!6Lc{Z75W`Lv`lzZlrXh&rro8> z!=19`BHpX86jf|y!GqmY)6T=db_|3e;e@tjz2~`(V-q*}b8oMeg`e7*_~uxf=eh~e zK4tv5C7Vrh&kqi=u&IghfPmBlwrMcVs;#8mm%M0i@vK(?Z4pgI76Y9>vYNzj83X-o z-@t<9F6CM=^>G=!F^^7D?nCE~+z8Cy&mYZX--jZkE&LLizM;b;NMHjgCIUN$(Npy1 z>{@jIg1TL&8|QsX11YEboJXTnDOCyBBH+>}%)=;VAyq;}qEzNSV}EQh6$Uj&EEAG@Zg=wHZuou)mQ%lrK5yaVrHE$px$VdxFs2bSt%BqHpU&aO#!6Ih0U2)HGXWl(u`-r`l$J z-*=@67Fc3(vntQp0{QZ|=Al&i8r@zjetvLXYB!WTr zVZc3uUI4(yn`VH7$%6|BlepELJ4gKF*)pB0_Bznvr zvwJGNB;kU&xrMv2t za5dUVwMNN4=s<;|wM5*-6_FVnK5EjklR(7d^b2;jWsm3@Uh1Rd`=nAn_kQpEJ^ML# zz-LUaT3@cx=32=->C}$$t=6xeppjQ98iCojyf%ZYNfmXMRJ%5wzbrp9h1{QA(kVCA zCn^slA9s-Byeixqa0~^G~RmLb=AdC7BY_DdkAgY&R-HGzl1es8L^DBb*3VvAAhw!9|SJ+YbcfU z;}c7C>%RIk(}?x`*(#iE!uJDqCaS(8*^l$oOR9d1r4am+uEYN0OUitC91AhlphpjG zO{xHk(Ki|bmy43Y+K0&}yJvfI+Y^W#w~JbigIoD|%=&xlxObUHYqX+ggNFgu+MNs2 z$-`;lSD(bodJB*8j17}=^7O1{5?bS$FZ&fB0ruNtTMiC;v6;mDqWIt{_!^l+#l~l{ zyUvcF$%w=(Xu4nPW{0cp!}AqtPb=5{o4nh<#D`{jyR4y~@s(q#mBA5c5%Cr9-&AQ5 z@I`lj_4X51V~>V?u+Lt4PtV(^`OX`K-QyAC<@lPgZR%3pgK+c{y7%zkuO}yoBhk?x zvPsT&XC9~odaypud>B#H$z2d|Io6Q!^8$TqtI!esn>^f75t}DfTq&Aldw2U z`TBvo2im}<6O{`UZ#X$Ls;<#BaalD_>nw9Q*l5trvV4|vAy<<|5A_+k9;HU;s~c!b zonKmiPatA+FdMFp56#Pg}rw)tHZ~p(7dJll6nlF9WRY5>R z=|zwiLa)+0yi!8wgepZ)dhY>|wn_~pn1mu-dIBQS2~tF9p_d>XDWOF`L_oUV#ozAt zUlO>?Ei-fH%$%7y&ogrp8O-F=`ksVH_>OQtK;8J(5Cz$>lmEbiR$z}AU~6%Cyun#nYbu;S=<^+(RL&a=D=eO>3F#Ve4w z?-oMuh?RA4EFp%#R&a>yaeKTgIE3b5QSHpk<%sFb!J%HBHbC~DVeBY9k`Yowyu2&Y-R z%aSgr8Ce>?Br~+XMP+~MdKB;_OXFpz4HmK`RegVr%P~4gGE9ym6(Bh+wtVo&LxBTO zOWOubtx@6V9A`;N<>}z))6WXe1DpU{5P)G7Jv>j;%3xJOHu$KYz`_+Huc&WPhx7n~ zhP_z*bIsH#vhMPm0|`7<*Q|q{2>{i;X|z#AMev+L4_U)tI5LWB- zTOf#As-Br9PfpruUg!X1L!7gV@dg$>CLp`y<7wG7{S5`X<;xjXa-oy*?ZOmZ-xKNU z+8zgNsl}N$&EBWlF^~RogxZ_}cJc2SxvVf5OPUo|lK{#(J*py@GLl13?sDLA!(GWe zq+#j?oBO$V)UMZ@g6X-=MWTuc$QLj53c;WS<}Rd)R|euiXm}X?Od9Xb zORowtVUM7T+uQ!6tox-gh~M;WT|fMlh=v#R9bF+mRSW4!P}Nd;60q?EJ!$krwTctD z<5pn#@-QLQOY`)zYHey|fu+FCE$GsAT1PKe`URiANKx)#FErv*zwx5$!ItQZq!S*K z)*rYo78~lI;D|#gqS{}P@ZNf`ubQ%Lt>v-Yi)EO#dz%J%b=Wb^iSHYRTXP-r-ZT5M zbwl$#9a05zfzfR`93S?ZCzszvws@Es4Jra{pYD?Xgb? zwtFvID(#vTIL3j+Re~>d`G)2gz26sG!IZi@XZ@e;1~(c!g3N^YFiUtv_Zx5&SvN*# z;>|Yj*~y9hHA0NEh3BlDb(J#y9K4E^y7<|Ov0XY2C&e!n>b2nhx!@r0LMpVZ&th%0 z>K(zgOjI{taDMr58_x@3Y1pD{xXb13-s~!1rJb;LV%%p8)|(B+UUiqgc>diCU4ZY7T>m^rSKDmPY!q0Z6Ff_bOLBLvF?3tePb=8v7jrPdXQ}IC z%;|uw;btZ;;0BC*i4%`|3_i^7g6P;B4f!G|B?usMgjdB7x>0y`&M#<<83_|ote!96 z8Uk5izIrkwCrmfiWZlW5-@=3`@6rlS_mT}|n{&<=L>s>udwD$dDwV`-yOj~9TQ<^? zG9NZ!l8cizniAht&6$&oX{WN76l$X-nQUJe+Undlb?!;0;oe9gRBp*<@hgN; znvF;XJrlzWZ*cXQUfzl7r15I1JD1P=+N?$J!~5ua^}R{R%Zo=cY#}m5ii|hnbhCvf zw1#ey)NoRKls+*AH>ZfAgIaP!3>gmIWTPnykT0>7$X#)0^ew43#d}^qgb6JOg+!O> z(u!foM9Ns%+8AA_eC{_8-6mM$_4EuT;GgrpQtiDWZ?;I2OH~J(TBp7&Ce7`Uts+52 z;fYjS*zAC{!uUyML_Jn*Li^klwrjp4(^HL;(1f^9fXJZ7evUz9NZ}I1dBWmJ64mTV zO0f|ZP5njU{LQ5y7)n!$?n=^r{|edYW3H$+AGFNBt|`Lx9Yr+HJk_by13i`+9O2?B zz^A@Kz`9DwIuVg~3ONPqGv8#PjTebS@iX63-Zq$S8gDzdiBIKQd3H9&33TNx=y10R z@ptDC?FM9Q;vWs%3UynJ;ta4^+!USc;P$0xX&E=~0Kw3y1AL#WDze4h4=*)G_jr<} zvND`slWYd)WVQ51C-%IP+GT<>HK9$pd&n27oI?C+uVAWoG^`}$20NqUW@g%69tyqB zuaXB%d+Pd{E$W&WvKbU|ay6)Effd+Nz=e>@tVIuV(X7xrGOYDt#qN4=Q(!qK%bdLG z5B9#<`wlBxmm*D&{xg(B3y79u@=X^PwH|DM=&d;I^!+5!JTz?$UFW+8^&h#D?%p6O z&#QM^UTV4QgkcTNG^Y`(uKDb}CR>V!TYF{>uMWMFkQ3gdJxj_`%D4!uJluKW!)N=2 zy+29wIH7HWz1VvnY2s9wi22U$B=qOnEl1e}rTd!%>}bc1d38lHOv?NBz_BNa=%w<~ zuGC#_4NRD(Z7S@IsnrXPlljdc+UM|(WDTFG13a3X{D~2=p>)u9&@@wWR@>EnsiB|uZ)n%sJ083~8^oY{P;VAAyS0R8^60#uu~t%4uf-hVBWED)ux|ZTr?4wU zGO*Kfh^a5Zm?1PT=`oI7#&q4X{LD(>go7_Qsc**aIDPr>y3M~cU*)F0trenOql_9$ z?O^Az@lU-)Po}V#LTC3!k5pU+ z$JS+IS`j4?DIX)>yZ1wng!`>{71+vLTo3X|DF&~ncEO`NcT(E4DfL8By#@Tu%vCk( z4TRE8RVI-QUe5m1|5mA&@RzyfD7doe2F}qqxA-y4^04IiA{I|mEAhWj7}2L@iL-<2$0ILcTB8oyDeXox5RTVkFCq zmNqT%f08d6C@H)OOoU&LA_cN_g<-wwr*R&{59Ot=!($~T8J|tb=GJ;EhK9(4ORTwlp`462cg zh?$WotVk9AEgWUIbNI}HWONF%JTk}Ie^Anj%*I>Re01P&XAEZCQJX9?Yru2>E^fE| z%6H@ctJ-056W^ncU^SFi5*G(N*Qo~aSf(O#o@`Zf8Dr(KY$dp-u>;!ST){1p=G17Eq+Em1?%(~f#^tk>(*fcAZOv7B=yG)Jlfu(gETfU(t03-0 zHmxD=l&bOS2d-m{cbxM%&-?S>)B6V^Q8?4nPrRXQ_CxHYO?sZG-zMemm4b)RNDeNsU&A^(eg zO^%-9{KSj;eNl{AE=<~x(x-Rs16L}RCYoepbHWj_ZCQo=#cR4$+JXb>Y4rWq{q-<$ zL6yXc4&@|oq_5Vx#JW%v#TFs2i)%E0blrMhfv;UxcsNNHVO9*PADwjO33AcTlGNEK zWV>f-3f~ed9DrUZ)CP%=Y&#ZhjbvDxs?5rJ?RPywx%}l%okSZ#jV2^jDrTV@U1HVW z7tB_sLcOBbgMC%@sViDzwGw$pinJz`33!$&p2EH$ZOx!wEmR{%%}kDh`jjY)tru#B z)H2QP*7O#9G_M!GrP99CG{L)TnURvixe-W$b`HLei_!G|y%3LZuv(9$t&jI-Bw*|P zH+f+uUS852WO;)(xu#c;l2VXCs`PR)9{+*&6$7`cbD$M1z8nE5%gfK z{n!*cGsAJ9pNmdi|Fe7H!y_F(UrVnGv^*|JD$tWHZ7IPq@(Z+U`jRveW6`99dDF<% zOKKWXhD}{>Fh%W5uCUt#1nZ6{c{kzQ&1&3`r*0$}*Y2oV_kf zFv+Z836i}(RB$AG`p{k@-d`X~hAqVbX%gNyBFx*+5msfGD4SLfHBRju=XG9_z2IOx z3{Q56d3Mis=+)2A>jkCE=h&iO2Sb(OsAbUoe296P3a`>u$K&_UxW~^v&V%16EX{CK z{P1K1y|U3Au%t2Ry#2y-gDJRXPvk&65OfwmJ2L&8?O2eAB3jSk#tPp zL+`*`C#D-_EHb8-Kfz2kg{pFN7woMK7kPDI4A}){>Pb$b2@{{*2K<(kRsRRrzNVdrktl+cb=*Z zc>B~@Xia(VVENf-3El?PCKsg2ozWHAP$3YXZos;=s=9FRL$_`3C&6{)mnhG{;W_A_ zquR>^&j41ZlCrVSz{q{Rev$_ncmmH+11AgeRb;)31 zT1g+#oGZ7lx?U93rDM05;a-$MXes6V@?6AMOo$L}i7MLYOEch;6KG=3K6x5*sdYV1 z+RuX08G;>FPQ0&PrI@SGu8uXdEkSHCkEr>6z?L?jr*AduV0}i2gEK1FQcp)JMVJnA zUPWuw*fOKAp^>Rm0ls7ch3YinDSDc?ZgsE#s!5lg*Udal92gv)GWNmu1rgnO+ZeWn zWhmNh@umQA=m9j4)?;U@6{?5Axr zd;NCZJgb9He5d@a5JoOjUTV}^KP}A$o4Rc;-HLDS-R&`0lFQuN3M)gA=4Cyzr2Nv! zHl5yn(_Wt5f?0u~Lrr(cFK*M+zRE{o9LpUL(!jUCruj)#8CxRnyt6ZNtX=g{E6%U}AA&F^hU^0j1`Gf%^3#1~0+gR)O;22xjh9iT^6TrGXt|Axh51-^uU*c!AD z*`>BoNOG0h8axeCidKTp8xlh_TIkAC7Q6R}Ss%W8%p5qi{1ViqMN!=HTtz|2Q3UGt1%A>VO+6LQLuS7EtVYZ2;G*DsP*=c4@Hbh1tNcNTW zvA^0YTO3F3%ukDR_e11o^%4YwA-e&^crw>gzYNHPeXNRI{oUX$&MI1#Z`RDAYGaH! z$1vG#t*%Y=XSb3?gD#`RwreCc%fqLg(d0x@@&G`Sm}^ zYXQ~-vRMb76Sqs#Ir4OMB?&W&6PlF`91yI(V4zEg6Fy(FAd|Wt=aH>twO*zhe!_TO z$AF7yRDrU6r&#T(vf7)~vfX%-@q)=l(cC#JK#Ov`n1kW#ox=W)YN!Zyg6yWt0oNH$%2)PBq%FxpbIP64-xBy#SQYW%^kvr}FU47?W>HU+@ZU6p&{D90#r5MI3u%`;X z=fh+Vb5-Sno#8dAT79SV!}@hu&YKGrqM@;+fs9~Lam;Fqxz?)K$KKBIuzu5Yhe7PwgV_;&+`i|-t8=YaP5cW~}1@Hh0L zY6d>*vVFT><5moI4T>ba%6aphAud@a(UZR!R5nLGEpBI>&(SIEW;TyK|1kEW-4nSh zq3KUsf}{xSJVwRB(%` zyz}$xDxxAInYUzV$q2C7(~1MH>hP4=q2&AS{PzXkVT)T30-xx4&!F!Y={h}W$a3E@ zWEo;s;>Y^Qxq&Zs6p!jC>E-?zYWebjm*7^CVNmLpfei;o}~_p6MQ-m%}^kRhIuyi}Av7Q)OC2Kx`M;slzVqH&)0-Tgr~m1|b8zF>jj# zzD8sV9s#yc^Mf};TkSr_s2cOooIinm{U(kGqBAmV$yw*QYzBPjM{j%M z$*J$$#X>`^_qbcm<8f(r3Urar^3Q697^c$iuo)&8bxOwF7oy1(=)SiU{gPS2*~aKR_j@`nz~oO$)?Y;x74!@iw}b4BH{+j;F?3g2D9 zHHIiO>-`5(vF&eD9TpWZudxBz6id1EF$gw!x6u@eah|7RsFev&4fbe~0FHk2MP9wc z4?+7`-j!mR9rOz5M}R|&-!SVJc~|OXpqqL->L&IqVFR}EOZ2i^Yi(gyAO(<<^4e`z z%U)VB&7BP$k4&7N%W%+>&zs3U7%nFJ_sDAGyRV)ytdg!TTte#GXJh|Ma~z2A?WnuN zG9C?m_tgU!TDsI(UEP#DcS zh;&F_A3z3V0F4LfMMjfTvq-L=yGUSCfX4ud0GV=%%89=5&>Sun zs}(2u`vJ96ZE4s0w*wc2!l8a{B2Q=USu#;=uw=$z^X9}=*3~ky@}|!MNMZ#A(GS?O z_DGUI^tij&!0(Y%1AU?=0v4nD>fc?bJgOq~XXjjG3O|?j3rynfY+#ieUU{nHYqv}* zP2(j~go1F7*iszdtb4Fu6&Jt+O)Tt|N1l1TKyIBV0Pw+o@^*Cq%F_U>zl##LVB>sp z$jq%GN0Jm|LvSgsfBs-evhVbNMrHK+ZH{>H8|C|yx^gJjj`$m3poWv9|6O@HaWqMy z+kf-ZX?XiMz^Pvnp)GncKbA@(XWnG4(x2hM!K(OzRlz+}bggG=H|mQyVczMKzHtnn zE=Kp9D=<5DD4b^V$1U1+h#qm_|47JbIAC`qTSpc+y$sTg4drV^2NbtYo0*H1+%^|>1lpmia zO(G(DEIYVt&F#xPt9N?~g5}>RDVb`Wv)Yy6aL;X+iPgMT5|W6n z!TLV{=>ej>YGKJ)Qn+zET3xYz82B3FOn*+liy;L(oJg zkPu%KKTy^W9fN^3vKx8pqf%8t4n-px9(4Gw4|hN#u8a^v&Z|8mjF_;xq`|otLX|@$ zfm-o~j_LdY2V*v(2_XXc4F~Gp$l6k8$*4RV6=hEmzb~T^G?16VshVfG&y$zU@1kpG zsSJD%Z26&>&!Y7BxhU^31ZYfXz7WSR(VPKKv?h8B`Z@vidX>vNz!3CrcJJ_^$?x2v zHtrNkP18&CPYaSu;p)K%w%XIguqOG1v7@ zS+gWgkVx94lAdiq z-cXFb*HxR%l5`Bdsmqp9r(S~nDR>CicJGPQQFVa@^6QvsG+}>$a2g9#s3$kub-_22 zefmnAzIp&{zMw?|OB08r9^R$`%8S|;(0o!K|EkWPOP$eI8WH{~V6rb`u*o!FT-f)S zRl{I<^MY`xUNrQDaR5Y@8d2SZ;Lv`#xgUeg$UW-3INz2|2YW% zpHT7NlYxJkA%AaCw7+!~`aic^h16~Z6z?es*t_AIOV)Gu^G;3R0sr9KfMz;r|AI>Zs#05sh#t{VK}pMek=Tc zL%_A||Nk1*|J?F_P70iJ8W!$F0gNW)zlsMT+b8i=&q_j}Y(xpr!MR?^KP(Rk#B?BzwP%L5X zonc;DxW@cIuh~76a{z2F#QC!@t7n6vx~L@;=P07bZ^QqXlxNarQrP{Eg$?Q_{+_7C z+TEH+8Ab;)?i1}aT|3Iqd-44rrEv7482G>Vn~5&?R{=Z?n|)v zs%=N7x{s(-yu~C#pPr-DyFj77ocR@1s_y}iPsr<&KF~8Ywm3^oD9bOn4vJf1zd0OB z{d&wq(e))H_EDb~T9_*{=m2uqcjAQC1&6w3T=h~ZZd3HOh9!ozihZQwcNYm3CTDR` zoqIby71j~4Qcz3)UynbNg-uET{Q7h$K)bk+YndU)zkEqc(X4qd9?vk{mztJ1lFqg7 zR&8VkV?CtBc%k_kRrLC;i;-t{GxXt!0W#(i#+y@09)J&?>21$2&hn1HgQ?xfoJ*q# z=H_N^se@C8n$m4yD!Z=jtuV$a?#po*#W4NU7FEvbqQ+EzmE?UqSFRT07>_&>&XO@E zC|lvBgLV+y z?<|?vd}6b?$c;Xf+Lnb64tKpCXtCx;CiL9QIn&eQX?pWE8N?~38f4CDk?axi*`!oN zjjR-A=;1F)BE3RMI(daxLj!5M#svUxYmlmTA1=!XvJ^5z@kW#euwyB^YME*L(k6jt z{}Dy_Yk(ilZ;X|wVTc_9=^hGAS8q=TH(0*ik|APiIB&+|!i>xURH4@Sw{5fxJY3K7 z!&xNGJ=3A6BYra8mpx{smv`gETN;n;;`FmNf|3#tpO~JH#4Y$)=aFS?ykke&eRMw$u+a^-J`N_uKZ*GVmwc`(EYmlHu!S`$iuWa z`t~~Zcg=I5$yfn}p=D4?NalFA3P8`;jf{m|-i_U~XiDA$$T{i9(#G@9X-7_R&~rX~ zO$|ItpsnxtWS5y>8@eW@-;p|jj?;1kT?$lZgM?D^Hlmk5CQLLc9fWND7(V~e`$G3S zr~(#iyNlS~Ef{*@P|Rz0uXHl!v-}2o-u8ipn>avci2SD_L%KNrXJRH}x&n{ipKGK@ zDm@3|P=O0xximw*(5`GYCv44J&0O8v;Uqy*2tvUFT;i#1!f(O*0^XLCYG@Rx-?ryB zLVdZYcg%H^)Z3R3%8A0@zji+XEfCwHbUtsCy;6KflCqJhvylPDzmo$ZBO_5p^pm^c z+Wvk7_l~+h*M2A;^IrfiS~%;0NnM9nLin)a)cI+rP*xgz99a6IcBb^CESJ^JUac)rkqBmKx7_I=M-_ z$qwD)=i=e)iW%N3K|I0xN}(LBvA&>2&x9g^W}Ty(i>8q1_s6hjha9rDExNF<6Z`-gtFG-0hSY#W~Rq0*w zzES1&NPVMyecY2j*9>y`2V;*?KM`GtpO81TBJQR!Y3U@!KC&_6^5i8}#qy(^X7=7i z1sBUNuun{F+xznPO}jU~`g4u_NLE(%VKT_kx9@2XbPPTYAJ^95kPaG~&^EXl8yM}W zhInp?DLXm;DD>u>eJnig8SwqoYpH%d7o|v-Oeq=5Z&7aW0=bl~^~w2iMVa!?wdT|t zz-HQ8v}(HL+uCsxcCnKcu_|%D=`b9+A`LFpXA~EcjwZQS|W6Pc#kIQ zGjgD*=aHVA=am})b=RxJXM8j79mw-i=uSFSIUAO$2(*{2WUDghOrpfx#j>EwxsrM< z3B`>D;+b~Wt2hHeVq9mwJgs%H)!)<+JvZapl0I7RSL`Kbq_0vpXI3-h8^wW=WI&^< zK|yv)O{t@q$}A_D%8@F7G3rwBn>%j>TEiw+BQ%#XvWG{8VFd0Sbh&@1N>^|S7i4sr z4=O6Bc)ylT*Bkvc)@@j?v(k|0V)mvBXUavU@GOIq=CNh;Al12K3NT^!n%EWiG(kJz zE!Nw-v#&6Ip_0n|0`6hQ&Y@2SLHF7}NCw72bVfg(mKg2TCGRJ4dP1EzROBww6 z7M<`9Cg`t{w#J~W&ERNnvp_KeV!lpTjK*obcQC!;y@z0=8=|e}dRz`V@xcdDXsO!Hw-9AeG zW9*d7Fh8Y?5D`m}@3o8xBffpz0f1knJBc%>QiSJm7BoU6x(jY>j%|jt7RvZfbaK{E zhU4a8K}8`2OHJ?B)c8EMmqH7b;7sYw~)w!P4a=+ZNJH9hLaaOdWdIULGvaN$&r zqaNC}@$+b))p&)8=-o#xWEb=%jFl4|(an%lMq3#h1{EJng|G!#AA8bujcXs6IDHN-gm(uePA{~!{2k{V+JeI3kOE` zEmIBB+MR!{bw;rATi7totHv@WfP8OgvKI6kBF5{Z2k=#>off?7Yg)u@jE z8}hf_K8q}&6nFgZMVLEAO<);1X`k3e;n33d=8Lh-RAI<8gKOWS7 zM&nXAI9cP$p+|N?;)(#C;atqg7O zSfCXc71ZjRn#NjNk^2_=ClYN4&{1}zCyNDq-?` z`$L!XE~j#xZR>Lxn13SrxyG@azEowvZVbjei0G6g&$wUo(>Mh(#LsyHs_3>^-E+#; zB*So9=HT@nZ%xT7cD*-f%|_=^4!8EEPn)PRdA8e>E00CjuuL^yXt`_UN&NLm7*Zi> z?V!T_KpxPD;APh@^oI=XHrXxS2TY?wnsL%4N^TN1|0aX(0{a*8=yi~!$SFmWe$+$z zd`R2NWEN*u^Z|izo`SJW=YA_ z&fd&%quqXi37`Bd&U@AeOBJAsU+7m0u7R0XmM351U4#mA-a%w{<0B zy$1Ua)(X&^(C~T!KEnsndUg}Re{=PacGEdRtFCOb^pbK6oq=K4-4%>7)DlndaXcV| z3Wn?4rAJVOyDS!sm)MOxiSXxTj$W~vH>;koqf!=IFQ222J(o}*0DA_TAA5Y2m}D?% zik06=6#C+SsdimUvMYLC!vR_8u!&SjQamoRGaDw8G$D2+u@yY>l=ZkT%3_ zLUZ7I4NevP1-S16%JpUHU!$Mr2KYZ9bDS{Dy*pY}5c?|gvtI#xY1RJj3Cjnl7Kys0L0E^yf~G8aqibN=DtlP6a2&@Ha^>DmeX`8w<9IRue7X z)$n=Pl|i6st+e&?{!pZery-3fhup1eRb@x~Cu=30uM~#6DsZL@O`@gIt9pca_b^Ti zhSazpiyW;TdryOk#c_1Y8Fgnxk7cPA0!xQCmTWCQLE3Q84QNoakErHC>BLmvwbhYnCQrW){UCT&P z^v~7V3$sq@wE6F-1ESe-!`4Z{%2Zy;bB1qmipSCNv4qO`ey_6w3^Fs$u zg0f_CBbT`PuA&cn+hAIyd$!PGj~%6@Q-y18mVdW^$yteQxM8a)yz_Yu?yOtaipxVt zki$UDT`5Cx5Ozye*OTxy{1N+wW_VFgxk-%E({aE8rU%O{DphtOxs?mW5)P*ZcsIz@ z%B)^uuMP5lD|@nI9k2u)iooDS1~?+((z{7Y`N|X zjW_8rE!&!$Ug4za;WV=nyJOEbju&@_ZG3U!gla676ctSRq;3S^mZ&`aIk5c}9C zNgkZMm%I@qcS5eN0C*E{8=2*i`{n=rLEEj}*hhb^1@~k>oY^%b$&WFZR0$f90ge@K}l@+rTqU$!$W<+eZAcUpES#1n-(=G-B z9N4&stzja!-E&RjCR)qOw0?gH2_m^A_RAZ6X@Etpdpfj3NSt@UsckfqoE9~$Y(>(dtf^^iq7>hKg5ghAN%)aUC0zs9e zVV&GZF4@Q~$HW;`<_z&7r%y#a$@?&nkb1GGb&A4ST+#1CsSn=@YvO`Nv7gi>pUWq{ zaPXK0aH%}?+*%3A+Macu;p9ZUE$+*roI$_jr|I>4hmkU&it*4W|BW5S(C55%dYw8t z7hNB4PkZrXu^9&9Jmt~vL-+$=!Ck`23IhPAwA(1KQ)Opl;sv>x8k zI+s5?b|9ugj)|93!`IX{!mzvs;{we1esa7`&Tgb1=p0q#;VQ97uH=A}bMR4xNl{A9 zMwUKbsb2ly<9xbrvu#}Tb)UV~aUK%>v%e~8hgW$U;{WhzuPE3+JN*%7KWZJ4-g`1eb*^gS=|9Sl)Zt(=L%?~Zk2+f1|?#NXvwmu1VFPH7pONC1n} zMnHnVOkBWs^yB3M+>g@oGTD%~^SwyW@7wrxJrha1&tDI#*aBLb+LgVaZnw$F z$$8@fU;;XClePO7R*J;EP76##MJf)-sI>!QkJEs8203+ja*_;H*wRv?v=2Q@g>roP z=n`t0G-^L?rOMx29buuk8WZpID^9&z4rjQS%RKH#JOr_~B~_Y{h^6crkbF6=mQTQp zb~s1c$GVqZs^C^CZ})m^0y?=uPg1m_Ob(@byE2&MbY)jyot3I}bsEO!p_S-slt6V# zpjzRvQK_4DD;ZSvs%?;%U{MKj7&ESl*55Un4**LE9#R7$-g)Y2`!AS&)6h=CoO=1} zF20Da?mkV|qILf9z}>0L(7H)*U^L5JG_g{@=6K#K_&dKZdt!_!gG!3(YDv~O9Q?xh zZO`2uc4nYA0^L6TK^a)~i@HvgD45MU;cAfa6B+$z!c~7On*R3sH|P zJc3b~){|mPxLu~D&I2x^2T|-}FWDfUMx~`Co=zvS5Iyc|pky>tASf(-B|Tu^sOAjB zmG6c~UkoJvbjnI|X5c@3P+ECffam1i<9_cqd_T&fKEL@XW>MBz7(cu`j^%t%`pyGv zm4i3AFB|##5aV$Q1uI&R<5d!kwwO_>xB5@lpSUQ&iQlE(4HwHunMw`~N|8k0v+Ise z(kG7QDSqUb?Cn?$r#a+WEN*8!IemTn_BiVl{w;6o8z%-5uI|{DwK%w25<>mG?|UDN z2y=q*cmhcHOjVZ4`WpY?{@cqAG@*PoEd%m}@R?D7M2P*bUaxFNq+@!(?M)ETPC<>V z=ZO`i@*DL;hI`mT6ZN9lM;tARlzAOf2Zb7nc{2cVe0{}3H{ln8<5f%XQZoizf|*^?*zp|npQ+TqIG!1HH^o` zpQp6|YkD56;M6xmP%JklahO&BD(tIbui0NSB@K2G(EfQ~7%LVa4I%VDpaVX&GL85| zmdYK{5wqxT=5%1xnEVAy`siiIABP?XUjOFcfzyJ9$dPP-U}ZW)`TsWbx1+$-x1-Nkz%jrtAefYtl;mRJ3~<*^F*DAo{i-p< zW1i%@qDy8g-{x>lHJ72|tegHuXYO}1;@n&SeY%x>Zz-PMNQ5nl?VJXzpPdY_2d)?d z`q?p8Gz|SLsR2N;`dP$8aWhC*z=)#JCa2ue{+>Cm)M(d9$iupL6}6#PzzFy+0q7QY z_yD-IEC`XX$$=XM?6o_8uH6^EnZFL`=La{R_9_E#^BO+?u_$T9TC<6}clcZ9*2w6` z%84QpC~hyoP6OAnP#53i+4T{k+_`VN6}VD;A&pzjP}OH)*T2K{bY#{3<;f*qdpOG# zvTJY!7H{QAl?4s4IIEF~*V}GheA!oh+-aTB@N&jq2__^v*E^QW|LW)$?7Oejb+zX4 ziRn0c{H>~`m0Bz~(}c`=Qb?^TTc1q}KxRjQ7`16X1(gIj1(hCud(<2F7m@~20W4DB z|3%UH{uiGH(k>Zy!!y5%pM4Qn`ulEQ0bep(L`)rV>-AgzVczLx$FoG{bdR>-Vn}TU zA40KMm?x$kfO$Ot{s*fJl|Es}=;ynr0RW{2EXC9;K!u_X>=gk~ltMchzBds~xY*M{ z6B(B=&85mN0_D(wfIJRJUel<}*kldc?J*8Ak{cxO&o%d-ZA`1Wk=<1Wf)Wcxqn7Cx z{SD4i?E9{38pIRVpMz1oh{u37268IRAz`Yh4FOE&SM_O}Q6MqdfRaXdv<&KRXVvBF zUCfU&I(6^*ke#mldX_JON?a3ep76a|!?3zJXe0@cLyuWAM43XT~HjvMOta7(?zP1CD)zN7G9Bc+U}arvm}B>M_^4!O-B_m0-B3CY(b zPRIFi?Hn?kdo~jj23q^!6U;pP_}e+_9qbp{-+xu3IlE%_i6eXd10m(s0w?m32Q%)X zqFuEa_p&v<>_5A|uiva|c2HXq)xg5`@F^rs^Qj(+77cO%D(|Jt>G_)tH2F;&@Y4Uu z-3YlVY~Z@O=G*+i>Q6saf)=~0XcG5D6_cJ=okA9LIbR6Q_e8Q*@SBIBsdMnN%#sD8`$a8)B<}R%cMNtN}8k=-s~}KU^W4PWq6}mztGFPI@=O zc5U<`68>C6my`$zjBy6&oBY%N=UOPT?h4;`%`fJ5bVQ=Bp9i(l+5GyOIiL<-;r1+R z2rEga|545K^bCVZX_w=66sCdC1w|zRzo_|Z9(91&$NlcRT$W*q@@yvo2ceiAHMH^+ z9<3$@v|NC?c2$Xf5dr!VK+C$yKftZJqB3QtsJ|`)1IzIU3SK(fLMX&$QxK`wWIsqw zOkl*KyPchky0#IMU2B>zC-%UlN7djw3AGW;zV9@$`uOWQbrd;}b%pF9S7QJH~)yX^B2>!`hup9fkV*~2-Jl)xtJWoBO- zH8eC>X3%_*`qaRit8qrN*8)^8AhN&OdG*~@cCQ#^S6C8F;XB|p1hZr);YbIULRx7T z1#Wc*E9q)=>9uVzs+RyO=0L)p3~b7>OcNFEAi(9iJ^Tke2hFYk%fAXuD+z%BQO3HBL9h zD5NC!4xp9yo%BamOPA20GFhs{j(jzPUVwZGmPQ5ui30?k#J3exg={e%c^X#lQY<$k zi!GqzGeWD)oeNRukG+^5drI5sVz=JWFCO4ksm6Bk5sgyB!tT3JsKn^)QN9g z(LaD%6iOVyM~loS47r+_XU*5WAO6VbPF3IY4PTSyO!p}s|En)69}*5oM5MY0r%Z># z7JynScSF)6hDqhk?>)j0yVcF_NoRROMu9EZ-4Gd-;q`LdJmI%ecK$}7@va9)7tlEd zn$GWfgysGHS7Lr}moJ1~2@VAR_M=(z`j+$@gTQS-r|Gz1PCH^O6Mj$I!7tH@=1$gxTzaj5Gaa<86jPG3oc7nM{ zPDVj-o#H0>^&8jz>)Q1jv^VLvBKLt}@4n$G1tjaTG6 z(*HW5iheSh2G;UwngvCF9$!Hh*Zq$xX>zser_CpYQX_&Fff`Q=!-WcStSyT5;u&+a znKW@S1+9P>DVYewKoJ@raAdz1b>!znGNhK$~r%~?g`w5&fQ$vRDJNt0n)#(^O0DpMP)v)0}oWTDb7aw`KcoE;}O_z^enT%9xAldp7gs z0?UGC##9R*`}>O_&->=HVp0d%uW>CEQPH*VVO%%lL$pWPZhH4F{1Q=BF@4O3!5hmC%*&`9eWkO0Bm-HP_j_xGQ7vNPG4WH!m}=DyFl?$32j?f#Gs zQ*Sv6g_482ybMY)IT0tCS>ege*lt*I;?uuloiBU?3_QU^rCILYa{%1*O~l?LoC+*mgAqi+I?l8@6T66ihM%& zY{`nyX4Apw@qtmsuV(D2G;tLpr;;8|_!M8nko+=YNTHNAz3Zmg>#UHgN~z(_C_m`y zaR9l`D`5_fWo~_Gid#rG)MXLiEPP8*X|hH64HdaL8SGJECR*BlocU>;Hf8fW5pD@| zyP@XNB4^-O4C~fsBh`$~RQ-aW0s0SP&GQ+-%6hAtWKb!XaW8!**c$-Ev+6`HZen)#^0?tmnStac5$Pm?v-TStipAtf|?A{rE*bk`hk54x)pJsEwM4| z{JN+}X=uYwLqk{ZljZshfp0!pY?b@23VBk0yai4*<>xI@AM{36!UY$Fxs=*kg2nhu zk(No_Feg%TO9N{iQ zW)v9111CB|zyZQ|dxytZ89aBvxe!V4D?7?70{__nESaj-C}w{xSy;zuS02YWA>r4) zxM-+9(k1}oNJ4hZj?U!J)`)JxbL1NZy|~%`=8!-qh1o?kt8gI^z=$r;lkl`Lb6^DP zADO&1v`V=TvTQGb;*-!??k>=kYY8@AC)Kmw>K5gt>?DiWrUBNvo{YZG#Li%o>>vc* zgtH6Qp$|Fz>tYYpBJ!9V+dJmxUH^GFErfk$30UaCpEW5jIrlA>&Qx_XA|-G?>W`p# zO;sSf>mzv(_U}`W`l&&)o{=9IWk6Qd4!rtC)DfQ4AI)#`4`Up@GEteo?&(mkgw_l- z>Vb>)?XN5(Q_YV8px4FaLX=_2c^oa(UzQ#VC~OP<%(}Xd#VZiYW24_)AGE$)W?9l7 zGJ&6eO0Suklv2Ap8;udJri%o6A+(AS-v(m9=% zp-Z>mUT9tHJ1yV6=-kJ?W85aAVE$B)wg6Tc@Y0!>msh|9Wpjkg8*nDoGyr<$@h$Dc zYRIJ>4`Kk+l|Q=2#9sio;-dax*hEph@>m#%)k_xa2+aricG{chrsWxK*NDByHh1ns z`EG7Yk+D9Z$(^6R(P0&rkMECYCA(0!UXL;^AQi#i21#kjb?pW1ek#NO6-#><`qRjJ z=0WVH=-uGlsw_`4scD4!yIHGG5ld|SK|NBCh!c-j?U%&pHU%#ppbvk2!sJHEDGqmc z{ic6sOv~{ezC4R(Lvi$sa%MM#pzv+bPtZw_v_n{fIAHGLmF$8=t9e69wMaTs!lrNP z+_!+LjocMuMx$S`ApqJ9!M!z)V-8@ovWDwx!A5;`0!NmiM{Xyg5T(4SzL(<>KH4Y; zKA{f`4m(Z9cp%d<`@>65PrvfV!0ZpV%^Xk_miR%AkU`E7FTnqpc=Tnct704X2I4*z z6+a@FgDmS#_pRTRvmUmdYd$_z{KLo&eacoCbN!hNvT&S%M&>?DtY%&`k{may`fx$R ziU6V`3a;qG)E`ikvm={*XDjuu4(Qk0%^v^@ZGb6|!r0H;J5QhyA{=?ry&^z2fGao` zLMW}r*`UbDdGAol6%8$83EEPL=r65I&VNrs%RnEjR}Oe#@ZpK6K5Jd)2r$1<+fC=_ zVytA*i7O}XY>P@clj1J^e<4`i|IVC>?f-s70~Edh0;6|xYuWi4v!}oI9*_4TSeJBu zeYd3#0f(z=d6i+_Z=7mAp3V5RJDcGt~% zvOh_9$bL1ZX2puH>Os>l74|iw64QO8X@Lbz3H-xwBd7%mAiv#`fcaZPu=4EzX-qdQ zQi>C)C40jBRr3W8sihTcYJP^+xE^J1o&*@WcsA4N6q>S3gZuis1B z(lUUJy-V)nZ0XrW(b+|Jq698j{U#~Hj?&NIb&Jzlq;+zdjE$JNcR zLW}^svu@)VZPUyN`8)n`t&~|z>3%&CC+rl7n?r<)m~TDHkTN?>uVZ5MunZ@imtoPp z7S^$K#H|*7$ELpj=`|#&wwkwn43jq&1YXOxM~%&Z9Uoptj#$M2>{g9Wh9jr*%Q-|f z*8@e_eg=Ghz&Cv6e2&gu&arg*lyBMwfs5mE0<*53Qh%&yT=X0GcogLGe7?9fWlsY4d89bRy^77E$I1u~TDt(&!~N)*UB}{y^)Z~Z zHof>^uioVm)hlRD#j(v3@JXB`CFA52`fHYN*01lGn8HLjrR^9@Yk%nU(Pko)sFmX! z>$I@vYMcVMm@*e%*mzT#pflMJPUzLBK37Bi%Of~YP+Aaf#PRifXuCBM9PRSef7G8P2@DWYnC)UgVP}^@${uav@{gl}FM$o2f40beTaKw8VU8}reWnD`r@_d0A{<6;X2Nkk;tUukh z+gF4c-aHG+Z+pN8dlO;BGLPv=G~wBFG1IuL7SE*-cOhkF_m`0^xOKQ84kb&paP5AJ z$rp!rN``9ezR}vsC%fM+GxZsed_(4m3iP&ZSGH*GP@#pccj}i0bsqVFyL84OMzVKN z+od?PI4$8YzQ%7cmGC5lu`6Vh&<&qNUGdJQ-b1}<1bW3ZN3D{tTT!q~rV)Vh7sC_i z39Sa>5R()4G1XH4!!Z3Oq|Zf?gu2PKhR8ocEj7qIsz%Cwxk$Aotk9fR-%%__2_n2X z0g)@_=T$exCUC>1K;>z$H=#$NEF%s3rL;wj0S8p^5h&HhpSt(qqn?lCueJr)?K^^a z!R1|3?;wI2b!Sg9ke$jp|Z~cpPx97?~Eo?@{qXHGqb?u?}{2Oy+R9&_|CLm9>_Q5Nc!1v9W@p-iA zor0!&V4Y2_)Pc8z|NWd+7If5U6;CSdK~D+cvn1IrnTS8upGkZr8Y? z4&fsLadCU@p4%3IS3EK|GMACB;-jbFfGWQ~Rq3zKE4gb2sT-79xqH`xA*ysPf<&I* zur+Vl(*Qz4SFV>_xot-rp?tQoXKYXx2EUg!T+V~XU(zS4kQ))43%{mhe?+e~iKp7_ zeD!&x)->=)e|=ZybIS#0i67YmjR^@`GI7zJ$-#v_E4ho#(Wh2_ioPGyJv|{<3}{{GuZoz2Sbq zc2n`gt$|h~>R<^)I`#&{Y<4WtAhrAwW*@3s7z`5UQ&7p~6Qf+8ZdKv-I{Z+OkdrrA zeN!PcuH@iVHWtIP1KBqyUK_(vpk6m=`%LlKH34C;XkvLEkC)IJ5z>98xIJw-TNnD6 zV7M*keB$Tf^Nwk%;f6_9LCq|FYh_(P4=+CbmX^5WeUuMNTLKqUiTX>_YbdR`7vo|^ z_&cr z@0My%&ANiylFsYitnGr_PhB2$xb!*Amz>|md>vElcUQ|wfItiI_x$9@F(L%& zf%x1wagSPrZmZxQhQK@75RF*;xz@S(e9Ooi0C)2IP**)b>e9cK?{|LNgWpCq24F-AOKaKSXyHHnk)Zq)WBFnwmFc@aV1vi**CfwtvPmU@KdD4^11A4zHF#=1=C$1< zH7i(U>J8a?otiq_RJn)~i$FFYb?yNbLL=7oLI>ZMx-M`cnw3aT@z2`NI_j~u!PPADs)1WUf)TTzNLbAvoe(X{Y8t1kd-x5Kso1B0KD#ot&#dm%s)}b@Ppzu^aXo6oKACQHZshaE zM4w;_YX<<)6JyKuHYsTEt;CTRpHe#NMmXfTehUx&Wl1Ls_+q02vg?r)(j@XlN@9!H zfrlrdIm$fi{;w95`^ekMP}$zkg(4Uf`3;{w^DJ;3se0V6)d-j$hKBqqdgl5ShI2(w z;aGO3K0dzc8!T!9b5q2JMK>xk6CvGw5k!A=;?J3;Dr!;=w|$uH8|Q(OM8j9^ZKp%* zG(9`b&2?+{N9rvW0V}7Nq#%+Z*-QclF=thGz}pqS*9Jo;y}rWs4vuKr%XmlcG3m#I zWhP1acBC9Tlyx&TAm_Wfy#uUf$iU>@_(BX_DR0LAuhrr#dxQS|I=?7un##zMF)7{% zXXfg!!v9)B>}y8ynJ3r2U^}Z6;ym*fZzY+?4Jl)p&_N-A7ZH4#eZ9uu0ostWYETINozl4Id3{3_Q^}UfDr@ICQ`NGUd_oW_ zKAl_r1(Yx7w|Fp-@LOArv6Y6iZDWoUp875RK3e{{2n}9YfrZt!iC_n#uL{_}`{zs@ zo=}Qu$P>J0k6Dn@t?4@O;!BH{>i!#d=T@Jit$1_P2P3^tFpLnN_cM2*&L2cbIeFwk zmV21_bh`;CtTXxV1mx%}URW&7xLp1fDZxeOtsHSDz9rK#(l~^dR$`I7_Q!U<1!vnS znq+(Osh|bfa>7Ck^Znx7op`NO}UF%}6wESc3?Wq@Sh(pG{ zu{d@}-KJWJC3<-bjy%XSbX#<%=qUFBmKJ)KH-36{GcYznbjOV<`6o)uONul788=NG7I6U1)%;T2Tyx~ly!_(?lI z=aaMjTENMgDh`Gl^ei#VsW|8rRpW#3=_X8eMBsBfsxg%-bWvTo9t0UzfEME z)}gS`oa_EXNunq0@GCE_10=~4a=>eTccz_E=qKBENE^?Qm^|1kQ^OvFKpdRN?lM() z7yCU-=2lo1U*BfzvI5I51do5f;5g<=IQu2`7oS=@muY&IVQOHEA2D#j?Ih>Nr-4gRDzWpj(^{2#XOfVywA z)#(r94UD-FM?9OGvQ^jg^T+K}`^yL^%lp`O?eL8!z0(upql+g6XKng-F~B9iAHRB< zoUyJ+O`+5hAmF~@1>?!lkkBQ-`vlBajjnr{E|S|V#zphlJ_@m;(f#Hi`R8K-(BpN# zaG;{@MmMhD!ajvk&FHRE%RGa#+cow^r>7XSO}ioZTQAqj@=JtOzs0IQaRo)qkuGI1 zr>YBh<8AbC{4gqJ#=^b4oBBH3q>g8~WqFI{`W+H)YT;Bp-a2{ka7eprqcQSsJfqly z)OSL6cs0z6;{l1c`C4#dG#jRQ-s&y`SZDkRSJYrk_O*arr>~8WS=TM)nR&B%zV#5A z)Hlli=|#Tvx7_~ZSz-$uM>G~f8jYZgH+BIkGF$NL>2{?pv%}r{sB86>7`!XxgcVRI zke3CG7{o1Ws=*fYnQ$K8Z@R9X#16e=oy_oQQ41~&=b0thj!{8}(}AFHI{uM#@`Z5dwPS#mpF1A@uz~ zq7j>Hd?K`owbU>#&MkDj{o~q;cu9b(*j5y) z4SkWm(>Whp!W$%evqCPl(4(~N+;fk`{#KsRXO}Kb=YSE8D4V1rZ-(#x^}MlgEj>pu8(8hbWNMLYYv zbJT_GkW~J!kv2kpy3!HdRF{^j*dhFDKhk7x-2(8X!v{BuQdt}LJm~EcbKaYn)LC^& z$elmA(4TwoAg7-_)kh}ry*qfVLC4JeJqK-Z+Itm(aJ>Wp*Fi3l zHpEhTD@ktpk9P#5oys%!YHQ*Czdn65Nx;&twq+rt&2LqnlDRPxDMG&2$aLtNv-lk- z%)?txVZ#-@K?cV0lLN{P={u7x%2<}=9bNmWn0q)X3OJq(*|6rZw93Hs6#1qLLi0Fu zsK*^~WL%mgZ46o@hX`ZFSFCuIFT|eeXx074IxTA87hcLoz0gadvpKZ2R4}|jA;TYo z!9>^liaxvSO6I*t`kjk_o_nE?U-fD+)sDzcm|lUK*}dd+Gqxhr^q>ny?9ex1Gkc%V zxMS@v^8z{50C^GoqSwg~#(cga%-bS?e;7p8u_WKdK+G}oGqMUb0A?M| zhhNG@(g1PF6g+^TKS2YiNcZvk^@vhMm4FH1PUmJ+p(y@l|9xb8vdLP6ud_R={2?ZD z{PM(*M5E$m%53SkJE>GWguQ#zGaQi+HNo-0GgoW%?DVVAcPuWyV*u~j}$95Zn z&8z>okup4$^iRJ}8WRaVr(Vkd#0(MpsXqq$I2r%CEF97Q=ymb8Oq2-z3DCmUw2|Bg z_+4hF9m%VcoOBG6a0Dg$4Q5zh#K7G)8F>d<^J_%|f733`QE8CQA;ddyWJ%wnYVt z*$9d0RHyw2l-TY$u9dYL>pcEpwECLAb*TO`Q8W1au2ihZcmb8&05qn}$x$&*s8gl= z)3&^pT3B06TAYg)bSHae=qwfDl&Dnlb|$)sH)FV*%XEEOPKirZ>W@R9D9qa;rgbAA zh-~*rb&H`T^rsriL12dK4q?V7!XC{We@J~oW?ADD&hrZGfuSo$q*7VkwWLedz3_OU z)c=uYVxlbHUQ41Jioi6-ufcVN)y!MeoV^bH_z?0`4X0)2U6C35hk+6M>#|5`-U8hj zU~}vNQy#3UR(~1vR5795eG~3*fHPqRUAtZ>UV+#{@3o}Xx}@&O@J%jx#7&gmhrOXv zQIaL^AMZ2VdcsBSH9haV};-k%a`f>c&Rf6=_RR!`Ey(f$SU=1Hh#x9I=dO8NY?QhY7&L?$TN4? zzx*+!k=rMB$tFA6e|~eWo}#|ZhhAN2Avz0 zrFJDf^UzYQue=;Luk5M*kK9ApcCiD`E{soRb-VsZSTu8n+NMV7^-W&D*{aLuA1KuQ zU1|2}&!LJaowl5-g6BGPsvoq6z)}xffaD?TR7x5ov4sPwi9Q~;wB0F2zW&g=WxMIC zh%*3MgJ3p#`w?z1*MIQ6y)hQ+G#3Y-#xXJI%{Rq=7_k6d?KhOFz$3KoP7--4H@Q3N z7=Y>KT=a|>@jgwJg?x1~a9)bkxxg)F3;;Wo-_)f$>+&vaQ0ivhBCZ@@o~O4u|IJ^1 z+=lC^*yob^hAZWG|8);!ge7xWRp@8;7@SsHgq;-T_R~Ra1@#H07C*~F_D1mgWmg^E zD~NlCE(4ec5q;x_?XLx10w--Dz+c`qQ=Mn)h7a%0;Ji62iIBPhW>9bqVUPPHc;bXW zPIpIW=}`6(y+oG4x7m=Z!Vyg*6_&@G<(!_<{E?7%8Z*+XdBRrm2&h|~0+!Xlx?_wv z?uberT1}eb9WT#cvMF9_*B0Th&t53d5Z5>?{mn78Na-u2aQ`vq`h1O;ki-7=+M?3O zFuB&b?ZR(I*Htt)sFhvq^P(oN2AR=k9BIWpUzq{ouU3BbXecF3islwITJv=du-5Xzp zo!EZik007y*4T6{E?JZ9uGui55{y<@D$x9fwZM3xO2>$Z!~nKgd<)Wx+?y(UtxZ>E zTZRSRtLpXo>A-5(F^Vf>$I~jvyM!A`nQ7-i#op!p*Ga2GDFu0I5j?XMSx7cPkVIJ5 zA`LWlxlCNr`s=hz{Y8#SYs0S~+j`{0l$@cN~ z9ksb%mLK1>V9^QTKaAX|TOKlm`3l$Z`%r=VGXtkVUKdM4k(+CiG#l{YA%Tgd5LkU| zC|4wN$GDK25R+CtHgk$yK%S93%QLZ1lwG=^Ij$2wGpf37<;>{Qc5?-tygW{Gb6y$t zFA!4B0-$a{_Df%&G|sU!jC9~_>PN<#R!xXp1fsWe_ z`%CAGMwt(-+;y3%kmqC3yAfu7R4j$BkpFa+&6L4j5;DC5EfrXZTi#+vA%l!- z7DejGboQ1^|FC|k(uVSwy5@m+O8*_w5l6mlI)bgps73+HH7ERWnY0ajA!A)#F!`Fv zR$eoN=%`CqUjH5?TB+&m$u_g7aP@Opq^vR!VujZ$Cg1jD@*f62p(|QF6^g`G&%^?l zcGAc=pBF_k__vhW15&7wH>n++N)({c*$?%rdf(@TC^ipzMgUtL3c`B;zx=pIxm4d>hLoKM3 zh6O7aWi41mJ_urzD020Gexlas?(?IAq-mHbi8x*Io{tbq#KB^JIm>@LpcIm}H zABEyK=6vKuhR!7>e{uD^xRJy@!osU%_a3E#R(RPI8ZWee5qL)YL661%Fd!HWV=p`e zvq&3z@o=N#h?9E96Jh1&Ol8Q$lodQML)S0EHmnRj0UAH5nHK~8(CX8Xo)UraML!<)tVM)dU4i#{U zH8}sRJRtv3!0HC1#HIUSW6zV2;Pi8AnJuJ|uNU3BBiHjAS|+)|=b2XZmX`!O7s@Wu z_>_bT1EP|F(*j0wD ztO0BmK%3~ymd25^`EO<6B?j*e90+JxEMjpYS`MRVxSdXn)ACw$V^$Yp=pjPJ;k#lM z6OMc;wn^m>%)Xh*@JoX5io+q@-qs1i;=-$G@@?uVASbK5cYRCVsVw#{^g@yfMh!bv zc}vyLy;sJo(bzr zIFfqm^Xhv4F)96sZ)!ybQP)?I8)Pne zFqS_VBU*5t3%w1`RNS3EY*)h6HS%_NR84354MMZd{bqudETqvx6>2L!kXgcymEK-3 z`=^Q_iyAHc!(O;bT8vhs9|SUBVO5Ou8=t9I-eL35bRe4(9~OR7v>W&ndetyV;Q4#b zxu($@cSY2bfol>w8Gz;;&_^)fh?6ZH6M5py%sBc$i%b*o+Aajk8G^zEvEBmi4^{HqK0DRv9OeRM%KbH&cfPUxodZ;4%#y#;LJ@ZcP7Faw4k6ShZ?d~~*+*q6hh6TbhnzE5!`RAfcmfa1$2C~pzid>i9KKy!| za(;l)t_XaL5%J9Hq`x4w3%nUJvkkA8$n1+LDU@1^O_dy{N4K81E#x7vd*K9fa+1mP z#8+^i*(@+$DpUQnPU^sFsUSkBiVCm{IHc3D&C5TePAx&8Dx!-RO0ziP`99>4xh>mJ zr7zIvfM`$b`8Oc$j#)z{o;V_cPHvty7?T)_BHYF>Z1AjwY#y!50OZMrIP zWd~pOj)dNi*w~K~`NYAabEw)6?#hYh4G#lIWE}~gKDv97T z6uA##LCH&5aG?d_t$50(ve@m$)Ca&91UU zF)j*ehgxL=K~kk;0t0}vzli|?pNjl0#FaT~hw|fJf*4wC+{TNA<&Wb{7O&ILU3+fJ z#=}AU{q}K?2XdT9Aw6R!Qm!_7(NPg``%F9=mhQ9oS?rIFx*EY5`(mHzif0d~In>Ea zJZ@ZHPH0C5;_m?PRV%W)+areIY5-B7@B<@l~T5m;U|kh3D~(QrnH?<=b*3 z9Q8k*gaeNjfP@!Bzad(WQm2Xz{FU$c#dc1KEKg0DkGwf&+=$W210@($2Z?GARbP*u z>virujA2Vw`xq|uCKba`RL{3(n>8Hn$^3mjmGRGv+0h_C9`CU_q~9A1^~k+KtjQoUp0rK24|LnC}5cBalk8S2IYIcd#r9!r$ zw6bpYc5%~b;wkL8%eTbAmx6C~;=K|V?42DMC$MKWE9MTDSe-nNNU@`bo110!>`$tH z$N_2Y|NP~wFA@%mqer(VuQI8*fSoDS%4dbRjl-=(#IV`j%3eEOuLf2wW=Az)@f%V5 zDO{YIbfFf3<*x{bz9Q0U`sPb}?1Uj6c(G2{@fhf$4L1TbPQREh-$sX?JXsO>5qhWp0toY6m~OjT>Mza^-lOp! zV^s!U$gcC*m829=?A;YD&tiI~N#2&jsram|#%<$!uG?eCNF~(c4SfkA-SY=@fB=hb2WZyKBVv|BoC!IM48L+xpx*ta~l zAg0BlUE}}iep)mff#h;BD?}bAy%HXK+lgu!p2StX0#(k`NFCOGy(@58beTvF6jXg) zhl}4>a;E!p#;JNl$b8!+A`7P}d?zZ}m-4D#v>knuQO zk+)g<_8j;bH%td#URzQWIsF=Mt1sM15G=CEE(b%%kQAtOGbcgF(i zTuAEPK+(D@oZM!TU`}!y}McsHNQ{>ll+%!XAL()K^R}WiV?tMn1-M2 z!NTZ$ETWCJ_xV1d74h3rk5_Lr%$j4o-%`u?(Drw>4`SGX6GWW0zZQf9%Xgn%{O9R& ziFA4RDD0{SGXGw&5%7`|8V@A=;?P}s5KTu$gMHM8-;3e=F?Qnl?#t!WC(gUNAan^9 z(eQ&&DM&Bj-n>94@Z?c<%3a+4r1IoK4Z=@$p%OOUdbUWm`p(3=khIWywfux2&n!{Q z?#3&G1*H&f8AzeHofT56ybz453GGO%INHSCa*^z{su=>s)~Psm{0EuD8fo@bPpIO( zom5~i{g@OaNH42s2ASr}`(lVp7SL+^S#~_Vo^rvhIDR-nm{Ywm44Jj7>az(4i@%@p zfZIOLc9dvKYViBY3(UY8p`u}1$R=of13uL=t1{p%n0*19rSm7{j(N}95MF;y-jF~jKFg1;ON{s`QSlt_`kI}NZz)wtajGWIE_G%p{6Pn} zx}%g}gVOwa&FLzg(;;6ke;7+oG_%AsM*-9NM_F%Bdq8|HTgew zwq56eq9mc)_lG#ewGTzH?-h}8ezw8eNy$C?OwY85nl@`jy*N44^Zu>_ecYQVq+S{$ zM{d8pezH4otnaeBS4hFM9bucT60I;T;!qk$`5Lfr)?E2|58L^^=@`Is_eq5|{)|FN zpySYXIK8p3{+K()YNNfb&~8AeGQZwnu;O&v=wartR*uBTQot`Carh@aS&`4qHq9zN z{>AZ*kZK$n~ z4z0&e*zDe`fEb^uke^<1K_K*9MWT-^Q`dl4#nrt9<_lsz40fhI>3|#l>c+(^BgO#y z>Xub;;fHA_A%1TiniMb39Cx7$Y>BsR1_P&mVO(UCt0=sThq8@kAsN&j_I}d-Y#BbH z640(`T+9uiD8o1549{c!tV=FJ3=CV-9wp-rFwz*Z@+Y4{Bh?1Yekv$q_+UA75X6ao zaDX^Z|F%@;ohnucan-K9s(u*!ya z?e8y9F0e&*sQzgwW61LTRU1R~?jl;<3OT>#Ju{lx1$sn+*=2(0u5uFcRx_F@ltekj zPam|ZdGyOq2MGehFVd0MdM<}Y@+!>XX?4 z!*7gbfAJFjaz@5}qFc{{1~*r?G@ka-NV4Z`yn1_&Kz#dmDiVOW7E)G zFTzkdA*R_@lmx90LC;S#Ub_)-wXcM^X|z!TAoLFZ>ENtEjRtvBli4X9Cb!Ozx@+4Nd&iI7E3^v^W^3{u(5a9P>8{>jr^6|$2^@=cBZ zD-xBkiOW9W2M!k5#x<+zXMr);_~A!-f`oPNuY3wN+~FyjtyM6^N>Nl1CfY;;0cYeA z#m)Z1K&6Fm2@Y@235N;4ZzKeKKfNPp8!&H{%0l#!b(RGIRD#mbmNrDzq~*5dtRv~w zILWJRytbZVYO(R4wuHPkkIsPMc)|561?yLRgn&ngwXSX0rE|eSHdxF>+YIXmvh1fi z&BY_hiFm}zs*cb7iD-(bd-iwe_iJyr;|ueY1U14M5He?+?dx@g~~bB;xeHq!^vbSw1fJ+S2!h ztZmQwLwX@%3J@ZqKZLtY2~Z9g{y`X9Xa>wC8;U~}?<4u!WDTf1ueJrXi!WOtA)O=k0*8;{eIe%C< z4BEn+OePn}03k%^@D$%?Dzb!(J80O%2m!bQv#saS0vF)?7;HpIs3pXDS#CVIzP-@d zFBanFxVnE46hcuaH@18fG_b0>N;SkRVrXI>YgY)hCg#lx!Xs1Js0cTtTV7T2HL=VVR zyq)KON?Ts~JuLp;*)iCX>PpSpe&P9_t&UGqaF|8hQ1ev|-X7(5@8y+lVVhdwLv~cQ ze`ca_E5jmtIJh7q$4ha@6axARcxSgDTd8T8g5`=PNr3i3bYb<9!vnzdxnd4PS1>Kq zyA15WntK>uo1Ys$^r1z*K26&ivn6}hlMG<(9;*)Id=zAf@isiOBOmN@Y{kH))<#bI zHv9xxd_PAq{!}5A8*eb!aQ0J4J#n9LW!DgvcQ0_%z@i!X?3P8a5Rgap`nc#HM(=9< ze0i79Cg3;sAZ=U1EjLH)oa(hp4N%VF9yS_g*#JdOI1o*?JWV=!;341+t(y%dpNYoJ zmI@)xkL08t`%<89M9Y`zV#2ipH-C#dKA$7yA9n~Ej~|G^>BdAdiBeTr3NK3K4%s6% z%L^;L{SR9VKkQ*2PUXqW<6HPFv@~)+Ps)ZRyg8soAgC$YLt2h}YWu%ai`Ec)uD4Ij z1wxy$UAl@ijeFTvHI7S0g_q~*Papqb)PU1E4J}LSaq4BG)i3FxBEhbh`qYFu*P<|2 z9wFy-BQlUOL^~x6Tt6_~&JK@96O~n{u!f1eu8x={aE!=xnftrmX@DXJz;)Zb{2H1< z^9V~d2S$(F@v}2H$L43-LmNb|NLrPtd`!GNx%aSGer3Gn@PzhSH2^unXRV@t@7f;z zeZpG+Gv!eoa7Ln5-7)$sI++~%UHY27CRw40b0|@Q1B*LRCw(F&%qvn#a7v?I&b)4J zqh_k%FK75m`#irFy>!ccCN%?aC_B&Yz3O54ag)1Obgx%^oy0+rNxb$ousmM&^-shI zEBR@~dc6ybh%;wrrJJfgk+ZmXgm$H)kDtm|5fW0j?6Mi`;b?Zar;;Opo3^?9F7c>O zAGci$6!Y%Hn!tbbDVDoTS^QxU86=(jI~lI_x`4$z?NlRbo4l&0xVe`@guwd&npX|8 z{mYhIkKzxL<9;gBMfBqTB^KrSui!`z4HfSGw0QRN(3Jo;w;97Y@l|Q#(f?#Tp z6K)G5%*14N^y*Zb8R?*MJO?Ir`iL-k#aI#8 zDiOcYTK~1~TOrp6p*5V;x&gXNzUN!sgpM=3jve}ww8Q=$?(Z&K;X-FLPi*q>>YT_H zK?Tf<#hn z7l#baZ*~hJ2h^bPI1#v-R(qi1q`f1D)>-xUNv^2C*^N$B*UkwxOa`eDjwK%au>0MV zrNXJzudS4KlreuYG|M+HhN$q)<;#*MDHj<^w?DD#w0AReM4S(mW{q_IQ4t?;EB|7^ zWt8bzPtO8+?bk#7(^e5w{2zw8;`(P1s*~*>@6K$B1U<9yF3>NEqA4dGZKFmaN3o2RIoIznZ?C`qq25fo&`|3tPtxpZ zN`0~UOnBJSKGe8HD1dU7ij7!>si9ik(EU3WPY_!D_hC39#Jr+sJI$h-e93BEE^Yg! zC@Fv4^aE)9xQDHLsPpks>p|}jjqszSq^Al?GQddR!x8~3``SOeTTU{zYl$`0(llMQ z6Cyb8T%dtou5Vr*e_@5v;mplr)(W0&2osDC7Kjq0W=QqN^AxFxAOihmR1juwZ%a%p z+%XR>;FW5NL-AGhg2n(puUi z>}E`ERuTN0I+b6Y%b`C#H2P!jcExXV6OV0NJunR(!qjd2*@mDzeNAW`i4l`efejfm zINh?4AYdchz)iz8QF8L2=kWd2N7+ohM%Rx>1CB8WJ*)d@e~n}WE4O@qP4lD~z|!fC zW4DT33VoSH9WbodHWlys%;0)B)z(n=sES zeAj`SEbL&Pm?k{}udufqtPDH0LI+*P08zpl#l133TfchB_i;6{4ZYN(MSg0NL^6dzl4>q(S0!`x9IoFu3;B6wsn(Q>L`SQ?Jb<;8az@S7cIEO#OrCqXg?5OGO`Vq+ zRy7_e&Xp~OIh6Z23fFDzJbr96gHLYG5z-DOuDq{7-7FPjOg|>f+nLt3%Fi>ITjnR0 z{E4-2UZ;>Bn~IsdRoR4Ci`ddA=6xUdEeB0AoQR%pe18*M9*?s`;m+0c;e*1US@|;w z#wJ$#)?>asSRkz*8n;J>EL`P~!Q0=DKp&Lrh~4a`&>$++t!xLILc^kca(gj`iNL1h%l=u1dQh|-?G{mDAlrp~ z9>LgRr=SrDwc2InYJLkP<)ZQaRJP}_#Bh1JGf8xIBhaZy7@Nd?l;e#+vK|TJ&5vch z_=P;bjTn_fj(`E)`LAT(9+Z|+eFK$J#n{u$=lPV4JSf*7BKjNJa|`Yye{Sdhr=RL4 zWu+w}isD6J1u1C%WGdR@$(hU zT)oV0{}-0GUhUQfa6B_;?NiC^WJx zjSYb17F4`71sgq9ykBV%^~0@ynLDXIFSjN@vY1S#udCXRAH%yOOo>%;opwYtLj$t5 zja!0ay5?I3e~shg!wwM0se7`Nwt@H%J!2QERQBh|eYJ;E9nPxT0BET0@ow`|{;k76 z_?b2%E2Jtlb*4TqhCVl`VAaTnp6oLi(fq#<;qK01@yLEq?nQ?SU#udA4~4QG<$lbvkEL;+08i&y?8Zmy zxXwYU0d<~uO0esW_*yZ3!jdQT-DXFLKK$tLxoauiRY@-wOzpsA<0_V*5uY?<<#(0lKBuX1C-JJU(Fv@<$wku1t^d0nft8V@tr)NUFtrvzck0b5w(ne<)OFTQHT>N4a7vd)rNdxuv&^4+4 zun#+sDpM9AYU@GY>wRYA=s)=tcS z_H##AQW3&fWYObAtCb6N?ex#HdWfECg_oBo{}#$U*P73e=lDA0wQRxb-atm?4p&)~Cng2za*cZ$p6 zwtg#Rt7I*K;J;3+$Aa2hMQs(dA*My>D4LVA`#PfD0h8Z&u})MZ2AXkC6)d zX?eVIJ&DrCdW;^Bv|fB4eYuwKc<)59x_I-y2c&PoI_@=YIpCy`d;v%|3itPf~aTq?Gzwv|JiM^mVPUF?pdt- z7Ij&z{mXj5ESAgrzO5+gg^P9oWLE{B!DhP_WpC=Q6i*VHEK)ux8;c&p zLKW*xjtO2riwQ7Dd@x#;+ijWt?V=$&JVq+Abe)~F{{YijfBcZrTY6G_J@FX$D>a@b zXrg8zwXII_PV%Wz=Ny0)C%FqlwFUdF6&LHh9i>afW^3ZNtiL7g4r@C1te$xln};(+ z?VdO+1eD~cOpDv@D>qZLto#?XRZvYkdcCg|i6gt}(v8tm!94I)-Ad-Fg-D^nW#GRx zjTSBN#eRzn3Z>JB!D2C3tyNrb?^VF{WJP!`JORy9SRVu_Qq%B2i^882a^aEyVN8`v zapZtI&Isf3MBUG^Z|__D7XIa)Jzt{4bz%@ZmXvIXG-sVE7rYP;d5G8A$yPVAO8cg( zBSlxp#*O5=AdHdeZ80=u7`Vp$=alHoqU*MzvYoHJCiK7>(8lD}VAgg3skFVW%~iUq z0OqRZ6>hZEVL(g_q>B&1dlX zw5MTJl{uXa8+$qV4d_iEa~_l3)0DQ` zN$sIqzs$C&U$vU|ds=9!A9x?Nio)-T*3rAYdoBKDiD2(o5#p<~-Rn>^llGPr4yA77 zR>ORAMYsnIV1WXo@m}rjVPkurRzCu`JE_;BbBW7aF74}NQ9Dr4OVQTH20?A3Zy#bo zvhT{O^Q_X%9_b=&;CX?vRUSiW#XI=0yz2(Kx2|Pd2LEJnF9ox|!f=wWAKNdcmL` z<#e;+ygGKJH6iJDuwDTFm*Og%{{hBjy$@$FWp`?(9|=AG)#~%Axf8 z!FF*OEWlXPHGm?$iwYjafLrU!HA4ha#}Pd+N4U^T}ae{8qzQE9aq(2wAInfy$s@E;ZpU>O3jLM8p5?po zTaVp+6vji-g^HsXIj}=Spaqeke-8)Vl%CvvvvLn8#q%@P+ zipcn%pVg<5CZ%S2!mjnQ)w0wzBk)62o(m?dn!S~fM+W>>L*lY;#cW51HL)7lkK(o? z_^gdjv+enwjl67 zG<6}zu>9AngTVaPtAoJ&(e!Y5ADY;Q!2H!fj2^(fTpkDJwj%ia*Q2< z#Jzr>BrvD&5L=#N6MaJEPN@ZRmUl7B@_a)V4A9K|ZA(%IH;$$2{xmm5oZr>Q8 zOII7G}R?G>G63mqP zGbu8hP#rLzr35XTgucRi%vR$y>JkO<`slj9;M}2Cd16f_z`XtO>PJ>g5X`m z#^nMcc4U)`m;N8P5$c$k{`-Sphsy zmWf?@pETd*b2pFv*a}}gj@E~)-EkgX&?r&mMMDR$q4IvIv65|r%}%p%|xJfGm+ z+Y>7F3}uYHe}}>*F7XMS;$l=|Pqec0J}Oqf4bck~oXxro%0?H|s9Elc$B5Q8Oa-A0 zs}v+2E;uft#AFMcexQX8v368t=1z;F1@+sCXHPC5+ZFOjI|iiZbxb zFs9Q4-!lFsv8EQ`)H@wK)N}s;iR8LGF3+Fbp)ka22QXXaRhvqf^e?$A^2IrQba#`O zVX3q=95Ihw&oB%D-$Ry^{j+!NH!%(U6F8oYLm1CD{{UD(KCvdCj-7g>W&EN1#~Snt z`+(sPIZvk_#OQ}8Kx`Q+@-7*4_ZP_Y_4pOEX#W7bZ8`iFe+3sW#Fd4_$asL|#NU~- zKZIH8fAN3aM|MZoeGf!C)TtH#I3WeUId~3LmdW*H@flxI@mAZrg-GerLESKbesk=X zV67>(ztlW9P&{Ay%(c)rjt9b5_yQeD{Aaj@G#C)4%Am=vQUkMR>LpH;6JXJ#u6a{4 z>Iby3Rhi+Lhna9CC91(Mm9T5c;o@N6sjlxa%go1^M$x!9bj&h>dKg!RGt?ufu@!mW zh%nC+M*jc|gOgyD0@*`ealm|dtq+C!#Rlfcu?Hzz>oK>?AZJpU%noqvnMAo}Z_Wes zI!68f05*kOw@N;VYwi3#m(yeOFR4>Jsp+?C=eg^%ct{Oj;)D+d$i74T2hfG5mxNpN`w3?|n8OGWwbB zbxbklXa43?x;g^Cx9~jxrXT+Rf6$l!(-h_!S2Hi+ipoglIEz8sUXvX4P>3Y=hnRf+ z0jo%FhG%lQK=Tt1#gX_+{85(@bUFLcUx^up2sLzaaTU24^@xWS{-(7|$R{o*CT?8I ztiwL@0&Vpcn#`lQ`(|AXcr3^FADfGc$C<>eBfl(OPLBTo8%|}jr~4tg-}|)=H^G}U zJ}v~QR_r`j)#`atTRkv(Ovhx-rfa#|n8=`drV0-wUssZtd!VuTr| zt@)KdvVXKnc2_PCWe(#KV& z)DL;;{LI;(`$Siyp!|PxI~lk`)SHKF=9JgOWu`dO5P}_hXR+~fBAR7w!Tra0`XUOp zdO*5-Kp6Z)czi%Qd`e9|B77nL0ICJY@!%k7TgT*$#7&~d+8$)<10L1C7rk2+00-iq zb}K;vu~WI@{{XF(Rt~T^GA~VkfEI}5o7h2wUl21o^qCBvkPq-j__8N#VGr6+ljjd0 z53Jswc^Yu?}nTIvVtc;SRhH8Hpi>k^Hy%{vh@_gB4`;JBC#g z$`SfcSWfDx!Br2};edrGKy%WQ@%XU?0stzU9~Ko+O9;r~d#IxD2^s zynQV}vSm~a1Iu#wjHp;pK2o@#+6enI-fp4$o%uixJz`NvR9IfU6h^p)=qbL+4yO;U z&-f3aEdy+^s0R>jRYLH5OpA^Tnm5)TF&toXnif?(5}v6HaQ6@#5}gU)%9-ERf6<@% zurN5b{LlK#hvVtDEG<6!@Rz<{Q2O)xg;d>AV7ebdSZ6_&lO)CmQ!g`(kuMc+e{8a~L#JFu9bIx!;0NfCA?0*SLO42P;(R z%?Hyn>PG{K_Y_?v?4P`?Ap-L;bllE;{uLAd0K;GYDRG*y&EL%S&ZNYr>*&CX-d+15 zV9o(%>9+(ACdjI1vGxuR@L8W&Rag0(r{;N)>_@#k>E%V`2*1e~&j(VQBQgh*F+8e$Lm&@*v1wj~?p>Mn7}+R_ zvQh5+nx7Y(ZyEf%1u)|MeIbVEdJvyGYDbbvEOOP>Ut`EGdez>%R*iXOKtG?E(wyB zp3GF_PrOX4{27T-vnrrRAA>MI3i19o?SJu!iE{q{{U5^D=KlcQCknWVl~#U$d7B<3 z%i1CQNVXYTZUIn^)8Mf!AD*QxuDwMiNve)_=$2L`H)?6)A9B7l2Z&56--8@xSwOum zdg*~KWOBC!T~`Ckd5o}Ak$ZhuZ{}xlpb@--RC~bRBw2i6Nwo?TKgIt5sE;Rj;#@Ia z@i=9Gvx}#8d)&GOr;O?iv_}z$0ICHoA$vx_t6rAlshoks3Jxl@?+KMFG^E}k(t>Oy zuM+)W059DkH;<>U<~_BD@o;E?svg(<0F37T(jWX40;NG5Fk3MEALD-4{{VIW0K}l@@NxA)XArB{%?%} zu>##QI^XHz%t5^Ep%%QN+Y?3Nktd0qv?L$*oP?&mJxKvtNd@;fq#-=j&X{W2&!dfSj^9fhEWKsm2*9Q zAR^50(2s)Ja{ds4WN)lK=4wy{rK`G_{{ShXDl!(^#ws@?z{(ey&sSzEI*Gy*wSE~I ztkcFzk0&BjEK`-!#%6PIR!C9AKNVXANiH;7ZFCF1F1Lt3N)NOSTIWbM%Ju0^)|TlF z;Ma24!@o~6NatvOX1NVq_U5x(# zSCkP+w*36>PNLK)s8x++E4^G_q#A&`95%78pocCtC3W~!jNt3=u;KX%`n#2#yhE)4 zxqJFYOjWD=Z`y=)_`QortGE_|D?t->=N$pW;9;fAi0ulgn;(k*0OB~=Ew2Z%$yPj`Sm7tf)5maVtZh$!wUbb#v5hQ;~IYSa^R22r)5h;uaQd#(;1Z27sJ5KTm{G~#g(GG$rB2H=v zDuEOMT|Cxv9&Y1fm!(`5o~Q=XK_miWBx~1{?8V4GB0O@Pqn0?@a37%QX4yh*X~|ri zYpDs$t%0^6#sFFa4!XGHus8y>*WBDSt)1pqjX*iFLu6BJDA_D+Fzz9p8gn%IdOvZ* z1^8ON9g5Eodk1#24>Z1N-N0&PPiq;w%f(6*qKv1IxW?m?^-DZv>34o34ZIvFNpwQn z%F31raJ(jpqh~3Q>^6a%<;1xp*CzGgc&{2DQmD~jVD@7^KI2rv055H&BWwcF0uI9$ zK%~{Pqi2q@G9?paJ`+tsGw1PA%*mP57^Pe;Dvnp?66`s>k!6_iiN{D#dGr1^?S2W5 z%1>i#uyIVLz(6era>PGEW@2hpP|NW(p6xs5lo6)0t;$RS;G^oI`EM~W`BBpNU6+;t z6s3xp6N6PB9(kD;Rc5N)@e6IUI`9^2*$EB@X6nuw=dXCy{Pf&|Yk0=sm3IBi_$fj-1}j_z$zJLnAGMhN@g<@Ag+)_eIhln1J->(g$ede z?_HkpsTYJJ7R`+>93(7stbb>E|m`m|yf4?j=SE!ByJcA(TBka{g>#2Ldu7 zEhy-CYOgt%l86CKKg_CdYVBfv2s|P#`%cvn+Soo|3jqbEUE9|MW)bm5DuLJcG8**( zSn_M)GU!%ijtsSuRY$!I39AM)0#6jEb38m#6Gd5ogf56sRHkf0m zF@6645(8>%)Mlji0XaSKJ*6Ydf?2=2*1W_1N9n=b$_-K8f5z(dnEs!GCRciu7??$7 zT_0v?0u?+|zU`lc%A1Lo@aOy&i04RW7?-+-P9f-rL0FiGQ@GXkOi>sXUq$!Lw#q)L zK^JyjYUVh52%@+O@GymzN}*M%xl-RE>5Y`)T7=?Fyji5l>6n1@ymH-NoulqtEzU4^ zZG|++EDeKBQB4m^-q{RA?*k;m(*&JYn5S5xXdZQOQ;d>$N1Av;G8`E zEYDwvQC^ag)W-RW>lvXjG<;0JbLL$_t|?fN-XQv5kaGYo(My!Y!!|i31mTVWKFJI9 zb5AH_ZZ+mveyue8Q~+``dzHUoX28Z%#~F(1i^Nj1jNUig zvm30RrRPi8BLyQsZ1^Jl7<+cqc zNT;L{o|PWfA(0q40|8HTbO>G%uKM6H4yufGk~^HmV1t^o5o7|2%j_`H>F%I~)}-B%er#-X#xmm8g2Cs;bCLSu!!#DxsqfgsI^NQnv-e4bV)_ zS*j2)5tJ$BCPKbK58QC6N?xkiNH#?38Xsnbc?b$oL9}p7d80qdJZ|#?5{?E2?%R|a zX>^n|q`K8UxL~D(j!MavL z!oX`SjMm?b!ZPo{61(3Y<3}>3978e}4N8W&VF4YXyESnuDQ)=Gfg(i!%;(q{lnS|v zl$2AsY^NH3IffNxDhAnRxy6eNw&e;BF@_;eAaCElA-Peic2i!F+XSIRZJ(xB#LLm% zc&A9bo!MB6*Mf-+G0SScTGTi?XaE3UqOX3thbiH}0_8ESa|0 zTRDu`zV%O}T6-2TvU(V}Gbb%St_-J~3X1H$=5cj28aF>#dLEBDo# z()V6twn$GjU%f?G#usUWfm3G0#3nGzHkPbps%EK-s+KKar>z4N95_N?3=_bAjUl{~3-|>r z^N!bu5nx_s=2Yet^USoJtd=sez_k+cBC>iW!sc__d)%+fAFFb}qT!#%vOYkZ2IJ9I zf(r)H?_vqyFez%=^Oze**9*4&%gKyI9s#BsEZ~b)_f>m5IIOX|;sjWzHwzjt7kBG% zi7kVXDKx5U)D{B^l$`ZT31Ewz2hiGM##fmB8!hn6!!pb>;%mwGLM7;c=+4ffGJq)5 z=V?l0#m@{I-R#F~xlV;m-;B?cO6I?@1X~K#^gPVwP%v%mEUQkAqEJ^C0B*}z-R;id z&@3`*i%)_!~}3 zvmLN8!k{@e>~7(eF0{`IyyF#JOETsOQnDB^UsCW@7sI)BW2)qV>Dr~$)hi}`Adwhp zwm7RfJk&_cV0_Q{mmRRHnCOBKlF0RH33CqWEOK^%qst!6uJWF~ALDXKgPp{^vpi(Y zLhXad;u+bjT;EZD(1MROQ>folqSc%_i%#Y6YalhTkj!Ssh?}LEmvF@tIlbIM3tj-Y z(Qk&g?99QG^8Fi^hAUybDV73sv~NAIG6cht6VOEasUl1P%jMyh29nNH(yE|>mkbX|y;a4G;H|}}46}O&QDl_aQ0;dc5YmW- z(d?GDw%9A>0hyvYhlN1}5w!2VW;Xu7ddsy`xjXMr@_ngZg-S?bsol|em#l+xd~sIF z{V)hb%wWB*QPC{m%oha|+e{Q}GU;mgnyawl#52805y+sslz%}=&avA7RYnL7%yOGb zfL=Ybh|s^37|MHbbmBN=Af?5V>`eFVFOIqakUv3%g+xURvkFYYAZmCQXxQ+lzxNsF zhJPsjj(AMsDz@qS3>}@m8yw(-U<3XWJj#|(M~Xo` z3Cu9(a>poxKAEKd0LYVimK&JD%}SfRq#e!!-vu64N_YB%7w}Xw|`SLHf$`a z&%f$F!yS^E%d${}b{nfd^?wk^JC7$IJQRL&z+`f6@; zXjO%@aXJ409jn%TX|&p1C0h1RJu6^Ut-sJhsaWv|1xA*P(EF8Lt;-9a!3lWGGU$gf zMW=OETDDalfwk}bM0RFQ!A7EBGKJ{--!y|ZxYxD(v|kPH(&ZLT?$hMK?{ zV}m--`GOUekp=G>^X(MV4WO%qgLuASbhil9ZgZL6L=ui_x2Oca&* zRD76LUX{|kTw1ksqPQw$Eds`zz>IT3?&x`>nBjO?0oS~`NU-xCf4I+(Kz!kPCdPevB zguX}UVXSu}eTDtZPRwj;pX5VuoCm9!J#ppEMLs;GXOp?wwn$P4$h&x3_i&OZJM<(*V>hF5JHJL)H8*08QM$cYz0j|$Y zdLBujDV(Jj$5FZOydbX<2?3@Mu6q31W=&orY^HNdR&#y1^oYQ%4o23_v&zM*S_?$R-`WCiy_crfz|xG9$@Z1vC9B@%VUF>2&wTNRn9kW!IwRhzqT zqop-k3(%w6a*BrNc`@;GaQX{s?P9|nIK|wf(z)r+yp9^7(WIKcgcBIJm1Ct3O5vXS z_SA+ig=Kdco3IO92-A-;)Mk-=C)u4+W>2_-VQCvBM&ZBXsd4Y9H#7b>SFFQ|$nFtB z=k6dZJ^ujW6z}nm=!qWyUo<)2^#@00qLkn9E>EZ$E@pvaw|$`sQOL~gz1Q}IVlcLb zcY%<7boK})ngRr?T39P&FgMnQ$Mprj1d)ZEQ5AtNL290F6^}BpFlPbK+k+nv#iFjo z4}C*$3!@Ab{{VZ-IO!<&G)kx&B73m=oM&O4kk`mzokc(=bYh{)Hfz14J;uM`F) zdMcrt7`L~6@US=-n|jV_RaTM}tZ9f!7J;E?IL9z`zqF6$Xu`WjrxVF4nKupmBHr^i ztjn4waouk4wkb8kGO-&4x2S+vkld&E-?cE*wFNL9@;7GOqNR?T z>Q%^`@(>Bc)T{giFi}dL>l)+s7xge29i{@xw++|U{^e{3&)dXylFsE`J63|X*n9Sj z$zt$bF=_0W%UnO zBko*A%TzSPHJ12$@dz6O7&cm~(@B&qSQZe8TI%59mN|48n1xr_uXJtEW!p%1hm+BK z2<`h3%JPt?sy5rmb-2S-#9VXr?eNE{eQ;OOoNcJk-91*TFIbuwQd);EHpK4z4f1ZFR|1W`LC1!CMJT*Y zv8*U{{$WbF$*U*EJxhp-Fy_|Td)>w*X7qI_pk$qjG0K65Mk*2MBkL%WL5w*U&l`3==~OomD34%yO#>*j*LDCh$`;dO%QwOCG$2hiQ!XKVaS>pZ2}O|) zGV__izMjx82?7~R<;=h!w2uc#W>EfObQ{;iaYjMvVqY=>=nQ<{_}zZ6HwUy&oDQ3M z6GJLNRmq>WQUm)Ox0tU@xJ&Px3b%=Yo!V6aq5(zthNe*bQb26AQomaxgudjDMhskE zf_5v3t-d8}O}TH<1RA_Rm?_vx8cLOuiHnG>XAqrwEG_57vF%cf^*_=At%4P1%}U#R zie{?d*;Y5YTH-#|J$@pWp9MlH_rrRcV>Dd5G->GSDEC8w0mlZ0FWr?A7+`y>`perp zs4ODBFLA6=nP9rvL8QZcM;GU0&I!ZeAu_-;)@Z1v^Kiv^3;=gcUVUN&mjj)u)O8xFZCSC zBaUyx0mEZK5ZpSve0@%&CND8s=Nq~1xgjuXv`SZiB@I$_i`;>ynwg$01P_B5e{z6- z#)^nbHiw#v1r!`MSTQ~U_S9Ho)@1E+zeWjG1-p)Ab2kjNOyeHqRM)f@C=gEL1#R@3 z351h_FqaK(^F$z!QH?@N5XK1gVt7sgn7=7xkuida%Bsq%jK(@6^%x&>{#vR0s8)~d z4Ir3>+J zf$IwIC0JgrT-NroGpS480re=v4jq_WO4v$dcj%wZVzE^w>KJ#1FNbp>N_%nT4T9Jn zeX+h)Fn?E}7G16DgC`FM%x-CLdMBz@jLuKH>WZ&Y8xKxe%L}6u2BI)47~58o z9?9pJo3jve7sNxd4^=j}V*9ei!y9z9@dCg_OjxnKrl8iwz5f8l@AQJO^vrCgrAsY* z{;b>OVWEZ3tXMCugtq12YlE0UEXHmP>;#~jGH%s@#W>%5IX)#_K1B|;PX$4w*a`{4 z5pLoRcLQ3u2?EM9G)6HkYT`^2jF++aPiAEFe{mk)D*YMyNs(x22-N5!S%0|0Odhd| zRo9%aTA8mgOxWIHcWCxmc9TmJ6J=2fU*wORlC2%$p-d$)RNBvUt%xH56$Zbo_vB!aV_w<@-Xw z345QY&5rmjbM1-k7RX&u%yWP=nvVYBi!Z{lj*Ma}-+7eet*c5om1b73zHf*wTd;Cp z!RZ(!kXay(`XK-XOT+2sa&g9gTC100h2Hs`93H3fwwxEvR*CCOY> zcQ)aPPZMRUL$CZnBS%o20#xw?ZfaTjK%Zw%EOLdEQ{ex^Tl7&<+M77i1nXCo8Xe|YaACz`Q42;?eNy7QOT+;K@`W2B1wDop6m3$k zwfKaAS+Ohi7zi4gG)r1366#<|sn#RVs5Za8XYwoh+e{r^pHNNWG|Bjtir*;vV!fOD zYn8@9BP;&^1=xp6d%^X!JQ&n|u&|`|Yg$yjyte2#-b!068wVFvTq7|w@OEyF9+N$A z)I?fAz{U)VJZiacvmKnm(xA}v(x}>VS9H$MrE(Wgj5_Dm;_jCEahq<`+x)>uWNp%? zvyB-1U!*p3iI*WzD*pg5DeI|o55v|=lp4~ zcNCUDrm6w52Zjd5Q{O>-NpQi7;q3b$Js|NBl#;z6+D~&k--qFv<|{&C=s$wThe7RD z{jnSQr~c&fKQh;rTjpo~0F3@>W?Yx3_D|zOhC=`fdl6C<911RZJrC3ajVfDr{{S-T z%vT)s%(;Bk8~y4YQhy>1%2sA;9H3Gz4^TIvl=wo+L2^F!LEE#;*5={|bUpf*+E8op z+TM7nWnn}wLD70vsy0!v!!0mj`KUVZ0*5|7$Cx+)IBBZ7aI4367jW8b7kzr5?No*_ zt1i=%chtqyL`TFXmQ>>;WxU)mj{2sruXq3(adU1(_D~OMG!n)Lg5)S_nY6~eC8z=m z2BB=yuN3u(hES{FN^YwuSv^voEE)8%#{4nsDa&+P(z{ogQudhNKuBrqO;2JvbvAn_ z5E8R@^!oEIiz2~>ZAeD4^h`sD@Et>yEknaHg$kwBy@`~3iKb2`pYgA?`8NVPgf}TI z>u&C^huM29NGjx)SNAW7Dz`dbwOnvh0eNo}rlRHLx_|nqUz+@0o)W`7$#yr)_O-hO z{{T>o^|yRQu^muLJ!-X`;q-{b?dgYV@NLC&EK;?|*A6l{;%E8@0bMrZO0NCkC}e@s z`yg_|?<)ysUJDF`mCLM#$mK(=l^fdFFjG*;BzruHfe#Z?oQ1xuUywE>Qfk&G@W$U2u#yU6xwn z*#7{I zK0*o)3PKd{5uoQ|gmF~5w5kb!uMaeHi0JbZ7q+#0H2ce_PUg`st??Iv`{2ZQH4<(T zKcpc%)%oRsuihi5=^!s+jl(~})0w)HSK?yWsThfQ^&jJPddf^{!9;r*>4FQ|A5z!< z0D>?Mq6HIx+zaWHFnvsu48r@Mb!GX)iQk+g=Cs<6hG--s`62%RP~y;41miO<6hK+@ z^H1(mve<=1T)M^^bBz5;Em{$jqRndZIp-cAco9>GI2qO3N>vZ66`-J56=n1NGglQR z7SNfX&t+jQ9rJtztyQ?ETdc~Ut4nP5@#L=ca_2Et2^JYFvEfLvew<{Q){>7Ik4jko z0Oo&4jkr-t{!!%Ax!NcZp7kwzra*XvWeJfmUe9UAVTh#E%3{x2Wj@)U(^B>~FT`#U zSO`5iv857d)LBt>-3=5woO%>HyHg;AXSj`4(9WgpTtD?5?nQdSyY16girgxCEH8U! zHIms~r4GA@f;q=y0QEyaV1p|6H)G;|;7wzGk?8zNm<9Y1(w`C~{{Y7*KnI8bgc0>4 z15H^sS4~f+LMKxgm0Ch}g5}?5-#s_m!SI0c{3qMnGt>N~Az_#$WnW_;A z4N`ire8-esS?(S)0-dau02g)k_i&SrPLqoLdrFf)!M_359HsVufU^vK-Rz1?5ey#a zjlpcR5=9j-bmSRRM=|VRJ7i!}XPXA9T>_3 zS!L5}tR7m#OgzkC4Dz_pd2b;Xbu}*9)n;KfWr0XQJm|(8+SBy}ir5)epN@j!vg#!s zh-qpE2IKJ7+EX}E=A}1m!I}R6jrwu!KT!AjAUc?!6>tau#8b#hxs2RF+b4Q(1UUZy z6xQYya~h#WLT3<7sK|q)fZhq$CIldGOO^-#hvJ+?39xopexhOvg|?*ttnS3w?mp3A z9^#(XeQD+G3h;aDL8s|veP|apykhs4q-SwkobvEE!KH|Y-IYM=P&#mzZibNyt{uy? zg6rO7)GH;vIp+4`#6Y;z;VJu5+ByU2Ev@R#HMcfarUA4aQEM!mjW{ovL6$J#r=Nd4 zLQInvloi%B=b2R4o^t+wS(KF)g-UEM(wC{GFy#yi(7g$Z9pqN}d*7)pGWAQ^nh)2Q zokhAuDvwmXdTouXV{S5zED~$CJVm@{QdZ|dy}9NWtYh_nd_m(AheUY|xgjdjb_S-l zqd0s@+)jQai_J%ve4A3v{{Y72=}ao_Clw8Na~8OQXlfn8oFInS{yhgXl(W33@o~&> zO08#+3UdRQQqgjh#Ht?jr&D-NqxCpeFO;FviB?UTm4X1>)05n0BLyE*%sfMY1q!-0)oL+!49eX{PdTLZfMY|MpDuwbIfE8j zvTT)B`BkDQc~2J6=qSDhXMoznl2-3aT+RG;Kg|y;nZ77@FZNtB|a5nXp!<+d)H(?<-|4K$+u$*AT7=_p>jloR-iz94f!hGqX>{jNRa>A8*xGT)_JPA}A1zRi4i+g4$*^3P zXE-awARaYWFu1L~?ygk}r4wyx+Z^cJFP)C};#JZ3nV_?e z#1CJHba6PG4=HdgoW_SJ!0CKM1z+*MZ5AFfz)qk&q03Ub5bBp5=UBo^Kq2x9iO3Vs zf;fQ(21fB5YwA4=JQ;lm=x6sbYFWcNv5!a{M^DxZucYigAb&-tqL0`@*mL@%so3O7 z8Th(|c)sC5E0HU2>H`AJp`9^{#X5myga?v^XQ`mVyJ3)~R-AD$&-g!8mP1@C)GcTk zZy+rxPjFDtH)+OoilQmXJ(0R4?7;A+P=n%7bQ=d8>&799et_V<09^EVgMh#$;DZfk z{h-nXkOO%?ksd(A%MymY_$Jdh`p4*J`gbuVNYpD|)9u7E>X;Cw^zu1v@Rbk&OBL&E zpEriBkEmjB7k8J{Sjnhl5X@*eMa?sSHwQoL>z!`lahw;l=KN|FyJXGc+ zd@L4Xu1~`0DsZri(Hi(a#{D>k$1>PO)BQw{btz$VaD>AOgaF_|W_v@Gp`2jiwgHs$ zri5^B3iW_E{7#%bBUc|tKlDcE)X}NTC6&Y#BTa}0t#rb)_I+R$vQVI&ahz%A#Z2L0 z$Tz&~9}%zu<``)-{{X1IqG*nHzc%U>Q(%yE4h{1KuH8<L;2aEAJbn?M#xIn4An$>s@R1h#6 zb}G028m1`AgldI0V;wh-TZ(7?^0i#LHRmuJYnRb$e8)^Da=~d5X6D}DdEty0{xTl{a;VmQbQxrnevoJC#5`6X=aDa!@6c_GMMxE5~^ zj-eJh61|h4PKb8|q|Q1bcq|)*#5k9)6W0#HUepIqSji=)sv}?&iOv*^6+pf>=hrc~ zt810bvBhKl5eA~+u-*vqZ;e5%5pU={cwQyrRJs^PEx7mHvW2FWvmh=3ry~6oW=}waYI-2%&UX zZ-)z?EJnTeS)#iRfNQ9MYPmu*U1jZPs4_=TOUWl$X9wzfUEJHg!(T!Onh1QOic-F0yH;1C>=;O_1^xVyW%<(sp=Kl^aru9~Wz zs;gJ`v`lxewVwNqF~pk=R30^3`7=R@SU!k@y}{b(bd6J#&RMmHVC^v6MP*g2T@Br0 zka46l#{9e+H1IaSpLqQ4e-i2R7u$kHPyB@U7^V3!x_(25;3--nYk>mn!CZ?}fwFCS zw!s+Ov?@vNIOJFcQ$5elP2Yk1W5tYXHV=>js~&0MCRqAy#pvj}dWxTqP8}+XtGxh< zzRn#+kH!z_+N_JM4r!u*K_xhYo1pp6PMTqiaO7$WkkQoz-|M^4!Zr~3BsUi&7S{y7 zRQCrbM+J|z<-oBx z7~m79DtQs4n2v4ZVK?DIg7uXv?1IOpNFk{?#1zR*M4dPzg20W>Dx_QV1fAo8(G~j82*}7xW*RVkR0N|z9 z-=ZIc_vs5wFx@_L6`a|azji&z^XCqAldC8`H4hm$?T%z2_AL^WQ#ZR_3>b7ArRv~I5Dt8}^yHG3yYiw?k)3>KzYNYH&Z-(ga_>C|~9V7KQ zttxJxm^h5Fty2!A{H-PVgwz!4oigRPZP3GvK7jXB-N?CGSx2_Klg8+Wj2b)q4C4?U@B^1E6r#c2*#jX1t8h&Ry^^3uvn!MuV!4mk-9GOlj2uej zMeFfaLb&3VY|K1w{{jmYJx+6>{^5lDDn_h{8~mJ2>Ay$yL6qC@hq?GSx{zF`yc1P_`p`C)4evafao<{q`|Kp zcwoN>bp)23w_2tZgDVJEtwvgJ!}^TkVfi1E^je5+qv?qzzxky&zSp8xY3raa{Vh^H zap2gGi#O1&;YmSTaU>+|a?DJ5fF1(TdCbm}raOk5$Eu7~v+Iz8@2O08qv(GXu{rHF z+w^GVR^jmZ#G9o;SVjgnl@Zz5{gE*vIk~>LGq3PTLWQbKe^*UG34RJ7$gY4$M~d{q zBKd1#_)9vOCFieT0J*X6w<VBi{$|+U4Hm-!@2?N9svh;`7+{uA9 zBdXJK{>@NVP)39v=$gs?4Hqq}vfGc)f{!O@Spjgm4dvM)E`xu@r;O^aL@1GZ(h zuS6sMgs^@;MX%S}O%)j7pN35JEB3Xr#2+McA@GOmeM|&1&79)t>U=`Xm&Um3&Cy(a zc5b68fKyK`P>K~6{SNY}zXT;lhM4v^&G8S$(cuj=V6DtB#JfL{C{F~_IZ<5UaYkub zF_Hx{m>8S0RJuCYfk&q-`(%xDGZkYl_tngf7Sf$Vo-c5-N!Z!`)agU0Px)b`DqfRo zyN~7-ZqHJ-*L3oW;_Qz^1g^uD{;@Y1lnHM&kRzF0DIJ(YRaiVc@6(TyBzp>#snyij zgxC&RAv^rUVtaBfz$Z81jQ2C(RB_B>u=qgjfKso>^#r9GFg>s!#^jj3J-6YZKG+TQ zPH59ybbW`+DLYWibaFk0LSvhr07{n9{uObp<5QHjm{E!URzR@#ckk0(SN}|Bd}Cmy zJAonM&~n+{$Y|~;Q4w6|Utc`R<4GtDeU~{aBayU*7L>+OR?^ikb${&Kx{EE-(v1Q& zuCPY&wSH0lXqNL3o2J_Kr0%dn8NO)F$Qo^Gz54tQpaec=5W+gP6|qKC2aNEZ`&e=; z9;(eRWm7U<w*Q`qQRCh{A9Wj;Ab3=%3nBsEIu$n$X{>cwb_h zn{8|240`PHjdTGMW;b|r8e)=P>Sv}%PBdmQ|5m22?&6TWS^VwdvoioUB(7yXJ%r&s z5k5@Mxr^GtTl|(Q*CsmLhIEnHn$)>FBQAV2EzkKH)+Xz0yZ6Pe%u_35rT6&TylPpc zj1t0#9hGWwD1D(6*q3G*6l)X;eYOyhhC4Re!QZF;5PXQnFs`NVf7RrL70lxq@FZpo zp@vq602YAc)X|MC5;66qP!5f$9=BFr=Y9f~cd|EM@GJDS%VP#bMUfb`d{JLTm+ZA% zNt%k+uSB`Pycn!w@&MLA5NxHuPE)VJtAbHZv;UWa(b0R;%%VA7H@urB<^32p!*Ep%TyJ z~?I!xbZ$2I|9HpS<>L&EG~MaqgM=98N{d4Ay(0J)13KwrSO) z0Gi%!VNjn^p5wo7=YCPaXl+PR9#1LWQQ5)q8b}^&lTiOzvXYAAwCVm6L4;syL9mvF zc$ufW7dZn}C^s1M`yuI{`eQN06g~M6FEPt%Z&jtw52yr<#r-8^5U#1i*E@9SZkt#c z3LU}gUVBjU+qSCxsY43bciRo%uoq_~Vo95uo2AxCvU;+I?H ziB_~EV}^T-_0We!OYC>RcR2r|h@1A0dB#F7Ui^g&y#=$+GjF_^FZ6vwk;3~s2yFWJ zsViiWiEG#Bm*YYJGetqBc)|+yDp|eIr_bhB@a}Ok%{F^ndNd1(+Ez=`&1phS6u73o zduQ)cArd<2=}g*Daw}p`DN4C zcR(A>tVpcoihk?lo$F{|*7KMv3t^kS_;uv*ky^1&>*yz9i#Si9Meg&MPJW1wFzbkn zS^tExBZAbLP+A<37sltY9AeyoNx+!^b@vA7yQJ1BZ+6LHw8OjVoG2mbB7IHbYP{w-Y^nE6k>l#_%;NN8#b446j232#!|F{( zg*_q(e78-Id}Lu1?(p?awl&LOw%0sT3L<{1<(bcNR>v>?`pPgwkC}zzAJaP-Fhmeb zjqVAj>?27lg7H@`yq)ldLu7}osF0rq(d~||!9`K&w*(Yt@hIeexlbw*X-{w801#ec z^FG9XAdAln14-H9ubK0x`2|F282g@_=fYn@rzi7DVeh<4w1|{g7SAtuCoEp5fAN0= zDN7E-Sq4Z}`wp^D&yDSM%1Kw@6S{K`2KWAeop0(6!$?`?{Ci%M;NQV@s<@?3-S=mt zVvozX%r@vH$U!HsUjLUde~0w%@wU`490K(_*krXAc1p>oX5*@-$WING$Z!y|4gL75 zpTGMSl>#m^?LL8CB8h6zf_cdc&Q;(w2(;m5^MqfJ1(@}(D)V^arM`Nn@2%I2s}G|$ zkzx*_Mfe)i!F@yG!63AbWYVgZ>Sm+zs^$+lxrL{@{M{MhgB&P2&~uA-^9o+dK@nxeaYBYcG{38_NU^&xl?J^ulq62LJ{pF!AMG&f+*V0z#Km26?xVs zx(ru09p#0_Dv|huCpflDF!dcQ0HWzmSqx>MPLlY*T$VpHW&ICeJO$67)?JHX1DTyW zMaJmf@$J!TPzX-my6p7Dcp1`at>I0U&{?6>|FeiW-S23Q#_^qPL`(HAqX3*YJb1RB z7X&{c_}o8xmfmX0{SdhP;kvblI_j?$*!5{#exgzikHfS(e^G^^QeK3U?~WZogji>E zs#O@_)@g;ILRxQ&wMtXwBit(9GCAmbjLfk z-E){y9`it1}Z$r^Sf{UTuk2Kq=i7 zaRT*$^f~jnvdZ?o16HMfp{X?3g@iD}`0|1bX`QHkBQ=11a7L^f#e{uU9TGcaEZY{g{GWNFlJ%}M zU~FhEHXZjHovS|t2D&#ibUJeC+k|cIr6L>>o0^88ds-ocsz$#=73aMokaxQX*Vj|x zuo|pHOL&)GC$Xn~g#7zkyM&3bfq^gzyd2q<^_QhQ27+k;Ih@~80)xbCo2bk!2A2dJ zGy~*W7h|;HW%-MMl7I|z>4i@b1z+x(N--7-nX31BQiBhs$%=A4y_A0Sw*I>0$srKsy9ZdIsDrLFzP(qpGl4N7W2`oDsa${79-eS9QY&E? z&05Z>UFvPD+8Ai3Z;NE%@GL=gh*-%Mu4PtM=Wg{qmX`K8CfPZqhr6Hdvy7=o>}PS6 zqf+M2;V0z+30>=&i%-9`nEIhPbH$WyjTfNj{4H9m9BQvv=>&}Ff3S-+j*rX|0G7R7 zF^{4J+NslOGYsBMTg8H*0F(5AGby=EJf^%7g9MdwucBs(bS0f>Rx0XEPqB2=rZaS0 zzUgn{=BR&$x?>jTypFKxs<(c^@Y|yU!s|8GdN#UAAlEu4vID&b5m-uoz*&Xs3dH;H zCsy+m9P+*~saI;f%Wz%6<2>9x3xt;-ZH9FOaQBVJ!yp|FHyE{3tYQ4kN&OZIGg+F` zRImndhOFtr@&sfK{A!=BljEhox(6I^x;Ka?%^m1=uH{bES?c|LnY>0jq6u@Zq2uY) zq$u-X7|nOBn?x(hzjG=fsOt|-Tqtre;o>BQ;0CxNNz1m8zX%64;|GlX1L%xEg|91L z_Co@RP!8T?4GL#S=|r#=ollq-J=++yt>}P|^cC_CkU#q2P=gY2Se9Imf4v#@PKv$O zslnFayQlGr^$&2>T7=6t2rqq8z|GuY$56NRN6*r5{{iuK`8JRT%c7;ZpC5CV0V{BD zTxFT^LnED+Ini_oRzwMm+(zO?$T=34~D6E@Ow}< z&a8*kr!M}r{?V=N%7-two$=JV8Ek0Nu!)nZXy&R>G0jY#X=CTN z>371klre{YfW_u8`n?CX)XA@aIchFTaotBQvNWNTXMzHqMSuk#a zZ6JM zxW_FK3X`JY`RkUdMm@{HQ_@^+O3hhV9^sIIPw{wtUjAwIdyYBxF{8F-$A(6o->@67 zS{Qvr#EyUH%ZSA}2og)7n0o3LeTPq;aT!IF_C0wM(MCgKuEa2U`;H`xq`6LTM>bCa zwG~VD&69SAwArIxt@CuG1x0J4L^ZU=-o0G(ccK{Fr0OxLBpg-iDJzC$lzI?9&qyx` z{NIfWb~#@K+K11$V%1^oU6IJ<7Urj9gy_b4x|wuWZm{8%J+Yomj2X?<8=V|5+NUvOhWXjeN-^?u*4(s&>re{-Nzt30Y~3J}r#A#aKM zed~+ibq?e$&kCMqfR{8n{M{5zLd!@f;37+Ih8kv6P3bFgksDVD+hY zJ7=y$FSG5~@}5CXOy0bUi}d(uQ&DLVL&afwE7um$GN!!gE$hGX22${!Fi!n^@FMxu zU;jS#+J?_>XWIwu0j_4U-QR*dd4qHrW6>U<^%DKw`;v^$pZWkrLpflr!Fo?hvwJ?@IZNivU%)J?8Z^Y_ zf5!wsIsgF3ktpQt{R4_jq10b60Z?VpXIC@iU`;)@Pe%V06cQ&K2)!g%1%Qvv^|Jy1 zsUm3G5~GA{C=!7HVZ}@M|3ayH4PYQFQvm3s08uAtAZ#A2%e3^lw+RF!0Dj&x{J%&T zG3Z|rWmU0NhtrDhBGRY{U9i*gaGGUYeZufC5Y_)hLQE+DzwuLH9&Q4OO_tIb6zYMO zFVXliIL62TYHV4u|3XnAAtk@R_KI<&8C1$jGkrrgu|&*p>VXZ01k#`={}%{=^ySqu z#)<#25I+_E;xRXrb#H2u2`~T*p-cke!F%ApCYbiP)W&}RhakoSxYTG6FxK(D+mn@_~oK=<$Y_u-|X;CL?9EwU&gG z%y`H?kbs2h2oVy<&u9V21|INTqiw&qU}f3JHe@V6f&c*LkfTaI3tKNyF#2v&A!+55 zvPj5x-1$o=;SVeX;XlBstffe?6p=-jNlKEbD*{bWst2wt4LjKf6kvdFn;Qj>9IL;% zp^(p+)sF`uc{&A5TI?(SNCH@8D3VblN9>9@%cl4qXMxQVT8zu*7|41ZBYd zkW;f1CKLXMO78ArqBC;6F}U|oRZAaBB+&H|7Xg%wg1SDENEF{;0T5j6(#1d%vW3WE zOtPlWPYoG;y@(%C2pa>cp+qPk^$eLc(-(#LC(T8|lk|?iY7F^2A2gnRrBzzcT0BKmwfK5GVh#1n3 zK_P;pLJMOgSgKqai@GZ@0@@&h#JKhD;fK{9KL#bhLBc@b-gf~ALpa<9M5SWNCiXxF zcn)Q0d?ewa(PjR6=f=ka@TpYNeKlKl#TIQ69&-=I3_hR`WWo?a-wFa{gdwRCAuQ2L zquj|UFK6JeU;oc8 z`G?8i`9fO$SSH8{Lod?_{~vOm4TDmKwspp4B0)_e)3z>=D*!;(1OMqG2!Izt6{eC^ zD8#gENSBdOA3udPK-!=#CJaaceqbTsdVDex0W>{98FAPT!ahGi25LQ}{=$VNaaGGk;Ssma?0CWx!Zy5zEaBw<@@ z+W;73xd!-D0PH@7@1n42kOB1o3`rxhk7zueI|l?qJTj0BfDVjCTqeme0}w$2LUQpw zl33`FxK0C^UyXz@IKu(_6cn{b*s#OWbA$lk2NZ&0h62xy=}>0w5qd;n!FiVBr~I2P!*7oN7QXB7Q`n37}&LRuM>5q&a!pFjykHIaXjmDl|3kw50i?8@s!Da6#@SK;qJI!-mUUCd2TMJA$p~`%wjp4O zQvSe;BBvlRqfe$C{{X8OV7X>`jQ`PShJc2Eg8q+OGk6{>*Npx@dd+0Y z|D)Jk&C-22wPkSi|LZk_86or+qr=?+Z4KRj0AVl7ci04GaZyQfpL>B4AuxMKT! z8HloVW-0MU(O1L}+UOKBSSh$|c*=P1e$V8*VP>aT3|#9i=(9UC{*#JJ9py;7E%IJL zJJOR)2Cnf;Cg~%$CkZ?wuu!wx+1U5IGOVOp=;aCGc~F1H>Ti@N>FMS5u3vUue$x|@ z`J%9LvK6b)=Ym{9Vg5+6LHe_m=pE}cVtdJIG~xpOf{TIs5Vf&?ayuw9CIZXef55GN z-#JF|+-9C%Ba<&_@0wTOEIo;Di2v+5uUMob!bkfbU{i15iUh2OZl-FDg;3HRoWDQ{}nOQH!WztayZKMQO8dAms%qy>7G=G(Y;^<1Gsr z?V4njF-H6YAO>poOawSZH=x6}4fnU@-1LX_^q7t3ZH1(wM{ZU@bnMNZ-ud#$cJZ7G zPcR(C#Ho@W4jnp=F(l{w1JDK(3X@D<&1Uf+bGe{f=vKLQa-G0JZ|=P=A*D8UUsOPt z$K9Gfk-ho#k6{@813*X4n=^P~Om@4%cj*l8n+R=2bCBX)F?{v6IQ#iCGeRZIK1qit zjo;a_6!p!e?iiU=+HqR|vFSV|<&?3}qw@AAORwiqJ9ywnFzcJoKLA}$BgUsqUb18Z zbOAxG!NFqh^J?a`-s}bQ_nUjxh{u!E+n~a^QO8?Uau45cu*`$Cx#r?CjHF0i@Nzt{ z+P7sjan=prY@}Fkvwlwc+1}!_!4BA%;GDzj89cSAVPJU&N!S=+_*7|E`;9#*dZ>%zGt0ynhPNwF0>t zTRCPC!2zE-Y}AY)5p!sXTxst!gskJwKqMqHgg9gJ^&ng-9=W*v z^V$fp+lJfM!Empr+_NqBFx2R%!&5l{c{vG4R60wh)9fK4h6-1<#Q2VyFc2;8(XZ~ zDfEQIy`C_7v>v7yddKNju~MDJB>ujIYv7OBEodn7m_(WXxGR%H(;v>FG`B6}v*b22 z|1fcYl2Gu=e88J>&&jhIUSLw{Kt#yR?=^{eLw^Pi&hUcntdeIFQ0IZ&_4xFH0ZJ41 z5v6)Ivt|kpL=E;fxbOUYuX!QF6Dp|ruvW~R&Wx2`zt63xU%>w+-ohBGBEx=5*3Y-* zE8oWE+|X->bn&*F^y^~&t((rDboLMLbG`lVHM!@o7Gj=JrItOvI4lC@?&y~!Tt}cU zx8?#8lHW4NG(woxQ)R}JeTw>cW#&PL_il&WZX?*@R7${q z^)|$uxO+iHp9%F1em%90pta!$ob+~rG&TPdxBS`wR(Dzf_rRO$uw;(yy~|1aX4WY- zXq<=~6&#plow_&D19R32_Mis)cj}`R-b`~H$AZVwB_eqytF;TCUI|^-(zEwOeMEnz z&idHX&=N9`qpf)6pu8u5#Wi-BVlsdQrkQbyzTkjYNYsO z>;!H2$20fUTi2ol;fU_nuRvqF(x^Pf=Cte@E>r~ZfvKfF7}=wnp-tgX=LUyYGllV^ zS?;kLx<0-89`UoxL>IJ!W+P`L&RP4Cq|E2qQFomhlv8x}zVVdFp|47>w0)sRWWxdz zdggXSM<(XREMcgBF))k-LFb>6`V0Dy<b~w2_jVj6+P3t2oF!JM*#yq(TT|@K zG6t4k%BjwDv)iGss7Zb@t(8O^plfVNvBHP5SJ)e$0J?18IcytnWx#_&n_Bne-A27n z{^;V_AQ7K9<5eGu1%=0Oe3QiBmeop}18&l;T-f$@OS8jBIQ9Ir8?asD39@DSt6;}R zTp}jF_z!@#5gl6Gz-%ptMrzLovRq35O+LBAx=qa0@ydz?6EI21#m9vAV1!XIeQOWv zAh)p0Cuz>4Q*qJuGe0mQniym$cf=&Q#sI5HS1Fkn6+*-6CkQp8YM6QJAYkny^R;Q6 z8m}t+101|PWfU`tBzt%9Z%{`x6imP!4iE~;j|x3kvTudKtg5S z^$(!>I%XccH14FtIW^RV6M~#nh|6diU`CQ)ybNmc@D{pw-LXR^c0_qMy^U$0f>=9i zKb;{FdRN?sTSb5EOSh1H8MH>-*8047k#tr{MQv*`K)%lBN@QPb=^~$x%%7v8(pUZK zCm!{n1eA~cbIoNswwM2E-ecly6Zw#cQta7Oq82={s3-4bx<9ogJ0&v4RzjF+7vRCOcHWZ2dt|-E@0;+iATBFys#i0gY?Fi${3GG2E8>ff*&oW5A{diKl zI@RAb-Ib=2h7FS*UzSrd?#a=7M}(^1&pM&#&>Z8JxU48Sd5p)P@wUaDSmuNJ1eV?m zk}t@lJlvWRd(Wo&%=6MExG%351@#O>d0d*9Bq~Z`y3r4{x_e}^ONJG*Zkrm7MuSb0?TJfB7K)JM(j%n=7*M-UHCNMh^0*z%5#M(A>$dZ4ZP=0w)pn z>}Dd`vZU*mzSM+gQMVTdu3Q(oMyxKb(pEvUjo=9)({3A%7;R2o%2d?Y$;L&$Y=*$c zviWuABpmi!F&;ss^M`n$@alH$JoG|m`0I$+YdSSOHFoD%p{S5%H|d<4_a|DFqvy2= zAvHeIm#BqH8jPM;8>AZ(wtfK+);6c#-ahI?t{;sXbL6c~r2oGA2yAl$;ntP6S??_n z_N|RPNq}GEoaME%`H%{q?94`k^$>Y)?0z=(Wl-FH{lfA2U*nr1+diSF82PMEgkSZhSpWepxi?}x*zuoj zM~=n}+*;W4w{Wnoh!SOq{4_M$Y9J#61+MyZIzT50@Ui)C9OTkht%}}fjh3amg^op< zH%5uHUj75P;VCr?x0X%NHZ>ZByTv%BqdVGc;n9ct@As ztrxm*Ii(-wAG2@r7wg%H& z?4$+uDeYfWqp|QSG#=DJv8R|z^eO8=ixV(No-4-P;F#w736<)SmUpsUc+9xTw+o-y zZYKXCc$I8FDCYoX-W_u>}{jmLAQf5QSCTCrBT;t{6k_l>N@>@ z1Zjt1_6O;{HHF~EaSA9cnsk17ZBZT#t~$mPf5}37emA$-A7QU{3bP5@fk!^gS%r%D z0o-GtvSYb1;dMkEc>x#dCP_2tAKs}Mw6E;@lp?SKg_Tq(osfYyX)o*%s=fB|kQeWJ zA=pDT-&lx5Q8^!(X3AstAKutiHEp}+!bJY`y|9(*CR|wh`FA9=aWtNlPUTe9i;~GZ z-Yo{jrY7|`FWLArdBh1+PCh-wYd$88ZF?*siMYG4XTJ)ib=|#R3}5Vz!3%VGr?5{e zdp29%ppgvs=FFWl8am%)610&BF{kXYdM`gv5l6%^1f0`MFn`@tZvAr5Sw66QK;z+d zDwdxC^f*u7yRg_-vpmK2uGm6q*jYs)wM8cM>T%y}_(lZ6SVt=g>a*PC-|O)C)CoF) zjF#!ytqAFN(yb+;?crMu0}FD-I)1+KFNN_6ST!XYLbab>;<0{+7?Za#Cc)Tk51ze( z>UTIXaJxQbtyELw7OOF5>hoo2AT}6c(5y9PH{al(-6-b5MQCjmoq%VpxGeXrzvv&d zp1IBX2l$?B^3uO{_tl_){m_IQtG5v6bELeNCyln#g*AH#))v=l<5P&#;7e$wet#Y7 z9eAkk>Yvvxz&;7hdG|Ay6Y@cnr&oLGU5=d3>d9Jzeu*vfVRgpedA|sFllq1+Q#u@z zCLHNtQhhr@v=)@b%?U1K2jUet8*y#Gz?IH(tqP+w5yNIUkEP>LYJU_-*|297A&60^ z6{-pDj=7Ui%Ubglzm~l+Cve4qw&RlD`kW`^xEm~p4)to9H&;VQs`4m$!TS#YcS^__ zJB)K{%}G3rS`3F19=E;!D!hPOc{YkUCheBk4fPL@-giS{LP~Xa27{8;gi%XU{_8Sg ziyu>f%RM#EM*tEIX|7UvhlF9?B)cK^%90uCWDy+r8rZsczq1uJ&;q0ZV%hUql72ggvP zEFs&ZH7-nh+rnSZt_mTgR^P}D-pkUjq;IA2`2}!(nBpJNVBt6t*zPh(I6y7?+Vu8p zf4x5;Wmv0X90Rwk7>s)1I+&xiwQ1vfA=l@yz6a^A?^xfBo$*n|i3qkdugtoJhtn7> zICo6V-HyjKT)*}|<7IQ3vu;bY?FV!wx5<P5bqR1R;d850M3niv!+-4cjTZw^jigwfo`OU77E4M1o`UmI z5NJmF9(?)yy{RmLA!$fwG**HR;~8MzUmvvjNTR^+ld=FL1{Us`k*Td}@vkyn;I?78V*kmL zK0uBewbkyIa2n~6x_wvj7&FF?9F3}R*ixrEQg+0bQBg-*;IDIfqf zMG|o*dqt9VH+I8+h?Qk5nHqiUh8&a_{$wJ6uzUrpg)DGD!OX?H>K@T|dhwNHlmF_e z9&*=HxRgeVIi(1A@FoA#JaFB^m%@g}>wDX9R9wkV>SubXC7&D0^ z+Mnot)?ON5xj-gBMI#-KmP?FZyTEI=7Sk~I+oyzUx?A9ml3mrMnnqCdI)D>krg1=| z8?n%E(YpN#6nmD)sx}+xAy3+X(MOZ>N))R4Hv0GEGBM}-E90PEqzmN!JtUfbycg~<4uZw0upJIrqx`l!`AZZEq*ii zvf%PpA=bRd!}#FOQ*`LW97iBc(p^J=`_DFW0=`gDUBJ9GSQ1@BI>#z1z=Qwq$`>K+ z0T{;bsrh>Q{2S@cC2D!%*skgBOrJU%?uDqAU(rRdcgzpb6r2g0TVqpdr^d%Y^2eE0 zN-p(}9ificx}SoL3*H~F4I+g!&_t~u{Jp>VfA%WmtJ;_iNMTs2HtSCdDnvI}da*aZ z)*F>D)j7pHPo|g^pPBLb8Y58nt=#)l zalfNg%E^RHuP)V^AB}4r+vi1Z5EHAn4lbBUXbbE`Cp6k(jk)o4Q?636t_<#Tua6y; za6y2M4_aiXsVR?PT>}r!Dy4Rta)JsP=V=s$dep{`e?-R@+L)xL=}xc`A)#;}-Zhy@ zzPqO-X1CB8cvQnhIfrVm)jt`K_&m28;lD`p929eb2=mp9NrK!C@_$Py51w^xJuwY# zwt@|W_HEuY%x6TRFd5>P5-5!IuqIq$$73kYo&4}O4c+zlyI0WR&gfMghu=#>`Ih-o zUbEt+9-z7*;*8%kzeX_Ew-1N5* zk*bY+LI2*E0yX>#-D98U-N3Z45~oQEqi{*19TjDMGVjwTuJl<({*O3~JD zh!si%+cfZtup1js@+K-3<{f$uPwo39q%v+41nDk`9;|?x9vE5AIt&r%lvzZ8G};9`3^RDp!ansQ?L!QduE!SH2Wj3xh^A( zfaq?&3{y~TT%+4R0QjPjLz75r&|z2tj~=kwm?;NONvwxo7w|+(_fC zh z2^HIQ-=w;_-fr%Qd_io@ginH*0nAmdiH8KKD?ukyXRGgp+Eq8WJW7VKf3C&oFoxfNhm>Zi0Y%Ibn4A z_=^Z-7{CI6qW^hF3Cfr&_?QN)eHrzJE z&H30SzgJzo0o+~I{=qh3cBgjrSzgdQs!MQe*pXREN<)o|@%{SXKBN$I&>>VU!PKIA@bw%PTBNlA7CC=uAE_SMgWO9y<^q%<(SHDxVoo-h`+byq zVXj%9+y~qyfnFn>8~ZWZ);Q;1rve+vPZ|$KpiDvg`8OMZMepkK!6tzo5*zRgTocHB z9Bqtb^MN{ub}h~cpZ$vd9OYMvV7Y*wE;8D$Bz#JYJZ%OA-Ev~~nX_Db+lx>r{@RK)zp586>9BLQgJLUfE}ndFg3iPga1cS0 zU<_rV0i=#Kv4qjlNgdQ1g}uxtyx4=Y z+P|b~xpo;mvuKf0shkw`ZhqwCtB?1ro+nD8@1Ony1l7TvxEa3r4X^41j_g0fIgaY* zdRnavO4($ZI#%pjwbf_KNiO}QU%CxKLyw*OMed{J5qgCNu5RBUN#q@oop;~Kp2vc1 zo7O47$!3kqK{4Wq*=B=h$lWGV71zyHZUN%0I`h4t?#@uKvXm%Ji;$5+0iIxj=A;)s zG2?-t*VouckRXkl7+)CArbU9Q2q|Gg={lW!F3k{H7R^E4^@gSm54k+!8t zbE}c*#lC8+$uG5{*YXSu+tqb(?0h)C^Uq+$s3FKC$6LTct3x|qBq=bt% z+m*D26`c*`!|FThcmE0st}YiAz?=yeSjg|m>*^^8I7nME{PGqD`z>#_1*HNBZ+h*J zJ%Bhz)!A;W(Wju8GcF2%O3n*HBi$sq+V%#`j5eJAx)DS|v!-10nSLF;p-=3)pu6Mt z57Gzux7OnsSkrcawv$+R9 zkR09XbEKv}jD2;Rn2KnVzXgVcl~lf2LIEAB}41EtSb*-Vneoy^tc} zO9sig`O2j>-Y3h`;_nIVyS_8uwr<3|zTle7!t%<~(Pj=Vt>ddtMoR z6%F}bgv2gp-|K^*9?yL^uX(`LBk{0szshGOWv-l8Mkbi6dnTyIc-6hkKgC>j?!1Rz zvvJDr0%KDo_hh-)xe)U8CK9^#?788@gd}xzC-)A|jIT%|ZW`oF8?7Cfmj3}dy|*0A zwD%a_7lV#DFo!5{7`&bpE)NRQW^c0IAHLu}8t|SxuQdq{T+{X!xzyX|U5VYHJAk*& zcn<4PKiJ233)5Bgsv4503Ue^~Q(rXqxm=F0JZqIR;&!u_SofD6sfaksb*cfVyzL>g zN1oRzyo>j#-Wx-|oEK;7JAh~5p|kP*ZVfT0Flf7j?pEA2Cr#n8~uG{u*46iT(m zb%9?Lc?*cE6jQ|L5=tcr_4W?{mWvyyzIi3%ySNQFw{ZaqXEyN=la3LcA!oYT5JZH= zbS53pzH}n=!d$$qlJ-6ewlh)(okKF(G4bvg8ymkHGg#x!HnV8%{={Wxa`jR0@UWe{ z@6tUp4fbLZtItW-&Ff_GVY}uEKZi?S134Jm^gLvH46QmXaqL{+2-`QakjSA0`|E4Q zM0dL3-?eX<`Qj}OQ^%xY-%4l80fMLrdOqc#$ErFB6ZU?+Aa%ot=)MlPA#>1OV|P2- zzfzigiMlW||IF9MEP9vdEptFclSjCbeVXOpfVb?&}qzUlfjVkKM{@j_IjO@=MBY`j}L7 ziVPNcvy$Z*@j>S!Mp)zFl?9Uc)eQ++(DPATe<%8dVxMWB#V-Al;3TiGqY3{k>11{} zMZ(u643U~q>c?gQ^722EC;7LloP;J7X3id%ig*p_YY#Lvd^F2E0hI8ZgL!8H1#J)i z0FWtaEqqT^j?c#RG0z)68(OsypntnT8918o?nF7K(rkm9yl+ld`Km2a|2l!>pZqjI z+8RC4;RO>+_nLGPW6lZz&T@s_8)qjql1W7&Ins`fwnJj zXUuT*gSedRwjO1csi+IA&$br1aw!W?BZe)VUEHPY+Mls7@F#?~;%$syGM91dbSfU~ zI_yiekj@4@Vx7MA7$n&bp7l=ZXwiyux8^lGz_VBIe751T2(9A56RBPusGX03ZGQ?S z(jPA(8Vb<9@oOA+ddQyZ8a=JVHiN@}Ezj6{ra=`!m97qWN@uTS8e9Y0A;9EInYd3e-0BuT z?9o(y7M<)yMNOq!lCJ=a8qiq3I_-pi+<-X?D~Lg{H{fSX>^#{gX(exZFC6`N4 zcjuQVw>>6kTU!g&XT}v9vJHjC)#$ra-BWL0$dN+&qgKBoXLXQUETJ{kCKgz9*uO2PH7bcb-)0T4k_szGGK&+ zgfytAgc5?ZgrtH9f^;b$DJ5|K_I;lH0o&`GYv($j>z!RFuN99)$>%$7Llif!IgBeR z+p6*Z#;!34a}kF!i_>#duIGQVY9VXELi>12sNX8&kMT9JOl$utZ@1&i;c{|_CS}I9 zN#5c$alGjJ;jnuxh-w~_T4h?K?!nfl@gm)KZ{P{zuJ;x7yN$oRGeP2~MVYw84d>!g zsY!>oAf{X|QcCtc8C_GzrIpQS!S@>eP!1#rFY zLbmBJ=2g=_dpHwlu`Y4rgq9TX73d4iqN+sC<{z)jmPD&C2@z&d z>_fCQr!&8T=5Gf8{Zg+HTrH?fZ`jIa-w2BQI4F14G7^C)P=<(G_GSiYf4)GcCW zUQYY5Tz4^R3E15!4Wthqod^@Z=z6fbZdyv+J0@@`DX!~(@j;(;T4^EKdmZ&W_i9lc zd8yeGsOLZOoEAD}V8i*M^_hvuuMLl^)3(S!29708Y;&}%)6QUUqWps#LehRM{o#4b z2BxNEWb~lfnB&9lyP~MMSD7Q1UcGy~vs{Wf*k_WFKKHdzyDPXHl1tY@MdZo4ZL#XtJY{0R_FWnBH=bBz~-&a zzc2nR8|U4J*#nO?=zuO4yC)&<jEim=F5P9RE@^H?KY@J{nzJDMBgmzquwDd~M?7#y$Ty}M=*9i`ksk5roS%6eWtrp&YQP5<>={Gr3XqNenJTyIRjE1CV0PbM zRa9`JLW{TQUg^?f!=D~?g)5UC)bb~P?R){LU(#KX{%#_OC1XtL>JAidlbuRdR>SOcDV z%%c;$`hF|zu3)Idzu2EYL!W-aUjMQ2v2f$o_#bYLwsuKIw$C)1bP+a;oGpA=0e#>8l*3B=>BLAHv9U6{X%YS>H zD~Xjf);|fOWxm$YYqiM{BXHsPL6?GgY5T&~%QfEv68%MPcZ-%e!oQ!uQ~hWGXkUDxEm>N-%6RI z?)ltbL^Gw^=C$|;r#?=`JM;L9V^1}Su^^-F3)P~nvV_bKjG|2+8n@v-Ipb&4G|~S} zTlX)`TNcSOWH!x8oS{DSKYL{Ejo&%pi6o-(N9T6Rx+sQ5-t!h*^!U@tD=IuM_jh$%YDvHM#q?ub zxA}SFmQ$L~nre7!vI{5JMiIA~uiUvjgE_JP^karcTt6t_dQLS%a^TKv6JidzU_ciMDr&m^pl4W;WX8S}`IZkB4+?z&FUvdk48DRUD?TybeNIVo*slLmzm! z3#7yQUp$)qV_asE>f+=z8Y>tX6`ZKXt*w&hIf{ycpgo$ZJbc%<1<&P1&%JeactzEBX6w zhxBESC)2gG3XvELK0YTm-Fwl$!~fBqC$nPuE&qQ0t3O~Fw$k=uFi5VZk)oaXI8PXg z{TO%R?P5n%?QJQ-1UZB^a{W^F6{Qo{Y^jR@BlHDxMk4MShNqjJbU3Z!XOfp=_PReB ztJ*clB<5`%s1fE5==hFx+ioX5?&W>ODTKF(I##nQ>0LRAUbFC53MxAAc@v=+$qe+O z@QW>0#LVHH-XwihcCeM|cM{E?zx8I%g?6Wkmo!R{$~I7@ahBTAeR-lxdt-O~Y}X~c z{&4G=+r0!60_3s0%e8B*=^h-9r&T;!d)^mQ?>&n-pwvhDa;>Vpw zC*Chw!%b$gJpymZ5B=}Y?lqD((%DQkr*>P7z98eT(Uhv!TJxO%+9cOymk!i7^-21a z9qmJ>b%p#+&#=$F9{GHp+g@HtlVlO=Tq}3rx*FZ^uyaL;Q?=ab2iqbcoz$tHp4~B; zJWg3~Tx<4iwPJ;DgvH;^%a0d8Dd#2cgEDi&8+JY>9p4u}>^zG-gsf8_(`Lg5rh;=d z)A>j8nbC&1p8~GGBww|;!&XGiJBEar-*X79DU$@LKbx7+NrBjlyA9NTc4U9>D!kD5 z(=wPiq{cgsNYG}uEtEV{{j^IxdpBp{GWWo9yZ7Fk6yafh zJ*mfCeAq)&=dAW%hPU?mqQ8ugPwL6^yl3(mBj5PK`eBhf55+`lNt#d|-lk5vjM}o7 zP@w+vaR&$LU{LwJgnGlqmy%!bI;WCCCwi#Mbr&F&?_2G`qKX3oNx!oZ(X|iD3 z-sAVD$?l$CbRIZyj7@6d4366;;qRTHu^029&zqz3>o@34)_H%Yp7jQu^B(?~$Bl)X zJ=g&dN*o-#7IflsX5VZqJ=Yj=b8~n1_jmUPj$?3v)Nfwfol}+EX?i#*d@DY?$hE0{ zYRW>B8Tsmv`ms8N!pE1#R~WL$zk8*PsUTSaO6Yd^R0v63PSb=udUoe?%=5-wvArLX zLq}nk<{vE>8sr_RtK<1BB?7Rgh$sG;9qDkMs10mgN7RM`fiQ*|ln9cN2jxt4=GKirnviSZYH*c^YHg(>G-g9uw zc&ZPqboY|_U7(IPdh>an*k#2&sx)+OwQj~ayJ5^6k%&!qoqdehsFtUtWr#(@mWek) zlnco%dz2p^`{|uj#K1qmxICx1Ki!L201a;nug$hBI;DLnXaRUAlyR<9Uz&AjG#=}> z{)Gg{oTJKG!?pMf9J{+A$}E$)2B#7o($;Vu-HAwDD<>7 zee_E&!#0q%)vd>0sJAY{_Sa2O>le3Pdpc6Q+~LPX{G4lHmsg{{Cv0K%66EkLnH^(W z=ZJ(j72{2^EsrRz+wAdP17YJ5&aN{0>3DUeci6+pkH&Pb=aVOGBNlhSRe@zI>bKK(^s5Cj0$n zuc35a1}~hKhf_qXp8o!^A~c-b^Kf>TQ#$;sckUHW?k{dqvd`#*r;pV@OmrJAiG zbyVa0hW*x&IrI@V);R|8j})A0hP7=F?2R#D#t$3X`mGt|Nc?QQRUtoPeom>Jm5X9d zIQmlYsh8H4e4Y{4AMgs7Ea-pj|EQ;=c7{&Z!Qli5z$f$L+J3Y&sD<@O|B~L6CX%_3 zY|^ZMqj?wj=5x>PdO~1pu_~j~cEoq^)wpNhPIG3+QO^*hN2u!cP&L2!?C;ia_FuSi5csI+|2zYa8MyZKHY*%$ExHJFx}G zu@vJV;6kG8vvflQ>x0F?@jCHTz7rc0-b)z{PO}h}`-&OxN5q%jR~n<95u7@P2mO{j zRM<@FI$>Iy7mxW{|kD4&dDiswQS5QdZ))W?xSq1Sld%|CxjZYLD0Xq zKSX@=hYuxpcE^RFhRb_F1|MG4cK_mug4aZW)-l1i*Kr^_b*Y#iK!#Ai{g0njMw)E* zG{?A{gj!P%R}(|z1}k}jPKjsd-N77obw?H-^+fV(yERQtm)8CESOHu{CkmN9wDe@& zh03s=nNjh5bpT_CTb%XUjB6%lan0&=5D63GQc`m2^K@@)P2c9BmF>&|@r;fDCNPXC zQWGTUuVyrY*y0#59EE0W>7&p3uDxxeL=C3;mmOF<4dWi5?7M0nMjB1+$5su-6>_zq zg59eNnpd5P{w`qRwHsp>odW0m1pM~AW-f{vSijDFvzIGfP zTg%Cl$mGShVI_H6)_E4}ybBV0iG>ek-H7JKm}@7BnFSjfX>WXv+Z2U2PrVKv48X>t z>WE zA71a-K3%;`@A7;!L}xbsN7SVWAD~D^fTvP*^+G6((_ecgMUby@Z*IG*ifZGYk7=3{ zpu)Z^u3L7s#y`H=H{RvZD90=w-i~K@)GgXz%At%~GE%q~0SDah%@Tcmv7|Hdo+uGd zXsiDaxo&1gwEYjz@=?P+-G}yR{i5b`tLJq1hVKPg#>C{}`B3oA6qJv;fNG@U)TXoi zixRLlPBHE&PK>aIFI=>4%`vs7vIc!yLI06H9zS4RVK)5D2WFO05|IIcI@u)Ie@WGw zC>cyGqBGK*B5R|=qf_vCo2XIDrqJ83AeL+Xu5HnqoFfFcDXKaOhR8r??r(a&l1h9% zZBIAxzyFy0>kT;M$j>RN?fAC(cFNbYlsmqC=kX`8&mIP4zkM?7>EgFIQ@g!n0=^J- zf#R+1FCto$F^twZJ2^sBv2Y(OhpXAYHu%}K!(}mQ_1emL`&wVx@_jHK*73wht>GC746NQM-NIF zK4jd{(eaINmdR~C+8$4z7lC!|K2}syOWS5pvy}(hxC?q{O7vw#o6(2oe|znH?yA}K(tR* z2&G<{Ng6V>{ih)kAC#-fwuC)^cNdMVRX3D&5Mj?7h=<36IhRU;+^%$OB07(3yb({D z`U1GPr?k=*B9+_}{>X+g%7(}te*euRh$#fDSyPPi21CCK?nH|}FXh@l^VjN~#HXgWMuxTJ$qEe#~U0lxF{c-uPm-!{e)n)O$9{hTkhwu=oX{7=W00R4JHhzhAN)irF zJ0R90;NPjQ*7k&j`k2N31NaV@M+r$55p}p!1(v^?y;9)@)8lt264Pucr40Rqp2_(4 zhlk<42yO}qv$0`qc*L)az4j@L&H3ZMnTF8_`>GesD{^#+YAZ<^w0$?9C>BNJ3X?Dp zRO-IoKkgwRuLBydHmf8mX5C#~UAxe$zb(5Z`cHLZVf7>o zPv%nL#>GLS$cO5w7CJ*?EAZFDsR1i3-CJ;uI&L9Hr-Hl$hcFAUnxPm4>Bwf)$7xRcQLg$& zeIjMv6GATcF=>>mxpp($12cL}1)cPh*Ygd5raA_tRnUE*nZAdLqqhYj?cLOfeVS+R z$Fu~6oCrS_k(Yz^C$Wh_o6-fR$-6{J!2`uSmuo0hn8DC?)}_CeA`ku<1x@Go$u^jU z=US)Vk)t9V-KhFQcVbr8CfUdA%KzqO22;3`z^rcW#wZZ=qq&>fNkDzu^vQ0pL-8x$ zcb$&yZH^o_cs!|vPFc$?WCy{Aq!1>xFnp3RAYGZ+HH6t-&~fAWmKvn}O-SW`fCUf# z!N_9hMrd#CD~4yXQ&1HYKxy*v(4n7Iu-}z`1#tyEw57bM&=>ZlEVwB9s*k&?*$1XR zx6~z(TXv>l-N&-$d|~$Mn6Kpd2R|C7U#~<@|93Y(H@eye1eVV2{CK-b+TM&JCch{@ zIj06i1tf)Zkbm`Lw-3n4t)$WiF)^!|)6{F-m`6l|dQZL!=z$fJS|$5M{87g!^VZoI zl*e13WEgKa#d&%PmsI=t1hNBWw0NQb?Z_rJ$C|H^;o`n(i_f~rFr6>NX(%TSuYfs=c&-G|85!IoIRi#`tlgM_jvBkzgR0re_Z zi{}ACj1S^JMk)z5cz+|8p0O5&+kdpgr^@CxU&@_YZ@lPwvMy7(tZuTv0TUD%@Qo@r zh1HuBcGezTTfwAIRc`?9ZCtxO=}QRw9%HQZ!l%r#cTlQ3B&SF@RO3}u5CTW$-|96q zk@NbeA}iZ*pH7XpEC&}wJM|=SphYv%yLOUVM4-v!zZ3zIGu^!@}jql!dBz`dZ$VKho9M zVcvQ{DbtZ9iZOj9uEeUG^jnNP0Ex5ah$0WOF%{DB0JO|zi&015cVtqG*~g`XJCi5r zlyq_XZwpr)BTMsUbUKupS)C&ZEL2IRbaE{d|LyOBYi$B z3S@=5ely;kr$2F4Jb|0%qYQq=O@BXbb?ODJ4dPGmE`L}$<I{40oIsjOsKNOn8=9*<-0)1#k{Kl3GBjiLTY3=sqF?B{JdKTtIt#nN+AQxGSp*t} zZ^m~5yK3B5Q|l?e1jCjlxCfs_Trc=?7w;jr&6qpHnTn&R^9a&P zLg3zB*;tfo#wsMm-EFWC$C-L1Bn@z_}u~o?=C3wh4VxVlVqC{D9xrfYTM}w_qLb=fZl;= zKVd|Y_27R31Ui#k?s)#{+L-R!qt+Xk7jd8ECiA`U=qOBoApLl5;v>#6Qo?{l*xCa` zia=~P-lciKS|{g_v~RY5IkT)KJtv6xgG{|wdkxFB7WcI(m)RbId~ z=u@I!UJH41B))9L3}od{bj4(e51tLNmYbxNd@^YHM+QTE8!ktMV;2Zrd2B-cW;NT% zi3(AwR-|l0k%DM|vnBHR^|KxTg&d7!uFLl1c?&Jb^l4;)d&8r|LzXBXZx=s=>C-~Q zffGwAHyMs{2}vG$<6qAmouHwR4XxX$Ip)Ejxv9F+6SK=Tk7@KIKGd_vnRmK4qcR zY5o;6R=Kw^ZEE^u*<^B0v85-HKEa$mzmql6xD$hdsCV^(M*qm%WDXiaF2#p9u(wyb ziq*44({zso+l3^e3(#pIDUXcop;ZH8eb6bvf>dwmR8q~pb@+jK@%3hbw0qK(QSEe% z;VJb_JEZFtbbr$&&aU>a?Jm%tfPCXZ_w=E1k}H&R`G;^S$sBwhtrq2kZjU9VN5nq27US#8<=(sxi})2!PFVJ$ZptY?-VeB3 z_zys3p+o3a?2t4+@){WbmFAFq3a+6<&BXs&R)?bx1dU>ndO882J$kclXaA-cdSlEc zE-6?F$8w5)JfO6vcP^2ue9-u8)|ao=fHytqMvcgKhaMyx`>OE=ZG^eY`Dx}&+sr$>)Miz2r@G;YhkROa}IlS^)xzMbLVQ=Qyl!5Bwm<|&Iml&DWx5Se2MXUVeF1D>U7wLN<(_ZyZ4`QA7U$hUr|Rg<=Q&%%qBV zD61)zrVT>Rq?*A!*F?;!Xd#A&Fut%1Zg${Y^?H8|KE+0BpJkr;pra11M%2up_Fy^V z{0~s~Fx{4QQAZrvykJdq4O5Iy*^j@=&o!d(6$NekHrO{fesDTlIDv6Rbe zV<8!6uMZ|UTuCxOR(`ZwsPC*S+(2tiM51Ir^R_|DdupN^#`?|VdtQgEeS^k-Z0Rbd z)^Px9dSB&_-T(tA%_l*13Q-j)&zLWA=}x9%*#@6GfAWOyebB#mvEFxP&2~CaSC^2s z8x!hz2V>cxoSS%F{Kv>sVlG*uylR);eHdL!18b`E)1;BfRs0r1{HK0kiCE+>##AG+ znw_Ykm>W17d>(e8*lBHki3?2!zgGem!Elt&793YhxsMaCTqSf zMpw`fOUc~3wwN*XBAP^*YB2T&EOaY)kl`RKN$B(20>=t&6>iXkz~qC8f=8+~oipT_fo;5S?+l-2=sjGPpo|k09DnP{oF>FK z0|sXQHUOF)Pb>hj3MfN?co@U$dYlpi-}ueehjNvB7D(JWRkrR^e8ZFOOl*$TOWXJE zbQd}T_7}xzoW@m|_Xn~W$KdZyZK8_HK@Nj7yXf#mQ)wU}r=TC+rJnmbtq=WkOAk4_ z1@DkB`ZK6<&SUt`eVY@-tf48PEfj`_$9FOQO{V^|YSO%jTuQig+5=X$S=s5giB5*! zI2`pk7#x!BN{UXQ6^CJTEIe^myEkWmh-4VU>1{2ZV#5~}T1*6h;yZ}>e&B2}lZ2dZ zw5OJ4yieoxc0)JI(d<1gOKc$s_Vmjfo|xD6qcLiLj)00fG>Il%X)W(-aDGTmMIeSN_9b&0gZiV*d8ctz(`9Zy<_gw;wP=c(%t zKYr!G@dqcVVXv#`9%)boTU590no1eOm-o?g@y$kb#jvmr{S^xeUwLzqiMYVAnPu{8 z9l6R7H;U?+ZxkZ7IlC*Fj<6JtoU?0SVRtl_INJAeBD2>otIewey4RfZ}4b)AgUl z{0@XD0D2EE#+56YA8ic$6m2)bKTY8WeJ1xLiXn)(#!wUt^gBShe5AlUsl+^iA^jS# zNGPJmaFmadrzNYJi&;p>T<>n2Ty`Q(E=YrpL0+ow=|Eoh`UiPH2`AJaJl~KyAnx}x zSGLZ8ay&)*7c5>h#<_tmisHVsz#RY`auCKcg)hk3)NAZB2Rxyfii$3AV18dvV^P9G zqH$!BGFf}g&&-Cn3-ZU@QzNG(!e({&JKy=F3evGC!X+v|8BsbZAkApU&#cCkp16|m z<_Of=FFA)2#(j~?c$d0+bCnH|ZBA@kl;3(T9p#Wydr$YKSn`t~-N&-A2Tw_h5!z!r z9k`y+Tl@#KvM>7FINbY;RMI*B{Cf2_y~Erc?QHLK1!PI>=!b8)ng%m+{#G2U6zm@FUc#K&gjb_yFJ(xt^F2pN(Qlz02| zswF-sOCfE|2t966tGBvS5-hNdmQl!PDIf*)%=K;vhmFUSi7B3D*#kr)^;8}QBL(`& zRX+6vi#ZDh-ogM{Ti_aQG>U$epDNJ0tQL2oUbICwQudOi@AP~2%+A1XtPFc1sAd=q z9a0=wIm%OuES1rzWwrpRbOSokFl8AnR%3yFDPum(nq%OthmN8EACpcsJZrj3aJOMp zOv(;Pi-x+U8iw}OYl+LSiG>Z`g4NQ!!OuTrf}w}*4V;Rd5EMA2N(}1+ z;~HpWjS|lFwP~o8)2tH+kqOZ8dx7-0V}#zI>u3_V`@JCfTZZ}pawSA;tw!LAm^-N_ zWcKCK7##UW3Rz+|rH6pur)%d1zkf6@C2PJ$#ZOnMlZ2EcM!$|5LUGMQZmwT8ZF7#T zAbLADS_T7%+PAW5IA$QBw$ca?{`F z6DeQI=@apdqAvBT9QDl?n$n?|R~z`Watd3iX#tdNWsA#v$woGEB!B+=bchm(0FX>X z7Zpot{0G>y5sAr^E|%YK5CfjV5JUrpBP@)=1u4%(;4xT~wR-i*)nVy(b-{SOL!f z$WA?a2}*YqL;%p6URVIgqDpEL1yXUcJ`j45H`fASlb|o|-kDoo1P)~3X&4}}h)1(GvBq%sKpqwr3WP;^obez$UK1=}A%gjli!6>d60R*q6=zd%0zXh>n#zC@3h`nn zLmI6wwipyx7PqJiWhKVjPeU^3Zlk5e3ql8d&dh!Zy^<|tEQmERcl&(W8}$LVE^xjI zsdHD2v`D1vvr_wslLjHEph%doUyGYlCVotAg#b}|=Xf_p5S7Ye;}UOC*+8Cw92V;R zP%B&Ks#sy!EJ&o%9aa|3PvceB{BU{~8+{o))uMyPxsHdU&%@624s@#$+j3)VWl&_* zRAR7=>O6HrNX@A!SNVF~J*NVWs?aHJOP?DwR$VpuQOpv*5<*K#kp+Ul%+BdoOibL? zdWu{xML$Oo&_)0(_DdsCNMAE)mv%yptUh%3ruy^)Oy8_rmGv=5W3*BtjTHoF~fEw;~BVd9vCwoRBO zt-wxEENv((I7Wb9>6T0M6F9#Ek|%c}eOeem3CDtwlvzJtc%^IV2*TxS7jdFXueI(v zppdqOpb7vT-lP}%RC=-zkkkoaw}Aq)4{iscDK8D1O|lg`&4qQXwjZ_e3qM@D@}U#$ z1SCJJTbGUKV%zI@JD5QOGbcT+CLe@sG=G|?V7;chfQwBIj!YHT6`3#tu?q2h1Y5bN}-M7a^GQ>~H+HELvHyWwx^lz+SmvKRe(zxbp^vNfU=|O4XT|n(< zA2dnOF@i3x(yslxIx#)!^G9Bp&gDUrk8Oe!l)5}1zQZ0jL-F>HOdY_O=H|LLSeSWQ zG@_4!X$JjNeNn^H_m5OKz~OQ@YT$u~OL|wYzS2#utC`_scb5bCk8)pbr>CaBwe@8P zTULEpKc(3+QjFjmTLAI1*T?PQp-F#yZ?v2*F)sFM;|6@Ne`OrLEXLNv&b=pj7vilw z!Z$)0WMEMF&nN_Ff2-g>WCDx^V|6m;shcjfV*F#X*^t&{wt|71J z1~4_u#WVzHP{AUftbcTr&X1K&S;OvP13DkCZb9VKK4b>yAm~F@Cg`t$$7jOVpBvj9&i$^vzNM=d%U|zdS+P2rvh?r? zVuPM&lx#};ljNfJ&!=nyXqjKAAIwPw@U657;|vgIv?K`Y?w8r6}mw1PyhHlgs=C(k%z{ zjA5}NxYVd*v@P(I*RZoON^596s<@k5kf1G14oUAduOgmcY!*4M1;lSV3j|U8~WTkq0@91#IvMFvU4$EOTMpxQ?M>e@h&O1C=*hA45dLr zX1n)8YyT;J>W1v)pKr=&%##pko5Cd=Kjf1D8y;|w_ai8M93Jb0LrhZ!+h=4%5fLSH z+In~&{p4jiPF*zEC;FKP4#HrG$ucJtcmN5^pN&o4-KKb!0x{~usUR0lPe<~<;A(Ur zI=DPe3P5rH1HdCjtG1GB)AF@!2P0iw0K>2Rgnv3+G4Z;OR#?F_nyS_66LFewa;~OL zf!{6*sYd{C)2zVJ(i+%jlawDdkf>Pj5hrv!P2I)}0{Hqy9dX!F=xcCT#dc~WhfGH2 z4nTP9js{E%!onl`<-C~sz*`3#6<9ec9qm;3k(K~>r1#DSODdH41 zY+nbQguL9cm6r zxdEhF5{m;7zX)mMZ?KeJK5RfFJeLKp-a4Rby^9qgIO`uTo-^`5pb@e$#)dmT~?Y(GD}uQXK*1 zTQ0kt5NWFW4RV6aR+IbK!D> z%OI=j-^C)nP5E}>MDjed(jOvemDobeGX?@(R6EypTpAd!oV~}d6FW8z|9SQKt~X^* z-l-*h=IX5~8MBo=9=o2WydqfL({|*RbQt26sP$ONZ73MHKTk~Op7+)v&4dm_2`mcF zd!LpE)@Z^;QfMRTlDKb_8Ia%d3zqPPs zwvv!Y#bR)0_Ae7mREYjx($qv$pK=+xIF)OAb-=*KjsjboE`HbXn;)WP*yz`#ZyAF(nmoyQvdQ_!G@v0B41EpAq)}+}LbnVReR&bvq!`<;ghmq*Ry?XS@ zwUgOVKrxi@0V&x{2xo8vGhzyHv;ddM9+NJj42`Na(MtrxKZjx$q+t?WZE-X=tCo&A z^t!1N9!5X*9kp9g${Ha}rqZ>P&$=Dhbx1w${EwvfsJSUS!B!>UDcMr+t7(JZri5)A zYc9uiW0y+YgQJE{So+OqE|%Qw>JbCrn4H zI<;>kjhI&$30EpcXTFhLrUr4-)XS+IrpRg^<_ARDHbt?#=m$|SZ?)CYpeAbv3c z3K%F|{FKYSpUwK|MezIXagc_U+$FKy zditz1Z}kE_9v}&dq-MO#-`&kM0dcE?z_>G)w9}Y0A^; z_QnzDgLlYhEGmO$a+fLFSZY0__c(Z<<*mk$eaju1FgUsu*Pakb4Uy122 zcTSJa{2uKh0VCF%|c4JV8@?xEH`W6TYNrvQ%YsODVsGqLHFBe%;f-5M0jn zHQ6A0I9WKy$C^jbyVtS8_1S4ryWcd+&_DHNP4KW`$H`&F3njNuw1y(bOIbRdB&l4v ziQD{Ab5OJ?0yfUwndS3WAub_?5(j$THV0SgT91ycq%cVy%w|DM>*baCPgxO!2%>{@ z1$T453Me;WlpIv%@Gs)Qj{6or`@an40@0N_)ZH?8tyyveaj<@_YYj^rPYK2FQhcWf zL@Ne1F)S@;Us`u9tvp^X&*YH*WWlD%<0+RYVUmjhV%uBDUVleaOn zGTNQRhB`3^dio|H=A@=r?k;mttQxRZ1T=v5YmYI$tJ(sb|!su3+qdO&zPaPfmgKkx2lV2p4S7)XD)y^QD zjIJ~PT-h~7=ehc!SMxCX+usE>p3mx>=V`n+9k^L@?!4NRo|QtY&vnC+Pf5G?`=jmL z5Um`9Fl&VlcAQf4jSO{^h|cRF{&m`cYFZJl8%ICaibC5_eA8zG&%O>9)}QrUx_KUs4FlKmH4}W z^F0>R&i4D!*+8`84QZiQ*VCg+S{%i2UD-h97$)`J=bn%*Cb_W0yi49$O7Z|6((&M?UjBVEE^q8 zQ4vQ*T!z1Rpm8(EwMEb8>Xvu>DY0vcrl0|+jiLhu_g z?s1kFeA`xo5D5R<#LNWMO{drU#x3*-`fGiqX0(p8VC`ZI9-H*I=lHfP-%|%xal?DZ z&admxVXc+)Iq?=5EhNqoY2H*0&qe*6j)(o~m2kU)%Cr6qy@QxYw5g5RRmXxy8L-=3 z(wff%i3Fk>*)mAL`y{mHJQl)wQe%5VGIy8n^aGilrzzYg-Pt7jqiQX}WrIk0JqQZ0 z5?6clyl9*R$GeKYHOC!O&K~Cn#GBy)jC!g zJLTA3foL2KW*D72oDQhL3wu!E_>lsZ6oWESP(Q=O8an1imM5V!u!rv)WHT^6QZ}0M z%njSQ-xgIn^PfuZeMAPM&pY4S9+ zE49}>20Xc=W;yi!o?kQ52Tj!S$-Dx9NVvvSxYTp#$HGhXjLc_7^SD_s6=QkKOHx)5 zXmL9Vn3dLzdOQMq4ycFMi2!M(G^0f@Um-VWx+vQUqpm+Nh<6p~ya~6?y@o^!!2TTG z1KFpCjIXHw2UwAZdXVF2L9Ja-sb*pMcOQas*oFRdO9KFm+ELVl$+cv9u=4Fu&zsJ$ zf7`pGw#A)PgxvgzDFZRx2QOHt)?2#ds7z4$lwJz^f?BHu=*qlBB9k#Ocj)75D-9rV z5&TUFTu>a(*e;s54ooBP*%y9%et-7r+w%ug%IyjX75+IA3rb_mLUPdM)4m5(a)h@7 z?RlCQ1)=>Xpi>Zn1M6fZ)r(_IN|}De@2lkslnNIifuD*H--+b2SKlt|YoRqCqSaI@ zZFE>B{nU(Ejki%50laLPfnD%?V+2L8^5uty3^t{IjJaiQ^dQs1mKH#-3e>WlMLoA6 z8i-~YeTF^~jYA0lYzUw(IYjk~1QfzSG6^E{vVqyq3_d_9gJm%Jga}2mJOI6c0~iGH z1;l}+Ot4UVu5Np0s(*nN9%_`4?O73qe-IlN{ZbI-SSmA#azNPa?~n3>RNqOP2GO)| zNobkeaHk`mUV{gAZ_$CUgyCZG@mP^}Nk&qZy>{l@xc6AXQiy=tFxcasQ!Gj9z&G~LML54*u!^`~T$mLg-GTQzgvXzmOzlUJ( z1!H=xhhHVFhrhkeo2iE;bN^h|g`A+*Wj4IVk0!l6_wd81k8rb=C(U7S87w5kyGiUM zH=$>{LZ)pzwCn7dnZg~GUjrErbq>11-q zXe^NBDT$Kll^?rcn$=$u4s9jgkoVIpDGNVvi~L&Gw`wYDl1m;D&M5kxAqK~Xq-Qa( zyA(i!y(Y+GAqRQ!*Fa9&ZWKE9r1{{p#dvyPy!;i z!T{P#N&Rl0;Ws5~%wVyQ1F}y~$L~zDv9f_RiOmsLTFdZ`mN#EQ_#~BttR7<*pXLg$ zf`5&+cH21nq{Sd?GD>T%<>1Ex6=e7Z+h@HY#kPvC5+@5$vS5x(!h|E8T;U-BJr^Hn zMd{NYobPvqJB18N=Z%e!IR9z@8Y$dZku*z8u43%!{49{|+6PmLL{v(oWbbSBY*AkE zDS>CSssBi|=F!I5Fa|QU<+uB(&nx{}Nmn#rFDOWQp*7ZZq??Wty6mbvs(x`uzO<{(be@CiYAx#?t3StV*(+tp3HoM*y9%?P0!mP-bw9tO1^%NQ8 zc1tl^OfD?E^|Hm{U+EVt3!%lV_f4sa2~8`ssQ1tq%CqS4B?J#z8-omY9O8YCq=;_Z zcpX3P@s>0LH3m=U)r6A3`J*feffe+k!r^sqo=p(FYxtV8S_%>8zjb$E1 z-1!>jkn(7m%!(NClJ?LZw<~FaL%T6wltZP4V^?fRVx4*H^~-Vw;pSw}Z^jLsMGP}x z$5SVnlcCiOtNXu|7pN~yboquqi7oJ1!A0gmEde!P2aU1J9Kfa5%HIwB7qQW;Sj8UM zIyF*k7I|Qg%d@rdkPlm)w?~8&#Qwcn0=HY_#5y=Po;2IZl z?9a#eT8wKa|@-#Uq_v=i|ygc=H z>jAfdN;g*}i`NfyKOCud`YgkvGL^4+dDrJMZQ;zOQd7bKZ4UNr1*kO2kXu7(#S_}I zX-9Qox@OhfAS;+GBS<;Q4PD)-Fj}9HWFjW>RAmy8Vdc(4`2pfL-QPnGrL=WJL3UuB z1)UAB*mtSaAsLnjHac4p_MmkdXwL;a3pYp}cT%N1otpKAJ*9NVJyWdai69_#{cKMU z_6k7>!i}!J!+j(JwMB{hJ4~x7X`DHF`5AE(yq*uo98EYMX$8VlDEe0v9+oaU=+aI3 zW^|v?8W6A6OFjmsg>+3*L;eqVx<;J>A72(B`Cwj3?IH1{-G9*LrXHID2Qir;Xk%7R z;`lKP2%6aP0}TEMl28AT`LkS0iz4w>84Kh*J1ZccJRl@9j?IAS{X8V6&`ihHOl+nr z%cyj6RX=mUjJLGp+V48CGs(_5wW7XfQ4aO4Q7$)zFNp`SWuL748CT`s`nzR>w%Qia z6XXrn{>Tl1^9B*ApB7Bk-@$f<;N?mrTEFUQv=eeq&VW^B`xgVI9`&g&$WENB|GOX2wT>4B&?%5U(^!N$4%e;a1D8SJI#Rpu$Kn2ukK~>qiJ%a>Bt!F z-9(e8nw|x4R&`~tIGtkfn8*;%@lz2~uF0&Mw`wH8Kv%45mX1Ip?vPJO)c z{n^(Lu6H6ZEaTd$ZIxmw3DCs=6mWFj-%InAY24o=A2kEh4yeAPBQl6^GI2Gz!m(9> z@abDQRwDlhO-$(-r zABLB-T_lVdTooZApWZc#2e&l~WRv1v5kUFWaNB*|Wz&v-%=SWgM%@bZ@}s6UWl}rR zb>&i-p71Z;lz<0{r`8 zuxd?!o-I9gLVYP2KGj;0o*0T_*x%JEUQ7(p;$YpLH{ZMyA_bYs9O=QRX@Ry=2egtg zv;ar(&-bMCJ>I)_>rBZBok04$69GE|to(k9PZP+V7ioR7Ti*7`!{=)D5eICb{Cc{J zbJy7v1*q4x!!g6@N)cr(u>$M$iJ8h)AB&v)3zv>@&zhp4+>H)HiLb65(J;%@=)ALi zXXD*4d$^P9e(AANpeBq=DF~VW?SFvTZx$K&-YkgNGrakG#w_jKInnp!qQ{sd@d9Su z)A;#*NdE4J5ucDY{(>Z6OG-?i_6^{4*8{ZwF$o?=sKx(N3O>t zCG$)q#v&3tgpQfnl_{MsFvB}KO7vdbKJAUYy**0VBNZplu&FZ4-A~{C>;@}c)OzXT z1;--V@HLTn#TKDd);xj1t|Jp<5Ggu(I!E9>9@=^Q(Iy3)j+47QKacLYUD8C=D}iA= zBRHL2MCc>hu5k!zSFb95?eVD7+3iE1W3*wg@N)vNOW^MK z;X|g+wS%{)1yCiAxXMr!0B&WN8$~Ac4n%^yVlVA!JwVHzAV&`1TO}E z-|o(54>wgOWas!C8M+Gn*LnwUhneBJqZAm>8Rx{27UZC=wSN4q&GXf6#!EDXUg|!L zAB)K>L(FHDbKmlMykFnmzG6M}T~uszU;4WVneH|+@%i>@2RHEpPlMUso;m-BiZR|ABL9@iND`J<>B7q*+Y#vyVMwZ*k21;ON*5IVz0 zfwM&ErnN`~cHZRp?OGOf%cRY%!tk<4L&Hd>PZ@NHb zXkb?%n-@&4*e2SPRvoAIo1n*KV!dlX{1` zqSnt`X>O!w!s#GlBY+t48>g&N z*ubMp7p1EeWMkLv=>tcBJXl9cIdc`-1=_3Mp2LKrWBYX_W|ltBxRZL}nNAIop(>QZ zS03ONcxIiJm~5n`)Ra%JC4T|{iit%oh$0h6oi#bhLp~2*#5J-8VZ~}z6anmRjm`Po z2=((zegAF$!R>ptrkx}nI~8F^deQvze}HutTo#+quf~0MF#4;sw6^?;mG0-qXNBok zP8v@r6Ts2w!M%{9qNIl7hgX|~lI_Vn6}wjv(PFhzzx-@V+u$-l^QQ}nz~nA#jif>H ztM@C5Isp9mb&gHfk&Ya@f4;E3NDv2mGamjmb8#DA-}9tlkcR(q4N`f4x78nfJfr)? z>1%M`0qbu;NBO>wJ)UP@@LB`(PNkg#SgDT`fN*!WuN3HEMz(g~i3#C797H3q>!LQe?Vil|JuP zC#MO{>nwC;W_rJRNN>vi_hn_$H0QP_JGMP-&ahQl*9evTeHe^gs2x7OJ&+aw`pjka z2MMmyALzcd?!BiJCQ&VO5c3STfw*9qYJ!&`%(3bDcJpltZ>iutSB8Q-<8e|AA_R7& z+e+^J1if?PTaTk;r#Fg=6FwODk^e}X^42lTjkuHFD23p#vNKSbF}Wv&SdJTI<(j2T z-e;tk?yR9RRZ$%3XmYW8tW*{S@&C)gHd2#qIw?~Iy3Zy$P!0SCQ*g5$gnf;IS zOVIP0fX~5HlI!ymeEJm9ly7ZMkcvk){$|~8jsr9CrH~!t} zUc0Ls<%V~j%0%vyLmjF#4{(}5=kLuTUSqUGr7!0tt>>>qf=o|I>uR1)ThYXj_n^Kg zH`Xk5%D$={)4IwL8!EVPtFpo3I;mO9N%tQD+y&W5RZ%Wu52K|5q3ODKdGl0fXBCNC z1T?pCa|xOu*GT%}&&iN_oyyoO68bepx~Tf}-~5q!o6}$MJU%#V$X7Xn)jq)b7}kOm zSnj^m6B5A0m&uD3t${JMceD0SGf7TKMK9W9Bm=qC<=vkF9({r+{@{#(3uyG}LJZU} z8Dx*@cE=w(s%yl0;8~=VopXMY&8qJU$~~ z)YxepIHy8TW-yB35iIp1ikshx5jYrSUL$W?0NFk4-Z3`uMwgTp)gwbfp9H&57OX6* z=ErP$=qR}2VFcT;V_beolRty$3C_E}>(s=L%v$X6*?n)s;&$I&+xPh&Pl+?vvt;Uj z5NoK=Nm~horE<{ibS_frzVQuCcypoXE3!tknBpq9e)!jl{^#w=tGYMVEU{m33YTAM z_$*4PmwUJQB4{BKfj^h-`q|EGyV>eyTfH$VNxRy=J)rryDEG^^ovaF1rx{O&&{M^x z6s9TT!|g>WN&RNg%kV1roVToQ=C_SLOk{&VT^^@?LuZuB=oIvudG=e^1!Bn(6I*Zk ztYrba+zv|0m{ZObSO+Y@abUBI5D6gc#^78ngmGj$!keyu_Xi4Msgwr#0V5<+7PL>t z2xB2$D{5Fi!Rg80H#Y*XVV>hqIU{|8umI(Qr@C^m$Ka0Dwfg$VKs-d-DFWjGR@QO_ zd}vssyX^=mSy(nXNv9&Oplv8W(lPc9TcKyPS1n(#)yl6Ar#pp6hqr<*Kda6G7qeYm zN{dH8;&{!79wj9md~P!sffooOB))tAG;zn6K@{W*sc=N-b^nub>JYwZ4Luvu}RIG1qBQO`?X*;&*}0dUSx9@s?=+5nS- zNSi9kvV7Y^kzun~nI*@v3AlEPk*g2oI@VjtS)U1D0My$)bOJaDxP0PDxegT>1c+eh zB+n_TD+CuisAo0Yj;S%FhnP`eml?Nu{6sx19tR<3AzB7dSFFKPmXFT*!L?~$ z^6}{J)=vc~`VM?bb@0mfe`ie{EBZrFeI{b+!jne3{zj=1S{;ZLWg18`2iOAU1}o(8u->-_eA1dtveO?0}Qq zs9y?CYk=keqvi@Mp91c;)^m&vvYEREseV2*6lv&$M0uf?4j!pB1?aW=dE&;UEDP5o zhyaD&Cg z&Qm$Xg<=Zx$(d^Md<06;k-7rpIWd!ntEMUCyHMn$F2UiQ9wWhH=x{=&ug=2qpSNZ-^@iW!D$Z_Kv>de(b|8qBh5ZgL zz^|=d?1AuHD@)d+)Cm4X*vKXHvx|)8pY48<{GBrIN!79Jiik~JOr8C>GyLq|mZR@s zziGsyb?9qVMt|8Gh7QLs(u8ScDW)ITipnIj?9!cIUEKTkzf0>tXiwnZvWt^L3CwMj zZ=n57-vr++@bL#3ql>JQ;n%~MV2cxgpxUEA7RCg7wt%bOAIN55voUaxwv46HT?>hH%XGkuEYI^IdIfL1{_{` zq!QK-x}FM5ou-XLF{I0QF`kaW`BCM4yDJP%aa0dE%s{ecMnjfU|Fr&W-gz}bnHFN< zkFALuD=nM82Xn_V8(N~r`nx76u5zt`@{F6aq($l)NKfI^uh_I3#4TW; zE`puN5*1a+)JXK)wS6SFY0ns5tUmqXHh!499{t_z_&R;tab)Etv`6W-k*T6nuEUIx z9aawUq`U9=(hyHWO!l_V+}WsxMbuZdNEeM=Re{jC3&|0>py%vfyQ8RM=tP&f_=xu{ zCs}rh?rSRUzVcQMUrPHW#Zo_!qAl^SB17m;#HE2zMckgQI}8fuJkNzx8X6MkqoiF* z!Hq2R3<4hR1`+i~oT&R%2A%205aP2)J-dEBmnSBEfxCjIUcUf<+!ziWbE>b_PmIke+d)vg|p)&_$q_r{|AjbMf z@X2$>VHm#{Utroh#iw{L4-g3H4Z?^Fh2i1(q}SNH?wrdsd<@ti=?U%7w9sX2=w=pY zE&whWikoqMCYhHrpeBV$_Y)ncCSS&+io@Va%T07$!1LPP1vMMp5cjNo{HOyK)^dMc z&2#^h9*GUUtS5Y4({orna?x-k9)I!pZEZnjQMBiKKM6emQt76s8>zbISA5}_4Q{o0 z%uuQ>M9>MFRu;!?<&5zfZRX7Dlhc(e*G!;~iPZn`8F+%qU$Gi zK8iC%NW1sDDKEwKNZ;JN8eW}I>Z$IY$ajV+St`0G1dOX4ABWJSy1CRdPIbgRbchF=UR~!xn-2l{`^h7emND1=(7b$ zA*Jqm;iRD(NSt`OUoP!kt)mL2{6(`C+S?XkEd^DrUL-*wGm4RCpk5qc=qSDUqkQn}d&D*3H zHT;gO`T*6CrLDE#M}h zn4lXiuHry^NjE?8e)7NMObqE=jOOhV)h~i_ z;@tU&QXU)IHvk6_0-`2$AQfE#U%dqs+tc)<)Of7lE-4s*XV*ZkgnF&;f{<{|ENv>V z0SH(=MU4x|C?hP@6DHU1>`#KdXYTh%qCi#Kvc3%}J)GWyt&tX?2SjSoGyw1WYiky+F< z;rsqtORc){x2w(mU%Kt)w^YgKB*^i$+z&#SPs)mObRE2j@m~~tSVLFb|4x})_;_(< zTh#a);^;Yy#Xs_S*~n`>x^F{Pt(8KWuJ=&kL%fvkfyxt6$T(C~m(WK{N>5mOMT!uv zO_i#%W;$~y?AUr&kgH#Uv|bC_B%(C^>^LS6UnY)KYX*BNQNpHnT19j^%0MsD*r=K; z4Q3C?agNjnc$z_a$raQTBGe*lmxS?AlS3*#bW%G(X8SlQ;dc8gM(B4Vk4 zmu}W;ez^!L0_50jS#rr#hE!TirG}#}go`IruUJD6dLoIzH{E{HpG;+WRXbsC)~i`6 z?;JyQa?1)+Z+@7M+Vh`lT2bZ3a{9p)uG=r6W&<>QjD?KZN;cnB@#o%exej*;y*g7#kg*-SEo-#cS>k_!*YMydpDYRuGb?dv0lkVo#mT?c5_Lk@ zcIc?Tis*j;s9WnEdhUNfP0^XN-FqM(H%n@|_1kWax9McYsL?+h`mz<8qcN5!)>@#k z{g1&;Aai=Ar}Nv-pa1$_CPFhBwr|(u8pvY|S=uH3qSlYlMxhs^ddLrGC(xg~;m{%x z%=;)ljsgNdi3qI78A5BzB^w;`j0{Yedq4b@n9-HgId6?=KP0d!$&Td2#~Ec|-WC{xeA$f+#44;LxoqPLnkMdD}72~u~!L;x%E}Vv3W>~9LxfewLHV6=-2q^RJU=pw%z}@WE=>) zrtoKcz^@?L!1bbb?)lZ!n8`lZ zsvhgcEc|AdgP}&sNl2YxJigeo*o>!_qZ1jP*>ueR3Hwx_iZh&}fanqn?9Q(iwaEBc zVf`-C=cOYpobdI&FhGvLG3%SjINY3-fvfWX{YUqU}ZYVN=%$?|)I-Cq}o z!g=HWd}M_<37HlEc4)nrA4%QQhY{C)8-%Y%jd}P#*k>BC257a*ok@%bt^%{nR1tn>j#! zOUAAfJCff!>9F>;7|1Ci*q9Avo&F5Ng2CR`=!(FUARDD!&u%*_>t@GjqyvH;Hc6-k1}mp)Yo1c!D^ z_HGk@EziF>et@-CJlt$B^#q#XG?M^ajn~l{t9B z?>6dRfzT{wqkB^GO337VvHMDk7_RsWPt7>jv2XBan|S3)NF1coF!kp#+Pl90y6wb- z>l`K9)~4U>Qe)Z?VJH1(G$fxMn9rFV>~1#>-l&|9J5}y8v#4rCv>kCHSKUg^D(Fbe z3cOk!vnC7HXqRvB27D!28Q_Nz0}=4z+FwYPK0%H^rB6|F{>=&6ck3Pxo)4~(V7urg z7!2o6W5oBOKn!Hm)sSMGqk!pX)e>2wX7$^(w#7-&PXKO!i-|)#54bA;QuWt)R8B1l z85GV%^;i4w-Qhq42NZe55iM<{gk*Q-+mYU4J%Lnm87uOx(INbPjLVL(CJwZ8HwOqr z8yd~{c*ztx<>>i4znC`we<4G5pw5;ylBd5Oh17;g-=)WORJ0(;X7L1sB-!ySSFUR| z-c}EL5T7bj7Qk{2HD^zrrE}eB$OCAB9J`A2Drb1}e}Ikzv5laH0y!r&`Q)xB-{uJQ zHi%E`wuG8VJwK^)=hAPP-gj$h`~xeJGvc^twEKz8go!o8tJfUZ;5gLm)YM;tj**l` z?(NYiyPa2FSZAejp_N^?0_b+kC4s6cBIF}pW*ngw2W=4M*OA=acZcutcn$(ZfT><&qIh7VAa6{h3*8R{fn zZuEXjA*otz02_%o{k$fkh6&@jhR+s*)l{SkXa)W4494Ag(h;nnruDAnW#|F23e?|- z0jV#kld9HJ}Ga7Q|$^|+)OB*k03eY zBHZ&Ltu4TL(~hSZjAzG;hfrsNTf=xs=Wsy>%HP^L2RLShQWagbY`*xW?Q*9ry8tJ{2i}QsFhV`pk%7%gIu%Y(oy!$XOp|qTxbjNi^IqDp8_-gIr}Kl)No?$kI@j;PCg_o@`!nXR<}(*9UiL?Hn!T)Iq3444nnKj3AM`6& z2=(f055K%5Sd}&Vk$l{>uV{w4erRv_@mEeusRJx*TWa1jS#NRi+$v(O;YN2RZkf0K zrqxyc{tuzJ4U)K(ymI)x@IaB`xi#rpjVQHVS*e69oB>22p|)>GX!-*ydIV`nw4Mr$ zn4@gT8~NEDPbO`t=55fQIDC%yYP^cEI250HV?w8C&~r6xNWb_r4bD!0R!M}WxE4oS zKtMOX(%E4WM7qjbdIg4?0?H1UZdVrqkG^ zsmE8neBZM>{TaWRT;OCM9Z7mpk_Mq&2VXXhG}_T~s}U9vP`tR5w*cg0eFsws(?P?l z#oG}Kzo))FgUJwSU8sjKI~zj$#3887j9f2k(&@;({#yIc0_vk#rnJr0RBDgjUPU9A zkv9g(m4UYGuMu|zO_#z?Qt5i`>M%&bHzaeo;8=uqsgriFOU~?&e`$A(RoZw>Hq_3P zTjjW-09C7BwTIe8Mef&e!l)_TjJ$#RoQWqEtbayyC)vsDmOjPi;RO!IcGg9w?VfLU zLB^>w%sO(wn#%9(q2>>Z(pFU=OHa2VdW9})=fpgNjwziP-=&*$YyvMk|5^f? z>{PM>R*DNBPuhyo>PTDmngJp8ddchG3O*n8j>)vU^!O?3jU)Gx4%7o_`y@}kY3Zv> zF}!mQJh_vgCR>wOlMP7R+R^4z-H-+DHggEqVnef_dg3m3>Q$=EPo!ai&j?TKG_vge zpqR3axdM!akFbrv?x`cafYj7_m5y-H?=~sEQpJt_T7-F8iHi>M<{sb7z*->Nz{9l8 z63ucdCPlEJ!PEqf`*G3`w)dc)nM(v}ued#6E^^|_$%s=8kn%p5cUqu9`%~DZr4h(E zQ4X2Nkz_^2#=uQ64OzF5rTV@^nH=OFQ~K#4sNj=YSM8Mll!N|&zOl_FP}Ei2%qG<9 zET(|HuLy(IeIMq$R5La?%9j?GcMzJ|D9OKp{BAhK58o#PmiVgjqa1VUC9oGU4j{o@ z(eK}Q>sz789f4$iB;4Z89>%!?v4z%We#QTmYmb(GsXr2m^6xhLdsNw__t`2s>6_JfLEwB`LvsqV6BI`-QRGCRyW;5sGJ9%+j5KPDiZ$Nr{K9 z7&eVYPBFkM-p&F&Gd|F*tkO^%(Bt3~9rj)GOC+!AeP+<{8L+gUO_OHUvX^)^O zd}FhJ?hZ%pT&MeQ$cn`Lp=+|D)5%3wp6>}g-9Z~Cd)ygsKmP!Gs`xtne$Nza&-Pr10M=}X;k74?JABEx8 z&CZ4%<{P(aeTrsTyx;y&xg|Hg+tgf*@Uf=XSYUk`#1A(>nYahQ#L_;qI@tToM?!Fn z-d+}L+WUMmRT<7T%l^GMNwXGP-AzJ37xS5pr)Tr*! zgKYCaWwY=2kAw&aAR!smz7>zI_KKj98E58G+!^9X=bv&FkKT2NgeAI^Iqz^W#(hEv zQX%OLoT)37J7WDan*Z>HchO~#k53v*h9A`W3@B>8FgvCep&V^Txe@P%i#2z0Lq7+< zp6szcmg$>Fv0sjug}xXnLCb-Shp;!@mS^ zFW=hrIlR{Dlj~#6{v0l|i^p%zf6ZUJU zq0*P*na_5)k_X9WvQ2vI7AzI!cKvN5pr?HswloUKxlSMf=1;}Y;c;2xchP`&qW-jsCPo6+deAFRvrCSTI*JmmQb zF+VCxF~1k-Fy}*vF%cyF;Ic9sXP68QjNFA=H!p=VqX){G znL_d8qK=9NrCAq(T#Xnx?Tc%Gf50^Q&PWM$_$xf8Y-3SRTYJ}fZ#WoRHY|T2SW_b@ zh+#;Z3n2NZE>CApOSW)s>RCAtexm~hOZ#pa0;8-yCn_Nn;;V{Xp3$RsKmE8OMwWbO zIbXy5I+&Re8DClVZwTxfv*iC4!8}>++@bXV9Ofw=Y{>l`-FR@*ygc6`h}v0`FLY57 z*6CK?1Hyiics}PX*{5&B5ZqX#CCw)OGl{&7ay3GB5#(^xgI)||8l4hKl-n4U6kkPi zHeW3j+>Uh13*YoVabGoqcNVZ)XX<-Y9guxRZabFyYeCQGRoJ(otzkUk#GIG7jX#fJ zWk_VoIlIaPh8|dqb%(}(T`_VMK4ag;4JGdFIGy6m$&!J6u_n=NqyM(I3+9*;HHRC? zQmp2fl5tltas-~DVV}3eA|cI4gd-Mz+Gpso$tkz$bYjZ|&`k70Aw4JfBUI6^>rC!s z!L%vv`m2El0w(F5f_H*gzap_3AQq>{I&qqZDgMaX-~v-_*M}p$HRU(wUkOQqT%&eZ zeF|PBVi7jjr1v9d=qLAoKkW>Z?mUSz2l`h27td^|#OZ&4rZRNL!~W>aJvs9Sd^z9<7ImTQ+BlI3 z^ErBdMimLC`-&6(vn+5lF*uI8yEzR;n!awFP(ER$T^b#K$pnMJd>Y(66SHbXqV^V@ za!1~ZqMM<_iv}2GenxW}z9$tE|7?l8A^fZ>{E!NXGaoYG@>&~0HINr)`)EjD`lGjY z-Y`W-fY*iYBfmtm6NKmmyl$fDs#+Nc-nDUVgDc`Qz6G~TJ-Hq=>_i{0wusZ%lRh=? z*^vq971~1^l|MZA+a}OHyczblJ*clOPcypj`VTa!pQ$bL@O+d}L&)#Eu1~5O>p#eO+ae!Dxxt}|(5o*W zx)PlY!eQT+l6FZroIkX5z|M?g<1Cq#BTz#N=MF^b8Q9UP*1^e9j`e*9kU6^lrdgcC zM{Eyhy&pXU+(D>mGr%m1GkK)Wdd;%DPk_Um=}T(L*HwfFSo?rpzy#c0zx`)myM`OG zPR)eN3GCEdKyo902@#-Sld~LId$lP_aeCLee*F@--|k@#gh&snr^eCSIzV$)F><+& zhdM${QtnoqY46_OcFRo92B_bI)vI(0Sx1K7hZRfZ_`{#ZDj+lN2-oBtWBz5JwOOTI zLOf5Y3{$zj4=h7<+|SJUm?)D8klErNKwoXtWI7R=1On!MC=z8^|i|G_rF{|{hpvVj2y%dOtGS_#)WN_+GH zj{MO=RDL?w<=4uCVdr$Q2BAbf26bCGD1>E^d~T};Zv%R<(&EDtFO2%aGIK_>r*%C_ zuEOHFW5@IQxm2T^HTCrzBi2xIatzj$X;8 z)pnjepqgYCXJ1eAs@~s`q{sz6X|3N5A!MkU9Hy7W^F&9@Ha; z9u}?5DfujyeRcfyjcy~!5Px&nI?^}`vNUPFckydXf`&`TkN-bgE)eYr+g;D|NOZ{;}zKz+IP*C}14eV%#bx!>IjsxI-Eqp7VWCH4tG7~kr9O<&Uk%U6Q>| z{-v>tEpS>YW8ci0dO7flHose0MUZC?rdZ!hR&L=X8y~YuOM+O0zCTmQN4AP*-Pipb zss*mj93T62akdu=>G2+Xkt{d-gt9TPHA1t!T=1|L5$dVu`fX)P^t~}8c(Dir+y7pj z^5&(|`Xuuer`kI2f51DvCFH+GBJAd+?yP{;DbzIT4mWQXBXRvgBKEf9q|WJ75pbQn zD=F4Z88eNi$r~1crT~24J1t|9+z#oZr{|1O)&($%36uGhQh6sXL%LDbfHxbGvkbz; zG~Q{o{=q!%PKC(o;7mwS!(~+mqBxw%`jmvMYax77?ZR;R(IYe|AZdSUA*uniEq%2mn{Oy0K9+&sVg=VW$gvmO5iaIGA4iQkAXkuz|V_ zv2-b!#g;_~+MXdkXDP$b7jt&D|5wxoId(`k!M<7$0dJy$K5m9Miho3fEzN+i`{7*u z?A^=Ek6=IV>{E|*l>ww4v&WZU_k|U-H(jYE_gh}^e9W!V^KSfbeT5N&VEd!HdK<3q zpWpV(0NM@bIqoYM(ym*nrCPQ?_|ST3!RV~gl!)iB0HO2b^A4wMzB(%V( zMN$G%e*wDDGQ*5Nso72)s0N)jGBO3JT9m!w5RyK+35i;Si)wgJLZdUC(1#2g%yUJ` zy0kSa_0%L`nw2QiUH)cki+HS$J5SGTN`JRCoENwk-!~-0oAfwuV+t)ZFb{loO(3{= z+;2)9u(&C+JE!3n4AnN?Ykn1Z<=2kpR7z#anwSId^$Nz?Ob)x7k3?W;%#=qqjli9iiW*5h;<>f( zleaK&o9$$gLX*DpPvVdi)8}L+#+*Q#@q~h#bVu69+L}ZpN;y$ZLo3vT?WYiX*dgye zEC1eM6vs#U=x424QbUxT1Flc}hvS}g^<>iInElLq>)icsR@6kUF#EERyi>;ZP&N!KKYC$Di|>fh z^QEX4?$tu!BQ0z`tmgKZ(V@WN^aVp*koFa~6NVoG3(C^m@7iqC_Z$N`OaEiB-Qv%M zqW5ZF_%cr76#i`MN8~!by`#QFp|~s9qNY}#aC=)bYR|2CQgi4CKlPombL=G06g5G% z5XMzXOW_5ItI=~JS)6jqQgc;qQAhcrMzfH z03+i=)>U&|Q@R{|!XEzAZX+7Pt6xxApnm>RqO92R%=_YVl>F`|cSZ-7g)@;MD>aT@ z7Y%iy<)-A2ZfExQ5)?33zow#AB;imoW`M|Pco2pP$ovsAmjwk#`S_m+YhI_WP9ap; zR2AN7F<3>drMVTyQPmQ+Wx`{#XTFLa0%$|gTEKlz{Il7$MD-~(X~V(>f_kn84oZ*eC6zB_mY-- zXrizG!#tQJ5?tw1d`jpG6qlql7wlHJ4Ca|mnSK|^jK^L$Xs@gV#Se3eAC2z%FgTXO zKwQ}!_+`x!Brw{1XzEsUR^k@!Xx$FTF+545)<@t2a=iK-(@)qf*X-8q0)5TSdjhS# z^Fbb7XsrS=>JDqzdjt@d2F?GfZdnntVqMJ%MhFSe;wh;J=|u0^2O`RowDZ9Emx}!n zad@q|4;8Nq$UQKk&bKXXGyNrtSg*~+GNEHJ)X38(V?AzBnv@Bg2YsnK?lE7+3G=kN zcK4bVoEv$$=H*hw>*!iKDpN-uFXzE8BA-G#^~idXZG>1jN-V!4(0rAzaT3tKd+%!S zQ+TaK#7RLt{bQ=IT3pYBYszlg&Buee9<0117XIUkMUxcV4rG+yc&{MEEkpFeww|It zhv>QnQlTAAj|M*JOT;#dy=r$;#sTc!6(3ZJj9Z#2X}|NP6K9eyVjOzy}Z5pC5BIC)j{a*Lgv=(%FUDvnV0*fpNGT5fMw?4<)rSC)R zSohb2F$d?f-(teTl+D0!ao6RCol#9APo$-6!qkht@NgE_QM0Sb-ib)W6A;db^W4^1 zU`UDuU42nd$)5o*&Ua1mw%0~W?!*T(os4u}KIPfbr?xRPH-vvM3sCB{YCk1Y=}pjLz0use z_u|XHk|7-xWbt3&3^7_Lp+Yd?J}w7$2F2wj(*Afa>>Z^w#0{wPe;z0pAD?f zKgGei5*cUCY|V&+C7+#8Vebj_Or20BDctz&FH#1BpN3p+P>{Ohkbl(mrOBD)73~aZ zTt#<9lEC--#SnDv@$=2GpK7{G3asx*85BvbGZ&Q|!PEa`+AJVb7ZdKxnX(iuWHs(n zpV>F%-Hh$YPRzN`9?Gu^TzN#h{GlG5v!xco6Nylt&m${3?FlZn tnOg&jC^Z|!3v1(!VfX!RuC1g}bNW=SvHI+@75?2E#I|dps{Z%<{{hE9bb9~* literal 0 HcmV?d00001 diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 426dfc81c7..738c41210f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -562,7 +562,7 @@ bool GUI_App::on_init_inner() wxInitAllImageHandlers(); wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); - wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); + wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG); DecorateSplashScreen(bmp); From 7270d222dfe410eefda01bcfab487d9b28c2e9f0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Sep 2020 12:10:07 +0200 Subject: [PATCH 434/826] Fix build on OsX --- src/slic3r/GUI/GUI_App.cpp | 4 ++++ src/slic3r/GUI/MainFrame.cpp | 37 +++++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 738c41210f..65c0bd878d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -562,7 +562,11 @@ bool GUI_App::on_init_inner() wxInitAllImageHandlers(); wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG); +#else + wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION DecorateSplashScreen(bmp); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 853d9a6d75..5a140f9a9c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -102,17 +102,17 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S } #else #if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - switch (wxGetApp().get_mode()) + switch (wxGetApp().get_app_mode()) { default: - case GUI_App::EMode::Editor: + case GUI_App::EAppMode::Editor: { #endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); #if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION break; } - case GUI_App::EMode::GCodeViewer: + case GUI_App::EAppMode::GCodeViewer: { SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG)); break; @@ -1355,13 +1355,13 @@ void MainFrame::init_menubar() #endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else auto menubar = new wxMenuBar(); - menubar->Append(fileMenu, _(L("&File"))); - if (editMenu) menubar->Append(editMenu, _(L("&Edit"))); - menubar->Append(windowMenu, _(L("&Window"))); - if (viewMenu) menubar->Append(viewMenu, _(L("&View"))); + menubar->Append(fileMenu, _L("&File")); + if (editMenu) menubar->Append(editMenu, _L("&Edit")); + menubar->Append(windowMenu, _L("&Window")); + if (viewMenu) menubar->Append(viewMenu, _L("&View")); // Add additional menus from C++ wxGetApp().add_config_menu(menubar); - menubar->Append(helpMenu, _(L("&Help"))); + menubar->Append(helpMenu, _L("&Help")); SetMenuBar(menubar); #endif // ENABLE_GCODE_VIEWER @@ -1369,7 +1369,11 @@ void MainFrame::init_menubar() // This fixes a bug on Mac OS where the quit command doesn't emit window close events // wx bug: https://trac.wxwidgets.org/ticket/18328 #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + wxMenu* apple_menu = menubar->OSXGetAppleMenu(); +#else wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu(); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else wxMenu *apple_menu = menubar->OSXGetAppleMenu(); #endif // ENABLE_GCODE_VIEWER @@ -1378,7 +1382,7 @@ void MainFrame::init_menubar() Close(); }, wxID_EXIT); } -#endif +#endif // __APPLE__ if (plater()->printer_technology() == ptSLA) update_menubar(); @@ -1429,6 +1433,21 @@ void MainFrame::init_menubar_as_gcodeviewer() m_gcodeviewer_menubar->Append(viewMenu, _L("&View")); m_gcodeviewer_menubar->Append(helpMenu, _L("&Help")); #endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + +#ifdef __APPLE__ + // This fixes a bug on Mac OS where the quit command doesn't emit window close events + // wx bug: https://trac.wxwidgets.org/ticket/18328 +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + wxMenu* apple_menu = menubar->OSXGetAppleMenu(); +#else + wxMenu* apple_menu = m_gcodeviewer_menubar->OSXGetAppleMenu(); +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (apple_menu != nullptr) { + apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent&) { + Close(); + }, wxID_EXIT); + } +#endif // __APPLE__ } #if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION From f6534f5f7a3cdc41d005212278d9d89604c116d4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Sep 2020 12:36:57 +0200 Subject: [PATCH 435/826] Follow-up of 7270d222dfe410eefda01bcfab487d9b28c2e9f0 -> Fix of build on OsX and Linux --- src/slic3r/GUI/MainFrame.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 5a140f9a9c..151648740d 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1370,7 +1370,7 @@ void MainFrame::init_menubar() // wx bug: https://trac.wxwidgets.org/ticket/18328 #if ENABLE_GCODE_VIEWER #if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - wxMenu* apple_menu = menubar->OSXGetAppleMenu(); + wxMenu* apple_menu = m_menubar->OSXGetAppleMenu(); #else wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu(); #endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION @@ -1438,7 +1438,7 @@ void MainFrame::init_menubar_as_gcodeviewer() // This fixes a bug on Mac OS where the quit command doesn't emit window close events // wx bug: https://trac.wxwidgets.org/ticket/18328 #if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - wxMenu* apple_menu = menubar->OSXGetAppleMenu(); + wxMenu* apple_menu = m_menubar->OSXGetAppleMenu(); #else wxMenu* apple_menu = m_gcodeviewer_menubar->OSXGetAppleMenu(); #endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION From 0f64b67ffa9f88055150b59c6314ec7d15377963 Mon Sep 17 00:00:00 2001 From: test Date: Tue, 8 Sep 2020 12:39:11 +0200 Subject: [PATCH 436/826] osx fix --- src/slic3r/Utils/Process.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 2301cd2504..fa5ecb1f07 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -18,6 +18,7 @@ // Fails to compile on Windows on the build server. #ifdef __APPLE__ #include + #include #endif #include @@ -57,7 +58,10 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance // On Apple the wxExecute fails, thus we use boost::process instead. BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; try { - path_to_open ? boost::process::spawn(bin_path, into_u8(*path_to_open)) : boost::process::spawn(bin_path, boost::process::args()); + std::vector args; + if (path_to_open) + args.emplace_back(into_u8(*path_to_open)); + boost::process::spawn(bin_path, args); } catch (const std::exception &ex) { BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); } From 946f51467fec8c90f7b85ae1d943f672f2c3d9bc Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 13:33:43 +0200 Subject: [PATCH 437/826] WIP Standalone G-code viewer --- src/CMakeLists.txt | 42 ++++++++---- src/PrusaSlicer.cpp | 11 ++- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/MainFrame.cpp | 25 ++----- src/slic3r/Utils/Process.cpp | 125 +++++++++++++++++++++++++++++++++++ src/slic3r/Utils/Process.hpp | 19 ++++++ src/slic3r/Utils/Thread.hpp | 6 +- 7 files changed, 192 insertions(+), 38 deletions(-) create mode 100644 src/slic3r/Utils/Process.cpp create mode 100644 src/slic3r/Utils/Process.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0b0b3c0eef..ca57ca5531 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,9 +106,9 @@ if (MINGW) set_target_properties(PrusaSlicer PROPERTIES PREFIX "") endif (MINGW) -if (NOT WIN32) - # Binary name on unix like systems (OSX, Linux) - set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") +if (NOT WIN32 AND NOT APPLE) + # Binary name on unix like systems (Linux, Unix) + set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () target_link_libraries(PrusaSlicer libslic3r cereal) @@ -209,20 +209,34 @@ if (WIN32) add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer) prusaslicer_copy_dlls(PrusaSlicerDllsCopy) -elseif (XCODE) - # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level - add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources" - COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) else () + if (APPLE) + # On OSX, the name of the binary matches the name of the Application. + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf PrusaSlicer prusa-slicer + COMMAND ln -sf PrusaSlicer prusa-gcodeviewer + COMMAND ln -sf PrusaSlicer PrusaGCodeViewer + WORKING_DIRECTORY "$" + COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer" + VERBATIM) + else () + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf prusa-slicer prusa-gcodeviewer + WORKING_DIRECTORY "$" + COMMENT "Symlinking the G-code viewer to PrusaSlicer" + VERBATIM) + endif () + if (XCODE) + # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") + else () + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources") + endif () add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources" + COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}" COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) -endif() + VERBATIM) +endif () # Slic3r binary install target if (WIN32) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2962f0cdfe..94996dc928 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -101,8 +102,14 @@ int CLI::run(int argc, char **argv) std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); - bool start_as_gcodeviewer = false; - + bool start_as_gcodeviewer = +#ifdef _WIN32 + false; +#else + // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. + boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); +#endif // _WIN32 + const std::vector &load_configs = m_config.option("load", true)->values; // load config files supplied via --load diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 5681ed66db..1c30078102 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -195,6 +195,8 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp + Utils/Process.cpp + Utils/Process.hpp Utils/Profile.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f6fd939e25..f4d7f03eca 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -9,7 +9,6 @@ #include //#include #include -#include #include #include @@ -31,6 +30,7 @@ #include "I18N.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" +#include "../Utils/Process.hpp" #include #include "GUI_App.hpp" @@ -40,12 +40,6 @@ #include #endif // _WIN32 -// For starting another PrusaSlicer instance on OSX. -// Fails to compile on Windows on the build server. -#ifdef __APPLE__ - #include -#endif - namespace Slic3r { namespace GUI { @@ -1054,8 +1048,8 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); -#if ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); +#if ENABLE_GCODE_VIEWER append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), [this](wxCommandEvent&) { if (m_plater->model().objects.empty() || @@ -1064,6 +1058,8 @@ void MainFrame::init_menubar() set_mode(EMode::GCodeViewer); }, "", nullptr); #endif // ENABLE_GCODE_VIEWER + append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview") + dots, _L("Open G-code viewer"), + [this](wxCommandEvent&) { start_new_gcodeviewer_open_file(this); }, "", nullptr); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); @@ -1180,20 +1176,11 @@ void MainFrame::init_menubar() windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), - [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, [this]() {return true; }, this); windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _(L("Open new instance")) + "\tCtrl+I", _(L("Open a new PrusaSlicer instance")), - [this](wxCommandEvent&) { - wxString path = wxStandardPaths::Get().GetExecutablePath(); -#ifdef __APPLE__ - boost::process::spawn((const char*)path.c_str()); -#else - wxExecute(wxStandardPaths::Get().GetExecutablePath(), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); -#endif - }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr); } // View menu diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp new file mode 100644 index 0000000000..2301cd2504 --- /dev/null +++ b/src/slic3r/Utils/Process.cpp @@ -0,0 +1,125 @@ +#include "Process.hpp" + +#include + +#include "../GUI/GUI.hpp" +// for file_wildcards() +#include "../GUI/GUI_App.hpp" +// localization +#include "../GUI/I18N.hpp" + +#include +#include + +#include +#include + +// For starting another PrusaSlicer instance on OSX. +// Fails to compile on Windows on the build server. +#ifdef __APPLE__ + #include +#endif + +#include + +namespace Slic3r { +namespace GUI { + +enum class NewSlicerInstanceType { + Slicer, + GCodeViewer +}; + +// Start a new Slicer process instance either in a Slicer mode or in a G-code mode. +// Optionally load a 3MF, STL or a G-code on start. +static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance_type, const wxString *path_to_open) +{ +#ifdef _WIN32 + wxString path; + wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE); + path += "\\"; + path += (instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer.exe" : "prusa-gcodeviewer.exe"; + std::vector args; + args.reserve(3); + args.emplace_back(path.wc_str()); + if (path_to_open != nullptr) + args.emplace_back(path_to_open->wc_str()); + args.emplace_back(nullptr); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << to_u8(path) << "\""; + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << to_u8(path); +#else + // Own executable path. + boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); + #if defined(__APPLE__) + { + bin_path = bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"); + // On Apple the wxExecute fails, thus we use boost::process instead. + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; + try { + path_to_open ? boost::process::spawn(bin_path, into_u8(*path_to_open)) : boost::process::spawn(bin_path, boost::process::args()); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); + } + } + #else // Linux or Unix + { + std::vector args; + args.reserve(3); + #ifdef __linux + static const char *gcodeviewer_param = "--gcodeviewer"; + { + // If executed by an AppImage, start the AppImage, not the main process. + // see https://docs.appimage.org/packaging-guide/environment-variables.html#id2 + const char *appimage_binary = std::getenv("APPIMAGE"); + if (appimage_binary) { + args.emplace_back(appimage_binary); + if (instance_type == NewSlicerInstanceType::GCodeViewer) + args.emplace_back(gcodeviewer_param); + } + } + #endif // __linux + std::string my_path; + if (args.empty()) { + // Binary path was not set to the AppImage in the Linux specific block above, call the application directly. + my_path = (bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer" : "prusa-gcodeviewer")).string(); + args.emplace_back(my_path.c_str()); + } + std::string to_open; + if (path_to_open) { + to_open = into_u8(*path_to_open); + args.emplace_back(to_open.c_str()); + } + args.emplace_back(nullptr); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << args[0] << "\""; + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << args[0]; + } + #endif // Linux or Unix +#endif // Win32 +} + +void start_new_slicer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, path_to_open); +} + +void start_new_gcodeviewer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::GCodeViewer, path_to_open); +} + +void start_new_gcodeviewer_open_file(wxWindow *parent) +{ + wxFileDialog dialog(parent ? parent : wxGetApp().GetTopWindow(), + _L("Open G-code file:"), + from_u8(wxGetApp().app_config->get_last_dir()), wxString(), + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() == wxID_OK) { + wxString path = dialog.GetPath(); + start_new_gcodeviewer(&path); + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/Utils/Process.hpp b/src/slic3r/Utils/Process.hpp new file mode 100644 index 0000000000..c6acaa643e --- /dev/null +++ b/src/slic3r/Utils/Process.hpp @@ -0,0 +1,19 @@ +#ifndef GUI_PROCESS_HPP +#define GUI_PROCESS_HPP + +class wxWindow; + +namespace Slic3r { +namespace GUI { + +// Start a new slicer instance, optionally with a file to open. +void start_new_slicer(const wxString *path_to_open = nullptr); +// Start a new G-code viewer instance, optionally with a file to open. +void start_new_gcodeviewer(const wxString *path_to_open = nullptr); +// Open a file dialog, ask the user to select a new G-code to open, start a new G-code viewer. +void start_new_gcodeviewer_open_file(wxWindow *parent = nullptr); + +} // namespace GUI +} // namespace Slic3r + +#endif // GUI_PROCESS_HPP diff --git a/src/slic3r/Utils/Thread.hpp b/src/slic3r/Utils/Thread.hpp index e9c76d2aba..194971c9eb 100644 --- a/src/slic3r/Utils/Thread.hpp +++ b/src/slic3r/Utils/Thread.hpp @@ -1,5 +1,5 @@ -#ifndef THREAD_HPP -#define THREAD_HPP +#ifndef GUI_THREAD_HPP +#define GUI_THREAD_HPP #include #include @@ -25,4 +25,4 @@ template inline boost::thread create_thread(Fn &&fn) } -#endif // THREAD_HPP +#endif // GUI_THREAD_HPP From 9ce3086f0209fdea07285635b50aceb79b49602a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Sep 2020 13:40:14 +0200 Subject: [PATCH 438/826] Splash screen : Try to fix scaling on Linux --- src/slic3r/GUI/GUI_App.cpp | 51 ++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c16aeaada5..1e0d4ef859 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -78,23 +78,33 @@ namespace GUI { class MainFrame; // ysFIXME -static int get_dpi_for_main_display() +static float get_scale_for_main_display() { wxFrame fr(nullptr, wxID_ANY, wxEmptyString); - return get_dpi_for_window(&fr); + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) + int dpi = get_dpi_for_window(&fr); + float sf = dpi != DPI_DEFAULT ? sf = (float)dpi / DPI_DEFAULT : 1.0; +#else + printf("dpi = %d\n", get_dpi_for_window(&fr)); + // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. + float sf = 0.1 * std::max(10, fr.GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + printf("scale factor = %f\n", sf); + return sf; } // scale input bitmap and return scale factor static float scale_bitmap(wxBitmap& bmp) { - int dpi = get_dpi_for_main_display(); - float sf = 1.0; + float sf = get_scale_for_main_display(); + // scale bitmap if needed - if (dpi != DPI_DEFAULT) { + if (sf > 1.0) { wxImage image = bmp.ConvertToImage(); if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) { - sf = (float)dpi / DPI_DEFAULT; int width = int(sf * image.GetWidth()); int height = int(sf * image.GetHeight()); image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); @@ -102,11 +112,13 @@ static float scale_bitmap(wxBitmap& bmp) bmp = wxBitmap(std::move(image)); } } + return sf; } -static void word_wrap_string(wxString& input, int line_len) +static void word_wrap_string(wxString& input, int line_px_len, float scalef) { + int line_len = std::roundf( (float)line_px_len / (scalef * 10)) + 10; int idx = -1; int cur_len = 0; for (size_t i = 0; i < input.Len(); i++) @@ -136,11 +148,12 @@ static void DecorateSplashScreen(wxBitmap& bmp) wxMemoryDC memDc(bmp); // draw an dark grey box at the left of the splashscreen. - // this box will be 2/9 of the weight of the bitmap, and be at the left. - const wxRect bannerRect(wxPoint(0, (bmp.GetHeight() / 9) * 2 - 2), wxPoint((bmp.GetWidth() / 5) * 2, bmp.GetHeight())); + // this box will be 2/5 of the weight of the bitmap, and be at the left. + int banner_width = (bmp.GetWidth() / 5) * 2 - 2; + const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight())); wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); - memDc.DrawRectangle(bannerRect); + memDc.DrawRectangle(banner_rect); // title wxString title_string = SLIC3R_APP_NAME; @@ -156,14 +169,15 @@ static void DecorateSplashScreen(wxBitmap& bmp) wxString cr_symbol = wxString::FromUTF8("\xc2\xa9"); wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" "%s 2011-2018 Alessandro Ranellucci.", - cr_symbol, year.Mid(year.Length() - 4), cr_symbol); + cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n"; wxFont copyright_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Larger(); - copyright_string += "Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + + copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others."); - word_wrap_string(copyright_string, 50); +// word_wrap_string(copyright_string, 50); + word_wrap_string(copyright_string, banner_width, scale_factor); wxCoord margin = int(scale_factor * 20); @@ -171,13 +185,13 @@ static void DecorateSplashScreen(wxBitmap& bmp) memDc.SetTextForeground(wxColour(237, 107, 33)); memDc.SetFont(title_font); - memDc.DrawLabel(title_string, bannerRect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT); + memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT); memDc.SetFont(version_font); - memDc.DrawLabel(version_string, bannerRect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); + memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); memDc.SetFont(copyright_font); - memDc.DrawLabel(copyright_string, bannerRect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT); + memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT); } class SplashScreen : public wxSplashScreen @@ -189,9 +203,10 @@ public: wxASSERT(bitmap.IsOk()); m_main_bitmap = bitmap; - int dpi = get_dpi_for_main_display(); +/* int dpi = get_dpi_for_main_display(); if (dpi != DPI_DEFAULT) - m_scale_factor = (float)dpi / DPI_DEFAULT; + m_scale_factor = (float)dpi / DPI_DEFAULT; */ + m_scale_factor = get_scale_for_main_display(); } void SetText(const wxString& text) From 844f62af66dd93a342827879fda6cb036003025a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Sep 2020 14:01:32 +0200 Subject: [PATCH 439/826] Cleanup toolpaths when changing printer to SLA --- src/slic3r/GUI/Plater.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 45a1f6ea82..3a4c31ebd7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2811,7 +2811,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool if (this->preview != nullptr) { // If the preview is not visible, the following line just invalidates the preview, // but the G-code paths or SLA preview are calculated first once the preview is made visible. - this->preview->get_canvas3d()->reset_gcode_toolpaths(); + reset_gcode_toolpaths(); this->preview->reload_print(); } #else @@ -5384,6 +5384,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->set_printer_technology(config.opt_enum(opt_key)); // print technology is changed, so we should to update a search list p->sidebar->update_searcher(); + p->reset_gcode_toolpaths(); } else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { bed_shape_changed = true; From ceaa61071a1b5315caa6b9b1677b7c13f50d6aea Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 14:07:47 +0200 Subject: [PATCH 440/826] Fix of the previous merge, Windows specific. --- src/slic3r/Utils/Process.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 96f5062070..b81a748277 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -49,9 +49,9 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance if (path_to_open != nullptr) args.emplace_back(path_to_open->wc_str()); args.emplace_back(nullptr); - BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << to_u8(path) << "\""; + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << into_u8(path) << "\""; if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) - BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << to_u8(path); + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << into_u8(path); #else // Own executable path. boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); From 0a4debc98c0499a2aa8cfa1940bc042c482fa323 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 14:25:10 +0200 Subject: [PATCH 441/826] Fix of a preceding merge --- src/slic3r/Utils/Process.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index b81a748277..561971b139 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -18,10 +18,7 @@ // Fails to compile on Windows on the build server. #ifdef __APPLE__ #include -<<<<<<< HEAD -======= #include ->>>>>>> vb_gcodeviewer_menu #endif #include From 07499ff9d099a2f2a3b2da5e6dfc8e7dba6f8f05 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Sep 2020 15:15:00 +0200 Subject: [PATCH 442/826] Fixed Scale on Linux --- src/slic3r/GUI/GUI_App.cpp | 13 +++++++------ src/slic3r/GUI/GUI_Utils.hpp | 7 +++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 1e0d4ef859..7b6d8c5aa8 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -77,9 +77,11 @@ namespace GUI { class MainFrame; -// ysFIXME static float get_scale_for_main_display() { + // ysFIXME : Workaround : + // wxFrame is created on the main monitor, so we can take a scale factor from this one + // before The Application and the Mainframe are created wxFrame fr(nullptr, wxID_ANY, wxEmptyString); #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) @@ -118,7 +120,9 @@ static float scale_bitmap(wxBitmap& bmp) static void word_wrap_string(wxString& input, int line_px_len, float scalef) { + // calculate count od symbols in one line according to the scale int line_len = std::roundf( (float)line_px_len / (scalef * 10)) + 10; + int idx = -1; int cur_len = 0; for (size_t i = 0; i < input.Len(); i++) @@ -176,7 +180,6 @@ static void DecorateSplashScreen(wxBitmap& bmp) _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others."); -// word_wrap_string(copyright_string, 50); word_wrap_string(copyright_string, banner_width, scale_factor); wxCoord margin = int(scale_factor * 20); @@ -198,14 +201,12 @@ class SplashScreen : public wxSplashScreen { public: SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxWindow* parent) - : wxSplashScreen(bitmap, splashStyle, milliseconds, parent, wxID_ANY) + : wxSplashScreen(bitmap, splashStyle, milliseconds, parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR) { wxASSERT(bitmap.IsOk()); m_main_bitmap = bitmap; -/* int dpi = get_dpi_for_main_display(); - if (dpi != DPI_DEFAULT) - m_scale_factor = (float)dpi / DPI_DEFAULT; */ m_scale_factor = get_scale_for_main_display(); } diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index f29e0cd848..1c88de5707 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -92,10 +92,13 @@ public: #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList this->SetFont(m_normal_font); #endif - // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. -#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + // Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3). + // So, calculate the m_em_unit value from the font size, as before +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) m_em_unit = std::max(10, 10.0f * m_scale_factor); #else + // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT From a13b732f278ad5a7afda879a5ad617648d1a85de Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 8 Sep 2020 15:30:01 +0200 Subject: [PATCH 443/826] Fixed loading current presets --- src/slic3r/GUI/GUI_App.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 65c0bd878d..612b44d5b0 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -692,9 +692,9 @@ bool GUI_App::on_init_inner() // ensure the selected technology is ptFFF plater_->set_printer_technology(ptFFF); } -#else - load_current_presets(); + else #endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + load_current_presets(); mainframe->Show(true); /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: From ce06fc6cb7bc09df8fcc18b006b780f9f16da9d0 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 8 Sep 2020 15:30:59 +0200 Subject: [PATCH 444/826] Added networking support for SL1 Digest authorization. Renamed login/password/authorization_type to printhost_user/printhost_password/printhost_authorization_type. Added initialization of physical printer preset with default values. --- src/libslic3r/Preset.cpp | 34 +++++++++++++++--------- src/libslic3r/Preset.hpp | 17 ++++++------ src/libslic3r/PrintConfig.cpp | 8 +++--- src/slic3r/GUI/Field.cpp | 2 +- src/slic3r/GUI/GUI.cpp | 2 +- src/slic3r/GUI/OptionsGroup.cpp | 2 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 24 +++++++++-------- src/slic3r/Utils/Http.cpp | 10 +++++++ src/slic3r/Utils/Http.hpp | 2 ++ src/slic3r/Utils/OctoPrint.cpp | 23 ++++++++++++++++ src/slic3r/Utils/OctoPrint.hpp | 15 +++++++++-- 11 files changed, 98 insertions(+), 41 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7aaa96c8cf..284c37435a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1365,9 +1365,10 @@ const std::vector& PhysicalPrinter::printer_options() "print_host", "printhost_apikey", "printhost_cafile", - "authorization_type", - "login", - "password" + "printhost_authorization_type", + // HTTP digest authentization (RFC 2617) + "printhost_user", + "printhost_password" }; } return s_opts; @@ -1412,11 +1413,11 @@ const std::set& PhysicalPrinter::get_preset_names() const bool PhysicalPrinter::has_empty_config() const { - return config.opt_string("print_host" ).empty() && - config.opt_string("printhost_apikey").empty() && - config.opt_string("printhost_cafile").empty() && - config.opt_string("login" ).empty() && - config.opt_string("password" ).empty(); + return config.opt_string("print_host" ).empty() && + config.opt_string("printhost_apikey" ).empty() && + config.opt_string("printhost_cafile" ).empty() && + config.opt_string("printhost_user" ).empty() && + config.opt_string("printhost_password").empty(); } void PhysicalPrinter::update_preset_names_in_config() @@ -1441,7 +1442,7 @@ void PhysicalPrinter::save(const std::string& file_name_from, const std::string& void PhysicalPrinter::update_from_preset(const Preset& preset) { - config.apply_only(preset.config, printer_options(), false); + config.apply_only(preset.config, printer_options(), true); // add preset names to the options list auto ret = preset_names.emplace(preset.name); update_preset_names_in_config(); @@ -1476,8 +1477,8 @@ bool PhysicalPrinter::delete_preset(const std::string& preset_name) return preset_names.erase(preset_name) > 0; } -PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : - name(name) +PhysicalPrinter::PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset) : + name(name), config(default_config) { update_from_preset(preset); } @@ -1514,6 +1515,13 @@ std::string PhysicalPrinter::get_preset_name(std::string name) PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector& keys) { + // Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options(). + for (const std::string &key : keys) { + const ConfigOptionDef *opt = print_config_def.get(key); + assert(opt); + assert(opt->default_value); + m_default_config.set_key_value(key, opt->default_value->clone()); + } } // Load all printers found in dir_path. @@ -1539,7 +1547,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const continue; } try { - PhysicalPrinter printer(name); + PhysicalPrinter printer(name, this->default_config()); printer.file = dir_entry.path().string(); // Load the preset file, apply preset values on top of defaults. try { @@ -1590,7 +1598,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti new_printer_name = (boost::format("Printer %1%") % ++cnt).str(); // create new printer from this preset - PhysicalPrinter printer(new_printer_name, preset); + PhysicalPrinter printer(new_printer_name, this->default_config(), preset); printer.loaded = true; save_printer(printer); } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 30edfc859a..ec11d59fae 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -460,8 +460,7 @@ private: // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. std::deque::iterator find_preset_internal(const std::string &name) { - Preset key(m_type, name); - auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); + auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(), [&name](const auto& l) { return l.name < name; }); if (it == m_presets.end() || it->name != name) { // Preset has not been not found in the sorted list of non-default presets. Try the defaults. for (size_t i = 0; i < m_num_default_presets; ++ i) @@ -539,9 +538,8 @@ namespace PresetUtils { class PhysicalPrinter { public: - PhysicalPrinter() {} - PhysicalPrinter(const std::string& name) : name(name){} - PhysicalPrinter(const std::string& name, const Preset& preset); + PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config) : name(name), config(default_config) {} + PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset); void set_name(const std::string &name); // Name of the Physical Printer, usually derived form the file name. @@ -698,6 +696,8 @@ public: // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string& new_name) const; + const DynamicPrintConfig& default_config() const { return m_default_config; } + private: PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); @@ -707,9 +707,7 @@ private: // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. std::deque::iterator find_printer_internal(const std::string& name) { - PhysicalPrinter printer(name); - auto it = std::lower_bound(m_printers.begin(), m_printers.end(), printer); - return it; + return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; }); } std::deque::const_iterator find_printer_internal(const std::string& name) const { @@ -723,6 +721,9 @@ private: // so that the addresses of the presets don't change during resizing of the container. std::deque m_printers; + // Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options(). + DynamicPrintConfig m_default_config; + // Selected printer. size_t m_idx_selected = size_t(-1); // The name of the preset which is currently select for this printer diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 770983ad59..239969a1d2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -133,13 +133,13 @@ void PrintConfigDef::init_common_params() // Options used by physical printers - def = this->add("login", coString); - def->label = L("Login"); + def = this->add("printhost_user", coString); + def->label = L("User"); // def->tooltip = L(""); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - def = this->add("password", coString); + def = this->add("printhost_password", coString); def->label = L("Password"); // def->tooltip = L(""); def->mode = comAdvanced; @@ -151,7 +151,7 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - def = this->add("authorization_type", coEnum); + def = this->add("printhost_authorization_type", coEnum); def->label = L("Authorization Type"); // def->tooltip = L(""); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 9cb3d726de..a21826205b 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1080,7 +1080,7 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("support_pillar_connection_mode") == 0) m_value = static_cast(ret_enum); - else if (m_opt_id == "authorization_type") + else if (m_opt_id == "printhost_authorization_type") m_value = static_cast(ret_enum); } else if (m_opt.gui_type == "f_enum_open") { diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 913716dfd7..6c76b62272 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -194,7 +194,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if(opt_key.compare("support_pillar_connection_mode") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if(opt_key == "authorization_type") + else if(opt_key == "printhost_authorization_type") config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); } break; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index cc10d815fc..dd00b3d688 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -755,7 +755,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config else if (opt_key == "support_pillar_connection_mode") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key == "authorization_type") { + else if (opt_key == "printhost_authorization_type") { ret = static_cast(config.option>(opt_key)->value); } } diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 12d1cd2871..ca2d625561 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -155,8 +155,9 @@ void PresetForPrinter::msw_rescale() // PhysicalPrinterDialog //------------------------------------------ -PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) - : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : + DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -186,7 +187,8 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); if (!printer) { const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - printer = new PhysicalPrinter(into_u8(printer_name), preset); + //FIXME Vojtech: WTF??? Memory leak? + printer = new PhysicalPrinter(into_u8(printer_name), m_printer.config, preset); // if printer_name is empty it means that new printer is created, so enable all items in the preset list m_presets.emplace_back(new PresetForPrinter(this, preset.name)); } @@ -249,7 +251,7 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog() void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "authorization_type") + if (opt_key == "printhost_authorization_type") this->update(); }; @@ -308,7 +310,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr host_line.append_widget(print_host_test); m_optgroup->append_line(host_line); - m_optgroup->append_single_option_line("authorization_type"); + m_optgroup->append_single_option_line("printhost_authorization_type"); option = m_optgroup->get_option("printhost_apikey"); option.opt.width = Field::def_width_wider(); @@ -368,7 +370,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_line(line); } - for (const std::string& opt_key : std::vector{ "login", "password" }) { + for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) { option = m_optgroup->get_option(opt_key); option.opt.width = Field::def_width_wider(); m_optgroup->append_single_option_line(option); @@ -385,20 +387,20 @@ void PhysicalPrinterDialog::update() // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) if (tech == ptFFF) { m_optgroup->show_field("host_type"); - m_optgroup->hide_field("authorization_type"); - for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->hide_field("printhost_authorization_type"); + for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) m_optgroup->hide_field(opt_key); } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); m_optgroup->hide_field("host_type"); - m_optgroup->show_field("authorization_type"); + m_optgroup->show_field("printhost_authorization_type"); - AuthorizationType auth_type = m_config->option>("authorization_type")->value; + AuthorizationType auth_type = m_config->option>("printhost_authorization_type")->value; m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); - for (const std::string& opt_key : std::vector{ "login", "password" }) + for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); } diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index a16aac5b5b..e55c21fe17 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -415,6 +415,16 @@ Http& Http::remove_header(std::string name) return *this; } +// Authorization by HTTP digest, based on RFC2617. +Http& Http::auth_digest(const std::string &user, const std::string &password) +{ + curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str()); + curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str()); + curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + + return *this; +} + Http& Http::ca_file(const std::string &name) { if (p && priv::ca_file_supported(p->curl)) { diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index f162362796..ae3660fbfe 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -64,6 +64,8 @@ public: Http& header(std::string name, const std::string &value); // Removes a header field. Http& remove_header(std::string name); + // Authorization by HTTP digest, based on RFC2617. + Http& auth_digest(const std::string &user, const std::string &password); // Sets a CA certificate file for usage with HTTPS. This is only supported on some backends, // specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store. // See also ca_file_supported(). diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index ee87f822fa..82d8a6bb63 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -170,6 +170,13 @@ std::string OctoPrint::make_url(const std::string &path) const } } +SL1Host::SL1Host(DynamicPrintConfig *config) : + OctoPrint(config), + authorization_type(dynamic_cast*>(config->option("printhost_authorization_type"))->value), + username(config->opt_string("printhost_user")), + password(config->opt_string("printhost_password")) +{ +} // SL1Host const char* SL1Host::get_name() const { return "SL1Host"; } @@ -191,4 +198,20 @@ bool SL1Host::validate_version_text(const boost::optional &version_ return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false; } +void SL1Host::set_auth(Http &http) const +{ + switch (authorization_type) { + case atKeyPassword: + http.header("X-Api-Key", get_apikey()); + break; + case atUserPassword: + http.auth_digest(username, password); + break; + } + + if (! get_cafile().empty()) { + http.ca_file(get_cafile()); + } +} + } diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 965019d859..4f8e4819fc 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -29,6 +29,8 @@ public: bool can_test() const override { return true; } bool can_start_print() const override { return true; } std::string get_host() const override { return host; } + const std::string& get_apikey() const { return apikey; } + const std::string& get_cafile() const { return cafile; } protected: virtual bool validate_version_text(const boost::optional &version_text) const; @@ -38,14 +40,14 @@ private: std::string apikey; std::string cafile; - void set_auth(Http &http) const; + virtual void set_auth(Http &http) const; std::string make_url(const std::string &path) const; }; class SL1Host: public OctoPrint { public: - SL1Host(DynamicPrintConfig *config) : OctoPrint(config) {} + SL1Host(DynamicPrintConfig *config); ~SL1Host() override = default; const char* get_name() const override; @@ -56,6 +58,15 @@ public: protected: bool validate_version_text(const boost::optional &version_text) const override; + +private: + void set_auth(Http &http) const override; + + // Host authorization type. + AuthorizationType authorization_type; + // username and password for HTTP Digest Authentization (RFC RFC2617) + std::string username; + std::string password; }; } From 3c14d883a1b9a73ae04324a1dc062791e5721a2b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 8 Sep 2020 16:11:01 +0200 Subject: [PATCH 445/826] PhysicalPrinterDialog: Fixed memory leak --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index ca2d625561..3d832ae569 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -187,8 +187,7 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); if (!printer) { const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - //FIXME Vojtech: WTF??? Memory leak? - printer = new PhysicalPrinter(into_u8(printer_name), m_printer.config, preset); + m_printer = PhysicalPrinter(into_u8(printer_name), m_printer.config, preset); // if printer_name is empty it means that new printer is created, so enable all items in the preset list m_presets.emplace_back(new PresetForPrinter(this, preset.name)); } @@ -197,9 +196,8 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : const std::set& preset_names = printer->get_preset_names(); for (const std::string& preset_name : preset_names) m_presets.emplace_back(new PresetForPrinter(this, preset_name)); + m_printer = *printer; } - assert(printer); - m_printer = *printer; if (m_presets.size() == 1) m_presets.front()->SuppressDelete(); From 6b10214bec25cbe8800bcd4f12f477a9eaf852e9 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 9 Sep 2020 09:06:50 +0200 Subject: [PATCH 446/826] Fixed export of pause print lines into gcode --- src/libslic3r/GCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0ee9ec0143..cfde3a03a7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1939,8 +1939,8 @@ namespace ProcessLayer #if !ENABLE_GCODE_VIEWER // add tag for time estimator gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n"; - gcode += config.pause_print_gcode; #endif // !ENABLE_GCODE_VIEWER + gcode += config.pause_print_gcode; } else { From e010d287c5c47bbb7faa018595f152b64e5d64c3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 9 Sep 2020 11:41:23 +0200 Subject: [PATCH 447/826] Fixed launching of new slicer instances on Windows. --- src/slic3r/Utils/Process.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 561971b139..375c10a6a5 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -47,7 +47,9 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance args.emplace_back(path_to_open->wc_str()); args.emplace_back(nullptr); BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << into_u8(path) << "\""; - if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + // Don't call with wxEXEC_HIDE_CONSOLE, PrusaSlicer in GUI mode would just show the splash screen. It would not open the main window though, it would + // just hang in the background. + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC) <= 0) BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << into_u8(path); #else // Own executable path. @@ -96,7 +98,7 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance } args.emplace_back(nullptr); BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << args[0] << "\""; - if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER) <= 0) + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER) <= 0) BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << args[0]; } #endif // Linux or Unix From 0d26df3cf6a2a75973a0c6df235089e12f1195ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 16:51:34 +0200 Subject: [PATCH 448/826] Preparation for new infill --- src/libslic3r/CMakeLists.txt | 2 ++ src/libslic3r/Fill/Fill.cpp | 1 + src/libslic3r/Fill/FillAdaptive.cpp | 19 +++++++++++ src/libslic3r/Fill/FillAdaptive.hpp | 53 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillBase.cpp | 2 ++ src/libslic3r/Fill/FillBase.hpp | 6 ++++ src/libslic3r/Print.hpp | 8 +++++ src/libslic3r/PrintConfig.cpp | 2 ++ src/libslic3r/PrintConfig.hpp | 3 +- 9 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/libslic3r/Fill/FillAdaptive.cpp create mode 100644 src/libslic3r/Fill/FillAdaptive.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3d241dd371..09f75c747c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -46,6 +46,8 @@ add_library(libslic3r STATIC Fill/Fill.hpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp + Fill/FillAdaptive.cpp + Fill/FillAdaptive.hpp Fill/FillBase.cpp Fill/FillBase.hpp Fill/FillConcentric.cpp diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 3c16527f07..c948df400e 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -345,6 +345,7 @@ void Layer::make_fills() f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; + f->adapt_fill_octree = this->object()->adaptiveInfillOctree(); // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp new file mode 100644 index 0000000000..cb11385983 --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -0,0 +1,19 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" + +#include "FillAdaptive.hpp" + +namespace Slic3r { + +void FillAdaptive::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out) +{ + +} + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp new file mode 100644 index 0000000000..e0a97a1b9b --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -0,0 +1,53 @@ +#ifndef slic3r_FillAdaptive_hpp_ +#define slic3r_FillAdaptive_hpp_ + +#include "FillBase.hpp" + +namespace Slic3r { + +namespace FillAdaptive_Internal +{ + struct CubeProperties + { + double edge_length; // Lenght of edge of a cube + double height; // Height of rotated cube (standing on the corner) + double diagonal_length; // Length of diagonal of a cube a face + double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created + double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created + }; + + struct Cube + { + Vec3d center; + size_t depth; + CubeProperties properties; + std::vector children; + }; + + struct Octree + { + Cube *root_cube; + Vec3d origin; + }; +}; // namespace FillAdaptive_Internal + +class FillAdaptive : public Fill +{ +public: + virtual ~FillAdaptive() {} + +protected: + virtual Fill* clone() const { return new FillAdaptive(*this); }; + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out); + + virtual bool no_sort() const { return true; } +}; + +} // namespace Slic3r + +#endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c760218c01..c1f38dad5c 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -16,6 +16,7 @@ #include "FillRectilinear.hpp" #include "FillRectilinear2.hpp" #include "FillRectilinear3.hpp" +#include "FillAdaptive.hpp" namespace Slic3r { @@ -37,6 +38,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipArchimedeanChords: return new FillArchimedeanChords(); case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); + case ipAdaptiveCubic: return new FillAdaptive(); default: throw std::invalid_argument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 2e9b647354..9f70b69e08 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -19,6 +19,10 @@ class ExPolygon; class Surface; enum InfillPattern : int; +namespace FillAdaptive_Internal { + struct Octree; +}; + class InfillFailedException : public std::runtime_error { public: InfillFailedException() : std::runtime_error("Infill failed") {} @@ -69,6 +73,8 @@ public: // In scaled coordinates. Bounding box of the 2D projection of the object. BoundingBox bounding_box; + FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr; + public: virtual ~Fill() {} diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index d436f90bf5..02dd6df356 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -30,6 +30,10 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; +namespace FillAdaptive_Internal { + struct Octree; +}; + // Print step IDs for keeping track of the print state. enum PrintStep { psSkirt, @@ -194,6 +198,7 @@ public: // Helpers to project custom facets on slices void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; + FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree; } private: // to be called from Print only. friend class Print; @@ -235,6 +240,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); + void prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; @@ -255,6 +261,8 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; + FillAdaptive_Internal::Octree* m_adapt_fill_octree = nullptr; + std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const; std::vector slice_modifiers(size_t region_id, const std::vector &z) const; std::vector slice_volumes(const std::vector &z, SlicingMode mode, const std::vector &volumes) const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 239969a1d2..718fae365b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -881,6 +881,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("adaptivecubic"); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); def->enum_labels.push_back(L("Triangles")); @@ -894,6 +895,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Adaptive Cubic")); def->set_default_value(new ConfigOptionEnum(ipStars)); def = this->add("first_layer_acceleration", coFloat); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b133a2e4eb..3726444fab 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -39,7 +39,7 @@ enum AuthorizationType { enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipCount, }; enum class IroningType { @@ -139,6 +139,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g keys_map["hilbertcurve"] = ipHilbertCurve; keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["octagramspiral"] = ipOctagramSpiral; + keys_map["adaptivecubic"] = ipAdaptiveCubic; } return keys_map; } From 34f38c4a79e426d4a479bdf33644b2cb77ed0eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 18:15:59 +0200 Subject: [PATCH 449/826] Building octree based on distance from mesh --- src/libslic3r/Fill/FillAdaptive.cpp | 86 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillAdaptive.hpp | 16 ++++++ src/libslic3r/PrintObject.cpp | 23 ++++++++ 3 files changed, 125 insertions(+) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index cb11385983..ce779ad005 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -1,6 +1,8 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Surface.hpp" +#include "../Geometry.hpp" +#include "../AABBTreeIndirect.hpp" #include "FillAdaptive.hpp" @@ -16,4 +18,88 @@ void FillAdaptive::_fill_surface_single( } +FillAdaptive_Internal::Octree* FillAdaptive::build_octree( + TriangleMesh &triangleMesh, + coordf_t line_spacing, + const BoundingBoxf3 &printer_volume, + const Vec3d &cube_center) +{ + using namespace FillAdaptive_Internal; + + if(line_spacing <= 0) + { + return nullptr; + } + + // The furthest point from center of bed. + double furthest_point = std::sqrt(((printer_volume.size()[0] * printer_volume.size()[0]) / 4.0) + + ((printer_volume.size()[1] * printer_volume.size()[1]) / 4.0) + + (printer_volume.size()[2] * printer_volume.size()[2])); + double max_cube_edge_length = furthest_point * 2; + + std::vector cubes_properties; + for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) + { + CubeProperties props{}; + props.edge_length = edge_length; + props.height = edge_length * sqrt(3); + props.diagonal_length = edge_length * sqrt(2); + props.line_z_distance = edge_length / sqrt(3); + props.line_xy_distance = edge_length / sqrt(6); + cubes_properties.push_back(props); + } + + if (triangleMesh.its.vertices.empty()) + { + triangleMesh.require_shared_vertices(); + } + + Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.0), Geometry::deg2rad(30.0)); + Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); + + AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); + Octree *octree = new Octree{new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}, cube_center}; + + FillAdaptive::expand_cube(octree->root_cube, cubes_properties, rotation_matrix, aabbTree, triangleMesh); + + return octree; +} + +void FillAdaptive::expand_cube( + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d &rotation_matrix, + const AABBTreeIndirect::Tree3f &distanceTree, + const TriangleMesh &triangleMesh) +{ + using namespace FillAdaptive_Internal; + + if (cube == nullptr || cube->depth == 0) + { + return; + } + + std::vector child_centers = { + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d(-1, -1, 1), + Vec3d( 1, 1, 1), Vec3d(-1, 1, 1), Vec3d( 1, -1, 1), Vec3d( 1, 1, -1) + }; + + double cube_radius_squared = (cube->properties.height * cube->properties.height) / 16; + + for (const Vec3d &child_center : child_centers) { + Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); + Vec3d closest_point = Vec3d::Zero(); + size_t closest_triangle_idx = 0; + + double distance_squared = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, + closest_triangle_idx,closest_point); + + if(distance_squared <= cube_radius_squared) { + cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); + FillAdaptive::expand_cube(cube->children.back(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); + } + } +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index e0a97a1b9b..49c5276a93 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_FillAdaptive_hpp_ #define slic3r_FillAdaptive_hpp_ +#include "../AABBTreeIndirect.hpp" + #include "FillBase.hpp" namespace Slic3r { @@ -46,6 +48,20 @@ protected: Polylines &polylines_out); virtual bool no_sort() const { return true; } + +public: + static FillAdaptive_Internal::Octree* build_octree( + TriangleMesh &triangleMesh, + coordf_t line_spacing, + const BoundingBoxf3 &printer_volume, + const Vec3d &cube_center); + + static void expand_cube( + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d &rotation_matrix, + const AABBTreeIndirect::Tree3f &distanceTree, + const TriangleMesh &triangleMesh); }; } // namespace Slic3r diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index aecf907710..272ee6e819 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -9,6 +9,8 @@ #include "Surface.hpp" #include "Slicing.hpp" #include "Utils.hpp" +#include "AABBTreeIndirect.hpp" +#include "Fill/FillAdaptive.hpp" #include #include @@ -360,6 +362,8 @@ void PrintObject::prepare_infill() } // for each layer #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + this->prepare_adaptive_infill_data(); + this->set_done(posPrepareInfill); } @@ -428,6 +432,25 @@ void PrintObject::generate_support_material() } } +void PrintObject::prepare_adaptive_infill_data() +{ + float fill_density = this->print()->full_print_config().opt_float("fill_density"); + float infill_extrusion_width = this->print()->full_print_config().opt_float("infill_extrusion_width"); + + coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); + + BoundingBoxf bed_shape(this->print()->config().bed_shape.values); + BoundingBoxf3 printer_volume(Vec3d(bed_shape.min(0), bed_shape.min(1), 0), + Vec3d(bed_shape.max(0), bed_shape.max(1), this->print()->config().max_print_height)); + + Vec3d model_center = this->model_object()->bounding_box().center(); + model_center(2) = 0.0f; // Set position in Z axis to 0 + // Center of the first cube in octree + + TriangleMesh mesh = this->model_object()->mesh(); + this->m_adapt_fill_octree = FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); +} + void PrintObject::clear_layers() { for (Layer *l : m_layers) From 9f049b26198f49f34539b4ed694bdb417eec5e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 22:18:51 +0200 Subject: [PATCH 450/826] Generating polylines from octree --- src/libslic3r/Fill/FillAdaptive.cpp | 57 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillAdaptive.hpp | 7 ++++ 2 files changed, 64 insertions(+) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index ce779ad005..cac9c1c3b2 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -15,7 +15,64 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { + Polylines infill_polylines; + this->generate_polylines(this->adapt_fill_octree->root_cube, this->z, this->adapt_fill_octree->origin, infill_polylines); + // Crop all polylines + polylines_out = intersection_pl(infill_polylines, to_polygons(expolygon)); +} + +void FillAdaptive::generate_polylines( + FillAdaptive_Internal::Cube *cube, + double z_position, + const Vec3d &origin, + Polylines &polylines_out) +{ + using namespace FillAdaptive_Internal; + + if(cube == nullptr) + { + return; + } + + double z_diff = std::abs(z_position - cube->center.z()); + + if (z_diff > cube->properties.height / 2) + { + return; + } + + if (z_diff < cube->properties.line_z_distance) + { + Point from( + scale_((cube->properties.diagonal_length / 2) * (cube->properties.line_z_distance - z_diff) / cube->properties.line_z_distance), + scale_(cube->properties.line_xy_distance - ((z_position - (cube->center.z() - cube->properties.line_z_distance)) / sqrt(2)))); + Point to(-from.x(), from.y()); + // Relative to cube center + + float rotation_angle = Geometry::deg2rad(120.0); + + for (int dir_idx = 0; dir_idx < 3; dir_idx++) + { + Vec3d offset = cube->center - origin; + Point from_abs(from), to_abs(to); + + from_abs.x() += scale_(offset.x()); + from_abs.y() += scale_(offset.y()); + to_abs.x() += scale_(offset.x()); + to_abs.y() += scale_(offset.y()); + + polylines_out.push_back(Polyline(from_abs, to_abs)); + + from.rotate(rotation_angle); + to.rotate(rotation_angle); + } + } + + for(Cube *child : cube->children) + { + generate_polylines(child, z_position, origin, polylines_out); + } } FillAdaptive_Internal::Octree* FillAdaptive::build_octree( diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 49c5276a93..9e1a196af1 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -33,6 +33,11 @@ namespace FillAdaptive_Internal }; }; // namespace FillAdaptive_Internal +// +// Some of the algorithms used by class FillAdaptive were inspired by +// Cura Engine's class SubDivCube +// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h +// class FillAdaptive : public Fill { public: @@ -49,6 +54,8 @@ protected: virtual bool no_sort() const { return true; } + void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, Polylines &polylines_out); + public: static FillAdaptive_Internal::Octree* build_octree( TriangleMesh &triangleMesh, From c311b84b212329f04d6ed48305b935db4cc6d498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 26 Aug 2020 23:28:52 +0200 Subject: [PATCH 451/826] Add function for check existence of triangle in define radius --- src/libslic3r/AABBTreeIndirect.hpp | 34 +++++++++++++++++++++++++++++ src/libslic3r/Fill/FillAdaptive.cpp | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index ec9b14a7ae..17d918aeb3 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -692,6 +692,40 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); } +// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// Returns true if exists some triangle in defined radius, false otherwise. +template +inline bool is_any_triangle_in_radius( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Point to which the closest point on the indexed triangle set is searched for. + const VectorType &point, + // Maximum distance in which triangle is search for + typename VectorType::Scalar &max_distance) +{ + using Scalar = typename VectorType::Scalar; + auto distancer = detail::IndexedTriangleSetDistancer + { vertices, faces, tree, point }; + + size_t hit_idx; + VectorType hit_point = VectorType::Ones() * (std::nan("")); + + if(tree.empty()) + { + return false; + } + + detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point); + + return hit_point.allFinite(); +} + } // namespace AABBTreeIndirect } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index cac9c1c3b2..ae067e659e 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -152,7 +152,7 @@ void FillAdaptive::expand_cube( triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, closest_triangle_idx,closest_point); - if(distance_squared <= cube_radius_squared) { + if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); FillAdaptive::expand_cube(cube->children.back(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); } From c0d21bd2b496bf8b6393cb395cfce169bd857c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 27 Aug 2020 01:59:35 +0200 Subject: [PATCH 452/826] Polylines merging --- src/libslic3r/Fill/FillAdaptive.cpp | 84 ++++++++++++++++++++++++----- src/libslic3r/Fill/FillAdaptive.hpp | 4 +- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index ae067e659e..adc6c0c6fb 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -15,18 +15,54 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - Polylines infill_polylines; + std::vector infill_polylines(3); this->generate_polylines(this->adapt_fill_octree->root_cube, this->z, this->adapt_fill_octree->origin, infill_polylines); - // Crop all polylines - polylines_out = intersection_pl(infill_polylines, to_polygons(expolygon)); + for (Polylines &infill_polyline : infill_polylines) { + // Crop all polylines + infill_polyline = intersection_pl(infill_polyline, to_polygons(expolygon)); + polylines_out.insert(polylines_out.end(), infill_polyline.begin(), infill_polyline.end()); + } + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRuna = 0; + BoundingBox bbox_svg = this->bounding_box; + { + ::Slic3r::SVG svg(debug_out_path("FillAdaptive-%d.svg", iRuna), bbox_svg); + for (const Polyline &polyline : polylines_out) + { + for (const Line &line : polyline.lines()) + { + Point from = line.a; + Point to = line.b; + Point diff = to - from; + + float shrink_length = scale_(0.4); + float line_slope = (float)diff.y() / diff.x(); + float shrink_x = shrink_length / (float)std::sqrt(1.0 + (line_slope * line_slope)); + float shrink_y = line_slope * shrink_x; + + to.x() -= shrink_x; + to.y() -= shrink_y; + from.x() += shrink_x; + from.y() += shrink_y; + + svg.draw(Line(from, to)); + } + } + } + + iRuna++; + } +#endif /* SLIC3R_DEBUG */ } void FillAdaptive::generate_polylines( FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, - Polylines &polylines_out) + std::vector &polylines_out) { using namespace FillAdaptive_Internal; @@ -52,7 +88,7 @@ void FillAdaptive::generate_polylines( float rotation_angle = Geometry::deg2rad(120.0); - for (int dir_idx = 0; dir_idx < 3; dir_idx++) + for (int i = 0; i < 3; i++) { Vec3d offset = cube->center - origin; Point from_abs(from), to_abs(to); @@ -62,7 +98,8 @@ void FillAdaptive::generate_polylines( to_abs.x() += scale_(offset.x()); to_abs.y() += scale_(offset.y()); - polylines_out.push_back(Polyline(from_abs, to_abs)); +// polylines_out[i].push_back(Polyline(from_abs, to_abs)); + this->merge_polylines(polylines_out[i], Line(from_abs, to_abs)); from.rotate(rotation_angle); to.rotate(rotation_angle); @@ -75,6 +112,35 @@ void FillAdaptive::generate_polylines( } } +void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) +{ + int eps = scale_(0.10); + bool modified = false; + + for (Polyline &polyline : polylines) + { + if (std::abs(new_line.a.x() - polyline.points[1].x()) < eps && std::abs(new_line.a.y() - polyline.points[1].y()) < eps) + { + polyline.points[1].x() = new_line.b.x(); + polyline.points[1].y() = new_line.b.y(); + modified = true; + } + + if (std::abs(new_line.b.x() - polyline.points[0].x()) < eps && std::abs(new_line.b.y() - polyline.points[0].y()) < eps) + { + polyline.points[0].x() = new_line.a.x(); + polyline.points[0].y() = new_line.a.y(); + modified = true; + } + } + + if(!modified) + { + polylines.emplace_back(Polyline(new_line.a, new_line.b)); + } +} + + FillAdaptive_Internal::Octree* FillAdaptive::build_octree( TriangleMesh &triangleMesh, coordf_t line_spacing, @@ -145,12 +211,6 @@ void FillAdaptive::expand_cube( for (const Vec3d &child_center : child_centers) { Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); - Vec3d closest_point = Vec3d::Zero(); - size_t closest_triangle_idx = 0; - - double distance_squared = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( - triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, - closest_triangle_idx,closest_point); if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 9e1a196af1..b2f4e37b1e 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -54,7 +54,9 @@ protected: virtual bool no_sort() const { return true; } - void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, Polylines &polylines_out); + void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &polylines_out); + + void merge_polylines(Polylines &polylines, const Line &new_line); public: static FillAdaptive_Internal::Octree* build_octree( From 14a7fbc9f70755f058d15be76318425537d3ca9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 27 Aug 2020 07:28:43 +0200 Subject: [PATCH 453/826] Switch to smart pointers --- src/libslic3r/Fill/FillAdaptive.cpp | 17 +++++++++-------- src/libslic3r/Fill/FillAdaptive.hpp | 6 +++--- src/libslic3r/Print.hpp | 8 +++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index adc6c0c6fb..96509923c8 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -16,7 +16,7 @@ void FillAdaptive::_fill_surface_single( Polylines &polylines_out) { std::vector infill_polylines(3); - this->generate_polylines(this->adapt_fill_octree->root_cube, this->z, this->adapt_fill_octree->origin, infill_polylines); + this->generate_polylines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_polylines); for (Polylines &infill_polyline : infill_polylines) { // Crop all polylines @@ -106,9 +106,9 @@ void FillAdaptive::generate_polylines( } } - for(Cube *child : cube->children) + for(const std::unique_ptr &child : cube->children) { - generate_polylines(child, z_position, origin, polylines_out); + generate_polylines(child.get(), z_position, origin, polylines_out); } } @@ -141,7 +141,7 @@ void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) } -FillAdaptive_Internal::Octree* FillAdaptive::build_octree( +std::unique_ptr FillAdaptive::build_octree( TriangleMesh &triangleMesh, coordf_t line_spacing, const BoundingBoxf3 &printer_volume, @@ -181,9 +181,10 @@ FillAdaptive_Internal::Octree* FillAdaptive::build_octree( Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); - Octree *octree = new Octree{new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}, cube_center}; + std::unique_ptr octree = std::unique_ptr( + new Octree{std::unique_ptr(new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}), cube_center}); - FillAdaptive::expand_cube(octree->root_cube, cubes_properties, rotation_matrix, aabbTree, triangleMesh); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangleMesh); return octree; } @@ -213,8 +214,8 @@ void FillAdaptive::expand_cube( Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { - cube->children.push_back(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]}); - FillAdaptive::expand_cube(cube->children.back(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); + cube->children.push_back(std::unique_ptr(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]})); + FillAdaptive::expand_cube(cube->children.back().get(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); } } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index b2f4e37b1e..fb1f2da8e3 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -23,12 +23,12 @@ namespace FillAdaptive_Internal Vec3d center; size_t depth; CubeProperties properties; - std::vector children; + std::vector> children; }; struct Octree { - Cube *root_cube; + std::unique_ptr root_cube; Vec3d origin; }; }; // namespace FillAdaptive_Internal @@ -59,7 +59,7 @@ protected: void merge_polylines(Polylines &polylines, const Line &new_line); public: - static FillAdaptive_Internal::Octree* build_octree( + static std::unique_ptr build_octree( TriangleMesh &triangleMesh, coordf_t line_spacing, const BoundingBoxf3 &printer_volume, diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 02dd6df356..1cc83dbe48 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -11,6 +11,7 @@ #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "GCode/ThumbnailData.hpp" +#include "Fill/FillAdaptive.hpp" #if ENABLE_GCODE_VIEWER #include "GCode/GCodeProcessor.hpp" #endif // ENABLE_GCODE_VIEWER @@ -30,9 +31,6 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; -namespace FillAdaptive_Internal { - struct Octree; -}; // Print step IDs for keeping track of the print state. enum PrintStep { @@ -198,7 +196,7 @@ public: // Helpers to project custom facets on slices void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; - FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree; } + FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree.get(); } private: // to be called from Print only. friend class Print; @@ -261,7 +259,7 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - FillAdaptive_Internal::Octree* m_adapt_fill_octree = nullptr; + std::unique_ptr m_adapt_fill_octree = nullptr; std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const; std::vector slice_modifiers(size_t region_id, const std::vector &z) const; From 867681ae56f85e828fb4fc9370d43605c05f1906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 27 Aug 2020 13:04:53 +0200 Subject: [PATCH 454/826] Fix discontinuous extrusion lines for adaptive infill --- src/libslic3r/Fill/FillAdaptive.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 96509923c8..a3068989ef 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -88,7 +88,7 @@ void FillAdaptive::generate_polylines( float rotation_angle = Geometry::deg2rad(120.0); - for (int i = 0; i < 3; i++) + for (int i = 0; i < polylines_out.size(); i++) { Vec3d offset = cube->center - origin; Point from_abs(from), to_abs(to); @@ -177,7 +177,7 @@ std::unique_ptr FillAdaptive::build_octree( triangleMesh.require_shared_vertices(); } - Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.0), Geometry::deg2rad(30.0)); + Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.264), Geometry::deg2rad(30.0)); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); From 65ba40f0445b938045f9f5e5cfdcf9acba9e4cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 30 Aug 2020 20:38:07 +0200 Subject: [PATCH 455/826] Fix crash on inconsistent input --- src/libslic3r/Fill/FillAdaptive.cpp | 2 +- src/libslic3r/PrintObject.cpp | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index a3068989ef..0563b612ab 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -149,7 +149,7 @@ std::unique_ptr FillAdaptive::build_octree( { using namespace FillAdaptive_Internal; - if(line_spacing <= 0) + if(line_spacing <= 0 || std::isnan(line_spacing)) { return nullptr; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 272ee6e819..43ebf58db7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,8 +434,16 @@ void PrintObject::generate_support_material() void PrintObject::prepare_adaptive_infill_data() { - float fill_density = this->print()->full_print_config().opt_float("fill_density"); - float infill_extrusion_width = this->print()->full_print_config().opt_float("infill_extrusion_width"); + const ConfigOptionFloatOrPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); + const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); + + if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) + { + return; + } + + float fill_density = opt_fill_density->value; + float infill_extrusion_width = opt_infill_extrusion_width->value; coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); From 9eeb5e4364f641c30eeb8bf78594455fbf1f50dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 31 Aug 2020 08:49:17 +0200 Subject: [PATCH 456/826] Fix wrong data type --- src/libslic3r/PrintObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 43ebf58db7..9d023a095c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,7 +434,7 @@ void PrintObject::generate_support_material() void PrintObject::prepare_adaptive_infill_data() { - const ConfigOptionFloatOrPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); + const ConfigOptionPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) From 33121b705a58d00ca80398d99ad5e60e8387a342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 2 Sep 2020 22:53:10 +0200 Subject: [PATCH 457/826] Change in passing octree struct --- src/libslic3r/Fill/Fill.cpp | 4 ++-- src/libslic3r/Layer.hpp | 6 +++++- src/libslic3r/Print.hpp | 9 ++++----- src/libslic3r/PrintObject.cpp | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index c948df400e..9d468a6aa9 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills() +void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -345,7 +345,7 @@ void Layer::make_fills() f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; - f->adapt_fill_octree = this->object()->adaptiveInfillOctree(); + f->adapt_fill_octree = adaptive_fill_octree; // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index c104d46da1..4c824a1093 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -13,6 +13,10 @@ class Layer; class PrintRegion; class PrintObject; +namespace FillAdaptive_Internal { + struct Octree; +}; + class LayerRegion { public: @@ -134,7 +138,7 @@ public: return false; } void make_perimeters(); - void make_fills(); + void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree); void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 1cc83dbe48..effb6bde90 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -11,7 +11,6 @@ #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "GCode/ThumbnailData.hpp" -#include "Fill/FillAdaptive.hpp" #if ENABLE_GCODE_VIEWER #include "GCode/GCodeProcessor.hpp" #endif // ENABLE_GCODE_VIEWER @@ -31,6 +30,9 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; +namespace FillAdaptive_Internal { + struct Octree; +}; // Print step IDs for keeping track of the print state. enum PrintStep { @@ -196,7 +198,6 @@ public: // Helpers to project custom facets on slices void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; - FillAdaptive_Internal::Octree* adaptiveInfillOctree() { return m_adapt_fill_octree.get(); } private: // to be called from Print only. friend class Print; @@ -238,7 +239,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - void prepare_adaptive_infill_data(); + std::unique_ptr prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; @@ -259,8 +260,6 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - std::unique_ptr m_adapt_fill_octree = nullptr; - std::vector slice_region(size_t region_id, const std::vector &z, SlicingMode mode) const; std::vector slice_modifiers(size_t region_id, const std::vector &z) const; std::vector slice_volumes(const std::vector &z, SlicingMode mode, const std::vector &volumes) const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 9d023a095c..1699cd5ad3 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -362,8 +362,6 @@ void PrintObject::prepare_infill() } // for each layer #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - this->prepare_adaptive_infill_data(); - this->set_done(posPrepareInfill); } @@ -373,13 +371,15 @@ void PrintObject::infill() this->prepare_infill(); if (this->set_started(posInfill)) { + std::unique_ptr octree = this->prepare_adaptive_infill_data(); + BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { + [this, &octree](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(); + m_layers[layer_idx]->make_fills(octree.get()); } } ); @@ -432,14 +432,14 @@ void PrintObject::generate_support_material() } } -void PrintObject::prepare_adaptive_infill_data() +std::unique_ptr PrintObject::prepare_adaptive_infill_data() { const ConfigOptionPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) { - return; + return std::unique_ptr{}; } float fill_density = opt_fill_density->value; @@ -448,15 +448,15 @@ void PrintObject::prepare_adaptive_infill_data() coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); BoundingBoxf bed_shape(this->print()->config().bed_shape.values); - BoundingBoxf3 printer_volume(Vec3d(bed_shape.min(0), bed_shape.min(1), 0), - Vec3d(bed_shape.max(0), bed_shape.max(1), this->print()->config().max_print_height)); + BoundingBoxf3 printer_volume(Vec3d(bed_shape.min.x(), bed_shape.min.y(), 0), + Vec3d(bed_shape.max.x(), bed_shape.max.y(), this->print()->config().max_print_height)); Vec3d model_center = this->model_object()->bounding_box().center(); - model_center(2) = 0.0f; // Set position in Z axis to 0 + model_center.z() = 0.0f; // Set position in Z axis to 0 // Center of the first cube in octree TriangleMesh mesh = this->model_object()->mesh(); - this->m_adapt_fill_octree = FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); + return FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); } void PrintObject::clear_layers() From 2debffc49629b57f8e5751bab4b363d11f7e7e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 07:52:53 +0200 Subject: [PATCH 458/826] Fix tests which expect make_fills without arguments --- src/libslic3r/Layer.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 4c824a1093..014d2623af 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -138,6 +138,7 @@ public: return false; } void make_perimeters(); + void make_fills() { this->make_fills(nullptr); }; void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree); void make_ironing(); From d09ac41d2c602f58a5bc6b549b53d67c3f1308bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 08:04:05 +0200 Subject: [PATCH 459/826] Octree's first cube depends on model size. --- src/libslic3r/Fill/FillAdaptive.cpp | 21 +++++++++++---------- src/libslic3r/Fill/FillAdaptive.hpp | 3 +-- src/libslic3r/PrintObject.cpp | 9 ++------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 0563b612ab..62c4a3af7b 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -142,9 +142,8 @@ void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) std::unique_ptr FillAdaptive::build_octree( - TriangleMesh &triangleMesh, + TriangleMesh &triangle_mesh, coordf_t line_spacing, - const BoundingBoxf3 &printer_volume, const Vec3d &cube_center) { using namespace FillAdaptive_Internal; @@ -154,10 +153,11 @@ std::unique_ptr FillAdaptive::build_octree( return nullptr; } - // The furthest point from center of bed. - double furthest_point = std::sqrt(((printer_volume.size()[0] * printer_volume.size()[0]) / 4.0) + - ((printer_volume.size()[1] * printer_volume.size()[1]) / 4.0) + - (printer_volume.size()[2] * printer_volume.size()[2])); + Vec3d bb_size = triangle_mesh.bounding_box().size(); + // The furthest point from the center of the bottom of the mesh bounding box. + double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + + ((bb_size.y() * bb_size.y()) / 4.0) + + (bb_size.z() * bb_size.z())); double max_cube_edge_length = furthest_point * 2; std::vector cubes_properties; @@ -172,19 +172,20 @@ std::unique_ptr FillAdaptive::build_octree( cubes_properties.push_back(props); } - if (triangleMesh.its.vertices.empty()) + if (triangle_mesh.its.vertices.empty()) { - triangleMesh.require_shared_vertices(); + triangle_mesh.require_shared_vertices(); } Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.264), Geometry::deg2rad(30.0)); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); - AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangleMesh.its.vertices, triangleMesh.its.indices); + AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + triangle_mesh.its.vertices, triangle_mesh.its.indices); std::unique_ptr octree = std::unique_ptr( new Octree{std::unique_ptr(new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}), cube_center}); - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangleMesh); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh); return octree; } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index fb1f2da8e3..c7539df5a7 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -60,9 +60,8 @@ protected: public: static std::unique_ptr build_octree( - TriangleMesh &triangleMesh, + TriangleMesh &triangle_mesh, coordf_t line_spacing, - const BoundingBoxf3 &printer_volume, const Vec3d &cube_center); static void expand_cube( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1699cd5ad3..1236a297fc 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -447,16 +447,11 @@ std::unique_ptr PrintObject::prepare_adaptive_inf coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); - BoundingBoxf bed_shape(this->print()->config().bed_shape.values); - BoundingBoxf3 printer_volume(Vec3d(bed_shape.min.x(), bed_shape.min.y(), 0), - Vec3d(bed_shape.max.x(), bed_shape.max.y(), this->print()->config().max_print_height)); - - Vec3d model_center = this->model_object()->bounding_box().center(); - model_center.z() = 0.0f; // Set position in Z axis to 0 // Center of the first cube in octree + Vec3d model_center = this->model_object()->bounding_box().center(); TriangleMesh mesh = this->model_object()->mesh(); - return FillAdaptive::build_octree(mesh, line_spacing, printer_volume, model_center); + return FillAdaptive::build_octree(mesh, line_spacing, model_center); } void PrintObject::clear_layers() From 398d429ce1913bc7916b0a422b8f1d33f2a20971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 11:56:41 +0200 Subject: [PATCH 460/826] Code cleanup --- src/libslic3r/Fill/FillAdaptive.cpp | 56 +++++++++++++++-------------- src/libslic3r/Fill/FillAdaptive.hpp | 10 ++++-- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 62c4a3af7b..91da86b69e 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -15,15 +15,20 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - std::vector infill_polylines(3); - this->generate_polylines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_polylines); + std::vector infill_lines_dir(3); + this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_lines_dir); - for (Polylines &infill_polyline : infill_polylines) { - // Crop all polylines - infill_polyline = intersection_pl(infill_polyline, to_polygons(expolygon)); - polylines_out.insert(polylines_out.end(), infill_polyline.begin(), infill_polyline.end()); + for (Lines &infill_lines : infill_lines_dir) + { + for (const Line &line : infill_lines) + { + polylines_out.emplace_back(line.a, line.b); + } } + // Crop all polylines + polylines_out = intersection_pl(polylines_out, to_polygons(expolygon)); + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRuna = 0; @@ -58,11 +63,11 @@ void FillAdaptive::_fill_surface_single( #endif /* SLIC3R_DEBUG */ } -void FillAdaptive::generate_polylines( +void FillAdaptive::generate_infill_lines( FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, - std::vector &polylines_out) + std::vector &dir_lines_out) { using namespace FillAdaptive_Internal; @@ -86,9 +91,8 @@ void FillAdaptive::generate_polylines( Point to(-from.x(), from.y()); // Relative to cube center - float rotation_angle = Geometry::deg2rad(120.0); - - for (int i = 0; i < polylines_out.size(); i++) + float rotation_angle = (2.0 * M_PI) / 3.0; + for (Lines &lines : dir_lines_out) { Vec3d offset = cube->center - origin; Point from_abs(from), to_abs(to); @@ -98,8 +102,8 @@ void FillAdaptive::generate_polylines( to_abs.x() += scale_(offset.x()); to_abs.y() += scale_(offset.y()); -// polylines_out[i].push_back(Polyline(from_abs, to_abs)); - this->merge_polylines(polylines_out[i], Line(from_abs, to_abs)); +// lines.emplace_back(from_abs, to_abs); + this->connect_lines(lines, Line(from_abs, to_abs)); from.rotate(rotation_angle); to.rotate(rotation_angle); @@ -108,35 +112,35 @@ void FillAdaptive::generate_polylines( for(const std::unique_ptr &child : cube->children) { - generate_polylines(child.get(), z_position, origin, polylines_out); + generate_infill_lines(child.get(), z_position, origin, dir_lines_out); } } -void FillAdaptive::merge_polylines(Polylines &polylines, const Line &new_line) +void FillAdaptive::connect_lines(Lines &lines, const Line &new_line) { int eps = scale_(0.10); bool modified = false; - for (Polyline &polyline : polylines) + for (Line &line : lines) { - if (std::abs(new_line.a.x() - polyline.points[1].x()) < eps && std::abs(new_line.a.y() - polyline.points[1].y()) < eps) + if (std::abs(new_line.a.x() - line.b.x()) < eps && std::abs(new_line.a.y() - line.b.y()) < eps) { - polyline.points[1].x() = new_line.b.x(); - polyline.points[1].y() = new_line.b.y(); + line.b.x() = new_line.b.x(); + line.b.y() = new_line.b.y(); modified = true; } - if (std::abs(new_line.b.x() - polyline.points[0].x()) < eps && std::abs(new_line.b.y() - polyline.points[0].y()) < eps) + if (std::abs(new_line.b.x() - line.a.x()) < eps && std::abs(new_line.b.y() - line.a.y()) < eps) { - polyline.points[0].x() = new_line.a.x(); - polyline.points[0].y() = new_line.a.y(); + line.a.x() = new_line.a.x(); + line.a.y() = new_line.a.y(); modified = true; } } if(!modified) { - polylines.emplace_back(Polyline(new_line.a, new_line.b)); + lines.push_back(new_line); } } @@ -182,8 +186,8 @@ std::unique_ptr FillAdaptive::build_octree( AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( triangle_mesh.its.vertices, triangle_mesh.its.indices); - std::unique_ptr octree = std::unique_ptr( - new Octree{std::unique_ptr(new Cube{cube_center, cubes_properties.size() - 1, cubes_properties.back()}), cube_center}); + auto octree = std::make_unique( + std::make_unique(cube_center, cubes_properties.size() - 1, cubes_properties.back()), cube_center); FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh); @@ -215,7 +219,7 @@ void FillAdaptive::expand_cube( Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { - cube->children.push_back(std::unique_ptr(new Cube{child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1]})); + cube->children.emplace_back(std::make_unique(child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1])); FillAdaptive::expand_cube(cube->children.back().get(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index c7539df5a7..570318aa40 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -24,12 +24,18 @@ namespace FillAdaptive_Internal size_t depth; CubeProperties properties; std::vector> children; + + Cube(const Vec3d ¢er, size_t depth, const CubeProperties &properties) + : center(center), depth(depth), properties(properties) {} }; struct Octree { std::unique_ptr root_cube; Vec3d origin; + + Octree(std::unique_ptr rootCube, const Vec3d &origin) + : root_cube(std::move(rootCube)), origin(origin) {} }; }; // namespace FillAdaptive_Internal @@ -54,9 +60,9 @@ protected: virtual bool no_sort() const { return true; } - void generate_polylines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &polylines_out); + void generate_infill_lines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &dir_lines_out); - void merge_polylines(Polylines &polylines, const Line &new_line); + void connect_lines(Lines &lines, const Line &new_line); public: static std::unique_ptr build_octree( From 03e103fcc89383866d7275fb583579fefee7dc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 13:05:28 +0200 Subject: [PATCH 461/826] Connect infill to perimeters --- src/libslic3r/Fill/FillAdaptive.cpp | 35 ++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 91da86b69e..d3246dc18b 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -3,6 +3,7 @@ #include "../Surface.hpp" #include "../Geometry.hpp" #include "../AABBTreeIndirect.hpp" +#include "../ShortestPath.hpp" #include "FillAdaptive.hpp" @@ -15,19 +16,47 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { + // Store grouped lines by its direction (multiple of 120°) std::vector infill_lines_dir(3); this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_lines_dir); + Polylines all_polylines; + all_polylines.reserve(infill_lines_dir[0].size() * 3); for (Lines &infill_lines : infill_lines_dir) { for (const Line &line : infill_lines) { - polylines_out.emplace_back(line.a, line.b); + all_polylines.emplace_back(line.a, line.b); } } - // Crop all polylines - polylines_out = intersection_pl(polylines_out, to_polygons(expolygon)); + if (params.dont_connect) + { + // Crop all polylines + polylines_out = intersection_pl(all_polylines, to_polygons(expolygon)); + } + else + { + // Crop all polylines + all_polylines = intersection_pl(all_polylines, to_polygons(expolygon)); + + Polylines boundary_polylines; + Polylines non_boundary_polylines; + for (const Polyline &polyline : all_polylines) + { + // connect_infill required all polylines to touch the boundary. + if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) + { + boundary_polylines.push_back(polyline); + } else { + non_boundary_polylines.push_back(polyline); + } + } + + boundary_polylines = chain_polylines(boundary_polylines); + FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end()); + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { From 000987451a6a537d752e5c683dd7f69018273dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 14:28:25 +0200 Subject: [PATCH 462/826] Fix bug in lines merging --- src/libslic3r/Fill/FillAdaptive.cpp | 30 +++++++++++++---------------- src/libslic3r/Fill/FillAdaptive.hpp | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index d3246dc18b..030debad62 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -145,35 +145,31 @@ void FillAdaptive::generate_infill_lines( } } -void FillAdaptive::connect_lines(Lines &lines, const Line &new_line) +void FillAdaptive::connect_lines(Lines &lines, Line new_line) { int eps = scale_(0.10); - bool modified = false; - - for (Line &line : lines) + for (size_t i = 0; i < lines.size(); ++i) { - if (std::abs(new_line.a.x() - line.b.x()) < eps && std::abs(new_line.a.y() - line.b.y()) < eps) + if (std::abs(new_line.a.x() - lines[i].b.x()) < eps && std::abs(new_line.a.y() - lines[i].b.y()) < eps) { - line.b.x() = new_line.b.x(); - line.b.y() = new_line.b.y(); - modified = true; + new_line.a = lines[i].a; + lines.erase(lines.begin() + i); + --i; + continue; } - if (std::abs(new_line.b.x() - line.a.x()) < eps && std::abs(new_line.b.y() - line.a.y()) < eps) + if (std::abs(new_line.b.x() - lines[i].a.x()) < eps && std::abs(new_line.b.y() - lines[i].a.y()) < eps) { - line.a.x() = new_line.a.x(); - line.a.y() = new_line.a.y(); - modified = true; + new_line.b = lines[i].b; + lines.erase(lines.begin() + i); + --i; + continue; } } - if(!modified) - { - lines.push_back(new_line); - } + lines.emplace_back(new_line.a, new_line.b); } - std::unique_ptr FillAdaptive::build_octree( TriangleMesh &triangle_mesh, coordf_t line_spacing, diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 570318aa40..44a2536f00 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -62,7 +62,7 @@ protected: void generate_infill_lines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &dir_lines_out); - void connect_lines(Lines &lines, const Line &new_line); + static void connect_lines(Lines &lines, Line new_line); public: static std::unique_ptr build_octree( From acedb66cdc5abb6d5f9c2d575eefb9a4f834a00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 16:08:40 +0200 Subject: [PATCH 463/826] Change to using raw_mesh instead of mesh --- src/libslic3r/PrintObject.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1236a297fc..d9c533939c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -447,11 +447,14 @@ std::unique_ptr PrintObject::prepare_adaptive_inf coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); - // Center of the first cube in octree - Vec3d model_center = this->model_object()->bounding_box().center(); + TriangleMesh mesh = this->model_object()->raw_mesh(); + mesh.transform(m_trafo, true); + // Apply XY shift + mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); - TriangleMesh mesh = this->model_object()->mesh(); - return FillAdaptive::build_octree(mesh, line_spacing, model_center); + // Center of the first cube in octree + Vec3d mesh_origin = mesh.bounding_box().center(); + return FillAdaptive::build_octree(mesh, line_spacing, mesh_origin); } void PrintObject::clear_layers() From aca212c5bca2bd8373c212c23e038c618a02a47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 19:21:55 +0200 Subject: [PATCH 464/826] Octree representation rework --- src/libslic3r/Fill/FillAdaptive.cpp | 51 ++++++++++++++++++----------- src/libslic3r/Fill/FillAdaptive.hpp | 44 ++++++++++++++----------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 030debad62..577ba7e610 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -18,7 +18,10 @@ void FillAdaptive::_fill_surface_single( { // Store grouped lines by its direction (multiple of 120°) std::vector infill_lines_dir(3); - this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin, infill_lines_dir); + this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), + this->z, this->adapt_fill_octree->origin,infill_lines_dir, + this->adapt_fill_octree->cubes_properties, + this->adapt_fill_octree->cubes_properties.size() - 1); Polylines all_polylines; all_polylines.reserve(infill_lines_dir[0].size() * 3); @@ -96,7 +99,9 @@ void FillAdaptive::generate_infill_lines( FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, - std::vector &dir_lines_out) + std::vector &dir_lines_out, + const std::vector &cubes_properties, + int depth) { using namespace FillAdaptive_Internal; @@ -107,16 +112,16 @@ void FillAdaptive::generate_infill_lines( double z_diff = std::abs(z_position - cube->center.z()); - if (z_diff > cube->properties.height / 2) + if (z_diff > cubes_properties[depth].height / 2) { return; } - if (z_diff < cube->properties.line_z_distance) + if (z_diff < cubes_properties[depth].line_z_distance) { Point from( - scale_((cube->properties.diagonal_length / 2) * (cube->properties.line_z_distance - z_diff) / cube->properties.line_z_distance), - scale_(cube->properties.line_xy_distance - ((z_position - (cube->center.z() - cube->properties.line_z_distance)) / sqrt(2)))); + scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance), + scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube->center.z() - cubes_properties[depth].line_z_distance)) / sqrt(2)))); Point to(-from.x(), from.y()); // Relative to cube center @@ -141,7 +146,10 @@ void FillAdaptive::generate_infill_lines( for(const std::unique_ptr &child : cube->children) { - generate_infill_lines(child.get(), z_position, origin, dir_lines_out); + if(child != nullptr) + { + generate_infill_lines(child.get(), z_position, origin, dir_lines_out, cubes_properties, depth - 1); + } } } @@ -206,15 +214,14 @@ std::unique_ptr FillAdaptive::build_octree( triangle_mesh.require_shared_vertices(); } - Vec3d rotation = Vec3d(Geometry::deg2rad(225.0), Geometry::deg2rad(215.264), Geometry::deg2rad(30.0)); + Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( triangle_mesh.its.vertices, triangle_mesh.its.indices); - auto octree = std::make_unique( - std::make_unique(cube_center, cubes_properties.size() - 1, cubes_properties.back()), cube_center); + auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh, cubes_properties.size() - 1); return octree; } @@ -223,12 +230,12 @@ void FillAdaptive::expand_cube( FillAdaptive_Internal::Cube *cube, const std::vector &cubes_properties, const Transform3d &rotation_matrix, - const AABBTreeIndirect::Tree3f &distanceTree, - const TriangleMesh &triangleMesh) + const AABBTreeIndirect::Tree3f &distance_tree, + const TriangleMesh &triangle_mesh, int depth) { using namespace FillAdaptive_Internal; - if (cube == nullptr || cube->depth == 0) + if (cube == nullptr || depth == 0) { return; } @@ -238,14 +245,18 @@ void FillAdaptive::expand_cube( Vec3d( 1, 1, 1), Vec3d(-1, 1, 1), Vec3d( 1, -1, 1), Vec3d( 1, 1, -1) }; - double cube_radius_squared = (cube->properties.height * cube->properties.height) / 16; + double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16; - for (const Vec3d &child_center : child_centers) { - Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cube->properties.edge_length / 4)); + for (size_t i = 0; i < 8; ++i) + { + const Vec3d &child_center = child_centers[i]; + Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cubes_properties[depth].edge_length / 4)); - if(AABBTreeIndirect::is_any_triangle_in_radius(triangleMesh.its.vertices, triangleMesh.its.indices, distanceTree, child_center_transformed, cube_radius_squared)) { - cube->children.emplace_back(std::make_unique(child_center_transformed, cube->depth - 1, cubes_properties[cube->depth - 1])); - FillAdaptive::expand_cube(cube->children.back().get(), cubes_properties, rotation_matrix, distanceTree, triangleMesh); + if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices, + distance_tree, child_center_transformed, cube_radius_squared)) + { + cube->children[i] = std::make_unique(child_center_transformed); + FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, rotation_matrix, distance_tree, triangle_mesh, depth - 1); } } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 44a2536f00..14694b766c 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -21,21 +21,18 @@ namespace FillAdaptive_Internal struct Cube { Vec3d center; - size_t depth; - CubeProperties properties; - std::vector> children; - - Cube(const Vec3d ¢er, size_t depth, const CubeProperties &properties) - : center(center), depth(depth), properties(properties) {} + std::unique_ptr children[8] = {}; + Cube(const Vec3d ¢er) : center(center) {} }; struct Octree { std::unique_ptr root_cube; Vec3d origin; + std::vector cubes_properties; - Octree(std::unique_ptr rootCube, const Vec3d &origin) - : root_cube(std::move(rootCube)), origin(origin) {} + Octree(std::unique_ptr rootCube, const Vec3d &origin, const std::vector &cubes_properties) + : root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {} }; }; // namespace FillAdaptive_Internal @@ -52,30 +49,37 @@ public: protected: virtual Fill* clone() const { return new FillAdaptive(*this); }; virtual void _fill_surface_single( - const FillParams ¶ms, + const FillParams ¶ms, unsigned int thickness_layers, - const std::pair &direction, - ExPolygon &expolygon, + const std::pair &direction, + ExPolygon &expolygon, Polylines &polylines_out); virtual bool no_sort() const { return true; } - void generate_infill_lines(FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, std::vector &dir_lines_out); + void generate_infill_lines( + FillAdaptive_Internal::Cube *cube, + double z_position, + const Vec3d & origin, + std::vector & dir_lines_out, + const std::vector &cubes_properties, + int depth); static void connect_lines(Lines &lines, Line new_line); public: static std::unique_ptr build_octree( - TriangleMesh &triangle_mesh, - coordf_t line_spacing, - const Vec3d &cube_center); + TriangleMesh &triangle_mesh, + coordf_t line_spacing, + const Vec3d & cube_center); static void expand_cube( - FillAdaptive_Internal::Cube *cube, - const std::vector &cubes_properties, - const Transform3d &rotation_matrix, - const AABBTreeIndirect::Tree3f &distanceTree, - const TriangleMesh &triangleMesh); + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d & rotation_matrix, + const AABBTreeIndirect::Tree3f &distance_tree, + const TriangleMesh & triangle_mesh, + int depth); }; } // namespace Slic3r From 5633526ecfa9215180a6009c4c300cbedf24fa83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Sep 2020 23:15:46 +0200 Subject: [PATCH 465/826] Enable changing adaptive infill density for different objects --- src/libslic3r/PrintObject.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d9c533939c..167be8a36b 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,17 +434,34 @@ void PrintObject::generate_support_material() std::unique_ptr PrintObject::prepare_adaptive_infill_data() { - const ConfigOptionPercent* opt_fill_density = this->print()->full_print_config().option("fill_density"); - const ConfigOptionFloatOrPercent* opt_infill_extrusion_width = this->print()->full_print_config().option("infill_extrusion_width"); + float fill_density = 0; + float infill_extrusion_width = 0; - if(opt_fill_density == nullptr || opt_infill_extrusion_width == nullptr || opt_fill_density->value <= 0 || opt_infill_extrusion_width->value <= 0) + // Compute the average of above parameters over all layers + for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) + { + for (size_t region_id = 0; region_id < this->m_layers[layer_idx]->m_regions.size(); ++region_id) + { + LayerRegion *layerm = this->m_layers[layer_idx]->m_regions[region_id]; + + // Check if region_id is used for this layer + if(!layerm->fill_surfaces.surfaces.empty()) { + const PrintRegionConfig ®ion_config = layerm->region()->config(); + + fill_density += region_config.fill_density; + infill_extrusion_width += region_config.infill_extrusion_width; + } + } + } + + fill_density /= this->m_layers.size(); + infill_extrusion_width /= this->m_layers.size(); + + if(fill_density <= 0 || infill_extrusion_width <= 0) { return std::unique_ptr{}; } - float fill_density = opt_fill_density->value; - float infill_extrusion_width = opt_infill_extrusion_width->value; - coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); TriangleMesh mesh = this->model_object()->raw_mesh(); From 5e9399247c414dc8b41db9b8ab8622754a0209eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 7 Sep 2020 09:14:06 +0200 Subject: [PATCH 466/826] Check if exist any boundary polyline --- src/libslic3r/Fill/FillAdaptive.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 577ba7e610..bf9cd7f9d2 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -51,13 +51,19 @@ void FillAdaptive::_fill_surface_single( if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) { boundary_polylines.push_back(polyline); - } else { + } + else + { non_boundary_polylines.push_back(polyline); } } - boundary_polylines = chain_polylines(boundary_polylines); - FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + if(!boundary_polylines.empty()) + { + boundary_polylines = chain_polylines(boundary_polylines); + FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + } + polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end()); } From 2f9dd9d9e80a628c792d209ae3a4e694f4abd333 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 9 Sep 2020 15:03:51 +0200 Subject: [PATCH 467/826] Completed implementation of 'File->GCode preview...' command --- src/PrusaSlicer.cpp | 123 ++++++++++++++++++++++------------ src/libslic3r/GCodeReader.cpp | 7 ++ src/slic3r/GUI/GUI_App.cpp | 1 - 3 files changed, 87 insertions(+), 44 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 9874a9d4d7..7972d49b76 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -140,37 +140,57 @@ int CLI::run(int argc, char **argv) m_print_config.apply(config); } - // Read input file(s) if any. - for (const std::string &file : m_input_files) { - if (! boost::filesystem::exists(file)) { - boost::nowide::cerr << "No such file: " << file << std::endl; - exit(1); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + // are we starting as gcodeviewer ? + for (auto it = m_actions.begin(); it != m_actions.end(); ++it) { + if (*it == "gcodeviewer") { + start_gui = true; + start_as_gcodeviewer = true; + m_actions.erase(it); + break; } - Model model; - try { - // When loading an AMF or 3MF, config is imported as well, including the printer technology. - DynamicPrintConfig config; - model = Model::read_from_file(file, &config, true); - PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); - if (printer_technology == ptUnknown) { - printer_technology = other_printer_technology; - } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { - boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + + // Read input file(s) if any. +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (!start_as_gcodeviewer) { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + for (const std::string& file : m_input_files) { + if (!boost::filesystem::exists(file)) { + boost::nowide::cerr << "No such file: " << file << std::endl; + exit(1); + } + Model model; + try { + // When loading an AMF or 3MF, config is imported as well, including the printer technology. + DynamicPrintConfig config; + model = Model::read_from_file(file, &config, true); + PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + } + else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + return 1; + } + // config is applied to m_print_config before the current m_config values. + config += std::move(m_print_config); + m_print_config = std::move(config); + } + catch (std::exception& e) { + boost::nowide::cerr << file << ": " << e.what() << std::endl; return 1; } - // config is applied to m_print_config before the current m_config values. - config += std::move(m_print_config); - m_print_config = std::move(config); - } catch (std::exception &e) { - boost::nowide::cerr << file << ": " << e.what() << std::endl; - return 1; + if (model.objects.empty()) { + boost::nowide::cerr << "Error: file is empty: " << file << std::endl; + continue; + } + m_models.push_back(model); } - if (model.objects.empty()) { - boost::nowide::cerr << "Error: file is empty: " << file << std::endl; - continue; - } - m_models.push_back(model); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // Apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) @@ -529,9 +549,11 @@ int CLI::run(int argc, char **argv) << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; */ } +#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION } else if (opt_key == "gcodeviewer") { - start_gui = true; + start_gui = true; start_as_gcodeviewer = true; +#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION } else { boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; return 1; @@ -555,28 +577,43 @@ int CLI::run(int argc, char **argv) // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] { +#else gui->CallAfter([gui, this, &load_configs] { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (!gui->initialized()) { return; } + +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + if (start_as_gcodeviewer) { + if (!m_input_files.empty()) + gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0])); + } else { +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - gui->mainframe->load_config(m_print_config); + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + gui->mainframe->load_config(m_print_config); #endif - if (! load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (! m_input_files.empty()) - gui->plater()->load_files(m_input_files, true, true); - if (! m_extra_config.empty()) - gui->mainframe->load_config(m_extra_config); + if (!load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + gui->mainframe->load_config_file(load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (!m_input_files.empty()) + gui->plater()->load_files(m_input_files, true, true); + if (!m_extra_config.empty()) + gui->mainframe->load_config(m_extra_config); +#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + } +#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION }); int result = wxEntry(argc, argv); return result; diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index ab77b01413..ee24d5bb76 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -1,6 +1,9 @@ #include "GCodeReader.hpp" #include #include +#if ENABLE_GCODE_VIEWER +#include +#endif // ENABLE_GCODE_VIEWER #include #include #include @@ -113,7 +116,11 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair Date: Wed, 9 Sep 2020 15:55:06 +0200 Subject: [PATCH 468/826] Refactoring of adaptive cubic infill: Don't create an octree for the infill if it is not needed. --- src/libslic3r/Fill/FillAdaptive.cpp | 97 ++++++++++++++++++++++++++--- src/libslic3r/Fill/FillAdaptive.hpp | 8 +++ src/libslic3r/PrintObject.cpp | 33 +--------- 3 files changed, 100 insertions(+), 38 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index bf9cd7f9d2..b1a40047d4 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -3,12 +3,93 @@ #include "../Surface.hpp" #include "../Geometry.hpp" #include "../AABBTreeIndirect.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" #include "../ShortestPath.hpp" #include "FillAdaptive.hpp" namespace Slic3r { +std::pair adaptive_fill_line_spacing(const PrintObject &print_object) +{ + // Output, spacing for icAdaptiveCubic and icSupportCubic + double adaptive_line_spacing = 0.; + double support_line_spacing = 0.; + + enum class Tristate { + Yes, + No, + Maybe + }; + struct RegionFillData { + Tristate has_adaptive_infill; + Tristate has_support_infill; + double density; + double extrusion_width; + }; + std::vector region_fill_data; + region_fill_data.reserve(print_object.print()->regions().size()); + bool build_octree = false; + for (const PrintRegion *region : print_object.print()->regions()) { + const PrintRegionConfig &config = region->config(); + bool nonempty = config.fill_density > 0; + bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic; + bool has_support_infill = nonempty && false; // config.fill_pattern == icSupportCubic; + region_fill_data.push_back(RegionFillData({ + has_adaptive_infill ? Tristate::Maybe : Tristate::No, + has_support_infill ? Tristate::Maybe : Tristate::No, + config.fill_density, + config.infill_extrusion_width + })); + build_octree |= has_adaptive_infill || has_support_infill; + } + + if (build_octree) { + // Compute the average of above parameters over all layers + for (const Layer *layer : print_object.layers()) + for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) { + RegionFillData &rd = region_fill_data[region_id]; + if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + rd.has_adaptive_infill = Tristate::Yes; + if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + rd.has_support_infill = Tristate::Yes; + } + + double adaptive_fill_density = 0.; + double adaptive_infill_extrusion_width = 0.; + int adaptive_cnt = 0; + double support_fill_density = 0.; + double support_infill_extrusion_width = 0.; + int support_cnt = 0; + + for (const RegionFillData &rd : region_fill_data) { + if (rd.has_adaptive_infill == Tristate::Yes) { + adaptive_fill_density += rd.density; + adaptive_infill_extrusion_width += rd.extrusion_width; + ++ adaptive_cnt; + } else if (rd.has_support_infill == Tristate::Yes) { + support_fill_density += rd.density; + support_infill_extrusion_width += rd.extrusion_width; + ++ support_cnt; + } + } + + auto to_line_spacing = [](int cnt, double density, double extrusion_width) { + if (cnt) { + density /= double(cnt); + extrusion_width /= double(cnt); + return extrusion_width / ((density / 100.0f) * 0.333333333f); + } else + return 0.; + }; + adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width); + support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width); + } + + return std::make_pair(adaptive_line_spacing, support_line_spacing); +} + void FillAdaptive::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, @@ -21,7 +102,7 @@ void FillAdaptive::_fill_surface_single( this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), this->z, this->adapt_fill_octree->origin,infill_lines_dir, this->adapt_fill_octree->cubes_properties, - this->adapt_fill_octree->cubes_properties.size() - 1); + int(this->adapt_fill_octree->cubes_properties.size()) - 1); Polylines all_polylines; all_polylines.reserve(infill_lines_dir[0].size() * 3); @@ -131,16 +212,16 @@ void FillAdaptive::generate_infill_lines( Point to(-from.x(), from.y()); // Relative to cube center - float rotation_angle = (2.0 * M_PI) / 3.0; + double rotation_angle = (2.0 * M_PI) / 3.0; for (Lines &lines : dir_lines_out) { Vec3d offset = cube->center - origin; Point from_abs(from), to_abs(to); - from_abs.x() += scale_(offset.x()); - from_abs.y() += scale_(offset.y()); - to_abs.x() += scale_(offset.x()); - to_abs.y() += scale_(offset.y()); + from_abs.x() += int(scale_(offset.x())); + from_abs.y() += int(scale_(offset.y())); + to_abs.x() += int(scale_(offset.x())); + to_abs.y() += int(scale_(offset.y())); // lines.emplace_back(from_abs, to_abs); this->connect_lines(lines, Line(from_abs, to_abs)); @@ -161,7 +242,7 @@ void FillAdaptive::generate_infill_lines( void FillAdaptive::connect_lines(Lines &lines, Line new_line) { - int eps = scale_(0.10); + auto eps = int(scale_(0.10)); for (size_t i = 0; i < lines.size(); ++i) { if (std::abs(new_line.a.x() - lines[i].b.x()) < eps && std::abs(new_line.a.y() - lines[i].b.y()) < eps) @@ -227,7 +308,7 @@ std::unique_ptr FillAdaptive::build_octree( triangle_mesh.its.vertices, triangle_mesh.its.indices); auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh, cubes_properties.size() - 1); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1); return octree; } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 14694b766c..dd7394384c 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -7,6 +7,8 @@ namespace Slic3r { +class PrintObject; + namespace FillAdaptive_Internal { struct CubeProperties @@ -82,6 +84,12 @@ public: int depth); }; +// Calculate line spacing for +// 1) adaptive cubic infill +// 2) adaptive internal support cubic infill +// Returns zero for a particular infill type if no such infill is to be generated. +std::pair adaptive_fill_line_spacing(const PrintObject &print_object); + } // namespace Slic3r #endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 087d3fe3c5..41a01e653c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,44 +434,17 @@ void PrintObject::generate_support_material() std::unique_ptr PrintObject::prepare_adaptive_infill_data() { - float fill_density = 0; - float infill_extrusion_width = 0; - - // Compute the average of above parameters over all layers - for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) - { - for (size_t region_id = 0; region_id < this->m_layers[layer_idx]->m_regions.size(); ++region_id) - { - LayerRegion *layerm = this->m_layers[layer_idx]->m_regions[region_id]; - - // Check if region_id is used for this layer - if(!layerm->fill_surfaces.surfaces.empty()) { - const PrintRegionConfig ®ion_config = layerm->region()->config(); - - fill_density += region_config.fill_density; - infill_extrusion_width += region_config.infill_extrusion_width; - } - } - } - - fill_density /= this->m_layers.size(); - infill_extrusion_width /= this->m_layers.size(); - - if(fill_density <= 0 || infill_extrusion_width <= 0) - { + auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); + if (adaptive_line_spacing == 0.) return std::unique_ptr{}; - } - - coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); TriangleMesh mesh = this->model_object()->raw_mesh(); mesh.transform(m_trafo, true); // Apply XY shift mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); - // Center of the first cube in octree Vec3d mesh_origin = mesh.bounding_box().center(); - return FillAdaptive::build_octree(mesh, line_spacing, mesh_origin); + return FillAdaptive::build_octree(mesh, adaptive_line_spacing, mesh_origin); } void PrintObject::clear_layers() From 88457bf4124310bdaca8d37e5b16e122b415e348 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Sep 2020 08:49:50 +0200 Subject: [PATCH 469/826] Tech ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION set as default --- src/PrusaSlicer.cpp | 33 ++-- src/libslic3r/AppConfig.cpp | 4 +- src/libslic3r/AppConfig.hpp | 12 +- src/libslic3r/Technologies.hpp | 1 - src/slic3r/GUI/GCodeViewer.cpp | 8 - src/slic3r/GUI/GLCanvas3D.cpp | 16 -- src/slic3r/GUI/GUI_App.cpp | 40 ++--- src/slic3r/GUI/GUI_App.hpp | 16 +- src/slic3r/GUI/GUI_Preview.cpp | 4 - src/slic3r/GUI/GUI_Preview.hpp | 10 +- src/slic3r/GUI/KBShortcutsDialog.cpp | 8 - src/slic3r/GUI/MainFrame.cpp | 233 ++------------------------- src/slic3r/GUI/MainFrame.hpp | 35 +--- src/slic3r/GUI/Plater.cpp | 50 +----- 14 files changed, 77 insertions(+), 393 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 7972d49b76..05e84b9416 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -140,7 +140,7 @@ int CLI::run(int argc, char **argv) m_print_config.apply(config); } -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER // are we starting as gcodeviewer ? for (auto it = m_actions.begin(); it != m_actions.end(); ++it) { if (*it == "gcodeviewer") { @@ -150,12 +150,12 @@ int CLI::run(int argc, char **argv) break; } } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // Read input file(s) if any. -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (!start_as_gcodeviewer) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER for (const std::string& file : m_input_files) { if (!boost::filesystem::exists(file)) { boost::nowide::cerr << "No such file: " << file << std::endl; @@ -188,9 +188,9 @@ int CLI::run(int argc, char **argv) } m_models.push_back(model); } -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // Apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) @@ -549,11 +549,11 @@ int CLI::run(int argc, char **argv) << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; */ } -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if !ENABLE_GCODE_VIEWER } else if (opt_key == "gcodeviewer") { start_gui = true; start_as_gcodeviewer = true; -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // !ENABLE_GCODE_VIEWER } else { boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; return 1; @@ -563,11 +563,11 @@ int CLI::run(int argc, char **argv) if (start_gui) { #ifdef SLIC3R_GUI // #ifdef USE_WX -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER GUI::GUI_App* gui = new GUI::GUI_App(start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); #else GUI::GUI_App *gui = new GUI::GUI_App(); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1"; if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) { @@ -577,22 +577,21 @@ int CLI::run(int argc, char **argv) // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] { #else gui->CallAfter([gui, this, &load_configs] { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - +#endif // ENABLE_GCODE_VIEWER if (!gui->initialized()) { return; } -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (start_as_gcodeviewer) { if (!m_input_files.empty()) gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0])); } else { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER_AS #if 0 // Load the cummulative config over the currently active profiles. //FIXME if multiple configs are loaded, only the last one will have an effect. @@ -611,9 +610,9 @@ int CLI::run(int argc, char **argv) gui->plater()->load_files(m_input_files, true, true); if (!m_extra_config.empty()) gui->mainframe->load_config(m_extra_config); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER }); int result = wxEntry(argc, argv); return result; diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index db3bd78ddf..2d96e0b50d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -179,10 +179,10 @@ std::string AppConfig::load() void AppConfig::save() { -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (!m_save_enabled) return; -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // The config is first written to a file with a PID suffix and then moved // to avoid race conditions with multiple instances of Slic3r diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 3f4ce20089..f22a6314ab 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -18,9 +18,9 @@ public: AppConfig() : m_dirty(false), m_orig_version(Semver::invalid()), -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER m_save_enabled(true), -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER m_legacy_datadir(false) { this->reset(); @@ -160,9 +160,9 @@ public: bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const { return get_3dmouse_device_numeric_value(name, "swap_yz", swap); } -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER void enable_save(bool enable) { m_save_enabled = enable; } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER static const std::string SECTION_FILAMENTS; static const std::string SECTION_MATERIALS; @@ -190,10 +190,10 @@ private: bool m_dirty; // Original version found in the ini file before it was overwritten Semver m_orig_version; -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER // Whether or not calls to save() should take effect bool m_save_enabled; -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // Whether the existing version is before system profiles & configuration updating bool m_legacy_datadir; }; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 2dbad472fe..a0484b259c 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,6 +59,5 @@ #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION (1 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2b9bf8ca46..5984afaa45 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -339,11 +339,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& reset(); load_toolpaths(gcode_result); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_editor()) -#else - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION load_shells(print, initialized); else { Pointfs bed_shape; @@ -879,11 +875,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) for (size_t i = 0; i < m_vertices_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_gcode_viewer()) -#else - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // for the gcode viewer we need all moves to correctly size the printbed m_paths_bounding_box.merge(move.position.cast()); else { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2f9f9464cd..00034087c7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2732,11 +2732,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_editor()) -#else - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); } @@ -4306,11 +4302,7 @@ void GLCanvas3D::update_ui_from_settings() #endif // ENABLE_RETINA_GL #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_editor()) -#else - if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); #else bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; @@ -5413,11 +5405,7 @@ void GLCanvas3D::_render_background() const { #if ENABLE_GCODE_VIEWER bool use_error_color = false; -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_editor()) { -#else - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION use_error_color = m_dynamic_background_enabled; if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); @@ -7146,11 +7134,7 @@ void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning if (!m_volumes.empty()) show = _is_any_volume_outside(); else { -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_editor()) { -#else - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index aeac415f7b..f6b0a44146 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -434,15 +434,15 @@ static void generic_exception_handle() IMPLEMENT_APP(GUI_App) -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER GUI_App::GUI_App(EAppMode mode) #else GUI_App::GUI_App() -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER : wxApp() -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER , m_app_mode(mode) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER , m_em_unit(10) , m_imgui(new ImGuiWrapper()) , m_wizard(nullptr) @@ -498,11 +498,11 @@ void GUI_App::init_app_config() if (!app_config) app_config = new AppConfig(); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (is_gcode_viewer()) // disable config save to avoid to mess it up for the editor app_config->enable_save(false); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // load settings app_conf_exists = app_config->exists(); @@ -577,11 +577,11 @@ bool GUI_App::on_init_inner() wxInitAllImageHandlers(); wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG); #else wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER DecorateSplashScreen(bmp); @@ -594,9 +594,9 @@ bool GUI_App::on_init_inner() // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (is_editor()) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER #ifdef __WXMSW__ associate_3mf_files(); #endif // __WXMSW__ @@ -611,9 +611,9 @@ bool GUI_App::on_init_inner() } } }); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // initialize label colors and fonts init_label_colours(); @@ -641,9 +641,9 @@ bool GUI_App::on_init_inner() Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); // application frame -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (is_editor()) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER scrn->SetText(_L("Creating settings tabs...")); mainframe = new MainFrame(); @@ -679,9 +679,9 @@ bool GUI_App::on_init_inner() static bool once = true; if (once) { once = false; -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (preset_updater != nullptr) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER check_updates(false); CallAfter([this] { @@ -689,9 +689,9 @@ bool GUI_App::on_init_inner() preset_updater->slic3r_update_notify(); preset_updater->sync(preset_bundle); }); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER #ifdef _WIN32 //sets window property to mainframe so other instances can indentify it @@ -700,7 +700,7 @@ bool GUI_App::on_init_inner() } }); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (is_gcode_viewer()) { mainframe->update_layout(); if (plater_ != nullptr) @@ -708,7 +708,7 @@ bool GUI_App::on_init_inner() plater_->set_printer_technology(ptFFF); } else -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER load_current_presets(); mainframe->Show(true); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index d63825de3e..9bf470a42d 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -94,7 +94,7 @@ static wxString dots("…", wxConvUTF8); class GUI_App : public wxApp { -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER public: enum class EAppMode : unsigned char { @@ -103,13 +103,13 @@ public: }; private: -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER bool m_initialized { false }; bool app_conf_exists{ false }; -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER EAppMode m_app_mode{ EAppMode::Editor }; -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER wxColour m_color_label_modified; wxColour m_color_label_sys; @@ -144,18 +144,18 @@ public: bool OnInit() override; bool initialized() const { return m_initialized; } -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER explicit GUI_App(EAppMode mode = EAppMode::Editor); #else GUI_App(); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER ~GUI_App() override; -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER EAppMode get_app_mode() const { return m_app_mode; } bool is_editor() const { return m_app_mode == EAppMode::Editor; } bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER static std::string get_gl_info(bool format_as_html, bool extensions); wxGLContext* init_glcontext(wxGLCanvas& canvas); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 530b3358e2..8ea54c6f18 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1234,11 +1234,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_editor() && !has_layers) -#else - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer && !has_layers) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else if (! has_layers) #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 6297663067..c0a457d9cd 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -194,9 +194,7 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, #if ENABLE_GCODE_VIEWER void update_bottom_toolbar(); void update_moves_slider(); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION void hide_layers_slider(); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER private: @@ -205,16 +203,12 @@ private: void bind_event_handlers(); void unbind_event_handlers(); -#if ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - void hide_layers_slider(); -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION -#else +#if !ENABLE_GCODE_VIEWER void show_hide_ui_elements(const std::string& what); void reset_sliders(bool reset_all); void update_sliders(const std::vector& layers_z, bool keep_z_range = false); -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER void on_size(wxSizeEvent& evt); void on_choice_view_type(wxCommandEvent& evt); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 632bc48ed0..0875b76a48 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -95,15 +95,7 @@ void KBShortcutsDialog::fill_shortcuts() const std::string& alt = GUI::shortkey_alt_prefix(); #if ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - bool is_gcode_viewer = wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer; -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_editor()) { -#else - if (!is_gcode_viewer) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER Shortcuts commands_shortcuts = { // File diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 5e3fe4cde1..2589691a3b 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -95,15 +95,15 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); } #else -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER switch (wxGetApp().get_app_mode()) { default: case GUI_App::EAppMode::Editor: { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER break; } case GUI_App::EAppMode::GCodeViewer: @@ -112,7 +112,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S break; } } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER #endif // _WIN32 // initialize status bar @@ -126,15 +126,10 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // initialize tabpanel and menubar init_tabpanel(); #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_gcode_viewer()) init_menubar_as_gcodeviewer(); else init_menubar_as_editor(); -#else - init_menubar_as_editor(); - init_menubar_as_gcodeviewer(); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #if _WIN32 // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad @@ -165,9 +160,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S sizer->Add(m_main_sizer, 1, wxEXPAND); SetSizer(sizer); // initialize layout from config -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (wxGetApp().is_editor()) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER update_layout(); sizer->SetSizeHints(this); Fit(); @@ -320,17 +315,10 @@ void MainFrame::update_layout() }; #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION ESettingsLayout layout = wxGetApp().is_gcode_viewer() ? ESettingsLayout::GCodeViewer : (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old); -#else - ESettingsLayout layout = (m_mode == EMode::GCodeViewer) ? ESettingsLayout::GCodeViewer : - (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : - wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : @@ -402,12 +390,10 @@ void MainFrame::update_layout() case ESettingsLayout::GCodeViewer: { m_main_sizer->Add(m_plater, 1, wxEXPAND); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); m_plater->enable_view_toolbar(false); m_plater->get_collapse_toolbar().set_enabled(false); m_plater->collapse_sidebar(true); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION m_plater->Show(); break; } @@ -514,17 +500,6 @@ void MainFrame::shutdown() m_settings_dialog.Close(); if (m_plater != nullptr) { -#if ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - // restore sidebar if it was hidden when switching to gcode viewer mode - if (m_restore_from_gcode_viewer.collapsed_sidebar) - m_plater->collapse_sidebar(false); - - // restore sla printer if it was deselected when switching to gcode viewer mode - if (m_restore_from_gcode_viewer.sla_technology) - m_plater->set_printer_technology(ptSLA); -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION -#endif // ENABLE_GCODE_VIEWER // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). m_plater->get_mouse3d_controller().shutdown(); @@ -625,9 +600,9 @@ void MainFrame::init_tabpanel() // or when the preset's "modified" status changes. Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this); // #ys_FIXME_to_delete -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (wxGetApp().is_editor()) -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER create_preset_tabs(); if (m_plater) { @@ -1093,17 +1068,6 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); fileMenu->AppendSeparator(); -#if ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), - [this](wxCommandEvent&) { - if (m_plater->model().objects.empty() || - wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES) - set_mode(EMode::GCodeViewer); - }, "", nullptr); -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION -#endif // ENABLE_GCODE_VIEWER append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview") + dots, _L("Open G-code viewer"), [this](wxCommandEvent&) { start_new_gcodeviewer_open_file(this); }, "", nullptr); fileMenu->AppendSeparator(); @@ -1319,7 +1283,6 @@ void MainFrame::init_menubar() // assign menubar to frame after appending items, otherwise special items // will not be handled correctly #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION m_menubar = new wxMenuBar(); m_menubar->Append(fileMenu, _L("&File")); if (editMenu) m_menubar->Append(editMenu, _L("&Edit")); @@ -1329,17 +1292,6 @@ void MainFrame::init_menubar() wxGetApp().add_config_menu(m_menubar); m_menubar->Append(helpMenu, _L("&Help")); SetMenuBar(m_menubar); -#else - m_editor_menubar = new wxMenuBar(); - m_editor_menubar->Append(fileMenu, _L("&File")); - if (editMenu) m_editor_menubar->Append(editMenu, _L("&Edit")); - m_editor_menubar->Append(windowMenu, _L("&Window")); - if (viewMenu) m_editor_menubar->Append(viewMenu, _L("&View")); - // Add additional menus from C++ - wxGetApp().add_config_menu(m_editor_menubar); - m_editor_menubar->Append(helpMenu, _L("&Help")); - SetMenuBar(m_editor_menubar); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else auto menubar = new wxMenuBar(); menubar->Append(fileMenu, _L("&File")); @@ -1356,11 +1308,7 @@ void MainFrame::init_menubar() // This fixes a bug on Mac OS where the quit command doesn't emit window close events // wx bug: https://trac.wxwidgets.org/ticket/18328 #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION wxMenu* apple_menu = m_menubar->OSXGetAppleMenu(); -#else - wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu(); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else wxMenu *apple_menu = menubar->OSXGetAppleMenu(); #endif // ENABLE_GCODE_VIEWER @@ -1387,11 +1335,6 @@ void MainFrame::init_menubar_as_gcodeviewer() append_menu_item(fileMenu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"), [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, [this]() {return can_export_toolpaths(); }, this); -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - fileMenu->AppendSeparator(); - append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"), - [this](wxCommandEvent&) { set_mode(EMode::Editor); }); -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); @@ -1407,28 +1350,16 @@ void MainFrame::init_menubar_as_gcodeviewer() // helpmenu auto helpMenu = generate_help_menu(); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION m_menubar = new wxMenuBar(); m_menubar->Append(fileMenu, _L("&File")); if (viewMenu != nullptr) m_menubar->Append(viewMenu, _L("&View")); m_menubar->Append(helpMenu, _L("&Help")); SetMenuBar(m_menubar); -#else - m_gcodeviewer_menubar = new wxMenuBar(); - m_gcodeviewer_menubar->Append(fileMenu, _L("&File")); - if (viewMenu != nullptr) - m_gcodeviewer_menubar->Append(viewMenu, _L("&View")); - m_gcodeviewer_menubar->Append(helpMenu, _L("&Help")); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #ifdef __APPLE__ // This fixes a bug on Mac OS where the quit command doesn't emit window close events // wx bug: https://trac.wxwidgets.org/ticket/18328 -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION wxMenu* apple_menu = m_menubar->OSXGetAppleMenu(); -#else - wxMenu* apple_menu = m_gcodeviewer_menubar->OSXGetAppleMenu(); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (apple_menu != nullptr) { apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent&) { Close(); @@ -1436,150 +1367,14 @@ void MainFrame::init_menubar_as_gcodeviewer() } #endif // __APPLE__ } - -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION -void MainFrame::set_mode(EMode mode) -{ - if (m_mode == mode) - return; - - wxBusyCursor busy; - - m_mode = mode; - switch (m_mode) - { - default: - case EMode::Editor: - { - update_layout(); - select_tab(0); - - m_plater->reset(); - m_plater->reset_gcode_toolpaths(); - - m_plater->Freeze(); - - // reinitialize undo/redo stack - m_plater->clear_undo_redo_stack_main(); - m_plater->take_snapshot(_L("New Project")); - - // restore sla printer if it was deselected when switching to gcode viewer mode - if (m_restore_from_gcode_viewer.sla_technology) { - m_plater->set_printer_technology(ptSLA); - m_restore_from_gcode_viewer.sla_technology = false; - } - - // switch view - m_plater->select_view_3D("3D"); - m_plater->select_view("iso"); - - // switch printbed - m_plater->set_bed_shape(); - - // switch menubar - SetMenuBar(m_editor_menubar); - - // show toolbars - m_plater->enable_view_toolbar(true); - - if (m_restore_from_gcode_viewer.collapse_toolbar_enabled) { - m_plater->get_collapse_toolbar().set_enabled(true); - m_restore_from_gcode_viewer.collapse_toolbar_enabled = false; - } - - // show sidebar - if (m_restore_from_gcode_viewer.collapsed_sidebar) { - m_plater->collapse_sidebar(false); - m_restore_from_gcode_viewer.collapsed_sidebar = false; - } - - m_plater->Thaw(); - -// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); - // Load the icon either from the exe, or from the ico file. -#if _WIN32 - { - - TCHAR szExeFileName[MAX_PATH]; - GetModuleFileName(nullptr, szExeFileName, MAX_PATH); - SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); - } -#else - SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -#endif // _WIN32 -#if ENABLE_GCODE_VIEWER_TASKBAR_ICON - if (m_taskbar_icon != nullptr) { - m_taskbar_icon->RemoveIcon(); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer"); - } -#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON - - break; - } - case EMode::GCodeViewer: - { - update_layout(); - - m_plater->reset(); - m_plater->reset_last_loaded_gcode(); - m_plater->reset_gcode_toolpaths(); - - m_plater->Freeze(); - - // reinitialize undo/redo stack - m_plater->clear_undo_redo_stack_main(); - m_plater->take_snapshot(_L("New Project")); - - // switch to FFF printer mode - m_restore_from_gcode_viewer.sla_technology = m_plater->set_printer_technology(ptFFF); - - // switch view - m_plater->select_view_3D("Preview"); - m_plater->select_view("iso"); - - // switch printbed - m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); - - // switch menubar - SetMenuBar(m_gcodeviewer_menubar); - - // hide toolbars - m_plater->enable_view_toolbar(false); - - if (wxGetApp().app_config->get("show_collapse_button") == "1") { - m_plater->get_collapse_toolbar().set_enabled(false); - m_restore_from_gcode_viewer.collapse_toolbar_enabled = true; - } - - // hide sidebar - if (wxGetApp().app_config->get("collapsed_sidebar") != "1") { - m_plater->collapse_sidebar(true); - m_restore_from_gcode_viewer.collapsed_sidebar = true; - } - - m_plater->Thaw(); - - SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG)); -#if ENABLE_GCODE_VIEWER_TASKBAR_ICON - if (m_taskbar_icon != nullptr) { - m_taskbar_icon->RemoveIcon(); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); - } -#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON - - break; - } - } -} -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER void MainFrame::update_menubar() { -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (wxGetApp().is_gcode_viewer()) return; -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER const bool is_fff = plater()->printer_technology() == ptFFF; @@ -2069,10 +1864,10 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX, "settings_dialog"), m_main_frame(mainframe) { -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (wxGetApp().is_gcode_viewer()) return; -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER #if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling @@ -2146,10 +1941,10 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) { -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (wxGetApp().is_gcode_viewer()) return; -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER const int& em = em_unit(); const wxSize& size = wxSize(85 * em, 50 * em); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 867e11e86b..868a684925 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -72,21 +72,7 @@ class MainFrame : public DPIFrame wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; #if ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - wxMenuBar* m_menubar{ nullptr }; -#else - wxMenuBar* m_editor_menubar{ nullptr }; - wxMenuBar* m_gcodeviewer_menubar{ nullptr }; - - struct RestoreFromGCodeViewer - { - bool collapsed_sidebar{ false }; - bool collapse_toolbar_enabled{ false }; - bool sla_technology{ false }; - }; - - RestoreFromGCodeViewer m_restore_from_gcode_viewer; -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION + wxMenuBar* m_menubar{ nullptr }; #endif // ENABLE_GCODE_VIEWER #if 0 @@ -149,20 +135,6 @@ class MainFrame : public DPIFrame ESettingsLayout m_layout{ ESettingsLayout::Unknown }; -#if ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION -public: - enum class EMode : unsigned char - { - Editor, - GCodeViewer - }; - -private: - EMode m_mode{ EMode::Editor }; -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION -#endif // ENABLE_GCODE_VIEWER - protected: virtual void on_dpi_changed(const wxRect &suggested_rect); virtual void on_sys_color_changed() override; @@ -190,11 +162,6 @@ public: #if ENABLE_GCODE_VIEWER void init_menubar_as_editor(); void init_menubar_as_gcodeviewer(); - -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - EMode get_mode() const { return m_mode; } - void set_mode(EMode mode); -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #else void init_menubar(); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 061084f57d..b4f900b0ce 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1369,9 +1369,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi this->MSWUpdateDragImageOnLeave(); #endif // WIN32 -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION if (wxGetApp().is_gcode_viewer()) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION // gcode section for (const auto& filename : filenames) { fs::path path(into_path(filename)); @@ -1385,33 +1383,11 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi return false; } else if (paths.size() == 1) { -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION plater->load_gcode(from_path(paths.front())); return true; -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - } - else { - if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { - - if (plater->model().objects.empty() || - wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { - wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer); - plater->load_gcode(from_path(paths.front())); - return true; - } - } - return false; - } -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION } -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION return false; } -#endif //ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION #endif // ENABLE_GCODE_VIEWER // editor section @@ -1423,18 +1399,6 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi return false; } -#if ENABLE_GCODE_VIEWER -#if !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { - if (wxMessageDialog((wxWindow*)plater, _L("Do you want to exit G-code preview ?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop model file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) - wxGetApp().mainframe->set_mode(MainFrame::EMode::Editor); - else - return false; - } -#endif // !ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION -#endif // ENABLE_GCODE_VIEWER - wxString snapshot_label; assert(! paths.empty()); if (paths.size() == 1) { @@ -1983,13 +1947,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership q->Layout(); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER set_current_panel(wxGetApp().is_editor() ? (wxPanel*)view3D : (wxPanel*)preview); if (wxGetApp().is_gcode_viewer()) preview->hide_layers_slider(); #else set_current_panel(view3D); -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); @@ -2009,9 +1973,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) #endif /* _WIN32 */ notification_manager = new NotificationManager(this->q); -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER if (wxGetApp().is_editor()) { -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); @@ -2038,9 +2002,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); #endif /* _WIN32 */ -#if ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#if ENABLE_GCODE_VIEWER } -#endif // ENABLE_GCODE_VIEWER_AS_STANDALONE_APPLICATION +#endif // ENABLE_GCODE_VIEWER // Initialize the Undo / Redo stack with a first snapshot. this->take_snapshot(_L("New Project")); @@ -5408,7 +5372,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->set_printer_technology(config.opt_enum(opt_key)); // print technology is changed, so we should to update a search list p->sidebar->update_searcher(); +#if ENABLE_GCODE_VIEWER p->reset_gcode_toolpaths(); +#endif // ENABLE_GCODE_VIEWER } else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { bed_shape_changed = true; From ea9a8b7e93942be8a3335c99b76a474e5bb480fc Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Sep 2020 09:43:45 +0200 Subject: [PATCH 470/826] Hides view toolbar in gcode viewer --- src/slic3r/GUI/MainFrame.cpp | 1 - src/slic3r/GUI/Plater.cpp | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 2589691a3b..06cb75efa3 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -391,7 +391,6 @@ void MainFrame::update_layout() { m_main_sizer->Add(m_plater, 1, wxEXPAND); m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); - m_plater->enable_view_toolbar(false); m_plater->get_collapse_toolbar().set_enabled(false); m_plater->collapse_sidebar(true); m_plater->Show(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4f900b0ce..f7fd608ba8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4001,7 +4001,11 @@ bool Plater::priv::init_view_toolbar() return false; view_toolbar.select_item("3D"); - view_toolbar.set_enabled(true); + +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER + view_toolbar.set_enabled(true); return true; } From a9a99de93926d19d8e3fac93d706a3e3f5b81f7b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 31 Aug 2020 10:37:42 +0200 Subject: [PATCH 471/826] Enable all tests for support point generator --- tests/sla_print/sla_supptgen_tests.cpp | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index 1d7a3ebee2..024f115b0b 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -105,26 +105,25 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") { REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); } -// FIXME: Not working yet -//TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowed]") { -// TriangleMesh mesh = make_cube(20., 20., 20.); +TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowed]") { + TriangleMesh mesh = make_cube(20., 20., 20.); -// hollow_mesh(mesh, HollowingConfig{}); + hollow_mesh(mesh, HollowingConfig{}); -// mesh.WriteOBJFile("cube_hollowed.obj"); + mesh.WriteOBJFile("cube_hollowed.obj"); -// auto bb = mesh.bounding_box(); -// auto h = float(bb.max.z() - bb.min.z()); -// Vec3f mv = bb.center().cast() - Vec3f{0.f, 0.f, 0.5f * h}; -// mesh.translate(-mv); -// mesh.require_shared_vertices(); + auto bb = mesh.bounding_box(); + auto h = float(bb.max.z() - bb.min.z()); + Vec3f mv = bb.center().cast() - Vec3f{0.f, 0.f, 0.5f * h}; + mesh.translate(-mv); + mesh.require_shared_vertices(); -// sla::SupportPointGenerator::Config cfg; -// sla::SupportPoints pts = calc_support_pts(mesh, cfg); -// sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON); + sla::SupportPointGenerator::Config cfg; + sla::SupportPoints pts = calc_support_pts(mesh, cfg); + sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON); -// REQUIRE(!pts.empty()); -//} + REQUIRE(!pts.empty()); +} TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]") { From 26d5c3036623f508bd4517e7258b1e1ea7cb44df Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 31 Aug 2020 10:38:24 +0200 Subject: [PATCH 472/826] Improvements to support point generator - Separate the 3 bands -- dangling, sloping and full overhanging -- regions and handle them with different support force deficits. - Use a heuristic for overhanging edges to increase the number of support points generated for them - Try to make overhangs and slopes deficit depend on stable area. --- src/libslic3r/SLA/SupportPointGenerator.cpp | 171 ++++++++++++++------ src/libslic3r/SLA/SupportPointGenerator.hpp | 24 ++- 2 files changed, 143 insertions(+), 52 deletions(-) diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 449269445d..7e884b6e3c 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -163,10 +163,10 @@ static std::vector make_layers( SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1]; //FIXME WTF? const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); - const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports - const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); + const float safe_angle = 35.f * (float(M_PI)/180.f); // smaller number - less supports + const float between_layers_offset = scaled(layer_height * std::tan(safe_angle)); const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports - const float slope_offset = float(scale_(layer_height / std::tan(slope_angle))); + const float slope_offset = scaled(layer_height * std::tan(slope_angle)); //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. for (SupportPointGenerator::Structure &top : layer_above.islands) { for (SupportPointGenerator::Structure &bottom : layer_below.islands) { @@ -181,6 +181,25 @@ static std::vector make_layers( Polygons bottom_polygons = top.polygons_below(); top.overhangs = diff_ex(top_polygons, bottom_polygons); if (! top.overhangs.empty()) { + + // Produce 2 bands around the island, a safe band for dangling overhangs + // and an unsafe band for sloped overhangs. + // These masks include the original island + auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); + auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); + + // Absolutely hopeless overhangs are those outside the unsafe band + top.overhangs = diff_ex(top_polygons, overh_mask); + + // Now cut out the supported core from the safe band + // and cut the safe band from the unsafe band to get distinct + // zones. + overh_mask = diff(overh_mask, dangl_mask); + dangl_mask = diff(dangl_mask, bottom_polygons); + + top.dangling_areas = intersection_ex(top_polygons, dangl_mask); + top.overhangs_slopes = intersection_ex(top_polygons, overh_mask); + top.overhangs_area = 0.f; std::vector> expolys_with_areas; for (ExPolygon &ex : top.overhangs) { @@ -196,8 +215,6 @@ static std::vector make_layers( overhangs_sorted.emplace_back(std::move(*p.first)); top.overhangs = std::move(overhangs_sorted); top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); - top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset)); - top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); } } } @@ -256,21 +273,9 @@ void SupportPointGenerator::process(const std::vector& slices, const // Now iterate over all polygons and append new points if needed. for (Structure &s : layer_top->islands) { // Penalization resulting from large diff from the last layer: -// s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area); s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); - //float force_deficit = s.support_force_deficit(m_config.tear_pressure()); - if (s.islands_below.empty()) { // completely new island - needs support no doubt - uniformly_cover({ *s.polygon }, s, point_grid, true); - } else if (! s.dangling_areas.empty()) { - // Let's see if there's anything that overlaps enough to need supports: - // What we now have in polygons needs support, regardless of what the forces are, so we can add them. - //FIXME is it an island point or not? Vojtech thinks it is. - uniformly_cover(s.dangling_areas, s, point_grid); - } else if (! s.overhangs_slopes.empty()) { - //FIXME add the support force deficit as a parameter, only cover until the defficiency is covered. - uniformly_cover(s.overhangs_slopes, s, point_grid); - } + add_support_points(s, point_grid); } m_throw_on_cancel(); @@ -288,6 +293,42 @@ void SupportPointGenerator::process(const std::vector& slices, const } } +void SupportPointGenerator::add_support_points(SupportPointGenerator::Structure &s, SupportPointGenerator::PointGrid3D &grid3d) +{ + // Select each type of surface (overrhang, dangling, slope), derive the support + // force deficit for it and call uniformly conver with the right params + + float tp = m_config.tear_pressure(); + float current = s.supports_force_total(); + static constexpr float SLOPE_DAMPING = .0015f; + static constexpr float DANGL_DAMPING = .09f; + + if (s.islands_below.empty()) { + // completely new island - needs support no doubt + // deficit is full, there is nothing below that would hold this island + uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfBoundaryOnly) ); + return; + } + + auto areafn = [](double sum, auto &p) { return sum + p.area() * SCALING_FACTOR * SCALING_FACTOR; }; + if (! s.dangling_areas.empty()) { + // Let's see if there's anything that overlaps enough to need supports: + // What we now have in polygons needs support, regardless of what the forces are, so we can add them. + + double a = std::accumulate(s.dangling_areas.begin(), s.dangling_areas.end(), 0., areafn); + uniformly_cover(s.dangling_areas, s, a * tp - current * DANGL_DAMPING * std::sqrt(1. - a / s.area), grid3d); + } + + if (! s.overhangs_slopes.empty()) { + double a = std::accumulate(s.overhangs_slopes.begin(), s.overhangs_slopes.end(), 0., areafn); + uniformly_cover(s.overhangs_slopes, s, a * tp - current * SLOPE_DAMPING * std::sqrt(1. - a / s.area), grid3d); + } + + if (! s.overhangs.empty()) { + uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d); + } +} + std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng) { // Triangulate the polygon with holes into triplets of 3D points. @@ -297,16 +338,16 @@ std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_m if (! triangles.empty()) { // Calculate area of each triangle. - std::vector areas; - areas.reserve(triangles.size() / 3); + auto areas = reserve_vector(triangles.size() / 3); + double aback = 0.; for (size_t i = 0; i < triangles.size(); ) { const Vec2f &a = triangles[i ++]; const Vec2f v1 = triangles[i ++] - a; const Vec2f v2 = triangles[i ++] - a; - areas.emplace_back(0.5f * std::abs(cross2(v1, v2))); - if (i != 3) - // Prefix sum of the areas. - areas.back() += areas[areas.size() - 2]; + + // Prefix sum of the areas. + areas.emplace_back(aback + 0.5f * std::abs(cross2(v1, v2))); + aback = areas.back(); } size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2)); @@ -316,7 +357,7 @@ std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_m double r = random_triangle(rng); size_t idx_triangle = std::min(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3; // Select a random point on the triangle. - double u = float(sqrt(random_float(rng))); + double u = float(std::sqrt(random_float(rng))); double v = float(random_float(rng)); const Vec2f &a = triangles[idx_triangle ++]; const Vec2f &b = triangles[idx_triangle++]; @@ -328,16 +369,37 @@ std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_m return out; } + +std::vector sample_expolygon(const ExPolygons &expolys, float samples_per_mm2, std::mt19937 &rng) +{ + std::vector out; + for (const ExPolygon &expoly : expolys) + append(out, sample_expolygon(expoly, samples_per_mm2, rng)); + + return out; +} + +void sample_expolygon_boundary(const ExPolygon & expoly, + float samples_per_mm, + std::vector &out, + std::mt19937 & rng) +{ + double point_stepping_scaled = scale_(1.f) / samples_per_mm; + for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { + const Polygon &contour = (i_contour == 0) ? expoly.contour : + expoly.holes[i_contour - 1]; + + const Points pts = contour.equally_spaced_points(point_stepping_scaled); + for (size_t i = 0; i < pts.size(); ++ i) + out.emplace_back(unscale(pts[i].x()), + unscale(pts[i].y())); + } +} + std::vector sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) { std::vector out = sample_expolygon(expoly, samples_per_mm2, rng); - double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary; - for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { - const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1]; - const Points pts = contour.equally_spaced_points(point_stepping_scaled); - for (size_t i = 0; i < pts.size(); ++ i) - out.emplace_back(unscale(pts[i].x()), unscale(pts[i].y())); - } + sample_expolygon_boundary(expoly, samples_per_mm_boundary, out, rng); return out; } @@ -359,17 +421,17 @@ static inline std::vector poisson_disk_from_samples(const std::vector raw_samples_sorted; - RawSample sample; - for (const Vec2f &pt : raw_samples) { - sample.coord = pt; - sample.cell_id = ((pt - corner_min) / radius).cast(); - raw_samples_sorted.emplace_back(sample); - } + + auto raw_samples_sorted = reserve_vector(raw_samples.size()); + for (const Vec2f &pt : raw_samples) + raw_samples_sorted.emplace_back(pt, ((pt - corner_min) / radius).cast()); + std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) { return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); }); @@ -464,11 +526,22 @@ static inline std::vector poisson_disk_from_samples(const std::vector bbdim.y()) std::swap(bbdim.x(), bbdim.y()); + double aspectr = bbdim.y() / bbdim.x(); + + support_force_deficit *= (1 + aspectr / 2.); + } + if (support_force_deficit < 0) return; @@ -485,13 +558,18 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure float min_spacing = poisson_radius; //FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon. - - std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng); + + std::vector raw_samples = + flags & icfBoundaryOnly ? + sample_expolygon_with_boundary(islands, samples_per_mm2, + 5.f / poisson_radius, m_rng) : + sample_expolygon(islands, samples_per_mm2, m_rng); + std::vector poisson_samples; for (size_t iter = 0; iter < 4; ++ iter) { poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, [&structure, &grid3d, min_spacing](const Vec2f &pos) { - return grid3d.collides_with(pos, &structure, min_spacing); + return grid3d.collides_with(pos, structure.layer->print_z, min_spacing); }); if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON) break; @@ -521,12 +599,13 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure 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, m_config.head_diameter/2.f, is_new_island); + m_output.emplace_back(float(pt(0)), float(pt(1)), structure.zlevel, m_config.head_diameter/2.f, flags & icfIsNew); structure.supports_force_this_layer += m_config.support_force(); grid3d.insert(pt, &structure); } } + void remove_bottom_points(std::vector &pts, float lvl) { // get iterator to the reorganized vector end diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index f1b3770254..4c809dba30 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -38,8 +38,8 @@ public: struct MyLayer; struct Structure { - Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) : - layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h) + Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) : + layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), zlevel(h) #ifdef SLA_SUPPORTPOINTGEN_DEBUG , unique_id(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) #endif /* SLA_SUPPORTPOINTGEN_DEBUG */ @@ -49,7 +49,7 @@ public: const BoundingBox bbox; const Vec2f centroid = Vec2f::Zero(); const float area = 0.f; - float height = 0; + float zlevel = 0; // How well is this ExPolygon held to the print base? // Positive number, the higher the better. float supports_force_this_layer = 0.f; @@ -159,8 +159,8 @@ public: grid.emplace(cell_id(pt.position), pt); } - bool collides_with(const Vec2f &pos, Structure *island, float radius) { - Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z)); + bool collides_with(const Vec2f &pos, float print_z, float radius) { + Vec3f pos3d(pos.x(), pos.y(), print_z); Vec3i cell = cell_id(pos3d); std::pair it_pair = grid.equal_range(cell); if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) @@ -198,7 +198,16 @@ private: SupportPointGenerator::Config m_config; void process(const std::vector& slices, const std::vector& heights); - void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false); + +public: + enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfBoundaryOnly = 0x2 }; + +private: + + void uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags = icfNone); + + void add_support_points(Structure& structure, PointGrid3D &grid3d); + void project_onto_mesh(std::vector& points) const; #ifdef SLA_SUPPORTPOINTGEN_DEBUG @@ -215,6 +224,9 @@ private: void remove_bottom_points(std::vector &pts, float lvl); +std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng); +void sample_expolygon_boundary(const ExPolygon &expoly, float samples_per_mm, std::vector &out, std::mt19937 &rng); + }} // namespace Slic3r::sla #endif // SUPPORTPOINTGENERATOR_HPP From a21ff4141be541f3fc3936b65a2b2f77ca3e6212 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 25 Aug 2020 13:40:06 +0200 Subject: [PATCH 473/826] Fix failing test due to changes in support point genertion --- src/libslic3r/ExPolygon.hpp | 8 ++++++++ src/libslic3r/Polygon.hpp | 8 ++++++++ tests/sla_print/sla_supptgen_tests.cpp | 5 ++--- tests/sla_print/sla_test_utils.cpp | 5 ++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 4aad3603fc..373853f972 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -333,6 +333,14 @@ extern std::list expoly_to_polypartition_input(const ExPolygons &expp) extern std::list expoly_to_polypartition_input(const ExPolygon &ex); extern std::vector polypartition_output_to_triangles(const std::list &output); +inline double area(const ExPolygons &polys) +{ + double s = 0.; + for (auto &p : polys) s += p.area(); + + return s; +} + } // namespace Slic3r // start Boost diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index ab7c171e3c..48dd1b64dd 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -86,6 +86,14 @@ inline double total_length(const Polygons &polylines) { return total; } +inline double area(const Polygons &polys) +{ + double s = 0.; + for (auto &p : polys) s += p.area(); + + return s; +} + // Remove sticks (tentacles with zero area) from the polygon. extern bool remove_sticks(Polygon &poly); extern bool remove_sticks(Polygons &polys); diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index 024f115b0b..ee9013a44c 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -89,8 +89,6 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") { sla::SupportPointGenerator::Config cfg; sla::SupportPoints pts = calc_support_pts(mesh, cfg); - REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); - Linef3 overh{ {0.f, -depth / 2.f, 0.f}, {0.f, depth / 2.f, 0.f}}; // Get all the points closer that 1 mm to the overhanging edge: @@ -102,7 +100,8 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") { }); REQUIRE(overh_pts.size() * cfg.support_force() > overh.length() * cfg.tear_pressure()); - REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); + double ddiff = min_point_distance(pts) - cfg.minimal_distance; + REQUIRE(ddiff > - 0.1 * cfg.minimal_distance); } TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowed]") { diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index f6b548fa05..a6a0f4304c 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -38,7 +38,10 @@ void test_support_model_collision(const std::string &obj_filename, Polygons intersections = intersection(sup_slice, mod_slice); - notouch = notouch && intersections.empty(); + double pinhead_r = scaled(input_supportcfg.head_front_radius_mm); + + // TODO:: make it strict without a threshold of PI * pihead_radius ^ 2 + notouch = notouch && area(intersections) < PI * pinhead_r * pinhead_r; } /*if (!notouch) */export_failed_case(support_slices, byproducts); From 50836914fc24290f20c886f563df21c1fd4e99df Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 10 Sep 2020 13:37:58 +0200 Subject: [PATCH 474/826] Calibration changes to address new algorithm behavior. --- src/libslic3r/SLA/SupportPointGenerator.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 4c809dba30..30c221c041 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -22,8 +22,9 @@ public: float density_relative {1.f}; float minimal_distance {1.f}; float head_diameter {0.4f}; - /////////////// - inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit) + + // Originally calibrated to 7.7f, reduced density by Tamas to 70% which is 11.1 (7.7 / 0.7) to adjust for new algorithm changes in tm_suppt_gen_improve + inline float support_force() const { return 11.1f / 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) }; From 7713a55d458a92468dac2d9c9e4802cf72644150 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 10 Sep 2020 13:39:43 +0200 Subject: [PATCH 475/826] Do a mesh split before openvdb conversion, unify each part's grid Do a mesh redistance after the part splitting and openvdb csgUnion --- src/libslic3r/OpenVDBUtils.cpp | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index c30052036e..53a71f194f 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -2,6 +2,7 @@ #include "OpenVDBUtils.hpp" #include #include +#include #include //#include "MTUtils.hpp" @@ -57,17 +58,42 @@ void Contour3DDataAdapter::getIndexSpacePoint(size_t n, // TODO: Do I need to call initialize? Seems to work without it as well but the // docs say it should be called ones. It does a mutex lock-unlock sequence all // even if was called previously. - openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, const openvdb::math::Transform &tr, float exteriorBandWidth, float interiorBandWidth, int flags) { +// openvdb::initialize(); +// return openvdb::tools::meshToVolume( +// TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, +// interiorBandWidth, flags); + openvdb::initialize(); - return openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, - interiorBandWidth, flags); + + TriangleMeshPtrs meshparts = mesh.split(); + + auto it = std::remove_if(meshparts.begin(), meshparts.end(), + [](TriangleMesh *m){ + m->require_shared_vertices(); + return !m->is_manifold() || m->volume() < EPSILON; + }); + + meshparts.erase(it, meshparts.end()); + + openvdb::FloatGrid::Ptr grid; + for (TriangleMesh *m : meshparts) { + auto gridptr = openvdb::tools::meshToVolume( + TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, + interiorBandWidth, flags); + + if (grid && gridptr) openvdb::tools::csgUnion(*grid, *gridptr); + else if (gridptr) grid = std::move(gridptr); + } + + grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth, interiorBandWidth); + + return grid; } openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, From b4e30cc8ad08de86fee89b947c35cd401ca9021a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Aug 2020 10:25:09 +0200 Subject: [PATCH 476/826] rotation finder experiments wip --- src/libslic3r/Optimizer.hpp | 1 + src/libslic3r/SLA/Rotfinder.cpp | 155 +++++++++++++++++-------- src/libslic3r/SLA/Rotfinder.hpp | 2 +- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 4 +- 4 files changed, 108 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimizer.hpp index 6495ae7ff4..1c94f3c1ed 100644 --- a/src/libslic3r/Optimizer.hpp +++ b/src/libslic3r/Optimizer.hpp @@ -368,6 +368,7 @@ template auto score_gradient(double s, const double (&grad)[N]) using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; +using AlgNLoptDIRECT = detail::NLoptAlg; // TODO: define others if needed... diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 81ef00e6b3..b4b1fae391 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -1,33 +1,113 @@ #include #include -#include +//#include +#include #include #include +#include +#include #include "Model.hpp" namespace Slic3r { namespace sla { -std::array find_best_rotation(const ModelObject& modelobj, +double area(const Vec3d &p1, const Vec3d &p2, const Vec3d &p3) { + Vec3d a = p2 - p1; + Vec3d b = p3 - p1; + Vec3d c = a.cross(b); + return 0.5 * c.norm(); +} + +using VertexFaceMap = std::vector>; + +VertexFaceMap create_vertex_face_map(const TriangleMesh &mesh) { + std::vector> vmap(mesh.its.vertices.size()); + + size_t fi = 0; + for (const Vec3i &tri : mesh.its.indices) { + for (int vi = 0; vi < tri.size(); ++vi) { + auto from = vmap[tri(vi)].begin(), to = vmap[tri(vi)].end(); + vmap[tri(vi)].insert(std::lower_bound(from, to, fi), fi); + } + } + + return vmap; +} + +// Try to guess the number of support points needed to support a mesh +double calculate_model_supportedness(const TriangleMesh & mesh, + const VertexFaceMap &vmap, + const Transform3d & tr) +{ + static const double POINTS_PER_UNIT_AREA = 1.; + static const Vec3d DOWN = {0., 0., -1.}; + + double score = 0.; + +// double zmin = mesh.bounding_box().min.z(); + +// std::vector normals(mesh.its.indices.size(), Vec3d::Zero()); + + double zmin = 0; + for (auto & v : mesh.its.vertices) + zmin = std::min(zmin, double((tr * v.cast()).z())); + + for (size_t fi = 0; fi < mesh.its.indices.size(); ++fi) { + const auto &face = mesh.its.indices[fi]; + Vec3d p1 = tr * mesh.its.vertices[face(0)].cast(); + Vec3d p2 = tr * mesh.its.vertices[face(1)].cast(); + Vec3d p3 = tr * mesh.its.vertices[face(2)].cast(); + +// auto triang = std::array {p1, p2, p3}; +// double a = area(triang.begin(), triang.end()); + double a = area(p1, p2, p3); + + double zlvl = zmin + 0.1; + if (p1.z() <= zlvl && p2.z() <= zlvl && p3.z() <= zlvl) { + score += a * POINTS_PER_UNIT_AREA; + continue; + } + + + Eigen::Vector3d U = p2 - p1; + Eigen::Vector3d V = p3 - p1; + Vec3d N = U.cross(V).normalized(); + + double phi = std::acos(N.dot(DOWN)) / PI; + + std::cout << "area: " << a << std::endl; + + score += a * POINTS_PER_UNIT_AREA * phi; +// normals[fi] = N; + } + +// for (size_t vi = 0; vi < mesh.its.vertices.size(); ++vi) { +// const std::vector &neighbors = vmap[vi]; + +// const auto &v = mesh.its.vertices[vi]; +// Vec3d vt = tr * v.cast(); +// } + + return score; +} + +std::array find_best_rotation(const ModelObject& modelobj, float accuracy, std::function statuscb, std::function stopcond) { - using libnest2d::opt::Method; - using libnest2d::opt::bound; - using libnest2d::opt::Optimizer; - using libnest2d::opt::TOptimizer; - using libnest2d::opt::StopCriteria; - - static const unsigned MAX_TRIES = 100000; + static const unsigned MAX_TRIES = 1000000; // return value - std::array rot; + std::array rot; // We will use only one instance of this converted mesh to examine different // rotations - const TriangleMesh& mesh = modelobj.raw_mesh(); + TriangleMesh mesh = modelobj.raw_mesh(); + mesh.require_shared_vertices(); +// auto vmap = create_vertex_face_map(mesh); +// simplify_mesh(mesh); // For current iteration number unsigned status = 0; @@ -44,40 +124,15 @@ std::array find_best_rotation(const ModelObject& modelobj, // the same for subsequent iterations (status goes from 0 to 100 but // iterations can be many more) auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries] - (double rx, double ry, double rz) + (const opt::Input<2> &in) { - const TriangleMesh& m = mesh; - // prepare the rotation transformation Transform3d rt = Transform3d::Identity(); + rt.rotate(Eigen::AngleAxisd(in[1], Vec3d::UnitY())); + rt.rotate(Eigen::AngleAxisd(in[0], Vec3d::UnitX())); - rt.rotate(Eigen::AngleAxisd(rz, Vec3d::UnitZ())); - rt.rotate(Eigen::AngleAxisd(ry, Vec3d::UnitY())); - rt.rotate(Eigen::AngleAxisd(rx, Vec3d::UnitX())); - - double score = 0; - - // For all triangles we calculate the normal and sum up the dot product - // (a scalar indicating how much are two vectors aligned) with each axis - // this will result in a value that is greater if a normal is aligned - // with all axes. If the normal is aligned than the triangle itself is - // orthogonal to the axes and that is good for print quality. - - // TODO: some applications optimize for minimum z-axis cross section - // area. The current function is only an example of how to optimize. - - // Later we can add more criteria like the number of overhangs, etc... - for(size_t i = 0; i < m.stl.facet_start.size(); i++) { - Vec3d n = m.stl.facet_start[i].normal.cast(); - - // rotate the normal with the current rotation given by the solver - n = rt * n; - - // We should score against the alignment with the reference planes - score += std::abs(n.dot(Vec3d::UnitX())); - score += std::abs(n.dot(Vec3d::UnitY())); - score += std::abs(n.dot(Vec3d::UnitZ())); - } + double score = sla::calculate_model_supportedness(mesh, {}, rt); + std::cout << score << std::endl; // report status if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) ); @@ -86,26 +141,24 @@ std::array find_best_rotation(const ModelObject& modelobj, }; // Firing up the genetic optimizer. For now it uses the nlopt library. - StopCriteria stc; - stc.max_iterations = max_tries; - stc.relative_score_difference = 1e-3; - stc.stop_condition = stopcond; // stop when stopcond returns true - TOptimizer solver(stc); + + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .rel_score_diff(1e-3) + .stop_condition(stopcond)); // We are searching rotations around the three axes x, y, z. Thus the // problem becomes a 3 dimensional optimization task. // We can specify the bounds for a dimension in the following way: - auto b = bound(-PI/2, PI/2); + auto b = opt::Bound{-PI, PI}; // Now we start the optimization process with initial angles (0, 0, 0) - auto result = solver.optimize_max(objfunc, - libnest2d::opt::initvals(0.0, 0.0, 0.0), - b, b, b); + auto result = solver.to_max().optimize(objfunc, opt::initvals({0.0, 0.0}), + opt::bounds({b, b})); // Save the result and fck off rot[0] = std::get<0>(result.optimum); rot[1] = std::get<1>(result.optimum); - rot[2] = std::get<2>(result.optimum); return rot; } diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 4469f9731d..583703203a 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -25,7 +25,7 @@ namespace sla { * * @return Returns the rotations around each axis (x, y, z) */ -std::array find_best_rotation( +std::array find_best_rotation( const ModelObject& modelobj, float accuracy = 1.0f, std::function statuscb = [] (unsigned) {}, diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index c847c84b48..3fd86b13f5 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -18,7 +18,7 @@ void RotoptimizeJob::process() auto r = sla::find_best_rotation( *o, - .005f, + 1.f, [this](unsigned s) { if (s < 100) update_status(int(s), @@ -31,7 +31,7 @@ void RotoptimizeJob::process() if (!was_canceled()) { for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], r[Z]}); + oi->set_rotation({r[X], r[Y], 0.}); auto trmatrix = oi->get_transformation().get_matrix(); Polygon trchull = o->convex_hull_2d(trmatrix); From c193d7c93081e0cbe6c9510436f413fe759e2b10 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 27 Aug 2020 23:13:05 +0200 Subject: [PATCH 477/826] Brute force optimization code, buggy yet wip wip wip refactor --- src/libslic3r/CMakeLists.txt | 3 +- .../Optimize/BruteforceOptimizer.hpp | 120 ++++++++++++ .../NLoptOptimizer.hpp} | 156 +-------------- src/libslic3r/Optimize/Optimizer.hpp | 182 ++++++++++++++++++ src/libslic3r/SLA/Concurrency.hpp | 80 +++++--- src/libslic3r/SLA/Rotfinder.cpp | 111 ++++++----- src/libslic3r/SLA/SupportTreeBuildsteps.cpp | 2 +- 7 files changed, 427 insertions(+), 227 deletions(-) create mode 100644 src/libslic3r/Optimize/BruteforceOptimizer.hpp rename src/libslic3r/{Optimizer.hpp => Optimize/NLoptOptimizer.hpp} (59%) create mode 100644 src/libslic3r/Optimize/Optimizer.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 09f75c747c..263920ecbb 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -215,7 +215,8 @@ add_library(libslic3r STATIC SimplifyMeshImpl.hpp SimplifyMesh.cpp MarchingSquares.hpp - Optimizer.hpp + Optimize/Optimizer.hpp + Optimize/NLoptOptimizer.hpp ${OpenVDBUtils_SOURCES} SLA/Pad.hpp SLA/Pad.cpp diff --git a/src/libslic3r/Optimize/BruteforceOptimizer.hpp b/src/libslic3r/Optimize/BruteforceOptimizer.hpp new file mode 100644 index 0000000000..da44725688 --- /dev/null +++ b/src/libslic3r/Optimize/BruteforceOptimizer.hpp @@ -0,0 +1,120 @@ +#ifndef BRUTEFORCEOPTIMIZER_HPP +#define BRUTEFORCEOPTIMIZER_HPP + +#include + +namespace Slic3r { namespace opt { + +namespace detail { +// Implementing a bruteforce optimizer + +template +constexpr long num_iter(const std::array &idx, size_t gridsz) +{ + long ret = 0; + for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i); + return ret; +} + +struct AlgBurteForce { + bool to_min; + StopCriteria stc; + size_t gridsz; + + AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {} + + template + void run(std::array &idx, + Result &result, + const Bounds &bounds, + Fn &&fn, + Cmp &&cmp) + { + if (stc.stop_condition()) return; + + if constexpr (D < 0) { + Input inp; + + auto max_iter = stc.max_iterations(); + if (max_iter && num_iter(idx, gridsz) >= max_iter) return; + + for (size_t d = 0; d < N; ++d) { + const Bound &b = bounds[d]; + double step = (b.max() - b.min()) / (gridsz - 1); + inp[d] = b.min() + idx[d] * step; + } + + auto score = fn(inp); + if (cmp(score, result.score)) { + result.score = score; + result.optimum = inp; + } + + } else { + for (size_t i = 0; i < gridsz; ++i) { + idx[D] = i; + run(idx, result, bounds, std::forward(fn), + std::forward(cmp)); + } + } + } + + template + Result optimize(Fn&& fn, + const Input &/*initvals*/, + const Bounds& bounds) + { + std::array idx = {}; + Result result; + + if (to_min) { + result.score = std::numeric_limits::max(); + run(idx, result, bounds, std::forward(fn), + std::less{}); + } + else { + result.score = std::numeric_limits::lowest(); + run(idx, result, bounds, std::forward(fn), + std::greater{}); + } + + return result; + } +}; + +} // namespace bruteforce_detail + +using AlgBruteForce = detail::AlgBurteForce; + +template<> +class Optimizer { + AlgBruteForce m_alg; + +public: + + Optimizer(const StopCriteria &cr = {}, size_t gridsz = 100) + : m_alg{cr, gridsz} + {} + + Optimizer& to_max() { m_alg.to_min = false; return *this; } + Optimizer& to_min() { m_alg.to_min = true; return *this; } + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) + { + return m_alg.optimize(std::forward(func), initvals, bounds); + } + + Optimizer &set_criteria(const StopCriteria &cr) + { + m_alg.stc = cr; return *this; + } + + const StopCriteria &get_criteria() const { return m_alg.stc; } +}; + +}} // namespace Slic3r::opt + +#endif // BRUTEFORCEOPTIMIZER_HPP diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp similarity index 59% rename from src/libslic3r/Optimizer.hpp rename to src/libslic3r/Optimize/NLoptOptimizer.hpp index 1c94f3c1ed..826b1632ae 100644 --- a/src/libslic3r/Optimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -12,134 +12,11 @@ #endif #include -#include -#include -#include -#include -#include -#include + +#include namespace Slic3r { namespace opt { -// A type to hold the complete result of the optimization. -template struct Result { - int resultcode; - std::array optimum; - double score; -}; - -// An interval of possible input values for optimization -class Bound { - double m_min, m_max; - -public: - Bound(double min = std::numeric_limits::min(), - double max = std::numeric_limits::max()) - : m_min(min), m_max(max) - {} - - double min() const noexcept { return m_min; } - double max() const noexcept { return m_max; } -}; - -// Helper types for optimization function input and bounds -template using Input = std::array; -template using Bounds = std::array; - -// A type for specifying the stop criteria. Setter methods can be concatenated -class StopCriteria { - - // If the absolute value difference between two scores. - double m_abs_score_diff = std::nan(""); - - // If the relative value difference between two scores. - double m_rel_score_diff = std::nan(""); - - // Stop if this value or better is found. - double m_stop_score = std::nan(""); - - // A predicate that if evaluates to true, the optimization should terminate - // and the best result found prior to termination should be returned. - std::function m_stop_condition = [] { return false; }; - - // The max allowed number of iterations. - unsigned m_max_iterations = 0; - -public: - - StopCriteria & abs_score_diff(double val) - { - m_abs_score_diff = val; return *this; - } - - double abs_score_diff() const { return m_abs_score_diff; } - - StopCriteria & rel_score_diff(double val) - { - m_rel_score_diff = val; return *this; - } - - double rel_score_diff() const { return m_rel_score_diff; } - - StopCriteria & stop_score(double val) - { - m_stop_score = val; return *this; - } - - double stop_score() const { return m_stop_score; } - - StopCriteria & max_iterations(double val) - { - m_max_iterations = val; return *this; - } - - double max_iterations() const { return m_max_iterations; } - - template StopCriteria & stop_condition(Fn &&cond) - { - m_stop_condition = cond; return *this; - } - - bool stop_condition() { return m_stop_condition(); } -}; - -// Helper class to use optimization methods involving gradient. -template struct ScoreGradient { - double score; - std::optional> gradient; - - ScoreGradient(double s, const std::array &grad) - : score{s}, gradient{grad} - {} -}; - -// Helper to be used in static_assert. -template struct always_false { enum { value = false }; }; - -// Basic interface to optimizer object -template class Optimizer { -public: - - Optimizer(const StopCriteria &) - { - static_assert (always_false::value, - "Optimizer unimplemented for given method!"); - } - - Optimizer &to_min() { return *this; } - Optimizer &to_max() { return *this; } - Optimizer &set_criteria(const StopCriteria &) { return *this; } - StopCriteria get_criteria() const { return {}; }; - - template - Result optimize(Func&& func, - const Input &initvals, - const Bounds& bounds) { return {}; } - - // optional for randomized methods: - void seed(long /*s*/) {} -}; - namespace detail { // Helper types for NLopt algorithm selection in template contexts @@ -166,19 +43,6 @@ struct IsNLoptAlg> { template using NLoptOnly = std::enable_if_t::value, T>; -// Helper to convert C style array to std::array. The copy should be optimized -// away with modern compilers. -template auto to_arr(const T *a) -{ - std::array r; - std::copy(a, a + N, std::begin(r)); - return r; -} - -template auto to_arr(const T (&a) [N]) -{ - return to_arr(static_cast(a)); -} enum class OptDir { MIN, MAX }; // Where to optimize @@ -357,24 +221,12 @@ public: void seed(long s) { m_opt.seed(s); } }; -template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } -template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } -template auto score_gradient(double s, const double (&grad)[N]) -{ - return ScoreGradient(s, detail::to_arr(grad)); -} - -// Predefinded NLopt algorithms that are used in the codebase +// Predefinded NLopt algorithms using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; using AlgNLoptDIRECT = detail::NLoptAlg; - -// TODO: define others if needed... - -// Helper defs for pre-crafted global and local optimizers that work well. -using DefaultGlobalOptimizer = Optimizer; -using DefaultLocalOptimizer = Optimizer; +using AlgNLoptMLSL = detail::NLoptAlg; }} // namespace Slic3r::opt diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp new file mode 100644 index 0000000000..05191eba26 --- /dev/null +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -0,0 +1,182 @@ +#ifndef OPTIMIZER_HPP +#define OPTIMIZER_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { namespace opt { + +// A type to hold the complete result of the optimization. +template struct Result { + int resultcode; // Method dependent + std::array optimum; + double score; +}; + +// An interval of possible input values for optimization +class Bound { + double m_min, m_max; + +public: + Bound(double min = std::numeric_limits::min(), + double max = std::numeric_limits::max()) + : m_min(min), m_max(max) + {} + + double min() const noexcept { return m_min; } + double max() const noexcept { return m_max; } +}; + +// Helper types for optimization function input and bounds +template using Input = std::array; +template using Bounds = std::array; + +// A type for specifying the stop criteria. Setter methods can be concatenated +class StopCriteria { + + // If the absolute value difference between two scores. + double m_abs_score_diff = std::nan(""); + + // If the relative value difference between two scores. + double m_rel_score_diff = std::nan(""); + + // Stop if this value or better is found. + double m_stop_score = std::nan(""); + + // A predicate that if evaluates to true, the optimization should terminate + // and the best result found prior to termination should be returned. + std::function m_stop_condition = [] { return false; }; + + // The max allowed number of iterations. + unsigned m_max_iterations = 0; + +public: + + StopCriteria & abs_score_diff(double val) + { + m_abs_score_diff = val; return *this; + } + + double abs_score_diff() const { return m_abs_score_diff; } + + StopCriteria & rel_score_diff(double val) + { + m_rel_score_diff = val; return *this; + } + + double rel_score_diff() const { return m_rel_score_diff; } + + StopCriteria & stop_score(double val) + { + m_stop_score = val; return *this; + } + + double stop_score() const { return m_stop_score; } + + StopCriteria & max_iterations(double val) + { + m_max_iterations = val; return *this; + } + + double max_iterations() const { return m_max_iterations; } + + template StopCriteria & stop_condition(Fn &&cond) + { + m_stop_condition = cond; return *this; + } + + bool stop_condition() { return m_stop_condition(); } +}; + +// Helper class to use optimization methods involving gradient. +template struct ScoreGradient { + double score; + std::optional> gradient; + + ScoreGradient(double s, const std::array &grad) + : score{s}, gradient{grad} + {} +}; + +// Helper to be used in static_assert. +template struct always_false { enum { value = false }; }; + +// Basic interface to optimizer object +template class Optimizer { +public: + + Optimizer(const StopCriteria &) + { + static_assert (always_false::value, + "Optimizer unimplemented for given method!"); + } + + // Switch optimization towards function minimum + Optimizer &to_min() { return *this; } + + // Switch optimization towards function maximum + Optimizer &to_max() { return *this; } + + // Set criteria for successive optimizations + Optimizer &set_criteria(const StopCriteria &) { return *this; } + + // Get current criteria + StopCriteria get_criteria() const { return {}; }; + + // Find function minimum or maximum for Func which has has signature: + // double(const Input &input) and input with dimension N + // + // Initial starting point can be given as the second parameter. + // + // For each dimension an interval (Bound) has to be given marking the bounds + // for that dimension. + // + // initvals have to be within the specified bounds, otherwise its undefined + // behavior. + // + // Func can return a score of type double or optionally a ScoreGradient + // class to indicate the function gradient for a optimization methods that + // make use of the gradient. + template + Result optimize(Func&& /*func*/, + const Input &/*initvals*/, + const Bounds& /*bounds*/) { return {}; } + + // optional for randomized methods: + void seed(long /*s*/) {} +}; + +namespace detail { + +// Helper to convert C style array to std::array. The copy should be optimized +// away with modern compilers. +template auto to_arr(const T *a) +{ + std::array r; + std::copy(a, a + N, std::begin(r)); + return r; +} + +template auto to_arr(const T (&a) [N]) +{ + return to_arr(static_cast(a)); +} + +} // namespace detail + +// Helper functions to create bounds, initial value +template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } +template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } +template auto score_gradient(double s, const double (&grad)[N]) +{ + return ScoreGradient(s, detail::to_arr(grad)); +} + +}} // namespace Slic3r::opt + +#endif // OPTIMIZER_HPP diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index 93ba8c4ebd..f82d6a39ee 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -21,28 +22,43 @@ template<> struct _ccr using SpinningMutex = tbb::spin_mutex; using BlockingMutex = tbb::mutex; + template + static IteratorOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (auto &el : range) fn(el); + } + + template + static IntegerOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (I i = range.begin(); i < range.end(); ++i) fn(i); + } + template - static IteratorOnly for_each(It from, - It to, - Fn && fn, - size_t granularity = 1) + static void for_each(It from, It to, Fn &&fn, size_t granularity = 1) { tbb::parallel_for(tbb::blocked_range{from, to, granularity}, [&fn, from](const auto &range) { - for (auto &el : range) fn(el); + loop_(range, std::forward(fn)); }); } - template - static IntegerOnly for_each(I from, - I to, - Fn && fn, - size_t granularity = 1) + template + static T reduce(I from, + I to, + const T & init, + Fn && fn, + MergeFn &&mergefn, + size_t granularity = 1) { - tbb::parallel_for(tbb::blocked_range{from, to, granularity}, - [&fn](const auto &range) { - for (I i = range.begin(); i < range.end(); ++i) fn(i); - }); + return tbb::parallel_reduce( + tbb::blocked_range{from, to, granularity}, init, + [&](const auto &range, T subinit) { + T acc = subinit; + loop_(range, [&](auto &i) { acc = mergefn(acc, fn(i, acc)); }); + return acc; + }, + std::forward(mergefn)); } }; @@ -55,23 +71,39 @@ public: using SpinningMutex = _Mtx; using BlockingMutex = _Mtx; - template - static IteratorOnly for_each(It from, - It to, - Fn &&fn, - size_t /* ignore granularity */ = 1) + template + static IteratorOnly loop_(It from, It to, Fn &&fn) { for (auto it = from; it != to; ++it) fn(*it); } - template - static IntegerOnly for_each(I from, - I to, - Fn &&fn, - size_t /* ignore granularity */ = 1) + template + static IntegerOnly loop_(I from, I to, Fn &&fn) { for (I i = from; i < to; ++i) fn(i); } + + template + static void for_each(It from, + It to, + Fn &&fn, + size_t /* ignore granularity */ = 1) + { + loop_(from, to, std::forward(fn)); + } + + template + static IntegerOnly reduce(I from, + I to, + const T & init, + Fn && fn, + MergeFn &&mergefn, + size_t /*granularity*/ = 1) + { + T acc = init; + loop_(from, to, [&](auto &i) { acc = mergefn(acc, fn(i, acc)); }); + return acc; + } }; using ccr = _ccr; diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index b4b1fae391..723e50eeb5 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -2,23 +2,19 @@ #include //#include -#include +#include #include +#include #include #include #include #include "Model.hpp" +#include + namespace Slic3r { namespace sla { -double area(const Vec3d &p1, const Vec3d &p2, const Vec3d &p3) { - Vec3d a = p2 - p1; - Vec3d b = p3 - p1; - Vec3d c = a.cross(b); - return 0.5 * c.norm(); -} - using VertexFaceMap = std::vector>; VertexFaceMap create_vertex_face_map(const TriangleMesh &mesh) { @@ -35,61 +31,75 @@ VertexFaceMap create_vertex_face_map(const TriangleMesh &mesh) { return vmap; } +// Find transformed mesh ground level without copy and with parallell reduce. +double find_ground_level(const TriangleMesh &mesh, + const Transform3d & tr, + size_t threads) +{ + size_t vsize = mesh.its.vertices.size(); + + auto minfn = [](double a, double b) { return std::min(a, b); }; + + auto findminz = [&mesh, &tr] (size_t vi, double submin) { + Vec3d v = tr * mesh.its.vertices[vi].template cast(); + return std::min(submin, v.z()); + }; + + double zmin = mesh.its.vertices.front().z(); + + return ccr_par::reduce(size_t(0), vsize, zmin, findminz, minfn, + vsize / threads); +} + // Try to guess the number of support points needed to support a mesh double calculate_model_supportedness(const TriangleMesh & mesh, - const VertexFaceMap &vmap, +// const VertexFaceMap &vmap, const Transform3d & tr) { - static const double POINTS_PER_UNIT_AREA = 1.; - static const Vec3d DOWN = {0., 0., -1.}; + static constexpr double POINTS_PER_UNIT_AREA = 1.; - double score = 0.; + if (mesh.its.vertices.empty()) return std::nan(""); -// double zmin = mesh.bounding_box().min.z(); + size_t Nthr = std::thread::hardware_concurrency(); + size_t facesize = mesh.its.indices.size(); -// std::vector normals(mesh.its.indices.size(), Vec3d::Zero()); + double zmin = find_ground_level(mesh, tr, Nthr); - double zmin = 0; - for (auto & v : mesh.its.vertices) - zmin = std::min(zmin, double((tr * v.cast()).z())); + auto score_mergefn = [&mesh, &tr, zmin](size_t fi, double subscore) { + + static const Vec3d DOWN = {0., 0., -1.}; - for (size_t fi = 0; fi < mesh.its.indices.size(); ++fi) { const auto &face = mesh.its.indices[fi]; - Vec3d p1 = tr * mesh.its.vertices[face(0)].cast(); - Vec3d p2 = tr * mesh.its.vertices[face(1)].cast(); - Vec3d p3 = tr * mesh.its.vertices[face(2)].cast(); + Vec3d p1 = tr * mesh.its.vertices[face(0)].template cast(); + Vec3d p2 = tr * mesh.its.vertices[face(1)].template cast(); + Vec3d p3 = tr * mesh.its.vertices[face(2)].template cast(); -// auto triang = std::array {p1, p2, p3}; -// double a = area(triang.begin(), triang.end()); - double a = area(p1, p2, p3); + Vec3d U = p2 - p1; + Vec3d V = p3 - p1; + Vec3d C = U.cross(V); + Vec3d N = C.normalized(); + double area = 0.5 * C.norm(); double zlvl = zmin + 0.1; if (p1.z() <= zlvl && p2.z() <= zlvl && p3.z() <= zlvl) { - score += a * POINTS_PER_UNIT_AREA; - continue; + // score += area * POINTS_PER_UNIT_AREA; + return subscore; } + double phi = 1. - std::acos(N.dot(DOWN)) / PI; + phi = phi * (phi > 0.5); - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - Vec3d N = U.cross(V).normalized(); + // std::cout << "area: " << area << std::endl; - double phi = std::acos(N.dot(DOWN)) / PI; + subscore += area * POINTS_PER_UNIT_AREA * phi; - std::cout << "area: " << a << std::endl; + return subscore; + }; - score += a * POINTS_PER_UNIT_AREA * phi; -// normals[fi] = N; - } + double score = ccr_seq::reduce(size_t(0), facesize, 0., score_mergefn, + std::plus{}, facesize / Nthr); -// for (size_t vi = 0; vi < mesh.its.vertices.size(); ++vi) { -// const std::vector &neighbors = vmap[vi]; - -// const auto &v = mesh.its.vertices[vi]; -// Vec3d vt = tr * v.cast(); -// } - - return score; + return score / mesh.its.indices.size(); } std::array find_best_rotation(const ModelObject& modelobj, @@ -97,7 +107,7 @@ std::array find_best_rotation(const ModelObject& modelobj, std::function statuscb, std::function stopcond) { - static const unsigned MAX_TRIES = 1000000; + static const unsigned MAX_TRIES = 100; // return value std::array rot; @@ -126,12 +136,14 @@ std::array find_best_rotation(const ModelObject& modelobj, auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries] (const opt::Input<2> &in) { + std::cout << "in: " << in[0] << " " << in[1] << std::endl; + // prepare the rotation transformation Transform3d rt = Transform3d::Identity(); rt.rotate(Eigen::AngleAxisd(in[1], Vec3d::UnitY())); rt.rotate(Eigen::AngleAxisd(in[0], Vec3d::UnitX())); - double score = sla::calculate_model_supportedness(mesh, {}, rt); + double score = sla::calculate_model_supportedness(mesh, rt); std::cout << score << std::endl; // report status @@ -142,10 +154,11 @@ std::array find_best_rotation(const ModelObject& modelobj, // Firing up the genetic optimizer. For now it uses the nlopt library. - opt::Optimizer solver(opt::StopCriteria{} - .max_iterations(max_tries) - .rel_score_diff(1e-3) - .stop_condition(stopcond)); + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .rel_score_diff(1e-6) + .stop_condition(stopcond), + 10 /*grid size*/); // We are searching rotations around the three axes x, y, z. Thus the // problem becomes a 3 dimensional optimization task. @@ -153,7 +166,7 @@ std::array find_best_rotation(const ModelObject& modelobj, auto b = opt::Bound{-PI, PI}; // Now we start the optimization process with initial angles (0, 0, 0) - auto result = solver.to_max().optimize(objfunc, opt::initvals({0.0, 0.0}), + auto result = solver.to_min().optimize(objfunc, opt::initvals({0.0, 0.0}), opt::bounds({b, b})); // Save the result and fck off diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 0e7af8d508..3c39c64e6b 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include namespace Slic3r { From c10ff4f503208f965219d901234baa44b6d6123f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 31 Aug 2020 18:53:44 +0200 Subject: [PATCH 478/826] fixing optimizer and concurrency::reduce --- .../Optimize/BruteforceOptimizer.hpp | 20 +++++-- src/libslic3r/SLA/Concurrency.hpp | 58 +++++++++++++----- src/libslic3r/SLA/Rotfinder.cpp | 33 +++++------ tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_optimizers.cpp | 59 +++++++++++++++++++ tests/sla_print/sla_print_tests.cpp | 12 ++++ 6 files changed, 142 insertions(+), 41 deletions(-) create mode 100644 tests/libslic3r/test_optimizers.cpp diff --git a/src/libslic3r/Optimize/BruteforceOptimizer.hpp b/src/libslic3r/Optimize/BruteforceOptimizer.hpp index da44725688..960676e7b0 100644 --- a/src/libslic3r/Optimize/BruteforceOptimizer.hpp +++ b/src/libslic3r/Optimize/BruteforceOptimizer.hpp @@ -1,7 +1,7 @@ #ifndef BRUTEFORCEOPTIMIZER_HPP #define BRUTEFORCEOPTIMIZER_HPP -#include +#include namespace Slic3r { namespace opt { @@ -24,19 +24,19 @@ struct AlgBurteForce { AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {} template - void run(std::array &idx, + bool run(std::array &idx, Result &result, const Bounds &bounds, Fn &&fn, Cmp &&cmp) { - if (stc.stop_condition()) return; + if (stc.stop_condition()) return false; if constexpr (D < 0) { Input inp; auto max_iter = stc.max_iterations(); - if (max_iter && num_iter(idx, gridsz) >= max_iter) return; + if (max_iter && num_iter(idx, gridsz) >= max_iter) return false; for (size_t d = 0; d < N; ++d) { const Bound &b = bounds[d]; @@ -46,17 +46,25 @@ struct AlgBurteForce { auto score = fn(inp); if (cmp(score, result.score)) { + double absdiff = std::abs(score - result.score); + result.score = score; result.optimum = inp; + + if (absdiff < stc.abs_score_diff() || + absdiff < stc.rel_score_diff() * std::abs(score)) + return false; } } else { for (size_t i = 0; i < gridsz; ++i) { idx[D] = i; - run(idx, result, bounds, std::forward(fn), - std::forward(cmp)); + if (!run(idx, result, bounds, std::forward(fn), + std::forward(cmp))) return false; } } + + return true; } template diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index f82d6a39ee..a7b5b6c61d 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -43,23 +43,36 @@ template<> struct _ccr }); } - template - static T reduce(I from, - I to, - const T & init, - Fn && fn, - MergeFn &&mergefn, - size_t granularity = 1) + template + static T reduce(I from, + I to, + const T &init, + MergeFn &&mergefn, + AccessFn &&access, + size_t granularity = 1 + ) { return tbb::parallel_reduce( tbb::blocked_range{from, to, granularity}, init, [&](const auto &range, T subinit) { T acc = subinit; - loop_(range, [&](auto &i) { acc = mergefn(acc, fn(i, acc)); }); + loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); }); return acc; }, std::forward(mergefn)); } + + template + static IteratorOnly reduce(I from, + I to, + const T & init, + MergeFn &&mergefn, + size_t granularity = 1) + { + return reduce( + from, to, init, std::forward(mergefn), + [](typename I::value_type &i) { return i; }, granularity); + } }; template<> struct _ccr @@ -92,18 +105,31 @@ public: loop_(from, to, std::forward(fn)); } - template - static IntegerOnly reduce(I from, - I to, - const T & init, - Fn && fn, - MergeFn &&mergefn, - size_t /*granularity*/ = 1) + template + static T reduce(I from, + I to, + const T & init, + MergeFn &&mergefn, + AccessFn &&access, + size_t /*granularity*/ = 1 + ) { T acc = init; - loop_(from, to, [&](auto &i) { acc = mergefn(acc, fn(i, acc)); }); + loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); }); return acc; } + + template + static IteratorOnly reduce(I from, + I to, + const T &init, + MergeFn &&mergefn, + size_t /*granularity*/ = 1 + ) + { + return reduce(from, to, init, std::forward(mergefn), + [](typename I::value_type &i) { return i; }); + } }; using ccr = _ccr; diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 723e50eeb5..b7bed1c911 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -31,7 +31,7 @@ VertexFaceMap create_vertex_face_map(const TriangleMesh &mesh) { return vmap; } -// Find transformed mesh ground level without copy and with parallell reduce. +// Find transformed mesh ground level without copy and with parallel reduce. double find_ground_level(const TriangleMesh &mesh, const Transform3d & tr, size_t threads) @@ -40,15 +40,13 @@ double find_ground_level(const TriangleMesh &mesh, auto minfn = [](double a, double b) { return std::min(a, b); }; - auto findminz = [&mesh, &tr] (size_t vi, double submin) { - Vec3d v = tr * mesh.its.vertices[vi].template cast(); - return std::min(submin, v.z()); + auto accessfn = [&mesh, &tr] (size_t vi) { + return (tr * mesh.its.vertices[vi].template cast()).z(); }; double zmin = mesh.its.vertices.front().z(); - - return ccr_par::reduce(size_t(0), vsize, zmin, findminz, minfn, - vsize / threads); + size_t granularity = vsize / threads; + return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity); } // Try to guess the number of support points needed to support a mesh @@ -65,7 +63,7 @@ double calculate_model_supportedness(const TriangleMesh & mesh, double zmin = find_ground_level(mesh, tr, Nthr); - auto score_mergefn = [&mesh, &tr, zmin](size_t fi, double subscore) { + auto accessfn = [&mesh, &tr, zmin](size_t fi) { static const Vec3d DOWN = {0., 0., -1.}; @@ -83,21 +81,18 @@ double calculate_model_supportedness(const TriangleMesh & mesh, double zlvl = zmin + 0.1; if (p1.z() <= zlvl && p2.z() <= zlvl && p3.z() <= zlvl) { // score += area * POINTS_PER_UNIT_AREA; - return subscore; + return 0.; } double phi = 1. - std::acos(N.dot(DOWN)) / PI; - phi = phi * (phi > 0.5); +// phi = phi * (phi > 0.5); // std::cout << "area: " << area << std::endl; - subscore += area * POINTS_PER_UNIT_AREA * phi; - - return subscore; + return area * POINTS_PER_UNIT_AREA * phi; }; - double score = ccr_seq::reduce(size_t(0), facesize, 0., score_mergefn, - std::plus{}, facesize / Nthr); + double score = ccr_par::reduce(size_t(0), facesize, 0., std::plus{}, accessfn, facesize / Nthr); return score / mesh.its.indices.size(); } @@ -107,7 +102,7 @@ std::array find_best_rotation(const ModelObject& modelobj, std::function statuscb, std::function stopcond) { - static const unsigned MAX_TRIES = 100; + static const unsigned MAX_TRIES = 10000; // return value std::array rot; @@ -158,10 +153,10 @@ std::array find_best_rotation(const ModelObject& modelobj, .max_iterations(max_tries) .rel_score_diff(1e-6) .stop_condition(stopcond), - 10 /*grid size*/); + 100 /*grid size*/); - // We are searching rotations around the three axes x, y, z. Thus the - // problem becomes a 3 dimensional optimization task. + // We are searching rotations around only two axes x, y. Thus the + // problem becomes a 2 dimensional optimization task. // We can specify the bounds for a dimension in the following way: auto b = opt::Bound{-PI, PI}; diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 30b93eafc9..501af0c6f3 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(${_TEST_NAME}_tests test_marchingsquares.cpp test_timeutils.cpp test_voronoi.cpp + test_optimizers.cpp test_png_io.cpp test_timeutils.cpp ) diff --git a/tests/libslic3r/test_optimizers.cpp b/tests/libslic3r/test_optimizers.cpp new file mode 100644 index 0000000000..6e84f6a691 --- /dev/null +++ b/tests/libslic3r/test_optimizers.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include + +#include + +void check_opt_result(double score, double ref, double abs_err, double rel_err) +{ + double abs_diff = std::abs(score - ref); + double rel_diff = std::abs(abs_diff / std::abs(ref)); + + bool abs_reached = abs_diff < abs_err; + bool rel_reached = rel_diff < rel_err; + bool precision_reached = abs_reached || rel_reached; + REQUIRE(precision_reached); +} + +template void test_sin(Opt &&opt) +{ + using namespace Slic3r::opt; + + auto optfunc = [](const auto &in) { + auto [phi] = in; + + return std::sin(phi); + }; + + auto init = initvals({PI}); + auto optbounds = bounds({ {0., 2 * PI}}); + + Result result_min = opt.to_min().optimize(optfunc, init, optbounds); + Result result_max = opt.to_max().optimize(optfunc, init, optbounds); + + check_opt_result(result_min.score, -1., 1e-2, 1e-4); + check_opt_result(result_max.score, 1., 1e-2, 1e-4); +} + +template void test_sphere_func(Opt &&opt) +{ + using namespace Slic3r::opt; + + Result result = opt.to_min().optimize([](const auto &in) { + auto [x, y] = in; + + return x * x + y * y + 1.; + }, initvals({.6, -0.2}), bounds({{-1., 1.}, {-1., 1.}})); + + check_opt_result(result.score, 1., 1e-2, 1e-4); +} + +TEST_CASE("Test brute force optimzer for basic 1D and 2D functions", "[Opt]") { + using namespace Slic3r::opt; + + Optimizer opt; + + test_sin(opt); + test_sphere_func(opt); +} diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index dad2b90971..1575ee0e6b 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -5,6 +5,7 @@ #include "sla_test_utils.hpp" #include +#include namespace { @@ -239,3 +240,14 @@ TEST_CASE("halfcone test", "[halfcone]") { m.require_shared_vertices(); m.WriteOBJFile("Halfcone.obj"); } + +TEST_CASE("Test concurrency") +{ + std::vector vals = grid(0., 100., 10.); + + double ref = std::accumulate(vals.begin(), vals.end(), 0.); + + double s = sla::ccr_par::reduce(vals.begin(), vals.end(), 0., std::plus{}); + + REQUIRE(s == Approx(ref)); +} From b4b9af410037fff27cfa0682e1deeb5744515d86 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 1 Sep 2020 20:08:42 +0200 Subject: [PATCH 479/826] cosmethics Comments and cosmethics --- src/libslic3r/CMakeLists.txt | 1 + .../Optimize/BruteforceOptimizer.hpp | 26 ++++++++++++++----- src/libslic3r/SLA/Rotfinder.cpp | 20 +++++--------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 263920ecbb..e30811133a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -217,6 +217,7 @@ add_library(libslic3r STATIC MarchingSquares.hpp Optimize/Optimizer.hpp Optimize/NLoptOptimizer.hpp + Optimize/BruteforceOptimizer.hpp ${OpenVDBUtils_SOURCES} SLA/Pad.hpp SLA/Pad.cpp diff --git a/src/libslic3r/Optimize/BruteforceOptimizer.hpp b/src/libslic3r/Optimize/BruteforceOptimizer.hpp index 960676e7b0..2daef538e7 100644 --- a/src/libslic3r/Optimize/BruteforceOptimizer.hpp +++ b/src/libslic3r/Optimize/BruteforceOptimizer.hpp @@ -8,14 +8,18 @@ namespace Slic3r { namespace opt { namespace detail { // Implementing a bruteforce optimizer +// Return the number of iterations needed to reach a specific grid position (idx) template -constexpr long num_iter(const std::array &idx, size_t gridsz) +long num_iter(const std::array &idx, size_t gridsz) { long ret = 0; for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i); return ret; } +// Implementation of a grid search where the search interval is sampled in +// equidistant points for each dimension. Grid size determines the number of +// samples for one dimension so the number of function calls is gridsize ^ dimension. struct AlgBurteForce { bool to_min; StopCriteria stc; @@ -23,6 +27,11 @@ struct AlgBurteForce { AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {} + // This function is called recursively for each dimension and generates + // the grid values for the particular dimension. If D is less than zero, + // the object function input values are generated for each dimension and it + // can be evaluated. The current best score is compared with the newly + // returned score and changed appropriately. template bool run(std::array &idx, Result &result, @@ -32,11 +41,12 @@ struct AlgBurteForce { { if (stc.stop_condition()) return false; - if constexpr (D < 0) { + if constexpr (D < 0) { // Let's evaluate fn Input inp; auto max_iter = stc.max_iterations(); - if (max_iter && num_iter(idx, gridsz) >= max_iter) return false; + if (max_iter && num_iter(idx, gridsz) >= max_iter) + return false; for (size_t d = 0; d < N; ++d) { const Bound &b = bounds[d]; @@ -45,12 +55,13 @@ struct AlgBurteForce { } auto score = fn(inp); - if (cmp(score, result.score)) { + if (cmp(score, result.score)) { // Change current score to the new double absdiff = std::abs(score - result.score); result.score = score; result.optimum = inp; + // Check if the required precision is reached. if (absdiff < stc.abs_score_diff() || absdiff < stc.rel_score_diff() * std::abs(score)) return false; @@ -58,9 +69,10 @@ struct AlgBurteForce { } else { for (size_t i = 0; i < gridsz; ++i) { - idx[D] = i; + idx[D] = i; // Mark the current grid position and dig down if (!run(idx, result, bounds, std::forward(fn), - std::forward(cmp))) return false; + std::forward(cmp))) + return false; } } @@ -90,7 +102,7 @@ struct AlgBurteForce { } }; -} // namespace bruteforce_detail +} // namespace detail using AlgBruteForce = detail::AlgBurteForce; diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index b7bed1c911..e8cc7df1b3 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -6,14 +6,11 @@ #include #include #include -#include -#include #include "Model.hpp" #include -namespace Slic3r { -namespace sla { +namespace Slic3r { namespace sla { using VertexFaceMap = std::vector>; @@ -51,7 +48,6 @@ double find_ground_level(const TriangleMesh &mesh, // Try to guess the number of support points needed to support a mesh double calculate_model_supportedness(const TriangleMesh & mesh, -// const VertexFaceMap &vmap, const Transform3d & tr) { static constexpr double POINTS_PER_UNIT_AREA = 1.; @@ -111,8 +107,6 @@ std::array find_best_rotation(const ModelObject& modelobj, // rotations TriangleMesh mesh = modelobj.raw_mesh(); mesh.require_shared_vertices(); -// auto vmap = create_vertex_face_map(mesh); -// simplify_mesh(mesh); // For current iteration number unsigned status = 0; @@ -147,21 +141,20 @@ std::array find_best_rotation(const ModelObject& modelobj, return score; }; - // Firing up the genetic optimizer. For now it uses the nlopt library. - + // Firing up the optimizer. opt::Optimizer solver(opt::StopCriteria{} .max_iterations(max_tries) .rel_score_diff(1e-6) .stop_condition(stopcond), - 100 /*grid size*/); + std::sqrt(max_tries)/*grid size*/); // We are searching rotations around only two axes x, y. Thus the // problem becomes a 2 dimensional optimization task. // We can specify the bounds for a dimension in the following way: auto b = opt::Bound{-PI, PI}; - // Now we start the optimization process with initial angles (0, 0, 0) - auto result = solver.to_min().optimize(objfunc, opt::initvals({0.0, 0.0}), + // Now we start the optimization process with initial angles (0, 0) + auto result = solver.to_min().optimize(objfunc, opt::initvals({0., 0.}), opt::bounds({b, b})); // Save the result and fck off @@ -171,5 +164,4 @@ std::array find_best_rotation(const ModelObject& modelobj, return rot; } -} -} +}} // namespace Slic3r::sla From 9f3e7617d86cd5c53a161a606c113b1c2e2b658a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 1 Sep 2020 20:08:59 +0200 Subject: [PATCH 480/826] Add Imgui popup for rotation gizmo under SLA --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 64 +++++++++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 64 ++++++++++++++++++------- src/slic3r/GUI/ImGuiWrapper.cpp | 4 +- 3 files changed, 112 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index f3e5656860..8365875d9a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -1,9 +1,15 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" #include +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "libslic3r/SLA/Rotfinder.hpp" namespace Slic3r { namespace GUI { @@ -194,6 +200,64 @@ void GLGizmoRotate::on_render_for_picking() const glsafe(::glPopMatrix()); } +GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &alignment) + : m_imgui{imgui} +{ + imgui->begin(_L("Rotation"), ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + float win_h = ImGui::GetWindowHeight(); + float x = alignment.x, y = alignment.y; + y = std::min(y, alignment.bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + + ImGui::SliderFloat(_L("Accuracy").c_str(), &state.accuracy, 0.01f, 1.f, "%.1f"); + + if (imgui->button(_L("Optimize orientation"))) { + std::cout << "Blip" << std::endl; + } + + static const std::vector options = { + _L("Least supports").ToStdString(), + _L("Suface quality").ToStdString() + }; + + if (imgui->combo(_L("Choose method"), options, state.method) ) { + std::cout << "method: " << state.method << std::endl; + } + + +} + +GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow() +{ + m_imgui->end(); +} + +void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) +{ + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + return; + +// m_rotoptimizewin_state.mobj = ; + RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; + +// if ((last_h != win_h) || (last_y != y)) +// { +// // ask canvas for another frame to render the window in the correct position +// m_parent.request_extra_frame(); +// if (last_h != win_h) +// last_h = win_h; +// if (last_y != y) +// last_y = y; +// } + +} + void GLGizmoRotate::render_circle() const { ::glBegin(GL_LINE_LOOP); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 7365a20c36..c547dfbc0b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -52,12 +52,12 @@ public: std::string get_tooltip() const override; protected: - virtual bool on_init(); - virtual std::string on_get_name() const { return ""; } - virtual void on_start_dragging(); - virtual void on_update(const UpdateData& data); - virtual void on_render() const; - virtual void on_render_for_picking() const; + bool on_init() override; + std::string on_get_name() const override { return ""; } + void on_start_dragging() override; + void on_update(const UpdateData& data) override; + void on_render() const override; + void on_render_for_picking() const override; private: void render_circle() const; @@ -94,46 +94,74 @@ public: } protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_set_state() + bool on_init() override; + std::string on_get_name() const override; + void on_set_state() override { for (GLGizmoRotate& g : m_gizmos) g.set_state(m_state); } - virtual void on_set_hover_id() + void on_set_hover_id() override { for (int i = 0; i < 3; ++i) m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } - virtual void on_enable_grabber(unsigned int id) + void on_enable_grabber(unsigned int id) override { if (id < 3) m_gizmos[id].enable_grabber(0); } - virtual void on_disable_grabber(unsigned int id) + void on_disable_grabber(unsigned int id) override { if (id < 3) m_gizmos[id].disable_grabber(0); } - virtual bool on_is_activable() const; - virtual void on_start_dragging(); - virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data) + bool on_is_activable() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_update(const UpdateData& data) override { for (GLGizmoRotate& g : m_gizmos) { g.update(data); } } - virtual void on_render() const; - virtual void on_render_for_picking() const + void on_render() const override; + void on_render_for_picking() const override { for (const GLGizmoRotate& g : m_gizmos) { g.render_for_picking(); } } + + void on_render_input_window(float x, float y, float bottom_limit) override; +private: + + class RotoptimzeWindow { + ImGuiWrapper *m_imgui = nullptr; + + public: + struct State { + enum Metods { mMinSupportPoints, mLegacy }; + + float accuracy = 1.f; + int method = mMinSupportPoints; + ModelObject *mobj = nullptr; + }; + + struct Alignment { float x, y, bottom_limit; }; + + RotoptimzeWindow(ImGuiWrapper *imgui, State &settings, const Alignment &bottom_limit); + ~RotoptimzeWindow(); + + RotoptimzeWindow(const RotoptimzeWindow&) = delete; + RotoptimzeWindow(RotoptimzeWindow &&) = delete; + RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete; + RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete; + }; + + RotoptimzeWindow::State m_rotoptimizewin_state = {}; }; } // namespace GUI diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e839fdf9b2..0fecc822db 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -425,10 +425,10 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& text(label); ImGui::SameLine(); - int selection_out = -1; + int selection_out = selection; bool res = false; - const char *selection_str = selection < (int)options.size() ? options[selection].c_str() : ""; + const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : ""; if (ImGui::BeginCombo("", selection_str)) { for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { From 0d4c67b9a34ed5699c2676ebb079c681c763b39a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Sep 2020 17:51:22 +0200 Subject: [PATCH 481/826] Mostly working, inefficiencies remain, status indication partly broken --- src/libslic3r/SLA/Rotfinder.cpp | 316 ++++++++++++++++-------- src/libslic3r/SLA/Rotfinder.hpp | 13 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 32 +-- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 7 +- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 20 +- 5 files changed, 264 insertions(+), 124 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index e8cc7df1b3..e033009aa4 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -5,27 +5,23 @@ #include #include #include -#include + +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include #include "Model.hpp" #include namespace Slic3r { namespace sla { -using VertexFaceMap = std::vector>; +inline bool is_on_floor(const SLAPrintObject &mo) +{ + auto opt_elevation = mo.config().support_object_elevation.getFloat(); + auto opt_padaround = mo.config().pad_around_object.getBool(); -VertexFaceMap create_vertex_face_map(const TriangleMesh &mesh) { - std::vector> vmap(mesh.its.vertices.size()); - - size_t fi = 0; - for (const Vec3i &tri : mesh.its.indices) { - for (int vi = 0; vi < tri.size(); ++vi) { - auto from = vmap[tri(vi)].begin(), to = vmap[tri(vi)].end(); - vmap[tri(vi)].insert(std::lower_bound(from, to, fi), fi); - } - } - - return vmap; + return opt_elevation < EPSILON || opt_padaround; } // Find transformed mesh ground level without copy and with parallel reduce. @@ -41,62 +37,163 @@ double find_ground_level(const TriangleMesh &mesh, return (tr * mesh.its.vertices[vi].template cast()).z(); }; - double zmin = mesh.its.vertices.front().z(); + double zmin = std::numeric_limits::max(); size_t granularity = vsize / threads; return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity); } -// Try to guess the number of support points needed to support a mesh -double calculate_model_supportedness(const TriangleMesh & mesh, - const Transform3d & tr) +// Get the vertices of a triangle directly in an array of 3 points +std::array get_triangle_vertices(const TriangleMesh &mesh, + size_t faceidx) { - static constexpr double POINTS_PER_UNIT_AREA = 1.; - - if (mesh.its.vertices.empty()) return std::nan(""); - - size_t Nthr = std::thread::hardware_concurrency(); - size_t facesize = mesh.its.indices.size(); - - double zmin = find_ground_level(mesh, tr, Nthr); - - auto accessfn = [&mesh, &tr, zmin](size_t fi) { - - static const Vec3d DOWN = {0., 0., -1.}; - - const auto &face = mesh.its.indices[fi]; - Vec3d p1 = tr * mesh.its.vertices[face(0)].template cast(); - Vec3d p2 = tr * mesh.its.vertices[face(1)].template cast(); - Vec3d p3 = tr * mesh.its.vertices[face(2)].template cast(); - - Vec3d U = p2 - p1; - Vec3d V = p3 - p1; - Vec3d C = U.cross(V); - Vec3d N = C.normalized(); - double area = 0.5 * C.norm(); - - double zlvl = zmin + 0.1; - if (p1.z() <= zlvl && p2.z() <= zlvl && p3.z() <= zlvl) { - // score += area * POINTS_PER_UNIT_AREA; - return 0.; - } - - double phi = 1. - std::acos(N.dot(DOWN)) / PI; -// phi = phi * (phi > 0.5); - - // std::cout << "area: " << area << std::endl; - - return area * POINTS_PER_UNIT_AREA * phi; - }; - - double score = ccr_par::reduce(size_t(0), facesize, 0., std::plus{}, accessfn, facesize / Nthr); - - return score / mesh.its.indices.size(); + const auto &face = mesh.its.indices[faceidx]; + return {Vec3d{mesh.its.vertices[face(0)].cast()}, + Vec3d{mesh.its.vertices[face(1)].cast()}, + Vec3d{mesh.its.vertices[face(2)].cast()}}; } -std::array find_best_rotation(const ModelObject& modelobj, - float accuracy, - std::function statuscb, - std::function stopcond) +std::array get_transformed_triangle(const TriangleMesh &mesh, + const Transform3d & tr, + size_t faceidx) +{ + const auto &tri = get_triangle_vertices(mesh, faceidx); + return {tr * tri[0], tr * tri[1], tr * tri[2]}; +} + +// Get area and normal of a triangle +struct Face { Vec3d normal; double area; }; +inline Face facestats(const std::array &triangle) +{ + Vec3d U = triangle[1] - triangle[0]; + Vec3d V = triangle[2] - triangle[0]; + Vec3d C = U.cross(V); + Vec3d N = C.normalized(); + double area = 0.5 * C.norm(); + + return {N, area}; +} + +inline const Vec3d DOWN = {0., 0., -1.}; +constexpr double POINTS_PER_UNIT_AREA = 1.; + +inline double get_score(const Face &fc) +{ + double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI; + phi = phi * (phi > 0.5); + phi = phi * phi * phi; + + return fc.area * POINTS_PER_UNIT_AREA * phi; +} + +template +double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) +{ + double initv = 0.; + auto mergefn = std::plus{}; + size_t grainsize = facecount / Nthreads; + size_t from = 0, to = facecount; + + return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize); +} + +// Try to guess the number of support points needed to support a mesh +double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + auto accessfn = [&mesh, &tr](size_t fi) { + Face fc = facestats(get_transformed_triangle(mesh, tr, fi)); + return get_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + size_t Nthreads = std::thread::hardware_concurrency(); + return sum_score(accessfn, facecount, Nthreads) / facecount; +} + +double get_model_supportedness_onfloor(const TriangleMesh &mesh, + const Transform3d & tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + size_t Nthreads = std::thread::hardware_concurrency(); + + double zmin = find_ground_level(mesh, tr, Nthreads); + double zlvl = zmin + 0.1; // Set up a slight tolerance from z level + + auto accessfn = [&mesh, &tr, zlvl](size_t fi) { + std::array tri = get_transformed_triangle(mesh, tr, fi); + Face fc = facestats(tri); + + if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) + return -fc.area * POINTS_PER_UNIT_AREA; + + return get_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + return sum_score(accessfn, facecount, Nthreads) / facecount; +} + +using XYRotation = std::array; + +// prepare the rotation transformation +Transform3d to_transform3d(const XYRotation &rot) +{ + Transform3d rt = Transform3d::Identity(); + rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY())); + rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX())); + return rt; +} + +XYRotation from_transform3d(const Transform3d &tr) +{ + Vec3d rot3d = Geometry::Transformation {tr}.get_rotation(); + return {rot3d.x(), rot3d.y()}; +} + +// Find the best score from a set of function inputs. Evaluate for every point. +template +std::array find_min_score(Fn &&fn, Cmp &&cmp, It from, It to) +{ + std::array ret; + + double score = std::numeric_limits::max(); + + for (auto it = from; it != to; ++it) { + double sc = fn(*it); + if (cmp(sc, score)) { + score = sc; + ret = *it; + } + } + + return ret; +} + +// collect the rotations for each face of the convex hull +std::vector get_chull_rotations(const TriangleMesh &mesh) +{ + TriangleMesh chull = mesh.convex_hull_3d(); + chull.require_shared_vertices(); + + size_t facecount = chull.its.indices.size(); + auto inputs = reserve_vector(facecount); + + for (size_t fi = 0; fi < facecount; ++fi) { + Face fc = facestats(get_triangle_vertices(chull, fi)); + + auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); + inputs.emplace_back(from_transform3d(Transform3d::Identity() * q)); + } + + return inputs; +} + +XYRotation find_best_rotation(const SLAPrintObject & po, + float accuracy, + std::function statuscb, + std::function stopcond) { static const unsigned MAX_TRIES = 10000; @@ -105,10 +202,10 @@ std::array find_best_rotation(const ModelObject& modelobj, // We will use only one instance of this converted mesh to examine different // rotations - TriangleMesh mesh = modelobj.raw_mesh(); + TriangleMesh mesh = po.model_object()->raw_mesh(); mesh.require_shared_vertices(); - // For current iteration number + // To keep track of the number of iterations unsigned status = 0; // The maximum number of iterations @@ -117,51 +214,66 @@ std::array find_best_rotation(const ModelObject& modelobj, // call status callback with zero, because we are at the start statuscb(status); - // So this is the object function which is called by the solver many times - // It has to yield a single value representing the current score. We will - // call the status callback in each iteration but the actual value may be - // the same for subsequent iterations (status goes from 0 to 100 but - // iterations can be many more) - auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries] - (const opt::Input<2> &in) - { - std::cout << "in: " << in[0] << " " << in[1] << std::endl; - - // prepare the rotation transformation - Transform3d rt = Transform3d::Identity(); - rt.rotate(Eigen::AngleAxisd(in[1], Vec3d::UnitY())); - rt.rotate(Eigen::AngleAxisd(in[0], Vec3d::UnitX())); - - double score = sla::calculate_model_supportedness(mesh, rt); - std::cout << score << std::endl; - + auto statusfn = [&statuscb, &status, max_tries] { // report status - if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) ); - - return score; + statuscb(unsigned(++status * 100.0/max_tries) ); }; - // Firing up the optimizer. - opt::Optimizer solver(opt::StopCriteria{} - .max_iterations(max_tries) - .rel_score_diff(1e-6) - .stop_condition(stopcond), - std::sqrt(max_tries)/*grid size*/); + // Different search methods have to be used depending on the model elevation + if (is_on_floor(po)) { - // We are searching rotations around only two axes x, y. Thus the - // problem becomes a 2 dimensional optimization task. - // We can specify the bounds for a dimension in the following way: - auto b = opt::Bound{-PI, PI}; + // If the model can be placed on the bed directly, we only need to + // check the 3D convex hull face rotations. - // Now we start the optimization process with initial angles (0, 0) - auto result = solver.to_min().optimize(objfunc, opt::initvals({0., 0.}), - opt::bounds({b, b})); + auto inputs = get_chull_rotations(mesh); - // Save the result and fck off - rot[0] = std::get<0>(result.optimum); - rot[1] = std::get<1>(result.optimum); + auto cmpfn = [](double a, double b) { return a < b; }; + auto objfn = [&mesh, &statusfn](const XYRotation &rot) { + statusfn(); + // We actually need the reverserotation to make the object lie on + // this face + Transform3d tr = to_transform3d(rot); + return get_model_supportedness_onfloor(mesh, tr); + }; + + rot = find_min_score<2>(objfn, cmpfn, inputs.begin(), inputs.end()); + } else { + + // Preparing the optimizer. + size_t grid_size = std::sqrt(max_tries); + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .stop_condition(stopcond), + grid_size); + + // We are searching rotations around only two axes x, y. Thus the + // problem becomes a 2 dimensional optimization task. + // We can specify the bounds for a dimension in the following way: + auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} }); + + auto result = solver.to_min().optimize( + [&mesh, &statusfn] (const XYRotation &rot) + { + statusfn(); + return get_model_supportedness(mesh, to_transform3d(rot)); + }, opt::initvals({0., 0.}), bounds); + + // Save the result and fck off + rot = result.optimum; + + std::cout << "best score: " << result.score << std::endl; + } return rot; } +double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr) +{ + TriangleMesh mesh = po.model_object()->raw_mesh(); + mesh.require_shared_vertices(); + + return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) : + get_model_supportedness(mesh, tr); +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 583703203a..4fa529600b 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -4,9 +4,11 @@ #include #include +#include + namespace Slic3r { -class ModelObject; +class SLAPrintObject; namespace sla { @@ -26,13 +28,16 @@ namespace sla { * @return Returns the rotations around each axis (x, y, z) */ std::array find_best_rotation( - const ModelObject& modelobj, + const SLAPrintObject& modelobj, float accuracy = 1.0f, std::function statuscb = [] (unsigned) {}, std::function stopcond = [] () { return false; } ); -} -} +double get_model_supportedness(const SLAPrintObject &mesh, + const Transform3d & tr); + +} // namespace sla +} // namespace Slic3r #endif // SLAROTFINDER_HPP diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 8365875d9a..77366c6335 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -200,6 +200,8 @@ void GLGizmoRotate::on_render_for_picking() const glsafe(::glPopMatrix()); } + + GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, State & state, const Alignment &alignment) @@ -215,20 +217,26 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, y = std::min(y, alignment.bottom_limit - win_h); ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - ImGui::SliderFloat(_L("Accuracy").c_str(), &state.accuracy, 0.01f, 1.f, "%.1f"); + static constexpr const char * button_txt = L("Optimize orientation"); + static constexpr const char * slider_txt = L("Accuracy"); - if (imgui->button(_L("Optimize orientation"))) { + float button_width = imgui->calc_text_size(_(button_txt)).x; + ImGui::PushItemWidth(100.); + //if (imgui->button(_(button_txt))) { + if (ImGui::ArrowButton(_(button_txt).c_str(), ImGuiDir_Down)){ std::cout << "Blip" << std::endl; } + ImGui::SliderFloat(_(slider_txt).c_str(), &state.accuracy, 0.01f, 1.f, "%.1f"); + static const std::vector options = { _L("Least supports").ToStdString(), _L("Suface quality").ToStdString() }; - if (imgui->combo(_L("Choose method"), options, state.method) ) { - std::cout << "method: " << state.method << std::endl; - } +// if (imgui->combo(_L("Choose method"), options, state.method) ) { +// std::cout << "method: " << state.method << std::endl; +// } } @@ -243,18 +251,10 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) return; -// m_rotoptimizewin_state.mobj = ; - RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; +// TODO: -// if ((last_h != win_h) || (last_y != y)) -// { -// // ask canvas for another frame to render the window in the correct position -// m_parent.request_extra_frame(); -// if (last_h != win_h) -// last_h = win_h; -// if (last_y != y) -// last_y = y; -// } +// m_rotoptimizewin_state.mobj = ?; +// RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index c547dfbc0b..c418c4b316 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -136,12 +136,14 @@ protected: } void on_render_input_window(float x, float y, float bottom_limit) override; + private: class RotoptimzeWindow { ImGuiWrapper *m_imgui = nullptr; public: + struct State { enum Metods { mMinSupportPoints, mLegacy }; @@ -152,7 +154,10 @@ private: struct Alignment { float x, y, bottom_limit; }; - RotoptimzeWindow(ImGuiWrapper *imgui, State &settings, const Alignment &bottom_limit); + RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &bottom_limit); + ~RotoptimzeWindow(); RotoptimzeWindow(const RotoptimzeWindow&) = delete; diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 3fd86b13f5..91a67cfa27 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -4,6 +4,7 @@ #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/MinAreaBoundingBox.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/SLAPrint.hpp" #include "slic3r/GUI/Plater.hpp" @@ -15,9 +16,26 @@ void RotoptimizeJob::process() if (obj_idx < 0) { return; } ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; + const SLAPrintObject *po = m_plater->sla_print().objects()[size_t(obj_idx)]; + + if (!o || !po) return; + + TriangleMesh mesh = o->raw_mesh(); + mesh.require_shared_vertices(); + +// for (auto inst : o->instances) { +// Transform3d tr = Transform3d::Identity(); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Z), Vec3d::UnitZ())); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Y), Vec3d::UnitY())); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(X), Vec3d::UnitX())); + +// double score = sla::get_model_supportedness(*po, tr); + +// std::cout << "Model supportedness before: " << score << std::endl; +// } auto r = sla::find_best_rotation( - *o, + *po, 1.f, [this](unsigned s) { if (s < 100) From 3b7ea5587e5b6c7d6938c746dd2855b95d242aa7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Sep 2020 19:43:07 +0200 Subject: [PATCH 482/826] Fix build on win --- src/libslic3r/SLA/Concurrency.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index a7b5b6c61d..300024c76d 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -5,7 +5,10 @@ #include #include #include + #include +#include + #include namespace Slic3r { From d5271220464fb8da07bdc805c68318ece201d3bb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 4 Sep 2020 20:20:06 +0200 Subject: [PATCH 483/826] Performance optimizations and bugfix --- src/libslic3r/SLA/Rotfinder.cpp | 16 ++++++++++++++-- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index e033009aa4..db8c0b9a8d 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -76,12 +76,20 @@ inline Face facestats(const std::array &triangle) inline const Vec3d DOWN = {0., 0., -1.}; constexpr double POINTS_PER_UNIT_AREA = 1.; +// The score function for a particular face inline double get_score(const Face &fc) { + // Simply get the angle (acos of dot product) between the face normal and + // the DOWN vector. double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI; + + // Only consider faces that have have slopes below 90 deg: phi = phi * (phi > 0.5); + + // Make the huge slopes more significant than the smaller slopes phi = phi * phi * phi; + // Multiply with the area of the current face return fc.area * POINTS_PER_UNIT_AREA * phi; } @@ -176,6 +184,8 @@ std::vector get_chull_rotations(const TriangleMesh &mesh) { TriangleMesh chull = mesh.convex_hull_3d(); chull.require_shared_vertices(); + double chull2d_area = chull.convex_hull().area(); + double area_threshold = chull2d_area / (scaled(1e3) * scaled(1.)); size_t facecount = chull.its.indices.size(); auto inputs = reserve_vector(facecount); @@ -183,8 +193,10 @@ std::vector get_chull_rotations(const TriangleMesh &mesh) for (size_t fi = 0; fi < facecount; ++fi) { Face fc = facestats(get_triangle_vertices(chull, fi)); - auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); - inputs.emplace_back(from_transform3d(Transform3d::Identity() * q)); + if (fc.area > area_threshold) { + auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); + inputs.emplace_back(from_transform3d(Transform3d::Identity() * q)); + } } return inputs; diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 91a67cfa27..10c09275c3 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -13,7 +13,8 @@ namespace Slic3r { namespace GUI { void RotoptimizeJob::process() { int obj_idx = m_plater->get_selected_object_idx(); - if (obj_idx < 0) { return; } + if (obj_idx < 0 || m_plater->sla_print().objects().size() <= obj_idx) + return; ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; const SLAPrintObject *po = m_plater->sla_print().objects()[size_t(obj_idx)]; From b991b613de893e0caa113abf8f3a56b2808306ab Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Sep 2020 14:33:55 +0200 Subject: [PATCH 484/826] Updated titlebar and splash screen + hidden statusbar for gcode viewer --- src/libslic3r/libslic3r_version.h.in | 3 +++ src/slic3r/GUI/GUI_App.cpp | 8 ++++++ src/slic3r/GUI/MainFrame.cpp | 38 ++++++++++++++++++++++------ version.inc | 3 +++ 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in index 26a67362b7..90f0812aba 100644 --- a/src/libslic3r/libslic3r_version.h.in +++ b/src/libslic3r/libslic3r_version.h.in @@ -6,4 +6,7 @@ #define SLIC3R_VERSION "@SLIC3R_VERSION@" #define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@" +#define GCODEVIEWER_APP_NAME "@GCODEVIEWER_APP_NAME@" +#define GCODEVIEWER_BUILD_ID "@GCODEVIEWER_BUILD_ID@" + #endif /* __SLIC3R_VERSION_H */ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f6b0a44146..e50d4015e7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -161,7 +161,15 @@ static void DecorateSplashScreen(wxBitmap& bmp) memDc.DrawRectangle(banner_rect); // title +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxString title_string = SLIC3R_APP_NAME; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); title_font.SetPointSize(24); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 06cb75efa3..4d242dec88 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -116,12 +116,17 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S #endif // _WIN32 // initialize status bar - m_statusbar = std::make_shared(this); + m_statusbar = std::make_shared(this); m_statusbar->set_font(GUI::wxGetApp().normal_font()); - m_statusbar->embed(this); - m_statusbar->set_status_text(_(L("Version")) + " " + - SLIC3R_VERSION + - _(L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases"))); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_statusbar->embed(this); + m_statusbar->set_status_text(_L("Version") + " " + + SLIC3R_VERSION + + _L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")); // initialize tabpanel and menubar init_tabpanel(); @@ -526,8 +531,7 @@ void MainFrame::shutdown() void MainFrame::update_title() { wxString title = wxEmptyString; - if (m_plater != nullptr) - { + if (m_plater != nullptr) { // m_plater->get_project_filename() produces file name including path, but excluding extension. // Don't try to remove the extension, it would remove part of the file name after the last dot! wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); @@ -535,7 +539,15 @@ void MainFrame::update_title() title += (project + " - "); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::string build_id = SLIC3R_BUILD_ID; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ size_t idx_plus = build_id.find('+'); if (idx_plus != build_id.npos) { // Parse what is behind the '+'. If there is a number, then it is a build number after the label, and full build ID is shown. @@ -550,7 +562,17 @@ void MainFrame::update_title() #endif } } - title += (wxString(build_id) + " " + _(L("based on Slic3r"))); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + title += wxString(build_id); + if (wxGetApp().is_editor()) + title += (" " + _L("based on Slic3r")); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + title += (wxString(build_id) + " " + _L("based on Slic3r")); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ SetTitle(title); } diff --git a/version.inc b/version.inc index 30b373bdfd..e5985fcd0d 100644 --- a/version.inc +++ b/version.inc @@ -7,3 +7,6 @@ set(SLIC3R_VERSION "2.3.0-alpha0") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_RC_VERSION "2,3,0,0") set(SLIC3R_RC_VERSION_DOTS "2.3.0.0") + +set(GCODEVIEWER_APP_NAME "Prusa GCode Viewer") +set(GCODEVIEWER_BUILD_ID "Prusa GCode Viewer-${SLIC3R_VERSION}+UNKNOWN") From 70cb67430cf8d5284e6ee101d1f2ac189c52699e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Sep 2020 11:40:09 +0200 Subject: [PATCH 485/826] Move rotation from building octree to infill generating --- src/libslic3r/Fill/FillAdaptive.cpp | 27 ++++++++++++++------------- src/libslic3r/Fill/FillAdaptive.hpp | 2 +- src/libslic3r/PrintObject.cpp | 9 ++++++++- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index b1a40047d4..db7cab50a0 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -97,11 +97,14 @@ void FillAdaptive::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { + Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); + Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); + // Store grouped lines by its direction (multiple of 120°) std::vector infill_lines_dir(3); this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), - this->z, this->adapt_fill_octree->origin,infill_lines_dir, - this->adapt_fill_octree->cubes_properties, + this->z, this->adapt_fill_octree->origin, rotation_matrix, + infill_lines_dir, this->adapt_fill_octree->cubes_properties, int(this->adapt_fill_octree->cubes_properties.size()) - 1); Polylines all_polylines; @@ -186,6 +189,7 @@ void FillAdaptive::generate_infill_lines( FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d &origin, + const Transform3d &rotation_matrix, std::vector &dir_lines_out, const std::vector &cubes_properties, int depth) @@ -197,7 +201,8 @@ void FillAdaptive::generate_infill_lines( return; } - double z_diff = std::abs(z_position - cube->center.z()); + Vec3d cube_center_tranformed = rotation_matrix * cube->center; + double z_diff = std::abs(z_position - cube_center_tranformed.z()); if (z_diff > cubes_properties[depth].height / 2) { @@ -208,14 +213,14 @@ void FillAdaptive::generate_infill_lines( { Point from( scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance), - scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube->center.z() - cubes_properties[depth].line_z_distance)) / sqrt(2)))); + scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube_center_tranformed.z() - cubes_properties[depth].line_z_distance)) / sqrt(2)))); Point to(-from.x(), from.y()); // Relative to cube center double rotation_angle = (2.0 * M_PI) / 3.0; for (Lines &lines : dir_lines_out) { - Vec3d offset = cube->center - origin; + Vec3d offset = cube_center_tranformed - (rotation_matrix * origin); Point from_abs(from), to_abs(to); from_abs.x() += int(scale_(offset.x())); @@ -235,7 +240,7 @@ void FillAdaptive::generate_infill_lines( { if(child != nullptr) { - generate_infill_lines(child.get(), z_position, origin, dir_lines_out, cubes_properties, depth - 1); + generate_infill_lines(child.get(), z_position, origin, rotation_matrix, dir_lines_out, cubes_properties, depth - 1); } } } @@ -301,14 +306,11 @@ std::unique_ptr FillAdaptive::build_octree( triangle_mesh.require_shared_vertices(); } - Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); - Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); - AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( triangle_mesh.its.vertices, triangle_mesh.its.indices); auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1); + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1); return octree; } @@ -316,7 +318,6 @@ std::unique_ptr FillAdaptive::build_octree( void FillAdaptive::expand_cube( FillAdaptive_Internal::Cube *cube, const std::vector &cubes_properties, - const Transform3d &rotation_matrix, const AABBTreeIndirect::Tree3f &distance_tree, const TriangleMesh &triangle_mesh, int depth) { @@ -337,13 +338,13 @@ void FillAdaptive::expand_cube( for (size_t i = 0; i < 8; ++i) { const Vec3d &child_center = child_centers[i]; - Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cubes_properties[depth].edge_length / 4)); + Vec3d child_center_transformed = cube->center + (child_center * (cubes_properties[depth].edge_length / 4)); if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices, distance_tree, child_center_transformed, cube_radius_squared)) { cube->children[i] = std::make_unique(child_center_transformed); - FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, rotation_matrix, distance_tree, triangle_mesh, depth - 1); + FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, distance_tree, triangle_mesh, depth - 1); } } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index dd7394384c..f337832238 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -63,6 +63,7 @@ protected: FillAdaptive_Internal::Cube *cube, double z_position, const Vec3d & origin, + const Transform3d & rotation_matrix, std::vector & dir_lines_out, const std::vector &cubes_properties, int depth); @@ -78,7 +79,6 @@ public: static void expand_cube( FillAdaptive_Internal::Cube *cube, const std::vector &cubes_properties, - const Transform3d & rotation_matrix, const AABBTreeIndirect::Tree3f &distance_tree, const TriangleMesh & triangle_mesh, int depth); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 474417b1ec..6ebfbc9247 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -444,7 +444,14 @@ std::unique_ptr PrintObject::prepare_adaptive_inf mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // Center of the first cube in octree Vec3d mesh_origin = mesh.bounding_box().center(); - return FillAdaptive::build_octree(mesh, adaptive_line_spacing, mesh_origin); + + Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); + Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()).inverse(); + + // Rotate mesh and build octree on it with axis-aligned (standart base) cubes + mesh.transform(rotation_matrix); + + return FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin); } void PrintObject::clear_layers() From e55d184a7d4ca992d06575a2230a09dcb4ee910f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Sep 2020 11:48:24 +0200 Subject: [PATCH 486/826] Fix missing initialization in TriangleMesh constructor --- src/libslic3r/TriangleMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 17edf1b5a8..49fc625af9 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -70,7 +70,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector &fac stl_get_size(&stl); } -TriangleMesh::TriangleMesh(const indexed_triangle_set &M) +TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false) { stl.stats.type = inmemory; From c26162499908dfb7d86be6cf04cae3bd2b67a46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 8 Sep 2020 11:49:26 +0200 Subject: [PATCH 487/826] A simple version of adaptive cubic support, for testing purposes --- src/libslic3r/Fill/FillAdaptive.hpp | 6 +++++ src/libslic3r/PrintObject.cpp | 38 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index f337832238..67a2d0f3fa 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -48,6 +48,12 @@ class FillAdaptive : public Fill public: virtual ~FillAdaptive() {} + static void insert_octant( + FillAdaptive_Internal::Cube * i_cube, + FillAdaptive_Internal::Cube * current, + int depth, + const std::vector &cubes_properties); + protected: virtual Fill* clone() const { return new FillAdaptive(*this); }; virtual void _fill_surface_single( diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 6ebfbc9247..645d36a38c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -11,6 +11,7 @@ #include "Utils.hpp" #include "AABBTreeIndirect.hpp" #include "Fill/FillAdaptive.hpp" +#include "Format/STL.hpp" #include #include @@ -432,6 +433,8 @@ void PrintObject::generate_support_material() } } +#define ADAPTIVE_SUPPORT_SIMPLE + std::unique_ptr PrintObject::prepare_adaptive_infill_data() { auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); @@ -445,6 +448,41 @@ std::unique_ptr PrintObject::prepare_adaptive_inf // Center of the first cube in octree Vec3d mesh_origin = mesh.bounding_box().center(); +#ifdef ADAPTIVE_SUPPORT_SIMPLE + if (mesh.its.vertices.empty()) + { + mesh.require_shared_vertices(); + } + + Vec3f vertical(0, 0, 1); + + indexed_triangle_set its_set; + its_set.vertices = mesh.its.vertices; + + // Filter out non overhanging faces + for (size_t i = 0; i < mesh.its.indices.size(); ++i) { + stl_triangle_vertex_indices vertex_idx = mesh.its.indices[i]; + + auto its_calculate_normal = [](const stl_triangle_vertex_indices &index, const std::vector &vertices) { + stl_normal normal = (vertices[index.y()] - vertices[index.x()]).cross(vertices[index.z()] - vertices[index.x()]); + return normal; + }; + + stl_normal normal = its_calculate_normal(vertex_idx, mesh.its.vertices); + stl_normalize_vector(normal); + + if(normal.dot(vertical) >= 0.707) { + its_set.indices.push_back(vertex_idx); + } + } + + mesh = TriangleMesh(its_set); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + Slic3r::store_stl(debug_out_path("overhangs.stl").c_str(), &mesh, false); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ +#endif /* ADAPTIVE_SUPPORT_SIMPLE */ + Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()).inverse(); From 680b1b98093264c7307e6694053f323437040a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 9 Sep 2020 09:20:06 +0200 Subject: [PATCH 488/826] Construct octree based on inserted points --- src/libslic3r/Fill/FillAdaptive.cpp | 43 +++++++++++++++++++++++++---- src/libslic3r/Fill/FillAdaptive.hpp | 17 ++++++++---- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index db7cab50a0..4667c30c90 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -91,10 +91,10 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob } void FillAdaptive::_fill_surface_single( - const FillParams ¶ms, + const FillParams ¶ms, unsigned int thickness_layers, - const std::pair &direction, - ExPolygon &expolygon, + const std::pair &direction, + ExPolygon &expolygon, Polylines &polylines_out) { Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); @@ -329,8 +329,8 @@ void FillAdaptive::expand_cube( } std::vector child_centers = { - Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d(-1, -1, 1), - Vec3d( 1, 1, 1), Vec3d(-1, 1, 1), Vec3d( 1, -1, 1), Vec3d( 1, 1, -1) + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), + Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) }; double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16; @@ -349,4 +349,37 @@ void FillAdaptive::expand_cube( } } +void FillAdaptive_Internal::Octree::propagate_point( + Vec3d point, + FillAdaptive_Internal::Cube * current, + int depth, + const std::vector &cubes_properties) +{ + using namespace FillAdaptive_Internal; + + if(depth <= 0) + { + return; + } + + size_t octant_idx = Octree::find_octant(point, current->center); + Cube * child = current->children[octant_idx].get(); + + // Octant not exists, then create it + if(child == nullptr) { + std::vector child_centers = { + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), + Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) + }; + + const Vec3d &child_center = child_centers[octant_idx]; + Vec3d child_center_transformed = current->center + (child_center * (cubes_properties[depth].edge_length / 4)); + + current->children[octant_idx] = std::make_unique(child_center_transformed); + child = current->children[octant_idx].get(); + } + + Octree::propagate_point(point, child, (depth - 1), cubes_properties); +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 67a2d0f3fa..d384776543 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -35,6 +35,17 @@ namespace FillAdaptive_Internal Octree(std::unique_ptr rootCube, const Vec3d &origin, const std::vector &cubes_properties) : root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {} + + inline static int find_octant(const Vec3d &i_cube, const Vec3d ¤t) + { + return (i_cube.z() > current.z()) * 4 + (i_cube.y() > current.y()) * 2 + (i_cube.x() > current.x()); + } + + static void propagate_point( + Vec3d point, + FillAdaptive_Internal::Cube *current_cube, + int depth, + const std::vector &cubes_properties); }; }; // namespace FillAdaptive_Internal @@ -48,12 +59,6 @@ class FillAdaptive : public Fill public: virtual ~FillAdaptive() {} - static void insert_octant( - FillAdaptive_Internal::Cube * i_cube, - FillAdaptive_Internal::Cube * current, - int depth, - const std::vector &cubes_properties); - protected: virtual Fill* clone() const { return new FillAdaptive(*this); }; virtual void _fill_surface_single( From 8fb9b290b261d3deb86fb7795b54fbd184110167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 9 Sep 2020 09:29:50 +0200 Subject: [PATCH 489/826] A prototype of adaptive support infill --- src/libslic3r/Fill/FillAdaptive.cpp | 115 ++++++++++++++++++++++++++++ src/libslic3r/Fill/FillAdaptive.hpp | 6 ++ src/libslic3r/PrintObject.cpp | 5 ++ 3 files changed, 126 insertions(+) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 4667c30c90..d921d8b919 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -382,4 +382,119 @@ void FillAdaptive_Internal::Octree::propagate_point( Octree::propagate_point(point, child, (depth - 1), cubes_properties); } +std::unique_ptr FillAdaptive::build_octree_for_adaptive_support( + TriangleMesh & triangle_mesh, + coordf_t line_spacing, + const Vec3d & cube_center, + const Transform3d &rotation_matrix) +{ + using namespace FillAdaptive_Internal; + + if(line_spacing <= 0 || std::isnan(line_spacing)) + { + return nullptr; + } + + Vec3d bb_size = triangle_mesh.bounding_box().size(); + // The furthest point from the center of the bottom of the mesh bounding box. + double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + + ((bb_size.y() * bb_size.y()) / 4.0) + + (bb_size.z() * bb_size.z())); + double max_cube_edge_length = furthest_point * 2; + + std::vector cubes_properties; + for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) + { + CubeProperties props{}; + props.edge_length = edge_length; + props.height = edge_length * sqrt(3); + props.diagonal_length = edge_length * sqrt(2); + props.line_z_distance = edge_length / sqrt(3); + props.line_xy_distance = edge_length / sqrt(6); + cubes_properties.push_back(props); + } + + if (triangle_mesh.its.vertices.empty()) + { + triangle_mesh.require_shared_vertices(); + } + + AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + triangle_mesh.its.vertices, triangle_mesh.its.indices); + + auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); + + double cube_edge_length = line_spacing; + size_t max_depth = octree->cubes_properties.size() - 1; + BoundingBoxf3 mesh_bb = triangle_mesh.bounding_box(); + Vec3f vertical(0, 0, 1); + + for (size_t facet_idx = 0; facet_idx < triangle_mesh.stl.facet_start.size(); ++facet_idx) + { + if(triangle_mesh.stl.facet_start[facet_idx].normal.dot(vertical) <= 0.707) + { + // The angle is smaller than PI/4, than infill don't to be there + continue; + } + + stl_vertex v_1 = triangle_mesh.stl.facet_start[facet_idx].vertex[0]; + stl_vertex v_2 = triangle_mesh.stl.facet_start[facet_idx].vertex[1]; + stl_vertex v_3 = triangle_mesh.stl.facet_start[facet_idx].vertex[2]; + + std::vector triangle_vertices = + {Vec3d(v_1.x(), v_1.y(), v_1.z()), + Vec3d(v_2.x(), v_2.y(), v_2.z()), + Vec3d(v_3.x(), v_3.y(), v_3.z())}; + + BoundingBoxf3 triangle_bb(triangle_vertices); + + Vec3d triangle_start_relative = triangle_bb.min - mesh_bb.min; + Vec3d triangle_end_relative = triangle_bb.max - mesh_bb.min; + + Vec3crd triangle_start_idx = Vec3crd( + std::floor(triangle_start_relative.x() / cube_edge_length), + std::floor(triangle_start_relative.y() / cube_edge_length), + std::floor(triangle_start_relative.z() / cube_edge_length)); + Vec3crd triangle_end_idx = Vec3crd( + std::floor(triangle_end_relative.x() / cube_edge_length), + std::floor(triangle_end_relative.y() / cube_edge_length), + std::floor(triangle_end_relative.z() / cube_edge_length)); + + for (int z = triangle_start_idx.z(); z <= triangle_end_idx.z(); ++z) + { + for (int y = triangle_start_idx.y(); y <= triangle_end_idx.y(); ++y) + { + for (int x = triangle_start_idx.x(); x <= triangle_end_idx.x(); ++x) + { + Vec3d cube_center_relative(x * cube_edge_length + (cube_edge_length / 2.0), y * cube_edge_length + (cube_edge_length / 2.0), z * cube_edge_length); + Vec3d cube_center_absolute = cube_center_relative + mesh_bb.min; + + double cube_center_absolute_arr[3] = {cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z()}; + double distance = 0, cord_u = 0, cord_v = 0; + + double dir[3] = {0.0, 0.0, 1.0}; + + double vert_0[3] = {triangle_vertices[0].x(), + triangle_vertices[0].y(), + triangle_vertices[0].z()}; + double vert_1[3] = {triangle_vertices[1].x(), + triangle_vertices[1].y(), + triangle_vertices[1].z()}; + double vert_2[3] = {triangle_vertices[2].x(), + triangle_vertices[2].y(), + triangle_vertices[2].z()}; + + if(intersect_triangle(cube_center_absolute_arr, dir, vert_0, vert_1, vert_2, &distance, &cord_u, &cord_v) && distance > 0 && distance <= cube_edge_length) + { + Vec3d cube_center_transformed(cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z() + (cube_edge_length / 2.0)); + Octree::propagate_point(cube_center_transformed, octree->root_cube.get(), max_depth, octree->cubes_properties); + } + } + } + } + } + + return octree; +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index d384776543..63043ce4e0 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -93,6 +93,12 @@ public: const AABBTreeIndirect::Tree3f &distance_tree, const TriangleMesh & triangle_mesh, int depth); + + static std::unique_ptr build_octree_for_adaptive_support( + TriangleMesh & triangle_mesh, + coordf_t line_spacing, + const Vec3d & cube_center, + const Transform3d &rotation_matrix); }; // Calculate line spacing for diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 645d36a38c..05debe8abb 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -433,6 +433,7 @@ void PrintObject::generate_support_material() } } +#define ADAPTIVE_SUPPORT #define ADAPTIVE_SUPPORT_SIMPLE std::unique_ptr PrintObject::prepare_adaptive_infill_data() @@ -489,7 +490,11 @@ std::unique_ptr PrintObject::prepare_adaptive_inf // Rotate mesh and build octree on it with axis-aligned (standart base) cubes mesh.transform(rotation_matrix); +#if defined(ADAPTIVE_SUPPORT) && !defined(ADAPTIVE_SUPPORT_SIMPLE) + return FillAdaptive::build_octree_for_adaptive_support(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin, rotation_matrix); +#else return FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin); +#endif } void PrintObject::clear_layers() From f49144a9ef3701e77c2ff812fddfb394c53134ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 10 Sep 2020 16:53:08 +0200 Subject: [PATCH 490/826] Move support cubic infill to separate class. Support infill is enabled in the GUI. --- src/libslic3r/Fill/Fill.cpp | 3 +- src/libslic3r/Fill/FillAdaptive.cpp | 44 +++++++++++++++++++++-------- src/libslic3r/Fill/FillAdaptive.hpp | 26 +++++++++++++++++ src/libslic3r/Fill/FillBase.cpp | 1 + src/libslic3r/Fill/FillBase.hpp | 3 ++ src/libslic3r/Layer.hpp | 4 +-- src/libslic3r/Print.hpp | 2 +- src/libslic3r/PrintConfig.cpp | 2 ++ src/libslic3r/PrintConfig.hpp | 3 +- src/libslic3r/PrintObject.cpp | 32 ++++++++++++--------- 10 files changed, 90 insertions(+), 30 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 9d468a6aa9..d68bc7afb3 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree) +void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -346,6 +346,7 @@ void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree) f->z = this->print_z; f->angle = surface_fill.params.angle; f->adapt_fill_octree = adaptive_fill_octree; + f->support_fill_octree = support_fill_octree; // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index d921d8b919..fb8c665ebd 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -35,7 +35,7 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob const PrintRegionConfig &config = region->config(); bool nonempty = config.fill_density > 0; bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic; - bool has_support_infill = nonempty && false; // config.fill_pattern == icSupportCubic; + bool has_support_infill = nonempty && config.fill_pattern == ipSupportCubic; region_fill_data.push_back(RegionFillData({ has_adaptive_infill ? Tristate::Maybe : Tristate::No, has_support_infill ? Tristate::Maybe : Tristate::No, @@ -90,22 +90,32 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob return std::make_pair(adaptive_line_spacing, support_line_spacing); } -void FillAdaptive::_fill_surface_single( - const FillParams ¶ms, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon &expolygon, - Polylines &polylines_out) +void FillAdaptive::_fill_surface_single(const FillParams & params, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon & expolygon, + Polylines & polylines_out) +{ + if(this->adapt_fill_octree != nullptr) + this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->adapt_fill_octree); +} + +void FillAdaptive::generate_infill(const FillParams & params, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon & expolygon, + Polylines & polylines_out, + FillAdaptive_Internal::Octree *octree) { Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); // Store grouped lines by its direction (multiple of 120°) std::vector infill_lines_dir(3); - this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), - this->z, this->adapt_fill_octree->origin, rotation_matrix, - infill_lines_dir, this->adapt_fill_octree->cubes_properties, - int(this->adapt_fill_octree->cubes_properties.size()) - 1); + this->generate_infill_lines(octree->root_cube.get(), + this->z, octree->origin, rotation_matrix, + infill_lines_dir, octree->cubes_properties, + int(octree->cubes_properties.size()) - 1); Polylines all_polylines; all_polylines.reserve(infill_lines_dir[0].size() * 3); @@ -382,7 +392,7 @@ void FillAdaptive_Internal::Octree::propagate_point( Octree::propagate_point(point, child, (depth - 1), cubes_properties); } -std::unique_ptr FillAdaptive::build_octree_for_adaptive_support( +std::unique_ptr FillSupportCubic::build_octree_for_adaptive_support( TriangleMesh & triangle_mesh, coordf_t line_spacing, const Vec3d & cube_center, @@ -497,4 +507,14 @@ std::unique_ptr FillAdaptive::build_octree_for_ad return octree; } +void FillSupportCubic::_fill_surface_single(const FillParams & params, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon & expolygon, + Polylines & polylines_out) +{ + if (this->support_fill_octree != nullptr) + this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->support_fill_octree); +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 63043ce4e0..45bfde8026 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -81,6 +81,13 @@ protected: static void connect_lines(Lines &lines, Line new_line); + void generate_infill(const FillParams & params, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon & expolygon, + Polylines & polylines_out, + FillAdaptive_Internal::Octree *octree); + public: static std::unique_ptr build_octree( TriangleMesh &triangle_mesh, @@ -93,7 +100,26 @@ public: const AABBTreeIndirect::Tree3f &distance_tree, const TriangleMesh & triangle_mesh, int depth); +}; +class FillSupportCubic : public FillAdaptive +{ +public: + virtual ~FillSupportCubic() = default; + +protected: + virtual Fill* clone() const { return new FillSupportCubic(*this); }; + + virtual bool no_sort() const { return true; } + + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out); + +public: static std::unique_ptr build_octree_for_adaptive_support( TriangleMesh & triangle_mesh, coordf_t line_spacing, diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c1f38dad5c..9001330aae 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -39,6 +39,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive(); + case ipSupportCubic: return new FillSupportCubic(); default: throw std::invalid_argument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 9f70b69e08..dd887b8c3e 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -73,7 +73,10 @@ public: // In scaled coordinates. Bounding box of the 2D projection of the object. BoundingBox bounding_box; + // Octree builds on mesh for usage in the adaptive cubic infill FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr; + // Octree builds on mesh for usage in the support cubic infill + FillAdaptive_Internal::Octree* support_fill_octree = nullptr; public: virtual ~Fill() {} diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 014d2623af..8d5db42fc0 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -138,8 +138,8 @@ public: return false; } void make_perimeters(); - void make_fills() { this->make_fills(nullptr); }; - void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree); + void make_fills() { this->make_fills(nullptr, nullptr); }; + void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree); void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index effb6bde90..98a1314112 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -239,7 +239,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::unique_ptr prepare_adaptive_infill_data(); + std::pair, std::unique_ptr> prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 718fae365b..72393a3f5a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -882,6 +882,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); def->enum_values.push_back("adaptivecubic"); + def->enum_values.push_back("supportcubic"); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); def->enum_labels.push_back(L("Triangles")); @@ -896,6 +897,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); def->enum_labels.push_back(L("Adaptive Cubic")); + def->enum_labels.push_back(L("Support Cubic")); def->set_default_value(new ConfigOptionEnum(ipStars)); def = this->add("first_layer_acceleration", coFloat); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 3726444fab..fa7edd10e1 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -39,7 +39,7 @@ enum AuthorizationType { enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount, }; enum class IroningType { @@ -140,6 +140,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["octagramspiral"] = ipOctagramSpiral; keys_map["adaptivecubic"] = ipAdaptiveCubic; + keys_map["supportcubic"] = ipSupportCubic; } return keys_map; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 05debe8abb..a102c32813 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -372,15 +372,15 @@ void PrintObject::infill() this->prepare_infill(); if (this->set_started(posInfill)) { - std::unique_ptr octree = this->prepare_adaptive_infill_data(); + auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data(); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this, &octree](const tbb::blocked_range& range) { + [this, &adaptive_fill_octree, &support_fill_octree](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(octree.get()); + m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get()); } } ); @@ -433,14 +433,18 @@ void PrintObject::generate_support_material() } } -#define ADAPTIVE_SUPPORT -#define ADAPTIVE_SUPPORT_SIMPLE +//#define ADAPTIVE_SUPPORT_SIMPLE -std::unique_ptr PrintObject::prepare_adaptive_infill_data() +std::pair, std::unique_ptr> PrintObject::prepare_adaptive_infill_data() { + using namespace FillAdaptive_Internal; + auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); - if (adaptive_line_spacing == 0.) - return std::unique_ptr{}; + + std::unique_ptr adaptive_fill_octree = {}, support_fill_octree = {}; + + if (adaptive_line_spacing == 0. && support_line_spacing == 0.) + return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree)); TriangleMesh mesh = this->model_object()->raw_mesh(); mesh.transform(m_trafo, true); @@ -490,11 +494,13 @@ std::unique_ptr PrintObject::prepare_adaptive_inf // Rotate mesh and build octree on it with axis-aligned (standart base) cubes mesh.transform(rotation_matrix); -#if defined(ADAPTIVE_SUPPORT) && !defined(ADAPTIVE_SUPPORT_SIMPLE) - return FillAdaptive::build_octree_for_adaptive_support(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin, rotation_matrix); -#else - return FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin); -#endif + if (adaptive_line_spacing != 0.) + adaptive_fill_octree = FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin); + + if (support_line_spacing != 0.) + support_fill_octree = FillSupportCubic::build_octree_for_adaptive_support(mesh, support_line_spacing, rotation_matrix * mesh_origin, rotation_matrix); + + return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree)); } void PrintObject::clear_layers() From f1f9785a8a3d10ad7a3bb91194e0d758cab5aee3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 10 Sep 2020 16:03:43 +0200 Subject: [PATCH 491/826] SplashScreen: * Show it on the display same as an Application * Code refactoring : All related functions moved to the SplashScreen class * Add a possibility o hide/show splash scree in Preferences --- src/libslic3r/AppConfig.cpp | 3 + src/slic3r/GUI/GUI_App.cpp | 370 ++++++++++++++++++++------------- src/slic3r/GUI/Preferences.cpp | 9 + 3 files changed, 234 insertions(+), 148 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 2d96e0b50d..5892b4a30d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -101,6 +101,9 @@ void AppConfig::set_defaults() if (get("use_inches").empty()) set("use_inches", "0"); + if (get("show_splash_screen").empty()) + set("show_splash_screen", "1"); + // Remove legacy window positions/sizes erase("", "main_frame_maximized"); erase("", "main_frame_pos"); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e50d4015e7..17b21783c8 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -78,176 +78,233 @@ namespace GUI { class MainFrame; -static float get_scale_for_main_display() -{ - // ysFIXME : Workaround : - // wxFrame is created on the main monitor, so we can take a scale factor from this one - // before The Application and the Mainframe are created - wxFrame fr(nullptr, wxID_ANY, wxEmptyString); - -#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) - int dpi = get_dpi_for_window(&fr); - float sf = dpi != DPI_DEFAULT ? sf = (float)dpi / DPI_DEFAULT : 1.0; -#else - printf("dpi = %d\n", get_dpi_for_window(&fr)); - // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. - float sf = 0.1 * std::max(10, fr.GetTextExtent("m").x - 1); -#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT - - printf("scale factor = %f\n", sf); - return sf; -} - -// scale input bitmap and return scale factor -static float scale_bitmap(wxBitmap& bmp) -{ - float sf = get_scale_for_main_display(); - - // scale bitmap if needed - if (sf > 1.0) { - wxImage image = bmp.ConvertToImage(); - if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) - { - int width = int(sf * image.GetWidth()); - int height = int(sf * image.GetHeight()); - image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); - - bmp = wxBitmap(std::move(image)); - } - } - - return sf; -} - -static void word_wrap_string(wxString& input, int line_px_len, float scalef) -{ - // calculate count od symbols in one line according to the scale - int line_len = std::roundf( (float)line_px_len / (scalef * 10)) + 10; - - int idx = -1; - int cur_len = 0; - for (size_t i = 0; i < input.Len(); i++) - { - cur_len++; - if (input[i] == ' ') - idx = i; - if (input[i] == '\n') - { - idx = -1; - cur_len = 0; - } - if (cur_len >= line_len && idx >= 0) - { - input[idx] = '\n'; - cur_len = static_cast(i) - idx; - } - } -} - -static void DecorateSplashScreen(wxBitmap& bmp) -{ - wxASSERT(bmp.IsOk()); - float scale_factor = scale_bitmap(bmp); - - // use a memory DC to draw directly onto the bitmap - wxMemoryDC memDc(bmp); - - // draw an dark grey box at the left of the splashscreen. - // this box will be 2/5 of the weight of the bitmap, and be at the left. - int banner_width = (bmp.GetWidth() / 5) * 2 - 2; - const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight())); - wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); - wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); - memDc.DrawRectangle(banner_rect); - - // title -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_GCODE_VIEWER - wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - wxString title_string = SLIC3R_APP_NAME; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - title_font.SetPointSize(24); - - // dynamically get the version to display - wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION); - wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Larger().Larger(); - - // create a copyright notice that uses the year that this file was compiled - wxString year(__DATE__); - wxString cr_symbol = wxString::FromUTF8("\xc2\xa9"); - wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" - "%s 2011-2018 Alessandro Ranellucci.", - cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n"; - wxFont copyright_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Larger(); - - copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + - _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + - _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others."); - - word_wrap_string(copyright_string, banner_width, scale_factor); - - wxCoord margin = int(scale_factor * 20); - - // draw the (orange) labels inside of our black box (at the left of the splashscreen) - memDc.SetTextForeground(wxColour(237, 107, 33)); - - memDc.SetFont(title_font); - memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT); - - memDc.SetFont(version_font); - memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); - - memDc.SetFont(copyright_font); - memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT); -} - class SplashScreen : public wxSplashScreen { public: - SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxWindow* parent) - : wxSplashScreen(bitmap, splashStyle, milliseconds, parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR) + SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition, bool is_decorated = false) + : wxSplashScreen(bitmap, splashStyle, milliseconds, nullptr, wxID_ANY, + wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR ) { wxASSERT(bitmap.IsOk()); m_main_bitmap = bitmap; - m_scale_factor = get_scale_for_main_display(); + if (!is_decorated) + Decorate(m_main_bitmap, pos, true); + + m_scale = get_display_scale(pos); + m_font = get_scaled_sys_font(get_splashscreen_display_scale_factor(pos)).Bold().Larger(); + + if (pos != wxDefaultPosition) { + this->SetPosition(pos); + this->CenterOnScreen(); + } } void SetText(const wxString& text) { - SetBmp(m_main_bitmap); + set_bitmap(m_main_bitmap); if (!text.empty()) { wxBitmap bitmap(m_main_bitmap); wxMemoryDC memDC; memDC.SelectObject(bitmap); - wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold().Larger(); - memDC.SetFont(font); + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + memDC.SetFont(m_font); memDC.SetTextForeground(wxColour(237, 107, 33)); - memDC.DrawText(text, int(m_scale_factor * 45), int(m_scale_factor * 215)); + memDC.DrawText(text, int(m_scale * 45), int(m_scale * 200)); memDC.SelectObject(wxNullBitmap); - SetBmp(bitmap); + set_bitmap(bitmap); } } - void SetBmp(wxBitmap& bmp) + static bool Decorate(wxBitmap& bmp, wxPoint screen_pos = wxDefaultPosition, bool force_decor = false) + { + if (!bmp.IsOk()) + return false; + + float screen_sf = get_splashscreen_display_scale_factor(screen_pos); + float screen_scale = get_display_scale(screen_pos); + + if (screen_sf == 1.0) { + // it means that we have just one display or all displays have a same scale + // Scale bitmap for this display and continue the decoration + scale_bitmap(bmp, screen_scale); + } + else if (force_decor) { + // if we are here, it means that bitmap is already scaled for the main display + // and now we should just scale it th the secondary monitor and continue the decoration + scale_bitmap(bmp, screen_sf); + } + else { + // if screens have different scale and this function is called with force_decor == false + // then just rescale the bitmap for the main display scale + scale_bitmap(bmp, get_display_scale()); + return false; + // Decoration will be continued later, from the SplashScreen constructor + } + + // use a memory DC to draw directly onto the bitmap + wxMemoryDC memDc(bmp); + + // draw an dark grey box at the left of the splashscreen. + // this box will be 2/5 of the weight of the bitmap, and be at the left. + int banner_width = (bmp.GetWidth() / 5) * 2 - 2; + const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight())); + wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); + wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); + memDc.DrawRectangle(banner_rect); + + wxFont sys_font = get_scaled_sys_font(screen_sf); + + // title +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_GCODE_VIEWER + wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxString title_string = SLIC3R_APP_NAME; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_GCODE_VIEWER +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxFont title_font = sys_font; + title_font.SetPointSize(3 * sys_font.GetPointSize()); + + + // dynamically get the version to display + wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION); + wxFont version_font = sys_font.Larger().Larger(); + + // create a copyright notice that uses the year that this file was compiled + wxString year(__DATE__); + wxString cr_symbol = wxString::FromUTF8("\xc2\xa9"); + wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" + "%s 2011-2018 Alessandro Ranellucci.", + cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n"; + wxFont copyright_font = sys_font.Larger(); + + copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + + _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + + _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" + + _L("Splash screen could be desabled from the \"Preferences\""); + + word_wrap_string(copyright_string, banner_width, screen_scale); + + wxCoord margin = int(screen_scale * 20); + + // draw the (orange) labels inside of our black box (at the left of the splashscreen) + memDc.SetTextForeground(wxColour(237, 107, 33)); + + memDc.SetFont(title_font); + memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT); + + memDc.SetFont(version_font); + memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); + + memDc.SetFont(copyright_font); + memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, margin), wxALIGN_BOTTOM | wxALIGN_LEFT); + + return true; + } + +private: + wxBitmap m_main_bitmap; + wxFont m_font; + float m_scale {1.0}; + + void set_bitmap(wxBitmap& bmp) { m_window->SetBitmap(bmp); m_window->Refresh(); m_window->Update(); } -private: - wxBitmap m_main_bitmap; - float m_scale_factor {1.0}; + static float get_splashscreen_display_scale_factor(wxPoint pos = wxDefaultPosition) + { + if (wxDisplay::GetCount() == 1) + return 1.0; + + wxFrame main_screen_fr(nullptr, wxID_ANY, wxEmptyString); + wxFrame splash_screen_fr(nullptr, wxID_ANY, wxEmptyString, pos); + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) + int main_dpi = get_dpi_for_window(&main_screen_fr); + int splash_dpi = get_dpi_for_window(&splash_screen_fr); + float sf = (float)splash_dpi / (float)main_dpi; +#else + // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. + float sf = (float)splash_screen_fr.GetTextExtent("m").x / (float)main_screen_fr.GetTextExtent("m").x; +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + return sf; + } + + static float get_display_scale(wxPoint pos = wxDefaultPosition) + { + // pos equals to wxDefaultPosition, when display is main + wxFrame fr(nullptr, wxID_ANY, wxEmptyString, pos); + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) + int dpi = get_dpi_for_window(&fr); + float scale = dpi != DPI_DEFAULT ? (float)dpi / DPI_DEFAULT : 1.0; +#else + // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. + float scale = 0.1 * std::max(10, fr.GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + return scale; + } + + static void scale_bitmap(wxBitmap& bmp, float scale) + { + if (scale == 1.0) + return; + + wxImage image = bmp.ConvertToImage(); + if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) + return; + + int width = int(scale * image.GetWidth()); + int height = int(scale * image.GetHeight()); + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + bmp = wxBitmap(std::move(image)); + } + + static wxFont get_scaled_sys_font(float screen_sf) + { + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + if (screen_sf != 1.0) + font.SetPointSize(int(screen_sf * (float)font.GetPointSize())); + + return font; + } + + static void word_wrap_string(wxString& input, int line_px_len, float scalef) + { + // calculate count od symbols in one line according to the scale + int line_len = int((float)line_px_len / (scalef * 10) + 0.5) + 10; + + int idx = -1; + int cur_len = 0; + for (size_t i = 0; i < input.Len(); i++) + { + cur_len++; + if (input[i] == ' ') + idx = i; + if (input[i] == '\n') + { + idx = -1; + cur_len = 0; + } + if (cur_len >= line_len && idx >= 0) + { + input[idx] = '\n'; + cur_len = static_cast(i) - idx; + } + } + } }; wxString file_wildcards(FileType file_type, const std::string &custom_extension) @@ -584,17 +641,32 @@ bool GUI_App::on_init_inner() */ wxInitAllImageHandlers(); - wxBitmap bitmap = create_scaled_bitmap("prusa_slicer_logo", nullptr, 400); + SplashScreen* scrn = nullptr; + if (app_config->get("show_splash_screen") == "1") + { #if ENABLE_GCODE_VIEWER - wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG); + wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG); #else - wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); + wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); #endif // ENABLE_GCODE_VIEWER - DecorateSplashScreen(bmp); + // Detect position (display) to show the splash screen + // Now this position is equal to the mainframe position + wxPoint splashscreen_pos = wxDefaultPosition; + if (app_config->has("window_mainframe")) { + auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); + if (metrics) + splashscreen_pos = metrics->get_rect().GetPosition(); + } - SplashScreen* scrn = new SplashScreen(bmp.IsOk() ? bmp : bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, nullptr); - scrn->SetText(_L("Loading configuration...")); + // try to decorate and/or scale the bitmap before splash screen creation + bool is_decorated = SplashScreen::Decorate(bmp, splashscreen_pos); + + // create splash screen with updated bmp + scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("prusa_slicer_logo", nullptr, 400), + wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos, is_decorated); + scrn->SetText(_L("Loading configuration...")); + } preset_bundle = new PresetBundle(); @@ -650,7 +722,9 @@ bool GUI_App::on_init_inner() // application frame #if ENABLE_GCODE_VIEWER - if (is_editor()) + if (scrn && is_editor()) +#else + if (scrn) #endif // ENABLE_GCODE_VIEWER scrn->SetText(_L("Creating settings tabs...")); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 242c3d851d..a3a23fd851 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -131,6 +131,15 @@ void PreferencesDialog::build() option = Option(def, "use_inches"); m_optgroup_general->append_single_option_line(option); */ + + // Show/Hide splash screen + def.label = L("Show splash screen"); + def.type = coBool; + def.tooltip = L("Show splash screen"); + def.set_default_value(new ConfigOptionBool{ app_config->get("show_splash_screen") == "1" }); + option = Option(def, "show_splash_screen"); + m_optgroup_general->append_single_option_line(option); + m_optgroup_camera = std::make_shared(this, _(L("Camera"))); m_optgroup_camera->label_width = 40; m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { From 20bd7b99f9c8d4ded94e6c01450ffdbfadc36c0a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 10 Sep 2020 19:35:26 +0200 Subject: [PATCH 492/826] Significant performance improvements for elevated and non-elevated case Apply bruteforce for elevated models --- src/libslic3r/Fill/FillAdaptive.hpp | 1 + src/libslic3r/SLA/Rotfinder.cpp | 135 +++++++++++++++---------- src/libslic3r/SLA/Rotfinder.hpp | 2 +- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 11 +- 4 files changed, 89 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index dd7394384c..23786530e3 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -4,6 +4,7 @@ #include "../AABBTreeIndirect.hpp" #include "FillBase.hpp" +#include "TriangleMesh.hpp" namespace Slic3r { diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index db8c0b9a8d..9378977663 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -1,11 +1,10 @@ #include -#include -//#include -#include #include #include +#include + #include "libslic3r/SLAPrint.hpp" #include "libslic3r/PrintConfig.hpp" @@ -61,23 +60,25 @@ std::array get_transformed_triangle(const TriangleMesh &mesh, } // Get area and normal of a triangle -struct Face { Vec3d normal; double area; }; -inline Face facestats(const std::array &triangle) -{ - Vec3d U = triangle[1] - triangle[0]; - Vec3d V = triangle[2] - triangle[0]; - Vec3d C = U.cross(V); - Vec3d N = C.normalized(); - double area = 0.5 * C.norm(); +struct Facestats { + Vec3d normal; + double area; - return {N, area}; -} + explicit Facestats(const std::array &triangle) + { + Vec3d U = triangle[1] - triangle[0]; + Vec3d V = triangle[2] - triangle[0]; + Vec3d C = U.cross(V); + normal = C.normalized(); + area = 0.5 * C.norm(); + } +}; inline const Vec3d DOWN = {0., 0., -1.}; constexpr double POINTS_PER_UNIT_AREA = 1.; // The score function for a particular face -inline double get_score(const Face &fc) +inline double get_score(const Facestats &fc) { // Simply get the angle (acos of dot product) between the face normal and // the DOWN vector. @@ -110,7 +111,7 @@ double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) if (mesh.its.vertices.empty()) return std::nan(""); auto accessfn = [&mesh, &tr](size_t fi) { - Face fc = facestats(get_transformed_triangle(mesh, tr, fi)); + Facestats fc{get_transformed_triangle(mesh, tr, fi)}; return get_score(fc); }; @@ -131,7 +132,7 @@ double get_model_supportedness_onfloor(const TriangleMesh &mesh, auto accessfn = [&mesh, &tr, zlvl](size_t fi) { std::array tri = get_transformed_triangle(mesh, tr, fi); - Face fc = facestats(tri); + Facestats fc{tri}; if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) return -fc.area * POINTS_PER_UNIT_AREA; @@ -161,56 +162,91 @@ XYRotation from_transform3d(const Transform3d &tr) } // Find the best score from a set of function inputs. Evaluate for every point. -template -std::array find_min_score(Fn &&fn, Cmp &&cmp, It from, It to) +template +std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) { std::array ret; double score = std::numeric_limits::max(); - for (auto it = from; it != to; ++it) { - double sc = fn(*it); - if (cmp(sc, score)) { - score = sc; - ret = *it; - } - } + size_t Nthreads = std::thread::hardware_concurrency(); + size_t dist = std::distance(from, to); + std::vector scores(dist, score); + + ccr_par::for_each(size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) { + if (stopfn()) return; + + scores[i] = fn(*(from + i)); + }, dist / Nthreads); + + auto it = std::min_element(scores.begin(), scores.end()); + + if (it != scores.end()) ret = *(from + std::distance(scores.begin(), it)); return ret; } // collect the rotations for each face of the convex hull -std::vector get_chull_rotations(const TriangleMesh &mesh) +std::vector get_chull_rotations(const TriangleMesh &mesh, size_t max_count) { TriangleMesh chull = mesh.convex_hull_3d(); chull.require_shared_vertices(); double chull2d_area = chull.convex_hull().area(); - double area_threshold = chull2d_area / (scaled(1e3) * scaled(1.)); + double area_threshold = chull2d_area / (scaled(1e3) * scaled(1.)); size_t facecount = chull.its.indices.size(); - auto inputs = reserve_vector(facecount); + + struct RotArea { XYRotation rot; double area; }; + + auto inputs = reserve_vector(facecount); + + auto rotcmp = [](const RotArea &r1, const RotArea &r2) { + double xdiff = r1.rot[X] - r2.rot[X], ydiff = r1.rot[Y] - r2.rot[Y]; + return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.; + }; + + auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) { + double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y]; + return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON; + }; for (size_t fi = 0; fi < facecount; ++fi) { - Face fc = facestats(get_triangle_vertices(chull, fi)); + Facestats fc{get_triangle_vertices(chull, fi)}; if (fc.area > area_threshold) { auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); - inputs.emplace_back(from_transform3d(Transform3d::Identity() * q)); + XYRotation rot = from_transform3d(Transform3d::Identity() * q); + RotArea ra = {rot, fc.area}; + + auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp); + + if (it == inputs.end() || !eqcmp(it->rot, rot)) + inputs.insert(it, ra); } } - return inputs; + inputs.shrink_to_fit(); + if (!max_count) max_count = inputs.size(); + std::sort(inputs.begin(), inputs.end(), + [](const RotArea &ra, const RotArea &rb) { + return ra.area > rb.area; + }); + + auto ret = reserve_vector(std::min(max_count, inputs.size())); + for (const RotArea &ra : inputs) ret.emplace_back(ra.rot); + + return ret; } -XYRotation find_best_rotation(const SLAPrintObject & po, - float accuracy, - std::function statuscb, - std::function stopcond) +Vec2d find_best_rotation(const SLAPrintObject & po, + float accuracy, + std::function statuscb, + std::function stopcond) { - static const unsigned MAX_TRIES = 10000; + static const unsigned MAX_TRIES = 1000; // return value - std::array rot; + XYRotation rot; // We will use only one instance of this converted mesh to examine different // rotations @@ -226,7 +262,7 @@ XYRotation find_best_rotation(const SLAPrintObject & po, // call status callback with zero, because we are at the start statuscb(status); - auto statusfn = [&statuscb, &status, max_tries] { + auto statusfn = [&statuscb, &status, &max_tries] { // report status statuscb(unsigned(++status * 100.0/max_tries) ); }; @@ -234,29 +270,26 @@ XYRotation find_best_rotation(const SLAPrintObject & po, // Different search methods have to be used depending on the model elevation if (is_on_floor(po)) { + std::vector inputs = get_chull_rotations(mesh, max_tries); + max_tries = inputs.size(); + // If the model can be placed on the bed directly, we only need to // check the 3D convex hull face rotations. - auto inputs = get_chull_rotations(mesh); - - auto cmpfn = [](double a, double b) { return a < b; }; auto objfn = [&mesh, &statusfn](const XYRotation &rot) { statusfn(); - // We actually need the reverserotation to make the object lie on - // this face Transform3d tr = to_transform3d(rot); return get_model_supportedness_onfloor(mesh, tr); }; - rot = find_min_score<2>(objfn, cmpfn, inputs.begin(), inputs.end()); + rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond); } else { - // Preparing the optimizer. - size_t grid_size = std::sqrt(max_tries); + size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls opt::Optimizer solver(opt::StopCriteria{} - .max_iterations(max_tries) - .stop_condition(stopcond), - grid_size); + .max_iterations(max_tries) + .stop_condition(stopcond), + gridsize); // We are searching rotations around only two axes x, y. Thus the // problem becomes a 2 dimensional optimization task. @@ -272,11 +305,9 @@ XYRotation find_best_rotation(const SLAPrintObject & po, // Save the result and fck off rot = result.optimum; - - std::cout << "best score: " << result.score << std::endl; } - return rot; + return {rot[0], rot[1]}; } double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr) diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 4fa529600b..96561a890f 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -27,7 +27,7 @@ namespace sla { * * @return Returns the rotations around each axis (x, y, z) */ -std::array find_best_rotation( +Vec2d find_best_rotation( const SLAPrintObject& modelobj, float accuracy = 1.0f, std::function statuscb = [] (unsigned) {}, diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 10c09275c3..978ccf8fcf 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -13,7 +13,7 @@ namespace Slic3r { namespace GUI { void RotoptimizeJob::process() { int obj_idx = m_plater->get_selected_object_idx(); - if (obj_idx < 0 || m_plater->sla_print().objects().size() <= obj_idx) + if (obj_idx < 0 || int(m_plater->sla_print().objects().size()) <= obj_idx) return; ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; @@ -35,15 +35,12 @@ void RotoptimizeJob::process() // std::cout << "Model supportedness before: " << score << std::endl; // } - auto r = sla::find_best_rotation( - *po, - 1.f, + Vec2d r = sla::find_best_rotation(*po, 0.75f, [this](unsigned s) { if (s < 100) - update_status(int(s), - _(L("Searching for optimal orientation"))); + update_status(int(s), _(L("Searching for optimal orientation"))); }, - [this]() { return was_canceled(); }); + [this] () { return was_canceled(); }); double mindist = 6.0; // FIXME From e9a325c9ca7203161282eedfe134bd7ff0563adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 10 Sep 2020 22:30:49 +0200 Subject: [PATCH 493/826] Fix rotation in support cubic infill --- src/libslic3r/Fill/FillAdaptive.cpp | 6 +++--- src/libslic3r/Fill/FillAdaptive.hpp | 2 +- src/libslic3r/PrintObject.cpp | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index fb8c665ebd..f1fa56c5bd 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -392,7 +392,7 @@ void FillAdaptive_Internal::Octree::propagate_point( Octree::propagate_point(point, child, (depth - 1), cubes_properties); } -std::unique_ptr FillSupportCubic::build_octree_for_adaptive_support( +std::unique_ptr FillSupportCubic::build_octree( TriangleMesh & triangle_mesh, coordf_t line_spacing, const Vec3d & cube_center, @@ -434,7 +434,7 @@ std::unique_ptr FillSupportCubic::build_octree_fo auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - double cube_edge_length = line_spacing; + double cube_edge_length = line_spacing / 2.0; size_t max_depth = octree->cubes_properties.size() - 1; BoundingBoxf3 mesh_bb = triangle_mesh.bounding_box(); Vec3f vertical(0, 0, 1); @@ -497,7 +497,7 @@ std::unique_ptr FillSupportCubic::build_octree_fo if(intersect_triangle(cube_center_absolute_arr, dir, vert_0, vert_1, vert_2, &distance, &cord_u, &cord_v) && distance > 0 && distance <= cube_edge_length) { Vec3d cube_center_transformed(cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z() + (cube_edge_length / 2.0)); - Octree::propagate_point(cube_center_transformed, octree->root_cube.get(), max_depth, octree->cubes_properties); + Octree::propagate_point(rotation_matrix * cube_center_transformed, octree->root_cube.get(), max_depth, octree->cubes_properties); } } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 45bfde8026..96f4467732 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -120,7 +120,7 @@ protected: Polylines &polylines_out); public: - static std::unique_ptr build_octree_for_adaptive_support( + static std::unique_ptr build_octree_for( TriangleMesh & triangle_mesh, coordf_t line_spacing, const Vec3d & cube_center, diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index a102c32813..2df7994eef 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -491,14 +491,14 @@ std::pair, std::unique_ptr Date: Thu, 10 Sep 2020 22:38:37 +0200 Subject: [PATCH 494/826] Fix typo in function build_octree --- src/libslic3r/Fill/FillAdaptive.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 96f4467732..4bb80fa063 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -120,7 +120,7 @@ protected: Polylines &polylines_out); public: - static std::unique_ptr build_octree_for( + static std::unique_ptr build_octree( TriangleMesh & triangle_mesh, coordf_t line_spacing, const Vec3d & cube_center, From 137e7a0712923080ca93949e73ab139fd04c5b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 10 Sep 2020 22:57:58 +0200 Subject: [PATCH 495/826] Fix compiler warnings and failing compilation on macOS --- src/libslic3r/Fill/FillAdaptive.cpp | 14 +++++++------- src/libslic3r/PrintObject.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index f1fa56c5bd..3b9212230d 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -435,7 +435,7 @@ std::unique_ptr FillSupportCubic::build_octree( auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); double cube_edge_length = line_spacing / 2.0; - size_t max_depth = octree->cubes_properties.size() - 1; + int max_depth = int(octree->cubes_properties.size()) - 1; BoundingBoxf3 mesh_bb = triangle_mesh.bounding_box(); Vec3f vertical(0, 0, 1); @@ -462,13 +462,13 @@ std::unique_ptr FillSupportCubic::build_octree( Vec3d triangle_end_relative = triangle_bb.max - mesh_bb.min; Vec3crd triangle_start_idx = Vec3crd( - std::floor(triangle_start_relative.x() / cube_edge_length), - std::floor(triangle_start_relative.y() / cube_edge_length), - std::floor(triangle_start_relative.z() / cube_edge_length)); + int(std::floor(triangle_start_relative.x() / cube_edge_length)), + int(std::floor(triangle_start_relative.y() / cube_edge_length)), + int(std::floor(triangle_start_relative.z() / cube_edge_length))); Vec3crd triangle_end_idx = Vec3crd( - std::floor(triangle_end_relative.x() / cube_edge_length), - std::floor(triangle_end_relative.y() / cube_edge_length), - std::floor(triangle_end_relative.z() / cube_edge_length)); + int(std::floor(triangle_end_relative.x() / cube_edge_length)), + int(std::floor(triangle_end_relative.y() / cube_edge_length)), + int(std::floor(triangle_end_relative.z() / cube_edge_length))); for (int z = triangle_start_idx.z(); z <= triangle_end_idx.z(); ++z) { diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 2df7994eef..e2dba5bb2a 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -377,7 +377,7 @@ void PrintObject::infill() BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this, &adaptive_fill_octree, &support_fill_octree](const tbb::blocked_range& range) { + [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get()); From 95b918f01d2374ae4a4e60b4aa9fe528fb019262 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Sep 2020 08:03:13 +0200 Subject: [PATCH 496/826] Updated Sys Info dialog, About dialog, Keyboard shortcuts dialog for gcode viewer --- .../icons/PrusaSlicer-gcodeviewer_192px.png | Bin 0 -> 11900 bytes src/slic3r/GUI/AboutDialog.cpp | 24 ++++++++++++++-- src/slic3r/GUI/GUI_App.cpp | 4 --- src/slic3r/GUI/KBShortcutsDialog.cpp | 4 +++ src/slic3r/GUI/MainFrame.cpp | 27 +++++++++--------- src/slic3r/GUI/SysInfoDialog.cpp | 24 ++++++++++++++-- 6 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 resources/icons/PrusaSlicer-gcodeviewer_192px.png diff --git a/resources/icons/PrusaSlicer-gcodeviewer_192px.png b/resources/icons/PrusaSlicer-gcodeviewer_192px.png new file mode 100644 index 0000000000000000000000000000000000000000..0e02430127a8e2ca1f3ae2b4e3329c5c6b198ec9 GIT binary patch literal 11900 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 P!T~CZ8VWUUEJFSlY)lBo literal 0 HcmV?d00001 diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index f95b8d93ba..8d9ea97b98 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -37,10 +37,17 @@ void AboutDialogLogo::onRepaint(wxEvent &event) // CopyrightsDialog // ----------------------------------------- CopyrightsDialog::CopyrightsDialog() +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, from_u8((boost::format("%1% - %2%") + % (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) + % _utf8(L("Portions copyright"))).str()), + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#else : DPIDialog(NULL, wxID_ANY, from_u8((boost::format("%1% - %2%") % SLIC3R_APP_NAME % _utf8(L("Portions copyright"))).str()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#endif // ENABLE_GCODE_VIEWER { this->SetFont(wxGetApp().normal_font()); this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -201,8 +208,13 @@ void CopyrightsDialog::onCloseDialog(wxEvent &) } AboutDialog::AboutDialog() +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, from_u8((boost::format(_utf8(L("About %s"))) % (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME)).str()), wxDefaultPosition, + wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#else : DPIDialog(NULL, wxID_ANY, from_u8((boost::format(_utf8(L("About %s"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition, wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#endif // ENABLE_GCODE_VIEWER { SetFont(wxGetApp().normal_font()); @@ -214,7 +226,11 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo +#if ENABLE_GCODE_VIEWER + m_logo_bitmap = ScalableBitmap(this, wxGetApp().is_editor() ? "PrusaSlicer_192px.png" : "PrusaSlicer-gcodeviewer_192px.png", 192); +#else m_logo_bitmap = ScalableBitmap(this, "PrusaSlicer_192px.png", 192); +#endif // ENABLE_GCODE_VIEWER m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL); @@ -223,7 +239,11 @@ AboutDialog::AboutDialog() // title { +#if ENABLE_GCODE_VIEWER + wxStaticText* title = new wxStaticText(this, wxID_ANY, wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME, wxDefaultPosition, wxDefaultSize); +#else wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_APP_NAME, wxDefaultPosition, wxDefaultSize); +#endif // ENABLE_GCODE_VIEWER wxFont title_font = GUI::wxGetApp().bold_font(); title_font.SetFamily(wxFONTFAMILY_ROMAN); title_font.SetPointSize(24); @@ -233,7 +253,7 @@ AboutDialog::AboutDialog() // version { - auto version_string = _(L("Version"))+ " " + std::string(SLIC3R_VERSION); + auto version_string = _L("Version")+ " " + std::string(SLIC3R_VERSION); wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); wxFont version_font = GetFont(); #ifdef __WXMSW__ @@ -294,7 +314,7 @@ AboutDialog::AboutDialog() wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); m_copy_rights_btn_id = NewControlId(); - auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _(L("Portions copyright"))+dots); + auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots); buttons->Insert(0, copy_rights_btn, 0, wxLEFT, 5); copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e50d4015e7..37ec10f1d2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -161,15 +161,11 @@ static void DecorateSplashScreen(wxBitmap& bmp) memDc.DrawRectangle(banner_rect); // title -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxString title_string = SLIC3R_APP_NAME; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); title_font.SetPointSize(24); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 0875b76a48..4affd13269 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -33,7 +33,11 @@ namespace Slic3r { namespace GUI { KBShortcutsDialog::KBShortcutsDialog() +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, wxString(wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) + " - " + _L("Keyboard Shortcuts"), +#else : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Keyboard Shortcuts"), +#endif // ENABLE_GCODE_VIEWER wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 4d242dec88..fbb7a190f0 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -118,11 +118,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // initialize status bar m_statusbar = std::make_shared(this); m_statusbar->set_font(GUI::wxGetApp().normal_font()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER if (wxGetApp().is_editor()) #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_statusbar->embed(this); m_statusbar->set_status_text(_L("Version") + " " + SLIC3R_VERSION + @@ -539,15 +537,11 @@ void MainFrame::update_title() title += (project + " - "); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ std::string build_id = SLIC3R_BUILD_ID; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ size_t idx_plus = build_id.find('+'); if (idx_plus != build_id.npos) { // Parse what is behind the '+'. If there is a number, then it is a build number after the label, and full build ID is shown. @@ -562,17 +556,13 @@ void MainFrame::update_title() #endif } } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #if ENABLE_GCODE_VIEWER title += wxString(build_id); if (wxGetApp().is_editor()) title += (" " + _L("based on Slic3r")); #else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ title += (wxString(build_id) + " " + _L("based on Slic3r")); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #endif // ENABLE_GCODE_VIEWER -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ SetTitle(title); } @@ -763,6 +753,9 @@ bool MainFrame::can_change_view() const int page_id = m_tabpanel->GetSelection(); return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; } +#if ENABLE_GCODE_VIEWER + case ESettingsLayout::GCodeViewer: { return true; } +#endif // ENABLE_GCODE_VIEWER } } @@ -889,9 +882,17 @@ static wxMenu* generate_help_menu() [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _(L"Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), [](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); - append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), - [](wxCommandEvent&) { Slic3r::GUI::about(); }); - helpMenu->AppendSeparator(); +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), + [](wxCommandEvent&) { Slic3r::GUI::about(); }); +#if ENABLE_GCODE_VIEWER + else + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), GCODEVIEWER_APP_NAME), _L("Show about dialog"), + [](wxCommandEvent&) { Slic3r::GUI::about(); }); +#endif // ENABLE_GCODE_VIEWER + helpMenu->AppendSeparator(); append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"), [](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); #if ENABLE_THUMBNAIL_GENERATOR_DEBUG diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 7a41aca1c3..34905fa6d4 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -34,9 +34,17 @@ std::string get_main_info(bool format_as_html) std::string line_end = format_as_html ? "
" : "\n"; if (!format_as_html) +#if ENABLE_GCODE_VIEWER + out << b_start << (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) << b_end << line_end; +#else out << b_start << SLIC3R_APP_NAME << b_end << line_end; +#endif // ENABLE_GCODE_VIEWER out << b_start << "Version: " << b_end << SLIC3R_VERSION << line_end; +#if ENABLE_GCODE_VIEWER + out << b_start << "Build: " << b_end << (wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID) << line_end; +#else out << b_start << "Build: " << b_end << SLIC3R_BUILD_ID << line_end; +#endif // ENABLE_GCODE_VIEWER out << line_end; out << b_start << "Operating System: " << b_end << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << line_end; out << b_start << "System Architecture: " << b_end << wxPlatformInfo::Get().GetArchName() << line_end; @@ -78,7 +86,11 @@ std::string get_mem_info(bool format_as_html) } SysInfoDialog::SysInfoDialog() - : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, (wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME)) + " - " + _L("System Information"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#else + : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("System Information"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +#endif // ENABLE_GCODE_VIEWER { wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SetBackgroundColour(bgr_clr); @@ -91,7 +103,11 @@ SysInfoDialog::SysInfoDialog() main_sizer->Add(hsizer, 1, wxEXPAND | wxALL, 10); // logo +#if ENABLE_GCODE_VIEWER + m_logo_bmp = ScalableBitmap(this, wxGetApp().is_editor() ? "PrusaSlicer_192px.png" : "PrusaSlicer-gcodeviewer_192px.png", 192); +#else m_logo_bmp = ScalableBitmap(this, "PrusaSlicer_192px.png", 192); +#endif // ENABLE_GCODE_VIEWER m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); hsizer->Add(m_logo, 0, wxALIGN_CENTER_VERTICAL); @@ -100,7 +116,11 @@ SysInfoDialog::SysInfoDialog() // title { +#if ENABLE_GCODE_VIEWER + wxStaticText* title = new wxStaticText(this, wxID_ANY, wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME, wxDefaultPosition, wxDefaultSize); +#else wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_APP_NAME, wxDefaultPosition, wxDefaultSize); +#endif // ENABLE_GCODE_VIEWER wxFont title_font = wxGetApp().bold_font(); title_font.SetFamily(wxFONTFAMILY_ROMAN); title_font.SetPointSize(22); @@ -154,7 +174,7 @@ SysInfoDialog::SysInfoDialog() } wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); - m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _(L("Copy to Clipboard")), wxDefaultPosition, wxDefaultSize); + m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _L("Copy to Clipboard"), wxDefaultPosition, wxDefaultSize); buttons->Insert(0, m_btn_copy_to_clipboard, 0, wxLEFT, 5); m_btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this); From a57fe34a763878a4423e1aa4b8504d8fc022f0e8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 11 Sep 2020 12:18:03 +0200 Subject: [PATCH 497/826] Updated icons for the top bar + Added icon for "Seam editing" --- resources/icons/add.svg | 31 ++++++---- resources/icons/arrange.svg | 21 +++---- resources/icons/copy.svg | 46 ++++++-------- resources/icons/delete_all.svg | 30 +++------ resources/icons/instance_add.svg | 78 ++++++++++++------------ resources/icons/instance_remove.svg | 73 ++++++++++------------ resources/icons/paste.svg | 33 +++++----- resources/icons/redo_toolbar.svg | 20 +++--- resources/icons/remove.svg | 94 +++++++++++++++++------------ resources/icons/seam.svg | 67 +++++++++----------- resources/icons/split_objects.svg | 21 ++++--- resources/icons/split_parts.svg | 20 +++--- resources/icons/undo_toolbar.svg | 20 +++--- 13 files changed, 266 insertions(+), 288 deletions(-) diff --git a/resources/icons/add.svg b/resources/icons/add.svg index 8a9b253de7..37050d7481 100644 --- a/resources/icons/add.svg +++ b/resources/icons/add.svg @@ -1,17 +1,22 @@ - + - - + + + + + + + diff --git a/resources/icons/arrange.svg b/resources/icons/arrange.svg index 4f30e979e3..62cf939e9f 100644 --- a/resources/icons/arrange.svg +++ b/resources/icons/arrange.svg @@ -1,24 +1,23 @@ - + - - + - + - + - + - + diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg index 9b8430dd79..345c2590be 100644 --- a/resources/icons/copy.svg +++ b/resources/icons/copy.svg @@ -1,37 +1,29 @@ - + - - - - - + + - - - - - + + diff --git a/resources/icons/delete_all.svg b/resources/icons/delete_all.svg index 80e2e503cb..dfa9438129 100644 --- a/resources/icons/delete_all.svg +++ b/resources/icons/delete_all.svg @@ -1,31 +1,17 @@ - + - - + - + - - - - - - - - - - + diff --git a/resources/icons/instance_add.svg b/resources/icons/instance_add.svg index 5ef492cfae..a466c51dbf 100644 --- a/resources/icons/instance_add.svg +++ b/resources/icons/instance_add.svg @@ -1,50 +1,46 @@ - + - + - + - + + + + diff --git a/resources/icons/instance_remove.svg b/resources/icons/instance_remove.svg index 466752ea89..7f9b4f7e1c 100644 --- a/resources/icons/instance_remove.svg +++ b/resources/icons/instance_remove.svg @@ -1,49 +1,42 @@ - + - + - + - + diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg index 028ffb8ea0..bcfe567de3 100644 --- a/resources/icons/paste.svg +++ b/resources/icons/paste.svg @@ -1,27 +1,22 @@ - + - + - - - - - + + diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg index d005f83736..2853d4eaa8 100644 --- a/resources/icons/redo_toolbar.svg +++ b/resources/icons/redo_toolbar.svg @@ -1,13 +1,13 @@ - + - + viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> + diff --git a/resources/icons/remove.svg b/resources/icons/remove.svg index acd21256cd..1bb830d91c 100644 --- a/resources/icons/remove.svg +++ b/resources/icons/remove.svg @@ -1,44 +1,60 @@ - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/seam.svg b/resources/icons/seam.svg index 119fb6afcc..a7e7980cc0 100644 --- a/resources/icons/seam.svg +++ b/resources/icons/seam.svg @@ -1,42 +1,35 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/split_objects.svg b/resources/icons/split_objects.svg index a7ccc5df86..e822fd35aa 100644 --- a/resources/icons/split_objects.svg +++ b/resources/icons/split_objects.svg @@ -1,19 +1,20 @@ - + - + - + - - + + + + diff --git a/resources/icons/split_parts.svg b/resources/icons/split_parts.svg index 82a2927706..5cfef0f330 100644 --- a/resources/icons/split_parts.svg +++ b/resources/icons/split_parts.svg @@ -1,18 +1,20 @@ - + - + - + - - + + + + diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg index 15778a7baf..c9e277b5f1 100644 --- a/resources/icons/undo_toolbar.svg +++ b/resources/icons/undo_toolbar.svg @@ -1,13 +1,13 @@ - + - + viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> + From dd6994c3b2c0e23350ec674e6d37f00ad55ef3f6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Sep 2020 15:19:23 +0200 Subject: [PATCH 498/826] Logging of memory used by the gcode processor and viewer --- src/libslic3r/GCode.cpp | 17 ++-- src/libslic3r/Print.cpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 166 +++++++++++++++++++++------------ src/slic3r/GUI/GCodeViewer.hpp | 12 ++- 4 files changed, 126 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cfde3a03a7..0c4e76cd7a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -787,10 +787,12 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ } #if ENABLE_GCODE_VIEWER + BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); m_processor.process_file(path_tmp, [print]() { print->throw_if_canceled(); }); DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); if (result != nullptr) *result = std::move(m_processor.extract_result()); + BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info(); #else GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data(); @@ -2452,14 +2454,17 @@ void GCode::process_layer( #endif /* HAS_PRESSURE_EQUALIZER */ _write(file, gcode); -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << + log_memory_info(); +#else BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << ", time estimator memory: " << - format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << - ", analyzer memory: " << - format_memsize_MB(m_analyzer.memory_used()) << - log_memory_info(); -#endif // !ENABLE_GCODE_VIEWER + format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << + ", analyzer memory: " << + format_memsize_MB(m_analyzer.memory_used()) << + log_memory_info(); +#endif // ENABLE_GCODE_VIEWER } void GCode::apply_print_config(const PrintConfig &print_config) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c405c764ef..50b752984b 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1583,7 +1583,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const // Slicing process, running at a background thread. void Print::process() { - BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); for (PrintObject *obj : m_objects) obj->make_perimeters(); this->set_status(70, L("Infilling layers")); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 5984afaa45..76b0e02fc9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -440,6 +440,19 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update buffers' render paths refresh_render_paths(false, false); + + if (Slic3r::get_logging_level() >= 5) { + long long paths_size = 0; + for (const TBuffer& buffer : m_buffers) { + paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + } + long long layers_zs_size = SLIC3R_STDVEC_MEMSIZE(m_layers_zs, double); + long long roles_size = SLIC3R_STDVEC_MEMSIZE(m_roles, Slic3r::ExtrusionRole); + long long extruder_ids_size = SLIC3R_STDVEC_MEMSIZE(m_extruder_ids, unsigned char); + BOOST_LOG_TRIVIAL(trace) << "Refreshed G-code extrusion paths, " + << format_memsize_MB(paths_size + layers_zs_size + roles_size + extruder_ids_size) + << log_memory_info(); + } } void GCodeViewer::reset() @@ -1209,6 +1222,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.vertices_gpu_size += buffer_vertices.size() * sizeof(float); + m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast(buffer.vertices.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS glsafe(::glGenBuffers(1, &buffer.vertices.id)); @@ -1221,6 +1235,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer.indices.count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int); + m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast(buffer.indices.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS if (buffer.indices.count > 0) { @@ -1278,6 +1293,27 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); + if (Slic3r::get_logging_level() >= 5) { + long long vertices_size = 0; + for (size_t i = 0; i < vertices.size(); ++i) { + vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); + } + long long indices_size = 0; + for (size_t i = 0; i < indices.size(); ++i) { + indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i], unsigned int); + } + long long paths_size = 0; + for (const TBuffer& buffer : m_buffers) { + paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + } + long long layers_zs_size = SLIC3R_STDVEC_MEMSIZE(m_layers_zs, double); + long long roles_size = SLIC3R_STDVEC_MEMSIZE(m_roles, Slic3r::ExtrusionRole); + long long extruder_ids_size = SLIC3R_STDVEC_MEMSIZE(m_extruder_ids, unsigned char); + BOOST_LOG_TRIVIAL(trace) << "Loaded G-code extrusion paths, " + << format_memsize_MB(vertices_size + indices_size + paths_size + layers_zs_size + roles_size + extruder_ids_size) + << log_memory_info(); + } + #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -2266,77 +2302,87 @@ void GCodeViewer::render_statistics() const ImGuiWrapper& imgui = *wxGetApp().imgui(); + auto add_time = [this, &imgui](const std::string& label, long long time) { + char buf[1024]; + sprintf(buf, "%lld ms (%s)", time, get_time_dhms(static_cast(time) * 0.001f).c_str()); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + + auto add_memory = [this, &imgui](const std::string& label, long long memory) { + static const float mb = 1024.0f * 1024.0f; + static const float inv_mb = 1.0f / mb; + static const float gb = 1024.0f * mb; + static const float inv_gb = 1.0f / gb; + char buf[1024]; + if (static_cast(memory) < gb) + sprintf(buf, "%lld bytes (%.3f MB)", memory, static_cast(memory) * inv_mb); + else + sprintf(buf, "%lld bytes (%.3f GB)", memory, static_cast(memory) * inv_gb); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + + auto add_counter = [this, &imgui](const std::string& label, long long counter) { + char buf[1024]; + sprintf(buf, "%lld", counter); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); + ImGui::SetNextWindowSizeConstraints({ 300, -1 }, { 600, -1 }); imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.results_time) + " ms"); + if (ImGui::CollapsingHeader("Time")) { + add_time(std::string("GCodeProcessor:"), m_statistics.results_time); - ImGui::Separator(); + ImGui::Separator(); + add_time(std::string("Load:"), m_statistics.load_time); + add_time(std::string("Refresh:"), m_statistics.refresh_time); + add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Load time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.load_time) + " ms"); + if (ImGui::CollapsingHeader("OpenGL calls")) { + add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); + add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); + add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_time) + " ms"); + if (ImGui::CollapsingHeader("CPU memory")) { + add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh paths time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_paths_time) + " ms"); + ImGui::Separator(); + add_memory(std::string("Paths:"), m_statistics.paths_size); + add_memory(std::string("Render paths:"), m_statistics.render_paths_size); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - ImGui::Separator(); + if (ImGui::CollapsingHeader("GPU memory")) { + add_memory(std::string("Vertices:"), m_statistics.vertices_gpu_size); + add_memory(std::string("Indices:"), m_statistics.indices_gpu_size); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_POINTS calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count)); + if (ImGui::CollapsingHeader("Other")) { + add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); + add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); + add_counter(std::string("Max vertices in vertex buffer:"), m_statistics.max_vertices_in_vertex_buffer); + add_counter(std::string("Max indices in index buffer:"), m_statistics.max_indices_in_index_buffer); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_LINES calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_lines_calls_count)); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_TRIANGLES calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_triangles_calls_count)); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor results:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.results_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Paths CPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.paths_size) + " bytes"); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Render paths CPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.render_paths_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Indices GPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Travel segments count:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.travel_segments_count)); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Extrude segments count:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.extrude_segments_count)); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } imgui.end(); } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 68fed6f334..abda780af7 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -264,7 +264,7 @@ class GCodeViewer #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics { - // times + // time long long results_time{ 0 }; long long load_time{ 0 }; long long refresh_time{ 0 }; @@ -279,15 +279,17 @@ class GCodeViewer long long indices_gpu_size{ 0 }; long long paths_size{ 0 }; long long render_paths_size{ 0 }; - // others + // other long long travel_segments_count{ 0 }; long long extrude_segments_count{ 0 }; + long long max_vertices_in_vertex_buffer{ 0 }; + long long max_indices_in_index_buffer{ 0 }; void reset_all() { reset_times(); reset_opengl(); reset_sizes(); - reset_counters(); + reset_others(); } void reset_times() { @@ -311,9 +313,11 @@ class GCodeViewer render_paths_size = 0; } - void reset_counters() { + void reset_others() { travel_segments_count = 0; extrude_segments_count = 0; + max_vertices_in_vertex_buffer = 0; + max_indices_in_index_buffer = 0; } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS From 776a775996f7c85308254ea6c01919b6a989d806 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Thu, 10 Sep 2020 19:37:31 +0200 Subject: [PATCH 499/826] Add missing forward declarations --- src/libslic3r/Fill/FillAdaptive.hpp | 1 + src/slic3r/Utils/Process.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 4bb80fa063..b24f206da2 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -8,6 +8,7 @@ namespace Slic3r { class PrintObject; +class TriangleMesh; namespace FillAdaptive_Internal { diff --git a/src/slic3r/Utils/Process.hpp b/src/slic3r/Utils/Process.hpp index c6acaa643e..65f90222dd 100644 --- a/src/slic3r/Utils/Process.hpp +++ b/src/slic3r/Utils/Process.hpp @@ -2,6 +2,7 @@ #define GUI_PROCESS_HPP class wxWindow; +class wxString; namespace Slic3r { namespace GUI { From ad20e369faa7757eb0ac032df3fb9a815f2f4dcd Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Thu, 10 Sep 2020 19:37:43 +0200 Subject: [PATCH 500/826] Include PrintConfig for the definition of AuthorizationType --- src/slic3r/Utils/OctoPrint.cpp | 1 - src/slic3r/Utils/OctoPrint.hpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index 82d8a6bb63..fad45f8225 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -11,7 +11,6 @@ #include -#include "libslic3r/PrintConfig.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GUI.hpp" #include "Http.hpp" diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 4f8e4819fc..ed1c61bd60 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -6,6 +6,7 @@ #include #include "PrintHost.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { From a32bb59d8e77578d37c65b1052ed1d05bca85559 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sat, 12 Sep 2020 18:17:03 +0200 Subject: [PATCH 501/826] Do not include (incorrect!) seconds in get_time_dhm --- src/libslic3r/Utils.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index ef531169d1..99b105a6b1 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -348,11 +348,11 @@ inline std::string get_time_dhm(float time_in_secs) char buffer[64]; if (days > 0) - ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs); + ::sprintf(buffer, "%dd %dh %dm", days, hours, minutes); else if (hours > 0) - ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs); + ::sprintf(buffer, "%dh %dm", hours, minutes); else if (minutes > 0) - ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); + ::sprintf(buffer, "%dm", minutes); return buffer; } From ba7f39afee6eff37bdc6110f9fb7e469795e396f Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sun, 13 Sep 2020 02:17:19 +0200 Subject: [PATCH 502/826] Introduce GUI_App::code_font() --- src/slic3r/GUI/GUI_App.cpp | 6 ++++++ src/slic3r/GUI/GUI_App.hpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d59a83e875..1673610da1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -869,6 +869,11 @@ void GUI_App::init_fonts() m_small_font.SetPointSize(11); m_bold_font.SetPointSize(13); #endif /*__WXMAC__*/ + + // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as + // DEFAULT in wxGtk. Use the TELETYPE family as a work-around + m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); } void GUI_App::update_fonts(const MainFrame *main_frame) @@ -884,6 +889,7 @@ void GUI_App::update_fonts(const MainFrame *main_frame) m_small_font = m_normal_font; m_bold_font = main_frame->normal_font().Bold(); m_em_unit = main_frame->em_unit(); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); } void GUI_App::set_label_clr_modified(const wxColour& clr) { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 9bf470a42d..56b2f7f1af 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -118,6 +118,7 @@ private: wxFont m_small_font; wxFont m_bold_font; wxFont m_normal_font; + wxFont m_code_font; int m_em_unit; // width of a "m"-symbol in pixels for current system font // Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls @@ -177,6 +178,7 @@ public: const wxFont& small_font() { return m_small_font; } const wxFont& bold_font() { return m_bold_font; } const wxFont& normal_font() { return m_normal_font; } + const wxFont& code_font() { return m_code_font; } int em_unit() const { return m_em_unit; } wxSize get_min_size() const; float toolbar_icon_scale(const bool is_limited = false) const; From cd4ad5e78b7b3477b589de13b40a2c945e01aaec Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sun, 13 Sep 2020 02:19:36 +0200 Subject: [PATCH 503/826] Introduce ConfigOptionDef::is_code to select code_font() --- src/libslic3r/Config.hpp | 2 ++ src/slic3r/GUI/Field.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 87e0208986..c6b7c77280 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1413,6 +1413,8 @@ public: bool multiline = false; // For text input: If true, the GUI text box spans the complete page width. bool full_width = false; + // For text input: If true, the GUI formats text as code (fixed-width) + bool is_code = false; // Not editable. Currently only used for the display of the number of threads. bool readonly = false; // Height of a multiline GUI text box. diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index a21826205b..44bb22e8de 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -373,7 +373,9 @@ void TextCtrl::BUILD() { const long style = m_opt.multiline ? wxTE_MULTILINE : wxTE_PROCESS_ENTER/*0*/; auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); - temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetFont(m_opt.is_code ? + Slic3r::GUI::wxGetApp().code_font(): + Slic3r::GUI::wxGetApp().normal_font()); if (! m_opt.multiline && !wxOSX) // Only disable background refresh for single line input fields, as they are completely painted over by the edit control. From 87534bf0d4d0c78d0a64dc4f9dcb0bc84778acb1 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sun, 13 Sep 2020 02:35:07 +0200 Subject: [PATCH 504/826] Format all G-code sections as code --- src/slic3r/GUI/Tab.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 29c9e33022..274c6b297c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1790,12 +1790,14 @@ void TabFilament::build() optgroup = page->new_optgroup(L("Start G-code"), 0); option = optgroup->get_option("start_filament_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("End G-code"), 0); option = optgroup->get_option("end_filament_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); @@ -2204,51 +2206,60 @@ void TabPrinter::build_fff() optgroup = page->new_optgroup(L("Start G-code"), 0); option = optgroup->get_option("start_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("End G-code"), 0); option = optgroup->get_option("end_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Before layer change G-code"), 0); option = optgroup->get_option("before_layer_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("After layer change G-code"), 0); option = optgroup->get_option("layer_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Tool change G-code"), 0); option = optgroup->get_option("toolchange_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Between objects G-code (for sequential printing)"), 0); option = optgroup->get_option("between_objects_gcode"); option.opt.full_width = true; + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Color Change G-code"), 0); option = optgroup->get_option("color_change_gcode"); + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Pause Print G-code"), 0); option = optgroup->get_option("pause_print_gcode"); + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); optgroup = page->new_optgroup(L("Template Custom G-code"), 0); option = optgroup->get_option("template_custom_gcode"); + option.opt.is_code = true; option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); From 0edbc59fa317dbd0bd282ddbd11c7176e16e89ef Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sun, 13 Sep 2020 02:35:32 +0200 Subject: [PATCH 505/826] Update FirmwareDialog to use GUI_App::code_font --- src/slic3r/GUI/FirmwareDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index fe7ff4e5de..9ebc250059 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -790,7 +790,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : SetFont(font); wxFont status_font = font;//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); status_font.MakeBold(); - wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); + wxFont mono_font = GUI::wxGetApp().code_font(); mono_font.MakeSmaller(); // Create GUI components and layout From 6434f54b74cb7242fb76ac238c6258a4086f682a Mon Sep 17 00:00:00 2001 From: charlie Date: Sat, 12 Sep 2020 02:18:36 +0200 Subject: [PATCH 506/826] fix build on arch linux --- src/PrusaSlicer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 05e84b9416..a12ad8bb73 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -589,7 +589,7 @@ int CLI::run(int argc, char **argv) #if ENABLE_GCODE_VIEWER if (start_as_gcodeviewer) { if (!m_input_files.empty()) - gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0])); + gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str())); } else { #endif // ENABLE_GCODE_VIEWER_AS #if 0 From 349dd60940dc7fcd3bce127a3a88952ed13f9e0f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Sep 2020 09:18:20 +0200 Subject: [PATCH 507/826] Small refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 20 ++++++++++---------- src/slic3r/GUI/GCodeViewer.hpp | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 76b0e02fc9..831a3885e8 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -392,7 +392,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (m_vertices_count == 0) + if (m_moves_count == 0) return; wxBusyCursor busy; @@ -406,7 +406,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update ranges for coloring / legend m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) continue; @@ -457,7 +457,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: void GCodeViewer::reset() { - m_vertices_count = 0; + m_moves_count = 0; for (TBuffer& buffer : m_buffers) { buffer.reset(); } @@ -882,11 +882,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #endif // ENABLE_GCODE_VIEWER_STATISTICS // vertices data - m_vertices_count = gcode_result.moves.size(); - if (m_vertices_count == 0) + m_moves_count = gcode_result.moves.size(); + if (m_moves_count == 0) return; - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (wxGetApp().is_gcode_viewer()) // for the gcode viewer we need all moves to correctly size the printbed @@ -1174,7 +1174,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // toolpaths data -> extract from result std::vector> vertices(m_buffers.size()); std::vector> indices(m_buffers.size()); - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) continue; @@ -1257,7 +1257,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #endif // ENABLE_GCODE_VIEWER_STATISTICS // layers zs / roles / extruder ids / cp color ids -> extract from result - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (move.type == EMoveType::Extrude) m_layers_zs.emplace_back(static_cast(move.position[2])); @@ -1410,12 +1410,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool m_statistics.render_paths_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS - m_sequential_view.endpoints.first = m_vertices_count; + m_sequential_view.endpoints.first = m_moves_count; m_sequential_view.endpoints.last = 0; if (!keep_sequential_current_first) m_sequential_view.current.first = 0; if (!keep_sequential_current_last) - m_sequential_view.current.last = m_vertices_count; + m_sequential_view.current.last = m_moves_count; // first pass: collect visible paths and update sequential view data std::vector> paths; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index abda780af7..50e41f4cf4 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -375,7 +375,7 @@ public: private: unsigned int m_last_result_id{ 0 }; - size_t m_vertices_count{ 0 }; + size_t m_moves_count{ 0 }; mutable std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; From 6ac19359326a01ea95744115cce1f0ebdc599d06 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 14 Sep 2020 17:25:47 +0200 Subject: [PATCH 508/826] Updated "undo/redo" and "search' icons for the toolbar * added "settings" and "search_blink" icons * suppress the icons scaling update when Plater is in the Preview mode * switched "layers_height" and "search" buttons in the toolbar --- resources/icons/redo_toolbar.svg | 20 ++++++---- resources/icons/search_.svg | 30 ++++++++++++-- resources/icons/search_blink.svg | 13 ++++++ resources/icons/settings.svg | 68 ++++++++++++++++++++++++++++++++ resources/icons/undo_toolbar.svg | 20 ++++++---- src/slic3r/GUI/GLCanvas3D.cpp | 58 +++++++++++++++------------ src/slic3r/GUI/wxExtensions.hpp | 2 +- 7 files changed, 164 insertions(+), 47 deletions(-) create mode 100644 resources/icons/search_blink.svg create mode 100644 resources/icons/settings.svg diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg index 2853d4eaa8..d2aca2cc7d 100644 --- a/resources/icons/redo_toolbar.svg +++ b/resources/icons/redo_toolbar.svg @@ -2,12 +2,16 @@ - + + + + + + + + diff --git a/resources/icons/search_.svg b/resources/icons/search_.svg index 679bb30f71..2985ceb561 100644 --- a/resources/icons/search_.svg +++ b/resources/icons/search_.svg @@ -1,4 +1,26 @@ - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/search_blink.svg b/resources/icons/search_blink.svg new file mode 100644 index 0000000000..d005f83736 --- /dev/null +++ b/resources/icons/search_blink.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/resources/icons/settings.svg b/resources/icons/settings.svg new file mode 100644 index 0000000000..db5bf458d7 --- /dev/null +++ b/resources/icons/settings.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg index c9e277b5f1..2fc25bf737 100644 --- a/resources/icons/undo_toolbar.svg +++ b/resources/icons/undo_toolbar.svg @@ -2,12 +2,16 @@ - + + + + + + + + diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 00034087c7..e0c8c4c5bd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4999,7 +4999,7 @@ bool GLCanvas3D::_init_main_toolbar() return false; item.name = "settings"; - item.icon_filename = "cog_.svg"; + item.icon_filename = "settings.svg"; item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; @@ -5011,35 +5011,16 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + /* if (!m_main_toolbar.add_separator()) return false; - - item.name = "layersediting"; - item.icon_filename = "layers_white.svg"; - item.tooltip = _utf8(L("Variable layer height")); - item.sprite_id = 11; - item.left.toggable = true; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = [this]()->bool - { - bool res = m_process->current_printer_technology() == ptFFF; - // turns off if changing printer technology - if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) - force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); - - return res; - }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; + */ item.name = "search"; item.icon_filename = "search_.svg"; item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; - item.sprite_id = 12; + item.sprite_id = 11; + item.left.toggable = true; item.left.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) { @@ -5053,6 +5034,27 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "layersediting"; + item.icon_filename = "layers_white.svg"; + item.tooltip = _utf8(L("Variable layer height")); + item.sprite_id = 12; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.visibility_callback = [this]()->bool { + bool res = m_process->current_printer_technology() == ptFFF; + // turns off if changing printer technology + if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) + force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); + + return res; + }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + item.left.render_callback = GLToolbarItem::Default_Render_Callback; + if (!m_main_toolbar.add_item(item)) + return false; + return true; } @@ -5161,10 +5163,10 @@ bool GLCanvas3D::_init_undoredo_toolbar() if (!m_undoredo_toolbar.add_item(item)) return false; - + /* if (!m_undoredo_toolbar.add_separator()) return false; - + */ return true; } @@ -5553,6 +5555,10 @@ void GLCanvas3D::_render_selection_center() const void GLCanvas3D::_check_and_update_toolbar_icon_scale() const { + // Don't update a toolbar scale, when we are on a Preview + if (wxGetApp().plater()->is_preview_shown()) + return; + float scale = wxGetApp().toolbar_icon_scale(); Size cnv_size = get_canvas_size(); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index e20e5c8bda..4f04bc5b20 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -338,7 +338,7 @@ class BlinkingBitmap : public wxStaticBitmap { public: BlinkingBitmap() {}; - BlinkingBitmap(wxWindow* parent, const std::string& icon_name = "redo_toolbar"); + BlinkingBitmap(wxWindow* parent, const std::string& icon_name = "search_blink"); ~BlinkingBitmap() {} From 067cde85f14107882327c2b29de671c914bd1153 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 14 Sep 2020 16:27:55 +0200 Subject: [PATCH 509/826] WIP Refactoring of exceptions: 1) All slicer's exceptions are now derived from Slic3r::Exception. 2) New exceptions are defined for slicing errors. 3) Exceptions are propagated to the Plater to show. It remains to modify the slicing back-end to throw the new SlicingError exceptions instead of std::runtime_error and to show the other exceptions by a message dialog instead of a notification. --- src/libslic3r/AppConfig.cpp | 3 +- src/libslic3r/BoundingBox.hpp | 5 +- src/libslic3r/Config.cpp | 15 ++-- src/libslic3r/Config.hpp | 85 +++++++++++---------- src/libslic3r/ExPolygon.cpp | 7 +- src/libslic3r/Exception.hpp | 28 +++++++ src/libslic3r/ExtrusionEntityCollection.hpp | 5 +- src/libslic3r/FileParserError.hpp | 8 +- src/libslic3r/Fill/FillBase.cpp | 3 +- src/libslic3r/Fill/FillBase.hpp | 6 +- src/libslic3r/Flow.cpp | 6 +- src/libslic3r/Flow.hpp | 7 +- src/libslic3r/Format/3mf.cpp | 17 +++-- src/libslic3r/Format/AMF.cpp | 13 ++-- src/libslic3r/Format/PRUS.cpp | 10 +-- src/libslic3r/Format/SL1.cpp | 11 +-- src/libslic3r/GCode.cpp | 21 ++--- src/libslic3r/GCode/GCodeProcessor.cpp | 10 +-- src/libslic3r/GCode/PostProcessor.cpp | 12 +-- src/libslic3r/GCode/PressureEqualizer.cpp | 8 +- src/libslic3r/GCode/PrintExtents.cpp | 2 +- src/libslic3r/GCodeSender.cpp | 2 +- src/libslic3r/GCodeTimeEstimator.cpp | 11 +-- src/libslic3r/Geometry.cpp | 3 +- src/libslic3r/MeshBoolean.cpp | 5 +- src/libslic3r/Model.cpp | 15 ++-- src/libslic3r/ModelArrange.hpp | 2 +- src/libslic3r/PlaceholderParser.cpp | 5 +- src/libslic3r/PlaceholderParser.hpp | 4 +- src/libslic3r/Polygon.cpp | 3 +- src/libslic3r/Polyline.cpp | 5 +- src/libslic3r/Preset.cpp | 19 ++--- src/libslic3r/PresetBundle.cpp | 14 ++-- src/libslic3r/Print.cpp | 5 +- src/libslic3r/PrintBase.cpp | 3 +- src/libslic3r/PrintObject.cpp | 5 +- src/libslic3r/PrintRegion.cpp | 5 +- src/libslic3r/SLAPrintSteps.cpp | 11 +-- src/libslic3r/Semver.hpp | 4 +- src/libslic3r/TriangleMesh.cpp | 5 +- src/libslic3r/Zipper.cpp | 3 +- src/slic3r/Config/Snapshot.cpp | 8 +- src/slic3r/Config/Version.cpp | 2 +- src/slic3r/GUI/3DScene.cpp | 2 +- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 61 ++++++++++----- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 30 ++++++++ src/slic3r/GUI/ConfigExceptions.hpp | 10 +-- src/slic3r/GUI/ConfigWizard.cpp | 2 +- src/slic3r/GUI/FirmwareDialog.cpp | 2 +- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/ImGuiWrapper.cpp | 2 +- src/slic3r/GUI/OptionsGroup.cpp | 5 +- src/slic3r/GUI/Plater.cpp | 26 +++---- src/slic3r/GUI/wxExtensions.cpp | 2 +- src/slic3r/Utils/FixModelByWin10.cpp | 26 +++---- src/slic3r/Utils/Http.cpp | 2 +- src/slic3r/Utils/Serial.cpp | 8 +- src/slic3r/Utils/UndoRedo.cpp | 2 +- tests/fff_print/test_data.cpp | 2 +- 59 files changed, 356 insertions(+), 249 deletions(-) create mode 100644 src/libslic3r/Exception.hpp diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 5892b4a30d..72c4fd0e92 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "AppConfig.hpp" +#include "Exception.hpp" #include #include @@ -126,7 +127,7 @@ std::string AppConfig::load() // ! But to avoid the use of _utf8 (related to use of wxWidgets) // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty /* - throw std::runtime_error( + throw Slic3r::RuntimeError( _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) + "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 08f01d8d85..71746f32ed 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -2,6 +2,7 @@ #define slic3r_BoundingBox_hpp_ #include "libslic3r.h" +#include "Exception.hpp" #include "Point.hpp" #include "Polygon.hpp" @@ -22,7 +23,7 @@ public: { if (points.empty()) { this->defined = false; - // throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor"); + // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); } else { typename std::vector::const_iterator it = points.begin(); this->min = *it; @@ -68,7 +69,7 @@ public: BoundingBox3Base(const std::vector& points) { if (points.empty()) - throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor"); + throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); typename std::vector::const_iterator it = points.begin(); this->min = *it; this->max = *it; diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index f3f365b478..25ef93430f 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -5,7 +5,6 @@ #include #include #include -#include // std::runtime_error #include #include #include @@ -218,7 +217,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coInts: return new ConfigOptionIntsNullable(); case coPercents: return new ConfigOptionPercentsNullable(); case coBools: return new ConfigOptionBoolsNullable(); - default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label); + default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label); } } else { switch (this->type) { @@ -238,7 +237,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coBool: return new ConfigOptionBool(); case coBools: return new ConfigOptionBools(); case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); - default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); + default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label); } } } @@ -535,7 +534,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const return opt_def->ratio_over.empty() ? 0. : static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } - throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); + throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } // Return an absolute value of a possibly relative config variable. @@ -546,7 +545,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati const ConfigOption *raw_opt = this->option(opt_key); assert(raw_opt != nullptr); if (raw_opt->type() != coFloatOrPercent) - throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); + throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); // Compute absolute value. return static_cast(raw_opt)->get_abs_value(ratio_over); } @@ -609,7 +608,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) std::getline(ifs, firstline); if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 && strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0) - throw std::runtime_error("Not a PrusaSlicer / Slic3r PE generated g-code."); + throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code."); } ifs.seekg(0, ifs.end); auto file_length = ifs.tellg(); @@ -621,7 +620,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) size_t key_value_pairs = load_from_gcode_string(data.data()); if (key_value_pairs < 80) - throw std::runtime_error(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); } // Load the config keys from the given string. @@ -750,7 +749,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre throw NoDefinitionException(opt_key); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) -// throw std::runtime_error(std::string("Invalid option name: ") + opt_key); +// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; ConfigOption *opt = optdef->create_default_option(); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 87e0208986..28b28b405a 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -13,6 +13,7 @@ #include #include "libslic3r.h" #include "clonable_ptr.hpp" +#include "Exception.hpp" #include "Point.hpp" #include @@ -34,31 +35,31 @@ extern bool unescape_string_cstyle(const std::string &str, std::string & extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); /// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::runtime_error { +class UnknownOptionException : public Slic3r::RuntimeError { public: UnknownOptionException() : - std::runtime_error("Unknown option exception") {} + Slic3r::RuntimeError("Unknown option exception") {} UnknownOptionException(const std::string &opt_key) : - std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} + Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {} }; /// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public std::runtime_error +class NoDefinitionException : public Slic3r::RuntimeError { public: NoDefinitionException() : - std::runtime_error("No definition exception") {} + Slic3r::RuntimeError("No definition exception") {} NoDefinitionException(const std::string &opt_key) : - std::runtime_error(std::string("No definition exception: ") + opt_key) {} + Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {} }; /// Indicate that an unsupported accessor was called on a config option. -class BadOptionTypeException : public std::runtime_error +class BadOptionTypeException : public Slic3r::RuntimeError { public: - BadOptionTypeException() : std::runtime_error("Bad option type exception") {} - BadOptionTypeException(const std::string &message) : std::runtime_error(message) {} - BadOptionTypeException(const char* message) : std::runtime_error(message) {} + BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {} + BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {} + BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {} }; // Type of a configuration value. @@ -167,7 +168,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->value = static_cast*>(rhs)->value; } @@ -175,7 +176,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->value == static_cast*>(&rhs)->value; } @@ -239,7 +240,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->values = static_cast*>(rhs)->values; } @@ -256,12 +257,12 @@ public: if (opt->type() == this->type()) { auto other = static_cast*>(opt); if (other->values.empty()) - throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector"); + throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector"); this->values.emplace_back(other->values.front()); } else if (opt->type() == this->scalar_type()) this->values.emplace_back(static_cast*>(opt)->value); else - throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type"); } } @@ -280,12 +281,12 @@ public: // Assign the first value of the rhs vector. auto other = static_cast*>(rhs); if (other->values.empty()) - throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector"); + throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector"); this->values[i] = other->get_at(j); } else if (rhs->type() == this->scalar_type()) this->values[i] = static_cast*>(rhs)->value; else - throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type"); } const T& get_at(size_t i) const @@ -310,9 +311,9 @@ public: else if (n > this->values.size()) { if (this->values.empty()) { if (opt_default == nullptr) - throw std::runtime_error("ConfigOptionVector::resize(): No default value provided."); + throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided."); if (opt_default->type() != this->type()) - throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type."); + throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type."); this->values.resize(n, static_cast*>(opt_default)->values.front()); } else { // Resize by duplicating the last value. @@ -329,7 +330,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionVector: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->values == static_cast*>(&rhs)->values; } @@ -341,9 +342,9 @@ public: // An option overrides another option if it is not nil and not equal. bool overriden_by(const ConfigOption *rhs) const override { if (this->nullable()) - throw std::runtime_error("Cannot override a nullable ConfigOption."); + throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types."); + throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types."); auto rhs_vec = static_cast*>(rhs); if (! rhs->nullable()) // Overridding a non-nullable object with another non-nullable object. @@ -361,9 +362,9 @@ public: // Apply an override option, possibly a nullable one. bool apply_override(const ConfigOption *rhs) override { if (this->nullable()) - throw std::runtime_error("Cannot override a nullable ConfigOption."); + throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types."); + throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types."); auto rhs_vec = static_cast*>(rhs); if (! rhs->nullable()) { // Overridding a non-nullable object with another non-nullable object. @@ -452,7 +453,7 @@ public: bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return vectors_equal(this->values, static_cast*>(&rhs)->values); } @@ -499,7 +500,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); double value; @@ -524,9 +525,9 @@ protected: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else - throw std::runtime_error("Serializing invalid number"); + throw Slic3r::RuntimeError("Serializing invalid number"); } static bool vectors_equal(const std::vector &v1, const std::vector &v2) { if (NULLABLE) { @@ -645,7 +646,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); int value; @@ -662,7 +663,7 @@ private: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else ss << v; } @@ -847,7 +848,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types"); assert(dynamic_cast(&rhs)); return *this == *static_cast(&rhs); } @@ -858,7 +859,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type"); assert(dynamic_cast(rhs)); *this = *static_cast(rhs); } @@ -1126,7 +1127,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else this->values.push_back(item_str.compare("1") == 0); } @@ -1139,7 +1140,7 @@ protected: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else ss << (v ? "1" : "0"); } @@ -1175,14 +1176,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionEnum: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionEnum: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum return this->value == (T)rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionEnum: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionEnum: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum this->value = (T)rhs->getInt(); } @@ -1259,14 +1260,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionEnumGeneric: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum return this->value == rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionEnumGeneric: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum this->value = rhs->getInt(); } @@ -1321,7 +1322,7 @@ public: case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } - default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1340,7 +1341,7 @@ public: case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } - default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); } } } @@ -1352,7 +1353,7 @@ public: case coInts: archive(*static_cast(opt)); break; case coPercents: archive(*static_cast(opt));break; case coBools: archive(*static_cast(opt)); break; - default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1371,7 +1372,7 @@ public: case coBool: archive(*static_cast(opt)); break; case coBools: archive(*static_cast(opt)); break; case coEnum: archive(*static_cast(opt)); break; - default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); } } // Make the compiler happy, shut up the warnings. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index daaab47555..5bdd5055ec 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "ExPolygon.hpp" +#include "Exception.hpp" #include "Geometry.hpp" #include "Polygon.hpp" #include "Line.hpp" @@ -435,7 +436,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const std::list output; int res = TPPLPartition().Triangulate_MONO(&input, &output); if (res != 1) - throw std::runtime_error("Triangulation failed"); + throw Slic3r::RuntimeError("Triangulation failed"); // convert output polygons for (std::list::iterator poly = output.begin(); poly != output.end(); ++poly) { @@ -548,7 +549,7 @@ void ExPolygon::triangulate_pp(Points *triangles) const int res = TPPLPartition().Triangulate_MONO(&input, &output); // int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) { if (res != 1) - throw std::runtime_error("Triangulation failed"); + throw Slic3r::RuntimeError("Triangulation failed"); *triangles = polypartition_output_to_triangles(output); } @@ -591,7 +592,7 @@ void ExPolygon::triangulate_p2t(Polygons* polygons) const } polygons->push_back(p); } - } catch (const std::runtime_error & /* err */) { + } catch (const Slic3r::RuntimeError & /* err */) { assert(false); // just ignore, don't triangulate } diff --git a/src/libslic3r/Exception.hpp b/src/libslic3r/Exception.hpp new file mode 100644 index 0000000000..8ec9f20c81 --- /dev/null +++ b/src/libslic3r/Exception.hpp @@ -0,0 +1,28 @@ +#ifndef _libslic3r_Exception_h_ +#define _libslic3r_Exception_h_ + +#include + +namespace Slic3r { + +// PrusaSlicer's own exception hierarchy is derived from std::runtime_error. +// Base for Slicer's own exceptions. +class Exception : public std::runtime_error { using std::runtime_error::runtime_error; }; +#define SLIC3R_DERIVE_EXCEPTION(DERIVED_EXCEPTION, PARENT_EXCEPTION) \ + class DERIVED_EXCEPTION : public PARENT_EXCEPTION { using PARENT_EXCEPTION::PARENT_EXCEPTION; } +// Critical exception produced by Slicer, such exception shall never propagate up to the UI thread. +// If that happens, an ugly fat message box with an ugly fat exclamation mark is displayed. +SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception); +SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError); +SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError); +SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError); +// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications. +SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception); +#undef SLIC3R_DERIVE_EXCEPTION + +} // namespace Slic3r + +#endif // _libslic3r_Exception_h_ diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index dfece6949b..5e40ab32ec 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -2,6 +2,7 @@ #define slic3r_ExtrusionEntityCollection_hpp_ #include "libslic3r.h" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { @@ -107,7 +108,7 @@ public: // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const override { - throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection"); + throw Slic3r::RuntimeError("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; @@ -117,7 +118,7 @@ public: } double length() const override { - throw std::runtime_error("Calling length() on a ExtrusionEntityCollection"); + throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection"); return 0.; } }; diff --git a/src/libslic3r/FileParserError.hpp b/src/libslic3r/FileParserError.hpp index 3f560fa4f5..b7e63d84e0 100644 --- a/src/libslic3r/FileParserError.hpp +++ b/src/libslic3r/FileParserError.hpp @@ -10,14 +10,14 @@ namespace Slic3r { // Generic file parser error, mostly copied from boost::property_tree::file_parser_error -class file_parser_error: public std::runtime_error +class file_parser_error: public Slic3r::RuntimeError { public: file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) : - std::runtime_error(format_what(msg, file, line)), + Slic3r::RuntimeError(format_what(msg, file, line)), m_message(msg), m_filename(file), m_line(line) {} file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) : - std::runtime_error(format_what(msg, file.string(), line)), + Slic3r::RuntimeError(format_what(msg, file.string(), line)), m_message(msg), m_filename(file.string()), m_line(line) {} // gcc 3.4.2 complains about lack of throw specifier on compiler // generated dtor @@ -35,7 +35,7 @@ private: std::string m_filename; unsigned long m_line; - // Format error message to be returned by std::runtime_error::what() + // Format error message to be returned by Slic3r::RuntimeError::what() static std::string format_what(const std::string &msg, const std::string &file, unsigned long l) { std::stringstream stream; diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 9001330aae..b0319efded 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -2,6 +2,7 @@ #include "../ClipperUtils.hpp" #include "../EdgeGrid.hpp" +#include "../Exception.hpp" #include "../Geometry.hpp" #include "../Surface.hpp" #include "../PrintConfig.hpp" @@ -40,7 +41,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive(); case ipSupportCubic: return new FillSupportCubic(); - default: throw std::invalid_argument("unknown type"); + default: throw Slic3r::InvalidArgument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index dd887b8c3e..77620e1181 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -11,6 +11,7 @@ #include "../libslic3r.h" #include "../BoundingBox.hpp" +#include "../Exception.hpp" #include "../Utils.hpp" namespace Slic3r { @@ -23,9 +24,10 @@ namespace FillAdaptive_Internal { struct Octree; }; -class InfillFailedException : public std::runtime_error { +// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError. +class InfillFailedException : public Slic3r::RuntimeError { public: - InfillFailedException() : std::runtime_error("Infill failed") {} + InfillFailedException() : Slic3r::RuntimeError("Infill failed") {} }; struct FillParams diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 1678be999c..e5dcf07310 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -53,7 +53,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key) else if (opt_key == "support_material_extrusion_width") return frSupportMaterial; else - throw std::runtime_error("opt_key_to_flow_role: invalid argument"); + throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument"); }; static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key) @@ -126,7 +126,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent { // we need layer height unless it's a bridge if (height <= 0 && bridge_flow_ratio == 0) - throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()"); + throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()"); float w; if (bridge_flow_ratio > 0) { @@ -151,7 +151,7 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, { // we need layer height unless it's a bridge if (height <= 0 && !bridge) - throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()"); + throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()"); // Calculate width from spacing. // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions. // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads. diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 7d6e35873d..9e57ce9079 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "Config.hpp" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { @@ -27,11 +28,11 @@ enum FlowRole { frSupportMaterialInterface, }; -class FlowError : public std::invalid_argument +class FlowError : public Slic3r::InvalidArgument { public: - FlowError(const std::string& what_arg) : invalid_argument(what_arg) {} - FlowError(const char* what_arg) : invalid_argument(what_arg) {} + FlowError(const std::string& what_arg) : Slic3r::InvalidArgument(what_arg) {} + FlowError(const char* what_arg) : Slic3r::InvalidArgument(what_arg) {} }; class FlowErrorNegativeSpacing : public FlowError diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 66dd0049cf..46a6c02af1 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1,4 +1,5 @@ #include "../libslic3r.h" +#include "../Exception.hpp" #include "../Model.hpp" #include "../Utils.hpp" #include "../GCode.hpp" @@ -123,11 +124,11 @@ const char* INVALID_OBJECT_TYPES[] = "other" }; -class version_error : public std::runtime_error +class version_error : public Slic3r::FileIOError { public: - version_error(const std::string& what_arg) : std::runtime_error(what_arg) {} - version_error(const char* what_arg) : std::runtime_error(what_arg) {} + version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} + version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} }; const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) @@ -607,7 +608,7 @@ namespace Slic3r { { // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } } } @@ -780,7 +781,7 @@ namespace Slic3r { { char error_buf[1024]; ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw std::runtime_error(error_buf); + throw Slic3r::FileIOError(error_buf); } return n; @@ -789,7 +790,7 @@ namespace Slic3r { catch (const version_error& e) { // rethrow the exception - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } catch (std::exception& e) { @@ -2360,9 +2361,9 @@ namespace Slic3r { continue; if (!volume->mesh().repaired) - throw std::runtime_error("store_3mf() requires repair()"); + throw Slic3r::FileIOError("store_3mf() requires repair()"); if (!volume->mesh().has_shared_vertices()) - throw std::runtime_error("store_3mf() requires shared vertices"); + throw Slic3r::FileIOError("store_3mf() requires shared vertices"); volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index af7b9b1b60..1a706afa92 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -7,6 +7,7 @@ #include #include "../libslic3r.h" +#include "../Exception.hpp" #include "../Model.hpp" #include "../GCode.hpp" #include "../PrintConfig.hpp" @@ -923,7 +924,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi { char error_buf[1024]; ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw std::runtime_error(error_buf); + throw Slic3r::FileIOError(error_buf); } return n; @@ -948,9 +949,9 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE)) { // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); - // throw std::runtime_error(msg.c_str()); + // throw Slic3r::FileIOError(msg.c_str()); const std::string msg = (boost::format(_(L("The selected amf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); - throw std::runtime_error(msg); + throw Slic3r::FileIOError(msg); } return true; @@ -994,7 +995,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model { // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } break; @@ -1147,9 +1148,9 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); if (! volume->mesh().repaired) - throw std::runtime_error("store_amf() requires repair()"); + throw Slic3r::FileIOError("store_amf() requires repair()"); if (! volume->mesh().has_shared_vertices()) - throw std::runtime_error("store_amf() requires shared vertices"); + throw Slic3r::FileIOError("store_amf() requires shared vertices"); const indexed_triangle_set &its = volume->mesh().its; const Transform3d& matrix = volume->get_matrix(); for (size_t i = 0; i < its.vertices.size(); ++i) { diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index d6f87197df..e2c38d9576 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -147,7 +147,7 @@ static void extract_model_from_archive( } } if (! trafo_set) - throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); + throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); // Extract the STL. StlHeader header; @@ -266,7 +266,7 @@ static void extract_model_from_archive( } if (! mesh_valid) - throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name); + throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name); // Add this mesh to the model. ModelVolume *volume = nullptr; @@ -303,7 +303,7 @@ bool load_prus(const char *path, Model *model) mz_bool res = MZ_FALSE; try { if (!open_zip_reader(&archive, path)) - throw std::runtime_error(std::string("Unable to init zip reader for ") + path); + throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path); std::vector scene_xml_data; // For grouping multiple STLs into a single ModelObject for multi-material prints. std::map group_to_model_object; @@ -316,10 +316,10 @@ bool load_prus(const char *path, Model *model) buffer.assign((size_t)stat.m_uncomp_size, 0); res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == MZ_FALSE) - std::runtime_error(std::string("Error while extracting a file from ") + path); + throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path); if (strcmp(stat.m_filename, "scene.xml") == 0) { if (! scene_xml_data.empty()) - throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path); + throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path); scene_xml_data = std::move(buffer); } else if (boost::iends_with(stat.m_filename, ".stl")) { // May throw std::exception diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index ff1af5d8b1..274f84f002 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -10,6 +10,7 @@ #include +#include "libslic3r/Exception.hpp" #include "libslic3r/SlicesToTriangleMesh.hpp" #include "libslic3r/MarchingSquares.hpp" #include "libslic3r/ClipperUtils.hpp" @@ -64,7 +65,7 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); + throw Slic3r::FileIOError(zip.get_errorstr()); boost::property_tree::ptree tree; std::stringstream ss(buf); @@ -80,7 +81,7 @@ PNGBuffer read_png(const mz_zip_archive_file_stat &entry, if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); + throw Slic3r::FileIOError(zip.get_errorstr()); return {std::move(buf), (name.empty() ? entry.m_filename : name)}; } @@ -94,7 +95,7 @@ ArchiveData extract_sla_archive(const std::string &zipfname, struct Arch: public MZ_Archive { Arch(const std::string &fname) { if (!open_zip_reader(&arch, fname)) - throw std::runtime_error(get_errorstr()); + throw Slic3r::FileIOError(get_errorstr()); } ~Arch() { close_zip_reader(&arch); } @@ -202,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg) if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || !opt_mirror_x || !opt_mirror_y || !opt_orient) - throw std::runtime_error("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 file"); RasterParams rstp; @@ -228,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg) auto *opt_init_layerh = cfg.option("initial_layer_height"); if (!opt_layerh || !opt_init_layerh) - throw std::runtime_error("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 file"); return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0c4e76cd7a..1788250f8a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,6 +1,7 @@ #include "libslic3r.h" #include "I18N.hpp" #include "GCode.hpp" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" #include "Geometry.hpp" @@ -286,7 +287,7 @@ namespace Slic3r { std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const { if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) - throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); std::string gcode; @@ -539,7 +540,7 @@ namespace Slic3r { if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < (int)m_tool_changes.size()) { if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) - throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer."); + throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, @@ -628,7 +629,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Check that there are extrusions on the very first layer. if (layers_to_print.size() == 1u) { if (!has_extrusions) - throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); + throw Slic3r::RuntimeError(_(L("There is an object with no extrusions on the first layer."))); } // In case there are extrusions on this layer, check there is a layer to lay it on. @@ -749,7 +750,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) - throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); #if !ENABLE_GCODE_VIEWER m_enable_analyzer = preview_data != nullptr; @@ -762,7 +763,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ if (ferror(file)) { fclose(file); boost::nowide::remove(path_tmp.c_str()); - throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } } catch (std::exception & /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. @@ -783,7 +784,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ msg += " !!!!! Failed to process the custom G-code template ...\n"; msg += "and\n"; msg += " !!!!! End of an error report for the custom G-code template ...\n"; - throw std::runtime_error(msg); + throw Slic3r::RuntimeError(msg); } #if ENABLE_GCODE_VIEWER @@ -817,7 +818,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ #endif // ENABLE_GCODE_VIEWER if (rename_file(path_tmp, path)) - throw std::runtime_error( + throw Slic3r::RuntimeError( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "Is " + path_tmp + " locked?" + '\n'); @@ -3006,7 +3007,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid); else - throw std::invalid_argument("Invalid argument supplied to extrude()"); + throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); return ""; } @@ -3211,7 +3212,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else if (path.role() == erGapFill) { speed = m_config.get_abs_value("gap_fill_speed"); } else { - throw std::invalid_argument("Invalid speed"); + throw Slic3r::InvalidArgument("Invalid speed"); } } if (this->on_first_layer()) @@ -3632,7 +3633,7 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr perimeters_or_infills_overrides = &infills_overrides; break; default: - throw std::invalid_argument("Unknown parameter!"); + throw Slic3r::InvalidArgument("Unknown parameter!"); } // First we append the entities, there are eec->entities.size() of them: diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index db69f4f0ba..0a5617559d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -319,13 +319,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) { boost::nowide::ifstream in(filename); if (!in.good()) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); // temporary file to contain modified gcode std::string out_path = filename + ".postprocess"; FILE* out = boost::nowide::fopen(out_path.c_str(), "wb"); if (out == nullptr) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); auto time_in_minutes = [](float time_in_seconds) { return int(::roundf(time_in_seconds / 60.0f)); @@ -418,7 +418,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) in.close(); fclose(out); boost::nowide::remove(out_path.c_str()); - throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } export_line.clear(); }; @@ -426,7 +426,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) while (std::getline(in, gcode_line)) { if (!in.good()) { fclose(out); - throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); } gcode_line += "\n"; @@ -460,7 +460,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) in.close(); if (rename_file(out_path, filename)) - throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + "Is " + out_path + " locked?" + '\n'); } diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index 25982959be..17aa76fb9d 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -79,7 +79,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line) if (! ::CreateProcessW( nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */, CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info)) - throw std::runtime_error(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); + throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); ::WaitForSingleObject(process_info.hProcess, INFINITE); ULONG rc = 0; ::GetExitCodeProcess(process_info.hProcess, &rc); @@ -98,13 +98,13 @@ static int run_script(const std::string &script, const std::string &gcode, std:: LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs); if (szArglist == nullptr || nArgs <= 0) { // CommandLineToArgvW failed. Maybe the command line escapment is invalid? - throw std::runtime_error(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path."); + throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path."); } std::wstring command_line; std::wstring command = szArglist[0]; if (! boost::filesystem::exists(boost::filesystem::path(command))) - throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); + throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); if (boost::iends_with(command, L".pl")) { // This is a perl script. Run it through the perl interpreter. // The current process may be slic3r.exe or slic3r-console.exe. @@ -115,7 +115,7 @@ static int run_script(const std::string &script, const std::string &gcode, std:: boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe"; if (! boost::filesystem::exists(path_perl)) { LocalFree(szArglist); - throw std::runtime_error(std::string("Perl interpreter ") + path_perl.string() + " does not exist."); + throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist."); } // Replace it with the current perl interpreter. quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line); @@ -187,7 +187,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config config.setenv_(); auto gcode_file = boost::filesystem::path(path); if (! boost::filesystem::exists(gcode_file)) - throw std::runtime_error(std::string("Post-processor can't find exported gcode file")); + throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file")); for (const std::string &scripts : config.post_process.values) { std::vector lines; @@ -205,7 +205,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config 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); + throw Slic3r::RuntimeError(msg); } } } diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 3b2a58a884..33601e5e9f 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -148,7 +148,7 @@ static inline int parse_int(const char *&line) char *endptr = NULL; long result = strtol(line, &endptr, 10); if (endptr == NULL || !is_ws_or_eol(*endptr)) - throw std::runtime_error("PressureEqualizer: Error parsing an int"); + throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int"); line = endptr; return int(result); }; @@ -160,7 +160,7 @@ static inline float parse_float(const char *&line) char *endptr = NULL; float result = strtof(line, &endptr); if (endptr == NULL || !is_ws_or_eol(*endptr)) - throw std::runtime_error("PressureEqualizer: Error parsing a float"); + throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float"); line = endptr; return result; }; @@ -229,7 +229,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi assert(false); } if (i == -1) - throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); + throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); buf.pos_provided[i] = true; new_pos[i] = parse_float(line); if (i == 3 && m_config->use_relative_e_distances.value) @@ -298,7 +298,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi set = true; break; default: - throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); + throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); } eatws(line); } diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 4a6624531b..a86411519f 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -94,7 +94,7 @@ static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_ent auto *extrusion_entity_collection = dynamic_cast(extrusion_entity); if (extrusion_entity_collection != nullptr) return extrusionentity_extents(*extrusion_entity_collection); - throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()"); + throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()"); return BoundingBoxf(); } diff --git a/src/libslic3r/GCodeSender.cpp b/src/libslic3r/GCodeSender.cpp index 9567e07d28..7bda299923 100644 --- a/src/libslic3r/GCodeSender.cpp +++ b/src/libslic3r/GCodeSender.cpp @@ -153,7 +153,7 @@ GCodeSender::set_baud_rate(unsigned int baud_rate) if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0) printf("Failed to set baud rate: %s\n", strerror(errno)); #else - //throw invalid_argument ("OS does not currently support custom bauds"); + //throw Slic3r::InvalidArgument("OS does not currently support custom bauds"); #endif } } diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index aa9ee2f643..a3e20ca2f4 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "GCodeTimeEstimator.hpp" #include "Utils.hpp" #include @@ -254,13 +255,13 @@ namespace Slic3r { { boost::nowide::ifstream in(filename); if (!in.good()) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); std::string path_tmp = filename + ".postprocess"; FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (out == nullptr) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); std::string normal_time_mask = "M73 P%s R%s\n"; std::string silent_time_mask = "M73 Q%s S%s\n"; @@ -278,7 +279,7 @@ namespace Slic3r { in.close(); fclose(out); boost::nowide::remove(path_tmp.c_str()); - throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } export_line.clear(); }; @@ -326,7 +327,7 @@ namespace Slic3r { if (!in.good()) { fclose(out); - throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); } // check tags @@ -383,7 +384,7 @@ namespace Slic3r { in.close(); if (rename_file(path_tmp, filename)) - throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + "Is " + path_tmp + " locked?" + '\n'); return true; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 00a4ad47c3..3b9fcd6176 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1,4 +1,5 @@ #include "libslic3r.h" +#include "Exception.hpp" #include "Geometry.hpp" #include "ClipperUtils.hpp" #include "ExPolygon.hpp" @@ -471,7 +472,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0))); size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1))); if (num_parts > cellw * cellh) - throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts); + throw Slic3r::InvalidArgument("%zu parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 66167c7209..dd34b28300 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "MeshBoolean.hpp" #include "libslic3r/TriangleMesh.hpp" #undef PI @@ -136,7 +137,7 @@ template void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &o if(CGAL::is_closed(out)) CGALProc::orient_to_bound_a_volume(out); else - std::runtime_error("Mesh not watertight"); + throw Slic3r::RuntimeError("Mesh not watertight"); } inline Vec3d to_vec3d(const _EpicMesh::Point &v) @@ -222,7 +223,7 @@ template void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B) } if (! success) - throw std::runtime_error("CGAL mesh boolean operation failed."); + throw Slic3r::RuntimeError("CGAL mesh boolean operation failed."); } void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 67fe63871d..dc06e3102d 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Model.hpp" #include "ModelArrange.hpp" #include "Geometry.hpp" @@ -116,13 +117,13 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else - throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); if (! result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) o->input_file = input_file; @@ -146,13 +147,13 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig else if (boost::algorithm::iends_with(input_file, ".zip.amf")) result = load_amf(input_file.c_str(), config, &model, check_version); else - throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); if (!result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) { @@ -817,7 +818,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const m_raw_bounding_box_valid = true; m_raw_bounding_box.reset(); if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); for (const ModelVolume *v : this->volumes) diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index afe146d438..124c5c018f 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -20,7 +20,7 @@ using VirtualBedFn = std::function; [[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&) { - throw std::runtime_error("Objects could not fit on the bed"); + throw Slic3r::RuntimeError("Objects could not fit on the bed"); } ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances); diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 527d82b4cb..0434e3a0aa 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1,4 +1,5 @@ #include "PlaceholderParser.hpp" +#include "Exception.hpp" #include "Flow.hpp" #include #include @@ -1303,7 +1304,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co if (!context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; - throw std::runtime_error(context.error_message); + throw Slic3r::RuntimeError(context.error_message); } return output; } @@ -1319,7 +1320,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu } // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. -// Throws std::runtime_error on syntax or runtime error. +// Throws Slic3r::RuntimeError on syntax or runtime error. bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index d744dba220..14be020aca 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -40,11 +40,11 @@ public: const DynamicConfig* external_config() const { return m_external_config; } // Fill in the template using a macro processing language. - // Throws std::runtime_error on syntax or runtime error. + // Throws Slic3r::RuntimeError on syntax or runtime error. std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr) const; // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. - // Throws std::runtime_error on syntax or runtime error. + // Throws Slic3r::RuntimeError on syntax or runtime error. static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr); // Update timestamp, year, month, day, hour, minute, second variables at the provided config. diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 48e63dab31..fc83ae8d47 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" +#include "Exception.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -16,7 +17,7 @@ Polyline Polygon::split_at_vertex(const Point &point) const for (const Point &pt : this->points) if (pt == point) return this->split_at_index(int(&pt - &this->points.front())); - throw std::invalid_argument("Point not found"); + throw Slic3r::InvalidArgument("Point not found"); return Polyline(); } diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 26aad83d2b..d24788c7bc 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "Polyline.hpp" +#include "Exception.hpp" #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" #include "Line.hpp" @@ -19,7 +20,7 @@ Polyline::operator Polylines() const Polyline::operator Line() const { if (this->points.size() > 2) - throw std::invalid_argument("Can't convert polyline with more than two points to a line"); + throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line"); return Line(this->points.front(), this->points.back()); } @@ -207,7 +208,7 @@ BoundingBox get_extents(const Polylines &polylines) const Point& leftmost_point(const Polylines &polylines) { if (polylines.empty()) - throw std::invalid_argument("leftmost_point() called on empty PolylineCollection"); + throw Slic3r::InvalidArgument("leftmost_point() called on empty PolylineCollection"); Polylines::const_iterator it = polylines.begin(); const Point *p = &it->leftmost_point(); for (++ it; it != polylines.end(); ++it) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 284c37435a..18a6e387cf 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1,5 +1,6 @@ #include +#include "Exception.hpp" #include "Preset.hpp" #include "AppConfig.hpp" @@ -107,7 +108,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem const std::string id = path.stem().string(); if (! boost::filesystem::exists(path)) { - throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); + throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); } VendorProfile res(id); @@ -117,7 +118,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem { auto res = tree.find(key); if (res == tree.not_found()) { - throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); } return res; }; @@ -129,7 +130,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data(); auto config_version = Semver::parse(config_version_str); if (! config_version) { - throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); } else { res.config_version = std::move(*config_version); } @@ -672,9 +673,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; preset.loaded = true; } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); } presets_loaded.emplace_back(preset); } catch (const std::runtime_error &err) { @@ -686,7 +687,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); } // Load a preset from an already parsed config file, insert it into the sorted sequence of presets @@ -1557,10 +1558,10 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const printer.loaded = true; } catch (const std::ifstream::failure& err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error& err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); } printers_loaded.emplace_back(printer); } @@ -1572,7 +1573,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); std::sort(m_printers.begin(), m_printers.end()); if (!errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); } // if there is saved user presets, contains information about "Print Host upload", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index ac1b0a7176..c100e6971a 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -157,7 +157,7 @@ void PresetBundle::setup_directories() subdir.make_preferred(); if (! boost::filesystem::is_directory(subdir) && ! boost::filesystem::create_directory(subdir)) - throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); } } @@ -207,7 +207,7 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); this->load_selections(config, preferred_model_id); } @@ -679,21 +679,21 @@ void PresetBundle::load_config_file(const std::string &path) boost::nowide::ifstream ifs(path); boost::property_tree::read_ini(ifs, tree); } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what()); } catch (const boost::property_tree::file_parser_error &err) { - throw std::runtime_error((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%") + throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%") % err.filename() % err.message() % err.line()).str()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); } // 2) Continue based on the type of the configuration file. ConfigFileType config_file_type = guess_config_file_type(tree); switch (config_file_type) { case CONFIG_FILE_TYPE_UNKNOWN: - throw std::runtime_error(std::string("Unknown configuration file type: ") + path); + throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path); case CONFIG_FILE_TYPE_APP_CONFIG: - throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file."); + throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file."); case CONFIG_FILE_TYPE_CONFIG: { // Initialize a config from full defaults. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 50b752984b..a73b7016fb 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1,5 +1,6 @@ #include "clipper/clipper_z.hpp" +#include "Exception.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -1507,7 +1508,7 @@ BoundingBox Print::total_bounding_box() const double Print::skirt_first_layer_height() const { if (m_objects.empty()) - throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects"); + throw Slic3r::InvalidArgument("skirt_first_layer_height() can't be called without PrintObjects"); return m_objects.front()->config().get_abs_value("first_layer_height"); } @@ -1603,7 +1604,7 @@ void Print::process() // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. m_tool_ordering = ToolOrdering(*this, -1, false); if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1)) - throw std::runtime_error("The print is empty. The model is not printable with current print settings."); + throw Slic3r::RuntimeError("The print is empty. The model is not printable with current print settings."); } this->set_done(psWipeTower); } diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index ab6ca3d350..7cdf6448c3 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "PrintBase.hpp" #include @@ -68,7 +69,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str filename = boost::filesystem::change_extension(filename, default_ext); return filename.string(); } catch (std::runtime_error &err) { - throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); + throw Slic3r::RuntimeError(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); } } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index e2dba5bb2a..30c070338d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -138,7 +139,7 @@ void PrintObject::slice() } }); if (m_layers.empty()) - throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); + throw Slic3r::RuntimeError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); this->set_done(posSlice); } @@ -426,7 +427,7 @@ void PrintObject::generate_support_material() // therefore they cannot be printed without supports. for (const Layer *layer : m_layers) if (layer->empty()) - throw std::runtime_error("Levitating objects cannot be printed without supports."); + throw Slic3r::RuntimeError("Levitating objects cannot be printed without supports."); #endif } this->set_done(posSupportMaterial); diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index b3ac6a4a5b..2a75cd621d 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Print.hpp" namespace Slic3r { @@ -13,7 +14,7 @@ unsigned int PrintRegion::extruder(FlowRole role) const else if (role == frSolidInfill || role == frTopSolidInfill) extruder = m_config.solid_infill_extruder; else - throw std::invalid_argument("Unknown role"); + throw Slic3r::InvalidArgument("Unknown role"); return extruder; } @@ -40,7 +41,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir } else if (role == frTopSolidInfill) { config_width = m_config.top_infill_extrusion_width; } else { - throw std::invalid_argument("Unknown role"); + throw Slic3r::InvalidArgument("Unknown role"); } } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index eaf9698198..19bd854881 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -187,7 +188,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) } if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) - throw std::runtime_error(L("Too much overlapping holes.")); + throw Slic3r::RuntimeError(L("Too many overlapping holes.")); auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); @@ -195,7 +196,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); } catch (const std::runtime_error &) { - throw std::runtime_error(L( + throw Slic3r::RuntimeError(L( "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); } @@ -241,7 +242,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. - throw std::runtime_error( + throw Slic3r::RuntimeError( L("Slicing had to be stopped due to an internal error: " "Inconsistent slice index.")); @@ -445,7 +446,7 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); if (!validate_pad(pad_mesh, pcfg)) - throw std::runtime_error( + throw Slic3r::RuntimeError( L("No pad can be generated for this model with the " "current configuration")); @@ -613,7 +614,7 @@ void SLAPrint::Steps::initialize_printer_input() for(const SliceRecord& slicerecord : o->get_slice_index()) { if (!slicerecord.is_valid()) - throw std::runtime_error( + throw Slic3r::RuntimeError( L("There are unprintable objects. Try to " "adjust support settings to make the " "objects printable.")); diff --git a/src/libslic3r/Semver.hpp b/src/libslic3r/Semver.hpp index 24ca74f837..f55fa9f9f4 100644 --- a/src/libslic3r/Semver.hpp +++ b/src/libslic3r/Semver.hpp @@ -10,6 +10,8 @@ #include "semver/semver.h" +#include "Exception.hpp" + namespace Slic3r { @@ -38,7 +40,7 @@ public: { auto parsed = parse(str); if (! parsed) { - throw std::runtime_error(std::string("Could not parse version string: ") + str); + throw Slic3r::RuntimeError(std::string("Could not parse version string: ") + str); } ver = parsed->ver; parsed->ver = semver_zero(); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 49fc625af9..8ba34e5160 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" @@ -420,7 +421,7 @@ std::deque TriangleMesh::find_unvisited_neighbors(std::vectorrepaired) - throw std::runtime_error("find_unvisited_neighbors() requires repair()"); + throw Slic3r::RuntimeError("find_unvisited_neighbors() requires repair()"); // If the visited list is empty, populate it with false for every facet. if (facet_visited.empty()) @@ -683,7 +684,7 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac { mesh = _mesh; if (! mesh->has_shared_vertices()) - throw std::invalid_argument("TriangleMeshSlicer was passed a mesh without shared vertices."); + throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices."); throw_on_cancel(); facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 02f022083b..7a95829cd0 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,5 +1,6 @@ #include +#include "Exception.hpp" #include "Zipper.hpp" #include "miniz_extension.hpp" #include @@ -29,7 +30,7 @@ public: SLIC3R_NORETURN void blow_up() const { - throw std::runtime_error(formatted_errorstr()); + throw Slic3r::RuntimeError(formatted_errorstr()); } bool is_alive() diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 30596b614d..45dc998741 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -315,7 +315,7 @@ size_t SnapshotDB::load_db() // Sort the snapshots by their date/time. std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; }); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); return m_snapshots.size(); } @@ -339,7 +339,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src { if (! boost::filesystem::is_directory(path_dst) && ! boost::filesystem::create_directory(path_dst)) - throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) if (Slic3r::is_ini_file(dir_entry)) @@ -429,7 +429,7 @@ const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &a this->restore_snapshot(snapshot, app_config); return snapshot; } - throw std::runtime_error(std::string("Snapshot with id " + id + " was not found.")); + throw Slic3r::RuntimeError(std::string("Snapshot with id " + id + " was not found.")); } void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config) @@ -501,7 +501,7 @@ boost::filesystem::path SnapshotDB::create_db_dir() subdir.make_preferred(); if (! boost::filesystem::is_directory(subdir) && ! boost::filesystem::create_directory(subdir)) - throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + subdir.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + subdir.string()); } return snapshots_dir; } diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index d00e4a2abf..04ce05ab5b 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -324,7 +324,7 @@ std::vector Index::load_db() } if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); return index_db; } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 70fec670c7..ca96af49c1 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1926,7 +1926,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, if (extrusion_entity_collection != nullptr) extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume); else { - throw std::runtime_error("Unexpected extrusion_entity type in to_verts()"); + throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()"); } } } diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 9675db10ef..9470359d04 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -41,6 +41,36 @@ namespace Slic3r { +bool SlicingProcessCompletedEvent::critical_error() const +{ + try { + this->rethrow_exception(); + } catch (const Slic3r::SlicingError &ex) { + // Exception derived from SlicingError is non-critical. + return false; + } catch (...) { + return true; + } +} + +std::string SlicingProcessCompletedEvent::format_error_message() const +{ + std::string error; + try { + this->rethrow_exception(); + } catch (const std::bad_alloc& ex) { + wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); + error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); + } catch (std::exception &ex) { + error = ex.what(); + } catch (...) { + error = "Unknown C++ exception."; + } + return error; +} + BackgroundSlicingProcess::BackgroundSlicingProcess() { boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data()); @@ -109,19 +139,19 @@ void BackgroundSlicingProcess::process_fff() switch (copy_ret_val) { case SUCCESS: break; // no error case FAIL_COPY_FILE: - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); + throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); break; case FAIL_FILES_DIFFERENT: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str()); break; case FAIL_RENAMING: - throw std::runtime_error((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str()); break; case FAIL_CHECK_ORIGIN_NOT_OPENED: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str()); break; case FAIL_CHECK_TARGET_NOT_OPENED: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str()); break; default: BOOST_LOG_TRIVIAL(warning) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << "."; @@ -210,7 +240,7 @@ void BackgroundSlicingProcess::thread_proc() // Process the background slicing task. m_state = STATE_RUNNING; lck.unlock(); - std::string error; + std::exception_ptr exception; try { assert(m_print != nullptr); switch(m_print->technology()) { @@ -221,15 +251,8 @@ void BackgroundSlicingProcess::thread_proc() } catch (CanceledException & /* ex */) { // Canceled, this is all right. assert(m_print->canceled()); - } catch (const std::bad_alloc& ex) { - wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. " - "If you are sure you have enough RAM on your system, this may also be a bug and we would " - "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); - error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); - } catch (std::exception &ex) { - error = ex.what(); } catch (...) { - error = "Unknown C++ exception."; + exception = std::current_exception(); } m_print->finalize(); lck.lock(); @@ -237,9 +260,9 @@ void BackgroundSlicingProcess::thread_proc() if (m_print->cancel_status() != Print::CANCELED_INTERNAL) { // Only post the canceled event, if canceled by user. // Don't post the canceled event, if canceled from Print::apply(). - wxCommandEvent evt(m_event_finished_id); - evt.SetString(GUI::from_u8(error)); - evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0)); + SlicingProcessCompletedEvent evt(m_event_finished_id, 0, + (m_state == STATE_CANCELED) ? SlicingProcessCompletedEvent::Cancelled : + exception ? SlicingProcessCompletedEvent::Error : SlicingProcessCompletedEvent::Finished, exception); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); } m_print->restart(); @@ -299,7 +322,7 @@ bool BackgroundSlicingProcess::start() // The background processing thread is already running. return false; if (! this->idle()) - throw std::runtime_error("Cannot start a background task, the worker thread is not idle."); + throw Slic3r::RuntimeError("Cannot start a background task, the worker thread is not idle."); m_state = STATE_STARTED; m_print->set_cancel_callback([this](){ this->stop_internal(); }); lck.unlock(); @@ -494,7 +517,7 @@ void BackgroundSlicingProcess::prepare_upload() if (m_print == m_fff_print) { m_print->set_status(95, _utf8(L("Running post-processing scripts"))); if (copy_file(m_temp_output_path, source_path.string()) != SUCCESS) { - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); + throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); } run_post_process_scripts(source_path.string(), m_fff_print->config()); m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 9fe1157b6e..1b2687e63a 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -37,6 +37,36 @@ public: PrintBase::SlicingStatus status; }; +class SlicingProcessCompletedEvent : public wxEvent +{ +public: + enum StatusType { + Finished, + Cancelled, + Error + }; + + SlicingProcessCompletedEvent(wxEventType eventType, int winid, StatusType status, std::exception_ptr exception) : + wxEvent(winid, eventType), m_status(status), m_exception(exception) {} + virtual wxEvent* Clone() const { return new SlicingProcessCompletedEvent(*this); } + + StatusType status() const { return m_status; } + bool finished() const { return m_status == Finished; } + bool success() const { return m_status == Finished; } + bool cancelled() const { return m_status == Cancelled; } + bool error() const { return m_status == Error; } + // Unhandled error produced by stdlib or a Win32 structured exception, or unhandled Slic3r's own critical exception. + bool critical_error() const; + // Only valid if error() + void rethrow_exception() const { assert(this->error()); assert(m_exception); std::rethrow_exception(m_exception); } + // Produce a human readable message to be displayed by a notification or a message box. + std::string format_error_message() const; + +private: + StatusType m_status; + std::exception_ptr m_exception; +}; + wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); // Print step IDs for keeping track of the print state. diff --git a/src/slic3r/GUI/ConfigExceptions.hpp b/src/slic3r/GUI/ConfigExceptions.hpp index 9038d3445e..181442d4e3 100644 --- a/src/slic3r/GUI/ConfigExceptions.hpp +++ b/src/slic3r/GUI/ConfigExceptions.hpp @@ -1,15 +1,15 @@ #include namespace Slic3r { -class ConfigError : public std::runtime_error { -using std::runtime_error::runtime_error; +class ConfigError : public Slic3r::RuntimeError { + using Slic3r::RuntimeError::RuntimeError; }; namespace GUI { class ConfigGUITypeError : public ConfigError { -using ConfigError::ConfigError; + using ConfigError::ConfigError; }; -} -} +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2cedbfdf78..c98b736b7c 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -123,7 +123,7 @@ Bundle& BundleMap::prusa_bundle() { auto it = find(PresetBundle::PRUSA_BUNDLE); if (it == end()) { - throw std::runtime_error("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); + throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); } return it->second; diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index fe7ff4e5de..5441c84ed5 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -766,7 +766,7 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) { return "Original Prusa CW1"; break; - default: throw std::runtime_error((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); + default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); } } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d59a83e875..40c8f96e59 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -571,7 +571,7 @@ void GUI_App::init_app_config() std::string error = app_config->load(); if (!error.empty()) // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - throw std::runtime_error( + throw Slic3r::RuntimeError( _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + "\n\n" + AppConfig::config_path() + "\n\n" + error); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 0fecc822db..f9a23cb51c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -927,7 +927,7 @@ void ImGuiWrapper::init_font(bool compress) if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { - throw std::runtime_error("ImGui: Could not load deafult font"); + throw Slic3r::RuntimeError("ImGui: Could not load deafult font"); } } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index dd00b3d688..14defd9a96 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "libslic3r/Exception.hpp" #include "libslic3r/Utils.hpp" #include "I18N.hpp" @@ -64,7 +65,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co break; case coNone: break; default: - throw /*//!ConfigGUITypeError("")*/std::logic_error("This control doesn't exist till now"); break; + throw Slic3r::LogicError("This control doesn't exist till now"); break; } } // Grab a reference to fields for convenience @@ -620,7 +621,7 @@ boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_ // Aggregate the strings the old way. // Currently used for the post_process config value only. if (opt_index != -1) - throw std::out_of_range("Can't deserialize option indexed value"); + throw Slic3r::OutOfRange("Can't deserialize option indexed value"); // return join(';', m_config->get(opt_key)}); return get_config_value(*m_config, opt_key); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f7fd608ba8..6d856960db 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -107,7 +107,7 @@ namespace GUI { wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); -wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, SlicingProcessCompletedEvent); wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); // Sidebar widgets @@ -1682,7 +1682,7 @@ struct Plater::priv void on_select_preset(wxCommandEvent&); void on_slicing_update(SlicingStatusEvent&); void on_slicing_completed(wxCommandEvent&); - void on_process_completed(wxCommandEvent&); + void on_process_completed(SlicingProcessCompletedEvent&); void on_export_began(wxCommandEvent&); void on_layer_editing_toggled(bool enable); void on_slicing_began(); @@ -3510,7 +3510,7 @@ bool Plater::priv::warnings_dialog() return res == wxID_OK; } -void Plater::priv::on_process_completed(wxCommandEvent &evt) +void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { // Stop the background task, wait until the thread goes into the "Idle" state. // At this point of time the thread should be either finished or canceled, @@ -3519,27 +3519,23 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - 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 (error) { - wxString message = evt.GetString(); - if (message.IsEmpty()) - message = _L("Export failed."); - notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D()); - this->statusbar()->set_status_text(message); + if (evt.error()) { + std::string message = evt.format_error_message(); + //FIXME show a messagebox if evt.critical_error(). + notification_manager->push_slicing_error_notification(message, *q->get_current_canvas3D()); + this->statusbar()->set_status_text(from_u8(message)); const wxString invalid_str = _L("Invalid data"); for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) sidebar->set_btn_label(btn, invalid_str); process_completed_with_error = true; } - if (canceled) + if (evt.cancelled()) this->statusbar()->set_status_text(_L("Cancelled")); - this->sidebar->show_sliced_info_sizer(success); + this->sidebar->show_sliced_info_sizer(evt.success()); // This updates the "Slice now", "Export G-code", "Arrange" buttons status. // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables @@ -3560,7 +3556,7 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) default: break; } - if (canceled) { + if (evt.cancelled()) { if (wxGetApp().get_mode() == comSimple) sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); show_action_buttons(true); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index d23c3415fa..6f79db5916 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -436,7 +436,7 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in, if (bmp == nullptr) { // Neither SVG nor PNG has been found, raise error - throw std::runtime_error("Could not load bitmap: " + bmp_name); + throw Slic3r::RuntimeError("Could not load bitmap: " + bmp_name); } return *bmp; diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 86ff79aaaf..bcab6daaf8 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -209,10 +209,10 @@ typedef std::functionGetResults(model.GetAddressOf()); else - throw std::runtime_error(L("Failed loading the input model.")); + throw Slic3r::RuntimeError(L("Failed loading the input model.")); Microsoft::WRL::ComPtr> meshes; hr = model->get_Meshes(meshes.GetAddressOf()); @@ -245,7 +245,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = model->RepairAsync(repairAsync.GetAddressOf()); status = winrt_async_await(repairAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Mesh repair failed.")); + throw Slic3r::RuntimeError(L("Mesh repair failed.")); repairAsync->GetResults(); on_progress(L("Loading repaired model"), 60); @@ -260,14 +260,14 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); status = winrt_async_await(saveToPackageAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); hr = saveToPackageAsync->GetResults(); Microsoft::WRL::ComPtr> generatorStreamAsync; hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); status = winrt_async_await(generatorStreamAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); Microsoft::WRL::ComPtr generatorStream; hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); @@ -299,7 +299,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); status = winrt_async_await(asyncRead, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); hr = buffer->get_Length(&length); if (length == 0) break; @@ -365,7 +365,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) model_object->add_instance(); if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false)) { boost::filesystem::remove(path_src); - throw std::runtime_error(L("Export of a temporary 3mf file failed")); + throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); } model.clear_objects(); model.clear_materials(); @@ -380,15 +380,15 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); boost::filesystem::remove(path_dst); if (! loaded) - throw std::runtime_error(L("Import of the repaired 3mf file failed")); + throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); if (model.objects.size() == 0) - throw std::runtime_error(L("Repaired 3MF file does not contain any object")); + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); if (model.objects.size() > 1) - throw std::runtime_error(L("Repaired 3MF file contains more than one object")); + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); if (model.objects.front()->volumes.size() == 0) - throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); if (model.objects.front()->volumes.size() > 1) - throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); } for (size_t i = 0; i < volumes.size(); ++ i) { diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index e55c21fe17..8c79a478a1 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -156,7 +156,7 @@ Http::priv::priv(const std::string &url) Http::tls_global_init(); if (curl == nullptr) { - throw std::runtime_error(std::string("Could not construct Curl object")); + throw Slic3r::RuntimeError(std::string("Could not construct Curl object")); } set_timeout_connect(DEFAULT_TIMEOUT_CONNECT); diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index 737e76c0b5..959c60c373 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -298,7 +298,7 @@ void Serial::set_baud_rate(unsigned baud_rate) auto handle_errno = [](int retval) { if (retval != 0) { - throw std::runtime_error( + throw Slic3r::RuntimeError( (boost::format("Could not set baud rate: %1%") % strerror(errno)).str() ); } @@ -346,7 +346,7 @@ void Serial::set_baud_rate(unsigned baud_rate) handle_errno(::cfsetspeed(&ios, baud_rate)); handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios)); #else - throw std::runtime_error("Custom baud rates are not currently supported on this OS"); + throw Slic3r::RuntimeError("Custom baud rates are not currently supported on this OS"); #endif } } @@ -358,7 +358,7 @@ void Serial::set_DTR(bool on) auto handle = native_handle(); #if defined(_WIN32) && !defined(__SYMBIAN32__) if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) { - throw std::runtime_error("Could not set serial port DTR"); + throw Slic3r::RuntimeError("Could not set serial port DTR"); } #else int status; @@ -369,7 +369,7 @@ void Serial::set_DTR(bool on) } } - throw std::runtime_error( + throw Slic3r::RuntimeError( (boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str() ); #endif diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 9c8d7a8c68..10b8062f7b 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -847,7 +847,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) - throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); + throw Slic3r::RuntimeError((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); m_active_snapshot_time = timestamp; model.clear_objects(); diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 09ca730ec7..8e5f6bafdc 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -137,7 +137,7 @@ TriangleMesh mesh(TestMesh m) { {0,1,2}, {2,1,3}, {4,0,5}, {4,1,0}, {6,4,7}, {7,4,5}, {4,8,1}, {0,2,5}, {5,2,9}, {2,10,9}, {10,3,11}, {2,3,10}, {9,10,12}, {13,9,12}, {3,1,8}, {11,3,8}, {10,11,8}, {4,10,8}, {6,12,10}, {4,6,10}, {7,13,12}, {6,7,12}, {7,5,9}, {13,7,9} }); break; default: - throw std::invalid_argument("Slic3r::Test::mesh(): called with invalid mesh ID"); + throw Slic3r::InvalidArgument("Slic3r::Test::mesh(): called with invalid mesh ID"); break; } From 1eadb6a1a937624b9bbfc33e6e4e2e4589c0e68c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 14 Sep 2020 18:01:25 +0200 Subject: [PATCH 510/826] Replaced some of Slic3r::RuntimeError exceptions with Slic3r::SlicingError. Only Slic3r::SlicingError are now displayed by a notification, other exceptions are shown by a pop-up dialog. --- src/libslic3r/Fill/FillBase.cpp | 1 - src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintObject.cpp | 4 ++-- src/libslic3r/SLAPrintSteps.cpp | 8 ++++---- src/slic3r/GUI/Plater.cpp | 11 +++++++++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index b0319efded..077555d2ca 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -2,7 +2,6 @@ #include "../ClipperUtils.hpp" #include "../EdgeGrid.hpp" -#include "../Exception.hpp" #include "../Geometry.hpp" #include "../Surface.hpp" #include "../PrintConfig.hpp" diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a73b7016fb..a82ab3dddc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1604,7 +1604,7 @@ void Print::process() // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. m_tool_ordering = ToolOrdering(*this, -1, false); if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1)) - throw Slic3r::RuntimeError("The print is empty. The model is not printable with current print settings."); + throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); } this->set_done(psWipeTower); } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 30c070338d..ddd41af012 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -139,7 +139,7 @@ void PrintObject::slice() } }); if (m_layers.empty()) - throw Slic3r::RuntimeError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); + throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); this->set_done(posSlice); } @@ -427,7 +427,7 @@ void PrintObject::generate_support_material() // therefore they cannot be printed without supports. for (const Layer *layer : m_layers) if (layer->empty()) - throw Slic3r::RuntimeError("Levitating objects cannot be printed without supports."); + throw Slic3r::SlicingError("Levitating objects cannot be printed without supports."); #endif } this->set_done(posSupportMaterial); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 19bd854881..d94bc682b3 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -188,7 +188,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) } if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) - throw Slic3r::RuntimeError(L("Too many overlapping holes.")); + throw Slic3r::SlicingError(L("Too many overlapping holes.")); auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); @@ -196,7 +196,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); } catch (const std::runtime_error &) { - throw Slic3r::RuntimeError(L( + throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); } @@ -446,7 +446,7 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); if (!validate_pad(pad_mesh, pcfg)) - throw Slic3r::RuntimeError( + throw Slic3r::SlicingError( L("No pad can be generated for this model with the " "current configuration")); @@ -614,7 +614,7 @@ void SLAPrint::Steps::initialize_printer_input() for(const SliceRecord& slicerecord : o->get_slice_index()) { if (!slicerecord.is_valid()) - throw Slic3r::RuntimeError( + throw Slic3r::SlicingError( L("There are unprintable objects. Try to " "adjust support settings to make the " "objects printable.")); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6d856960db..ec632611f0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3524,8 +3524,15 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) if (evt.error()) { std::string message = evt.format_error_message(); - //FIXME show a messagebox if evt.critical_error(). - notification_manager->push_slicing_error_notification(message, *q->get_current_canvas3D()); + if (evt.critical_error()) { + if (q->m_tracking_popup_menu) + // We don't want to pop-up a message box when tracking a pop-up menu. + // We postpone the error message instead. + q->m_tracking_popup_menu_error_message = message; + else + show_error(q, message); + } else + notification_manager->push_slicing_error_notification(message, *q->get_current_canvas3D()); this->statusbar()->set_status_text(from_u8(message)); const wxString invalid_str = _L("Invalid data"); for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) From 5d8c4b4476d83e0ea2a152074a4d4d9524e9c60a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 14 Sep 2020 16:27:38 +0200 Subject: [PATCH 511/826] Fixed missing return --- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 9470359d04..605a98eea0 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -49,8 +49,8 @@ bool SlicingProcessCompletedEvent::critical_error() const // Exception derived from SlicingError is non-critical. return false; } catch (...) { - return true; } + return true; } std::string SlicingProcessCompletedEvent::format_error_message() const From 5e4ba271066acd4b1c4dd7efb1ef93b2dfa55d84 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Sep 2020 08:18:54 +0200 Subject: [PATCH 512/826] Another small refactoring --- src/slic3r/GUI/GCodeViewer.cpp | 66 +++++++++++++++++----------------- src/slic3r/GUI/GCodeViewer.hpp | 3 +- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 831a3885e8..61f61988c7 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -441,18 +441,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update buffers' render paths refresh_render_paths(false, false); - if (Slic3r::get_logging_level() >= 5) { - long long paths_size = 0; - for (const TBuffer& buffer : m_buffers) { - paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } - long long layers_zs_size = SLIC3R_STDVEC_MEMSIZE(m_layers_zs, double); - long long roles_size = SLIC3R_STDVEC_MEMSIZE(m_roles, Slic3r::ExtrusionRole); - long long extruder_ids_size = SLIC3R_STDVEC_MEMSIZE(m_extruder_ids, unsigned char); - BOOST_LOG_TRIVIAL(trace) << "Refreshed G-code extrusion paths, " - << format_memsize_MB(paths_size + layers_zs_size + roles_size + extruder_ids_size) - << log_memory_info(); - } + log_memory_used("Refreshed G-code extrusion paths, "); } void GCodeViewer::reset() @@ -1293,26 +1282,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); - if (Slic3r::get_logging_level() >= 5) { - long long vertices_size = 0; - for (size_t i = 0; i < vertices.size(); ++i) { - vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); - } - long long indices_size = 0; - for (size_t i = 0; i < indices.size(); ++i) { - indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i], unsigned int); - } - long long paths_size = 0; - for (const TBuffer& buffer : m_buffers) { - paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } - long long layers_zs_size = SLIC3R_STDVEC_MEMSIZE(m_layers_zs, double); - long long roles_size = SLIC3R_STDVEC_MEMSIZE(m_roles, Slic3r::ExtrusionRole); - long long extruder_ids_size = SLIC3R_STDVEC_MEMSIZE(m_extruder_ids, unsigned char); - BOOST_LOG_TRIVIAL(trace) << "Loaded G-code extrusion paths, " - << format_memsize_MB(vertices_size + indices_size + paths_size + layers_zs_size + roles_size + extruder_ids_size) - << log_memory_info(); + long long vertices_size = 0; + for (size_t i = 0; i < vertices.size(); ++i) { + vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); } + long long indices_size = 0; + for (size_t i = 0; i < indices.size(); ++i) { + indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i], unsigned int); + } + log_memory_used("Loaded G-code extrusion paths, ", vertices_size + indices_size); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); @@ -1506,13 +1484,13 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool it->path_id = id; } - unsigned int size_in_vertices = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; + unsigned int segments_count = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; unsigned int size_in_indices = 0; switch (buffer->render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = size_in_vertices; break; } + case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = segments_count; break; } case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (size_in_vertices - 1); break; } + case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; } } it->sizes.push_back(size_in_indices); @@ -2411,6 +2389,26 @@ bool GCodeViewer::is_travel_in_z_range(size_t id) const return is_in_z_range(path); } +void GCodeViewer::log_memory_used(const std::string& label, long long additional) const +{ + if (Slic3r::get_logging_level() >= 5) { + long long paths_size = 0; + long long render_paths_size = 0; + for (const TBuffer& buffer : m_buffers) { + paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath); + for (const RenderPath& path : buffer.render_paths) { + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); + } + } + long long layers_zs_size = SLIC3R_STDVEC_MEMSIZE(m_layers_zs, double); + BOOST_LOG_TRIVIAL(trace) << label + << format_memsize_MB(additional + paths_size + render_paths_size + layers_zs_size) + << log_memory_info(); + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 50e41f4cf4..7ced11be9c 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -143,7 +143,7 @@ class GCodeViewer Color color; size_t path_id; std::vector sizes; - std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) + std::vector offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements()) }; // buffer containing data for rendering a specific toolpath type @@ -470,6 +470,7 @@ private: return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]); } bool is_travel_in_z_range(size_t id) const; + void log_memory_used(const std::string& label, long long additional = 0) const; }; } // namespace GUI From 5fc82cecfe1cbc9c8b866018d11e2584d671d3b9 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Sep 2020 15:23:39 +0200 Subject: [PATCH 513/826] Fixed crash when starting the application on a secondary monitor --- src/slic3r/GUI/PrintHostDialogs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 216af5df49..96299c3813 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -140,8 +140,6 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) { const auto em = GetTextExtent("m").x; - SetSize(wxSize(HEIGHT * em, WIDTH * em)); - auto *topsizer = new wxBoxSizer(wxVERTICAL); job_list = new wxDataViewListCtrl(this, wxID_ANY); @@ -168,6 +166,8 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) topsizer->Add(btnsizer, 0, wxEXPAND); SetSizer(topsizer); + SetSize(wxSize(HEIGHT * em, WIDTH * em)); + job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); }); btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { From 4d5d1390f04ba3fa2a9b98bba8b38461c3d50019 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 15 Sep 2020 16:40:52 +0200 Subject: [PATCH 514/826] Added a missing include for gcc --- src/slic3r/Utils/Serial.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index 959c60c373..0c1ad1de5b 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -1,5 +1,7 @@ #include "Serial.hpp" +#include "libslic3r/Exception.hpp" + #include #include #include From af785d148688789a4bfb4d520bc454ed7d4e3df3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Sep 2020 11:08:58 +0200 Subject: [PATCH 515/826] Fix hollowing crash when splitting broken object has zero parts. --- src/libslic3r/OpenVDBUtils.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 53a71f194f..31ae203ddf 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -64,11 +64,6 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, float interiorBandWidth, int flags) { -// openvdb::initialize(); -// return openvdb::tools::meshToVolume( -// TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, -// interiorBandWidth, flags); - openvdb::initialize(); TriangleMeshPtrs meshparts = mesh.split(); @@ -83,15 +78,23 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, openvdb::FloatGrid::Ptr grid; for (TriangleMesh *m : meshparts) { - auto gridptr = openvdb::tools::meshToVolume( + auto subgrid = openvdb::tools::meshToVolume( TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, interiorBandWidth, flags); - if (grid && gridptr) openvdb::tools::csgUnion(*grid, *gridptr); - else if (gridptr) grid = std::move(gridptr); + if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); + else if (subgrid) grid = std::move(subgrid); } - grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth, interiorBandWidth); + if (grid) { + grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth, + interiorBandWidth); + } else if(meshparts.empty()) { + // Splitting failed, fall back to hollow the original mesh + grid = openvdb::tools::meshToVolume( + TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, + interiorBandWidth, flags); + } return grid; } From 743d6643ae0bfa33dfdd961eb9458ac377694dd9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 16 Sep 2020 15:04:01 +0200 Subject: [PATCH 516/826] Drop rubbish tests --- tests/sla_print/CMakeLists.txt | 2 +- tests/sla_print/sla_treebuilder_tests.cpp | 99 ----------------------- 2 files changed, 1 insertion(+), 100 deletions(-) delete mode 100644 tests/sla_print/sla_treebuilder_tests.cpp diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index d071b49739..dc583f1a1c 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -1,7 +1,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_print_tests.cpp - sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp + sla_test_utils.hpp sla_test_utils.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp deleted file mode 100644 index 91c2ea6f8e..0000000000 --- a/tests/sla_print/sla_treebuilder_tests.cpp +++ /dev/null @@ -1,99 +0,0 @@ -//#include -//#include - -//#include "libslic3r/TriangleMesh.hpp" -//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" -//#include "libslic3r/SLA/SupportTreeMesher.hpp" - -//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { -// using namespace Slic3r; - -// TriangleMesh cube = make_cube(10., 10., 10.); - -// sla::SupportConfig cfg = {}; // use default config -// sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; -// sla::SupportableMesh sm{cube, pts, cfg}; - -// size_t steps = 45; -// SECTION("Bridge is straight horizontal and pointing away from the cube") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// REQUIRE(std::isinf(hit.distance())); - -// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// cube.require_shared_vertices(); -// cube.WriteOBJFile("cube1.obj"); -// } - -// SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// REQUIRE(std::isinf(hit.distance())); - -// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// cube.require_shared_vertices(); -// cube.WriteOBJFile("cube2.obj"); -// } -//} - - -//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { -// using namespace Slic3r; - -// TriangleMesh sphere = make_sphere(1.); - -// sla::SupportConfig cfg = {}; // use default config -// cfg.head_back_radius_mm = cfg.head_front_radius_mm; -// sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; -// sla::SupportableMesh sm{sphere, pts, cfg}; - -// size_t steps = 45; -// SECTION("Bridge is straight horizontal and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere1.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } - -// SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere2.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } - -// SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere3.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } -//} From 7a10e23470384ba13062a059b48425b86a58b7ed Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 16 Sep 2020 15:45:53 +0200 Subject: [PATCH 517/826] Use multiple index buffers to render toolpaths in preview --- src/slic3r/GUI/GCodeViewer.cpp | 860 ++++++++++++++++++--------------- src/slic3r/GUI/GCodeViewer.hpp | 31 +- 2 files changed, 480 insertions(+), 411 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 61f61988c7..5251c01df6 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -129,16 +129,19 @@ void GCodeViewer::TBuffer::reset() { // release gpu memory vertices.reset(); - indices.reset(); + for (IBuffer& buffer : indices) { + buffer.reset(); + } // release cpu memory + indices = std::vector(); paths = std::vector(); render_paths = std::vector(); } -void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) +void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { - Path::Endpoint endpoint = { i_id, s_id, move.position }; + Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, @@ -561,7 +564,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const // the data needed is contained into the Extrude TBuffer const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - if (buffer.vertices.id == 0 || buffer.indices.id == 0) + if (!buffer.has_data()) return; // collect color information to generate materials @@ -611,10 +614,13 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); // get indices data from index buffer on gpu - std::vector indices = std::vector(buffer.indices.count); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.size() * sizeof(unsigned int)), indices.data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + MultiIndexBuffer indices; + for (size_t i = 0; i < buffer.indices.size(); ++i) { + indices.push_back(IndexBuffer(buffer.indices[i].count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[i].id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.back().size() * sizeof(unsigned int)), indices.back().data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } auto get_vertex = [&vertices, floats_per_vertex](unsigned int id) { // extract vertex from vector of floats @@ -672,6 +678,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const for (size_t i = 0; i < buffer.render_paths.size(); ++i) { // get paths segments from buffer paths const RenderPath& render_path = buffer.render_paths[i]; + const IndexBuffer& ibuffer = indices[render_path.index_buffer_id]; const Path& path = buffer.paths[render_path.path_id]; float half_width = 0.5f * path.width; // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar @@ -687,7 +694,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const unsigned int end = start + render_path.sizes[j]; for (size_t k = start; k < end; k += static_cast(indices_per_segment)) { - Segment curr = generate_segment(indices[k + start_vertex_offset], indices[k + end_vertex_offset], half_width, half_height); + Segment curr = generate_segment(ibuffer[k + start_vertex_offset], ibuffer[k + end_vertex_offset], half_width, half_height); if (k == start) { // starting endpoint vertices/normals out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right @@ -710,7 +717,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_vertices_count += 2; size_t first_vertex_id = k - static_cast(indices_per_segment); - Segment prev = generate_segment(indices[first_vertex_id + start_vertex_offset], indices[first_vertex_id + end_vertex_offset], half_width, half_height); + Segment prev = generate_segment(ibuffer[first_vertex_id + start_vertex_offset], ibuffer[first_vertex_id + end_vertex_offset], half_width, half_height); float disp = 0.0f; float cos_dir = prev.dir.dot(curr.dir); if (cos_dir > -0.9998477f) { @@ -895,274 +902,277 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // format data into the buffers to be rendered as points auto add_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id)); - buffer_indices.push_back(static_cast(buffer_indices.size())); + std::vector& buffer_vertices, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(curr.position[j]); + } + buffer.add_path(curr, index_buffer_id, static_cast(buffer_indices.size()), static_cast(move_id)); + buffer_indices.push_back(static_cast(buffer_indices.size())); }; // format data into the buffers to be rendered as lines auto add_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { - // x component of the normal to the current segment (the normal is parallel to the XY plane) - float normal_x = (curr.position - prev.position).normalized()[1]; + std::vector& buffer_vertices, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + // add starting vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(prev.position[j]); + } + // add starting vertex normal x component + buffer_vertices.push_back(normal_x); + // add starting index + buffer_indices.push_back(static_cast(buffer_indices.size())); + buffer.add_path(curr, index_buffer_id, static_cast(buffer_indices.size() - 1), static_cast(move_id - 1)); + buffer.paths.back().first.position = prev.position; } - // add starting vertex normal x component - buffer_vertices.push_back(normal_x); - // add starting index - buffer_indices.push_back(static_cast(buffer_indices.size())); - buffer.add_path(curr, static_cast(buffer_indices.size() - 1), static_cast(move_id - 1)); - buffer.paths.back().first.position = prev.position; - } - Path& last_path = buffer.paths.back(); - if (last_path.first.i_id != last_path.last.i_id) { - // add previous vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); + Path& last_path = buffer.paths.back(); + if (last_path.first.i_id != last_path.last.i_id) { + // add previous vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(prev.position[j]); + } + // add previous vertex normal x component + buffer_vertices.push_back(normal_x); + // add previous index + buffer_indices.push_back(static_cast(buffer_indices.size())); } - // add previous vertex normal x component - buffer_vertices.push_back(normal_x); - // add previous index - buffer_indices.push_back(static_cast(buffer_indices.size())); - } - // add current vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } - // add current vertex normal x component - buffer_vertices.push_back(normal_x); - // add current index - buffer_indices.push_back(static_cast(buffer_indices.size())); - last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + // add current vertex position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(curr.position[j]); + } + // add current vertex normal x component + buffer_vertices.push_back(normal_x); + // add current index + buffer_indices.push_back(static_cast(buffer_indices.size())); + last_path.last = { index_buffer_id, static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; }; // format data into the buffers to be rendered as solid auto add_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float prev_length; - auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { - // append position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(position[j]); - } - // append normal - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(normal[j]); - } - }; - auto store_triangle = [](std::vector& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { - buffer_indices.push_back(i1); - buffer_indices.push_back(i2); - buffer_indices.push_back(i3); - }; - auto extract_position_at = [](const std::vector& vertices, size_t id) { - return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); - }; - auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { - vertices[id + 0] = position[0]; - vertices[id + 1] = position[1]; - vertices[id + 2] = position[2]; - }; - auto append_dummy_cap = [store_triangle](std::vector& buffer_indices, unsigned int id) { - store_triangle(buffer_indices, id, id, id); - store_triangle(buffer_indices, id, id, id); - }; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id - 1)); - buffer.paths.back().first.position = prev.position; - } - - unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); - - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f left = -right; - Vec3f up = right.cross(dir); - Vec3f bottom = -up; - - Path& last_path = buffer.paths.back(); - - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - - float length = (curr_pos - prev_pos).norm(); - if (last_path.vertices_count() == 1) { - // 1st segment - - // vertices 1st endpoint - store_vertex(buffer_vertices, prev_pos + half_height * up, up); - store_vertex(buffer_vertices, prev_pos + half_width * right, right); - store_vertex(buffer_vertices, prev_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, prev_pos + half_width * left, left); - - // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); - store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); - - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - - // dummy triangles outer corner cap - append_dummy_cap(buffer_indices, starting_vertices_size); - - // triangles sides - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); - - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); - } - else { - // any other segment - float displacement = 0.0f; - float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev_dir + dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; - - size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); - size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); - Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id); - Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id); - - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible - if (can_displace) { - if (is_right_turn) { - prev_right_pos -= displacement_vec; - update_position_at(buffer_vertices, prev_right_id, prev_right_pos); - right_displaced = true; + std::vector& buffer_vertices, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + static Vec3f prev_dir; + static Vec3f prev_up; + static float prev_length; + auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { + // append position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(position[j]); } - else { - prev_left_pos -= displacement_vec; - update_position_at(buffer_vertices, prev_left_id, prev_left_pos); - left_displaced = true; + // append normal + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(normal[j]); } + }; + auto store_triangle = [](IndexBuffer& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { + buffer_indices.push_back(i1); + buffer_indices.push_back(i2); + buffer_indices.push_back(i3); + }; + auto extract_position_at = [](const std::vector& vertices, size_t id) { + return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); + }; + auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { + vertices[id + 0] = position[0]; + vertices[id + 1] = position[1]; + vertices[id + 2] = position[2]; + }; + auto append_dummy_cap = [store_triangle](IndexBuffer& buffer_indices, unsigned int id) { + store_triangle(buffer_indices, id, id, id); + store_triangle(buffer_indices, id, id, id); + }; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, index_buffer_id, static_cast(buffer_indices.size()), static_cast(move_id - 1)); + buffer.paths.back().first.position = prev.position; } - if (!is_sharp) { - // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible + unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f left = -right; + Vec3f up = right.cross(dir); + Vec3f bottom = -up; + + Path& last_path = buffer.paths.back(); + + float half_width = 0.5f * last_path.width; + float half_height = 0.5f * last_path.height; + + Vec3f prev_pos = prev.position - half_height * up; + Vec3f curr_pos = curr.position - half_height * up; + + float length = (curr_pos - prev_pos).norm(); + if (last_path.vertices_count() == 1) { + // 1st segment + + // vertices 1st endpoint + store_vertex(buffer_vertices, prev_pos + half_height * up, up); + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); + + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + + // dummy triangles outer corner cap + append_dummy_cap(buffer_indices, starting_vertices_size); + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); + } + else { + // any other segment + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + } + + Vec3f displacement_vec = displacement * prev_dir; + bool can_displace = displacement > 0.0f && displacement < prev_length&& displacement < length; + + size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); + size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); + Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id); + Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id); + + bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible if (can_displace) { if (is_right_turn) { - prev_left_pos += displacement_vec; - update_position_at(buffer_vertices, prev_left_id, prev_left_pos); - left_displaced = true; - } - else { - prev_right_pos += displacement_vec; + prev_right_pos -= displacement_vec; update_position_at(buffer_vertices, prev_right_id, prev_right_pos); right_displaced = true; } + else { + prev_left_pos -= displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } } - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // vertices position matches that of the previous segment 2nd endpoint, if displaced - store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); - } - else { - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced - if (is_right_turn) { + if (!is_sharp) { + // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible + if (can_displace) { + if (is_right_turn) { + prev_left_pos += displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } + else { + prev_right_pos += displacement_vec; + update_position_at(buffer_vertices, prev_right_id, prev_right_pos); + right_displaced = true; + } + } + + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // vertices position matches that of the previous segment 2nd endpoint, if displaced store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(buffer_vertices, prev_pos + half_width * left, left); - } - else { - store_vertex(buffer_vertices, prev_pos + half_width * right, right); store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); } - } - - // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); - store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); - - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); - - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); else { - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1); + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced + if (is_right_turn) { + store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); + } + else { + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); + } + } + + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); + + // triangles outer corner cap + if (is_right_turn) { + if (left_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1); + } } - } - else { - if (right_displaced) - // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); else { - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0); - store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0); + if (right_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0); + } } + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); } - // triangles sides - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3); - store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3); - store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5); - - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); - } - - last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; - prev_dir = dir; - prev_up = up; - prev_length = length; + last_path.last = { index_buffer_id, static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + prev_dir = dir; + prev_up = up; + prev_length = length; }; // toolpaths data -> extract from result std::vector> vertices(m_buffers.size()); - std::vector> indices(m_buffers.size()); + std::vector indices(m_buffers.size()); + for (auto i : indices) { + i.push_back(IndexBuffer()); + } for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) @@ -1174,7 +1184,34 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned char id = buffer_id(curr.type); TBuffer& buffer = m_buffers[id]; std::vector& buffer_vertices = vertices[id]; - std::vector& buffer_indices = indices[id]; + MultiIndexBuffer& buffer_indices = indices[id]; + if (buffer_indices.empty()) + buffer_indices.push_back(IndexBuffer()); + + static const size_t THRESHOLD = 1024 * 1024 * 1024; + // if adding the indices for the current segment exceeds the threshold size of the current index buffer + // create another index buffer, and move the current path indices into it + if (buffer_indices.back().size() >= THRESHOLD - static_cast(buffer.indices_per_segment())) { + buffer_indices.push_back(IndexBuffer()); + if (curr.type == EMoveType::Extrude || curr.type == EMoveType::Travel) { + if (!(prev.type != curr.type || !buffer.paths.back().matches(curr))) { + Path& last_path = buffer.paths.back(); + size_t delta_id = last_path.last.i_id - last_path.first.i_id; + + // move indices of the last path from the previous into the new index buffer + IndexBuffer& src_buffer = buffer_indices[buffer_indices.size() - 2]; + IndexBuffer& dst_buffer = buffer_indices[buffer_indices.size() - 1]; + std::move(src_buffer.begin() + last_path.first.i_id, src_buffer.end(), std::back_inserter(dst_buffer)); + src_buffer.erase(src_buffer.begin() + last_path.first.i_id, src_buffer.end()); + + // updates path indices + last_path.first.b_id = buffer_indices.size() - 1; + last_path.first.i_id = 0; + last_path.last.b_id = buffer_indices.size() - 1; + last_path.last.i_id = delta_id; + } + } + } switch (curr.type) { @@ -1185,17 +1222,17 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case EMoveType::Retract: case EMoveType::Unretract: { - add_as_point(curr, buffer, buffer_vertices, buffer_indices, i); + add_as_point(curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } case EMoveType::Extrude: { - add_as_solid(prev, curr, buffer, buffer_vertices, buffer_indices, i); + add_as_solid(prev, curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } case EMoveType::Travel: { - add_as_line(prev, curr, buffer, buffer_vertices, buffer_indices, i); + add_as_line(prev, curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } default: { break; } @@ -1220,18 +1257,22 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); // indices - const std::vector& buffer_indices = indices[i]; - buffer.indices.count = buffer_indices.size(); + for (size_t j = 0; j < indices[i].size(); ++j) { + const IndexBuffer& buffer_indices = indices[i][j]; + buffer.indices.push_back(IBuffer()); + IBuffer& ibuffer = buffer.indices.back(); + ibuffer.count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int); - m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast(buffer.indices.count)); + m_statistics.indices_gpu_size += ibuffer.count * sizeof(unsigned int); + m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast(ibuffer.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (buffer.indices.count > 0) { - glsafe(::glGenBuffers(1, &buffer.indices.id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.count * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (ibuffer.count > 0) { + glsafe(::glGenBuffers(1, &ibuffer.id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_indices.size() * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } } } @@ -1240,9 +1281,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); - m_statistics.travel_segments_count = indices[travel_buffer_id].size() / m_buffers[travel_buffer_id].indices_per_segment(); + const MultiIndexBuffer& travel_buffer_indices = indices[travel_buffer_id]; + for (size_t i = 0; i < travel_buffer_indices.size(); ++i) { + m_statistics.travel_segments_count = travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment(); + } unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); - m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / m_buffers[extrude_buffer_id].indices_per_segment(); + const MultiIndexBuffer& extrude_buffer_indices = indices[extrude_buffer_id]; + for (size_t i = 0; i < extrude_buffer_indices.size(); ++i) { + m_statistics.extrude_segments_count = extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment(); + } #endif // ENABLE_GCODE_VIEWER_STATISTICS // layers zs / roles / extruder ids / cp color ids -> extract from result @@ -1288,7 +1335,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } long long indices_size = 0; for (size_t i = 0; i < indices.size(); ++i) { - indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i], unsigned int); + for (size_t j = 0; j < indices[i].size(); ++j) { + indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i][j], unsigned int); + } } log_memory_used("Loaded G-code extrusion paths, ", vertices_size + indices_size); @@ -1380,7 +1429,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool auto travel_color = [this](const Path& path) { return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : + ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : Travel_Colors[0] /* Move */); }; @@ -1396,7 +1445,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool m_sequential_view.current.last = m_moves_count; // first pass: collect visible paths and update sequential view data - std::vector> paths; + std::vector> paths; for (TBuffer& buffer : m_buffers) { // reset render paths buffer.render_paths.clear(); @@ -1417,7 +1466,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool continue; // store valid path - paths.push_back({ &buffer, i }); + paths.push_back({ &buffer, path.first.b_id, static_cast(i) }); m_sequential_view.endpoints.first = std::min(m_sequential_view.endpoints.first, path.first.s_id); m_sequential_view.endpoints.last = std::max(m_sequential_view.endpoints.last, path.last.s_id); @@ -1434,7 +1483,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // searches the path containing the current position for (const Path& path : buffer.paths) { if (path.contains(m_sequential_view.current.last)) { - unsigned int offset = m_sequential_view.current.last - path.first.s_id; + unsigned int offset = static_cast(m_sequential_view.current.last - path.first.s_id); if (offset > 0) { if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) offset = 2 * offset - 1; @@ -1443,11 +1492,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool offset = indices_count * (offset - 1) + (indices_count - 6); } } - offset += path.first.i_id; + offset += static_cast(path.first.i_id); // gets the index from the index buffer on gpu unsigned int index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[path.first.b_id].id)); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(unsigned int)), static_cast(sizeof(unsigned int)), static_cast(&index))); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -1464,8 +1513,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } // second pass: filter paths by sequential data and collect them by color - for (const auto& [buffer, id] : paths) { - const Path& path = buffer->paths[id]; + for (const auto& [buffer, index_buffer_id, path_id] : paths) { + const Path& path = buffer->paths[path_id]; if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) continue; @@ -1477,18 +1526,21 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool default: { color = { 0.0f, 0.0f, 0.0f }; break; } } - auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); + unsigned int ibuffer_id = index_buffer_id; + auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), + [color, ibuffer_id](const RenderPath& path) { return path.index_buffer_id == ibuffer_id && path.color == color; }); if (it == buffer->render_paths.end()) { it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath()); it->color = color; - it->path_id = id; + it->path_id = path_id; + it->index_buffer_id = index_buffer_id; } unsigned int segments_count = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; unsigned int size_in_indices = 0; switch (buffer->render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = segments_count; break; } + case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = segments_count; break; } case TBuffer::ERenderPrimitiveType::Line: case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; } } @@ -1531,7 +1583,8 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("uniform_color", color4); }; - auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { + auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] + (const TBuffer& buffer, unsigned int index_buffer_id, EOptionsColors color_id, GLShaderProgram& shader) { set_uniform_color(Options_Colors[static_cast(color_id)], shader); shader.set_uniform("zoom", zoom); shader.set_uniform("percent_outline_radius", 0.0f); @@ -1543,34 +1596,40 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); for (const RenderPath& path : buffer.render_paths) { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; + ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } glsafe(::glDisable(GL_POINT_SPRITE)); glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_lines_calls_count; + ++m_statistics.gl_multi_lines_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } }; - auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_triangles_calls_count; + ++m_statistics.gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } }; @@ -1585,10 +1644,7 @@ void GCodeViewer::render_toolpaths() const for (unsigned char i = begin_id; i < end_id; ++i) { const TBuffer& buffer = m_buffers[i]; - if (!buffer.visible) - continue; - - if (buffer.vertices.id == 0 || buffer.indices.id == 0) + if (!buffer.visible || !buffer.has_data()) continue; GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); @@ -1604,38 +1660,40 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + for (size_t j = 0; j < buffer.indices.size(); ++j) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[j].id)); - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: - { - EOptionsColors color; - switch (buffer_type(i)) + switch (buffer.render_primitive_type) { - case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } - case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } - case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } - case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } - case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } - case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + case TBuffer::ERenderPrimitiveType::Point: + { + EOptionsColors color; + switch (buffer_type(i)) + { + case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } + case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } + case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } + case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } + case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } + case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + } + render_as_points(buffer, static_cast(j), color, *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Line: + { + render_as_lines(buffer, static_cast(j), *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: + { + render_as_triangles(buffer, static_cast(j), *shader); + break; + } } - render_as_points(buffer, color, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Line: - { - render_as_lines(buffer, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: - { - render_as_triangles(buffer, *shader); - break; - } - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); @@ -1698,90 +1756,90 @@ void GCodeViewer::render_legend() const std::function callback = nullptr) { if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) - { - default: - case EItemType::Rect: - { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - break; - } - case EItemType::Circle: - { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { - draw_list->AddCircleFilled(center, 0.5f * icon_size, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - radius = 0.5f * icon_size * 0.01f * 33.0f; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + ImVec2 pos = ImGui::GetCursorScreenPos(); + switch (type) + { + default: + case EItemType::Rect: + { + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; } - else - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + case EItemType::Circle: + { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { + draw_list->AddCircleFilled(center, 0.5f * icon_size, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = 0.5f * icon_size; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + radius = 0.5f * icon_size * 0.01f * 33.0f; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + } + else + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - break; - } - case EItemType::Hexagon: - { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); - break; - } - case EItemType::Line: - { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1}, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); - break; - } - } + break; + } + case EItemType::Hexagon: + { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + break; + } + case EItemType::Line: + { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + break; + } + } - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + // draw text + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); + if (callback != nullptr) { + if (ImGui::MenuItem(label.c_str())) + callback(); + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - // to avoid the tooltip to change size when moving the mouse - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + // to avoid the tooltip to change size when moving the mouse + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); } } + else + imgui.text(label); - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - } - } - else - imgui.text(label); - - if (!visible) - ImGui::PopStyleVar(); + if (!visible) + ImGui::PopStyleVar(); }; auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -1969,13 +2027,13 @@ void GCodeViewer::render_legend() const append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets); break; } - case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } + case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } + case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } + case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } - case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } + case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } + case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } default: { break; } } @@ -2001,10 +2059,10 @@ void GCodeViewer::render_legend() const } break; } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } case EViewType::Tool: { @@ -2237,7 +2295,7 @@ void GCodeViewer::render_legend() const auto any_option_available = [this]() { auto available = [this](EMoveType type) { const TBuffer& buffer = m_buffers[buffer_id(type)]; - return buffer.visible && buffer.indices.count > 0; + return buffer.visible && buffer.has_data(); }; return available(EMoveType::Color_change) || @@ -2250,7 +2308,7 @@ void GCodeViewer::render_legend() const auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { const TBuffer& buffer = m_buffers[buffer_id(move_type)]; - if (buffer.visible && buffer.indices.count > 0) + if (buffer.visible && buffer.has_data()) append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); }; @@ -2276,7 +2334,7 @@ void GCodeViewer::render_legend() const #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const { - static const float offset = 230.0f; + static const float offset = 250.0f; ImGuiWrapper& imgui = *wxGetApp().imgui(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 7ced11be9c..73c4ad7b24 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -18,6 +18,9 @@ namespace GUI { class GCodeViewer { using Color = std::array; + using IndexBuffer = std::vector; + using MultiIndexBuffer = std::vector; + static const std::vector Extrusion_Role_Colors; static const std::vector Options_Colors; static const std::vector Travel_Colors; @@ -112,10 +115,12 @@ class GCodeViewer { struct Endpoint { - // index into the indices buffer - unsigned int i_id{ 0u }; - // sequential id - unsigned int s_id{ 0u }; + // index of the index buffer + unsigned int b_id{ 0 }; + // index into the index buffer + size_t i_id{ 0 }; + // sequential id (index into the vertex buffer) + size_t s_id{ 0 }; Vec3f position{ Vec3f::Zero() }; }; @@ -134,14 +139,15 @@ class GCodeViewer bool matches(const GCodeProcessor::MoveVertex& move) const; size_t vertices_count() const { return last.s_id - first.s_id + 1; } - bool contains(unsigned int id) const { return first.s_id <= id && id <= last.s_id; } + bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; } }; // Used to batch the indices needed to render paths struct RenderPath { Color color; - size_t path_id; + unsigned int path_id; + unsigned int index_buffer_id; std::vector sizes; std::vector offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements()) }; @@ -158,7 +164,7 @@ class GCodeViewer ERenderPrimitiveType render_primitive_type; VBuffer vertices; - IBuffer indices; + std::vector indices; std::string shader; std::vector paths; @@ -166,7 +172,10 @@ class GCodeViewer bool visible{ false }; void reset(); - void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); + // b_id index of buffer contained in this->indices + // i_id index of first index contained in this->indices[b_id] + // s_id index of first vertex contained in this->vertices + void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); unsigned int indices_per_segment() const { switch (render_primitive_type) { @@ -194,6 +203,8 @@ class GCodeViewer default: { return 0; } } } + + bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; } }; // helper to render shells @@ -350,8 +361,8 @@ public: struct Endpoints { - unsigned int first{ 0 }; - unsigned int last{ 0 }; + size_t first{ 0 }; + size_t last{ 0 }; }; Endpoints endpoints; From 8579ecceed19d855fdf1c072bd01465222abca6e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 17 Sep 2020 08:18:16 +0200 Subject: [PATCH 518/826] Legend layout -> estimated time move to bottom --- src/slic3r/GUI/GCodeViewer.cpp | 127 +++++++++++++++++---------------- 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 5251c01df6..bb0786238e 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1965,60 +1965,6 @@ void GCodeViewer::render_legend() const offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size); } - // total estimated printing time section - if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { - ImGui::AlignTextToFramePadding(); - switch (m_time_estimate_mode) - { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); - break; - } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); - break; - } - } - ImGui::SameLine(); - imgui.text(short_time(get_time_dhms(time_mode.time))); - - auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { - bool show = false; - for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { - if (i != static_cast(mode) && - short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { - show = true; - break; - } - } - if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { - if (imgui.button(label)) { - m_time_estimate_mode = mode; - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - } - }; - - switch (m_time_estimate_mode) - { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: - { - show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); - break; - } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: - { - show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); - break; - } - } - ImGui::Spacing(); - } - // extrusion paths section -> title switch (m_view_type) { @@ -2027,13 +1973,13 @@ void GCodeViewer::render_legend() const append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets); break; } - case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } - case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } - case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } - case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } + case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } + case EViewType::Width: { imgui.title(_u8L("Width (mm)")); break; } + case EViewType::Feedrate: { imgui.title(_u8L("Speed (mm/s)")); break; } + case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } - case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } + case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } + case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } default: { break; } } @@ -2327,6 +2273,67 @@ void GCodeViewer::render_legend() const add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom GCodes")); } + // total estimated printing time section + if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f }); + ImGui::Separator(); + ImGui::PopStyleColor(); + ImGui::Spacing(); + + ImGui::AlignTextToFramePadding(); + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); + break; + } + } + ImGui::SameLine(); + imgui.text(short_time(get_time_dhms(time_mode.time))); + + auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { + bool show = false; + for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { + if (i != static_cast(mode) && + short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { + show = true; + break; + } + } + if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + if (imgui.button(label)) { + m_time_estimate_mode = mode; + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + }; + + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + break; + } + } + } + imgui.end(); ImGui::PopStyleVar(); } From a40fc1fe2c3a0127e31b406c962b95fc9dac7878 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 17 Sep 2020 08:46:27 +0200 Subject: [PATCH 519/826] Refactoring in toolpaths generation --- src/slic3r/GUI/GCodeViewer.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index bb0786238e..16d929122a 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1213,29 +1213,23 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } - switch (curr.type) + switch (buffer.render_primitive_type) { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: + case TBuffer::ERenderPrimitiveType::Point: { add_as_point(curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } - case EMoveType::Extrude: - { - add_as_solid(prev, curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); - break; - } - case EMoveType::Travel: + case TBuffer::ERenderPrimitiveType::Line: { add_as_line(prev, curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } - default: { break; } + case TBuffer::ERenderPrimitiveType::Triangle: + { + add_as_solid(prev, curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + break; + } } } @@ -1670,12 +1664,12 @@ void GCodeViewer::render_toolpaths() const EOptionsColors color; switch (buffer_type(i)) { - case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } + case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } - case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } + case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } - case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } - case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } + case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } } render_as_points(buffer, static_cast(j), color, *shader); break; From 3ca6278ac9bf53f9521bee15b7989b1e97bd652a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 17 Sep 2020 08:59:36 +0200 Subject: [PATCH 520/826] Refactoring in GCodeViewer initialization --- src/slic3r/GUI/GCodeViewer.cpp | 28 +++++----------------------- src/slic3r/GUI/GCodeViewer.hpp | 1 - 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 16d929122a..16bb27157f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -287,6 +287,8 @@ const std::vector GCodeViewer::Range_Colors {{ bool GCodeViewer::init() { + bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); + for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; @@ -302,18 +304,21 @@ bool GCodeViewer::init() { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; buffer.vertices.format = VBuffer::EFormat::Position; + buffer.shader = is_glsl_120 ? "options_120" : "options_110"; break; } case EMoveType::Extrude: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + buffer.shader = "gouraud_light"; break; } case EMoveType::Travel: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; buffer.vertices.format = VBuffer::EFormat::PositionNormal1; + buffer.shader = "toolpaths_lines"; break; } } @@ -321,7 +326,6 @@ bool GCodeViewer::init() set_toolpath_move_type_visible(EMoveType::Extrude, true); m_sequential_view.marker.init(); - init_shaders(); std::array point_sizes; ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); @@ -847,28 +851,6 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fclose(fp); } -void GCodeViewer::init_shaders() -{ - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Count); - - bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - for (unsigned char i = begin_id; i < end_id; ++i) { - switch (buffer_type(i)) - { - case EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Extrude: { m_buffers[i].shader = "gouraud_light"; break; } - case EMoveType::Travel: { m_buffers[i].shader = "toolpaths_lines"; break; } - default: { break; } - } - } -} - void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_STATISTICS diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 73c4ad7b24..8c5d8b743f 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -459,7 +459,6 @@ public: void export_toolpaths_to_obj(const char* filename) const; private: - void init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; From 46d747bfaa2be1b468978979e196a7ddc8e54b1f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 17 Sep 2020 10:13:14 +0200 Subject: [PATCH 521/826] Reduced threshold to split index buffers for toolpaths render --- src/slic3r/GUI/GCodeViewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 16bb27157f..dccf09e46f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1170,7 +1170,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (buffer_indices.empty()) buffer_indices.push_back(IndexBuffer()); - static const size_t THRESHOLD = 1024 * 1024 * 1024; + static const size_t THRESHOLD = 1024 * 1024 * 128; // if adding the indices for the current segment exceeds the threshold size of the current index buffer // create another index buffer, and move the current path indices into it if (buffer_indices.back().size() >= THRESHOLD - static_cast(buffer.indices_per_segment())) { From fb4493c9d1ed3ebc4cee198304dc95d967da254e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 17 Sep 2020 11:42:58 +0200 Subject: [PATCH 522/826] Restore estimated time lines in sidebar info --- src/libslic3r/GCode.cpp | 4 ++-- src/slic3r/GUI/Plater.cpp | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1788250f8a..431ad38304 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -721,9 +721,9 @@ namespace DoExport { static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) { const GCodeProcessor::Result& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; + get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; } } // namespace DoExport diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec632611f0..8e27381768 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1166,8 +1166,27 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siCost, info_text, new_label); #if ENABLE_GCODE_VIEWER - // hide the estimate time - p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") + p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + else { + info_text = ""; + new_label = _L("Estimated printing time") + ":"; + if (ps.estimated_normal_print_time != "N/A") { + new_label += format_wxstr("\n - %1%", _L("normal mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time)); + + // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate + //if (p->plater->is_sidebar_collapsed()) + p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); + p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); + + } + if (ps.estimated_silent_print_time != "N/A") { + new_label += format_wxstr("\n - %1%", _L("stealth mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_silent_print_time)); + } + p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + } #else if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); From 0b2a399b6b9093e1458a86c28c751d91d8eb0ce7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 17 Sep 2020 15:11:22 +0200 Subject: [PATCH 523/826] New values for GCodeViewer::Extrusion_Role_Colors --- src/slic3r/GUI/GCodeViewer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index dccf09e46f..85ee1138a2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -237,14 +237,14 @@ void GCodeViewer::SequentialView::Marker::render() const ImGui::PopStyleVar(); } -const std::vector GCodeViewer::Extrusion_Role_Colors {{ +const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.75f, 0.75f, 0.75f }, // erNone - { 1.00f, 0.90f, 0.43f }, // erPerimeter + { 1.00f, 0.90f, 0.30f }, // erPerimeter { 1.00f, 0.49f, 0.22f }, // erExternalPerimeter { 0.12f, 0.12f, 1.00f }, // erOverhangPerimeter { 0.69f, 0.19f, 0.16f }, // erInternalInfill { 0.59f, 0.33f, 0.80f }, // erSolidInfill - { 0.94f, 0.33f, 0.33f }, // erTopSolidInfill + { 0.94f, 0.25f, 0.25f }, // erTopSolidInfill { 1.00f, 0.55f, 0.41f }, // erIroning { 0.30f, 0.50f, 0.73f }, // erBridgeInfill { 1.00f, 1.00f, 1.00f }, // erGapFill @@ -254,7 +254,7 @@ const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.70f, 0.89f, 0.67f }, // erWipeTower { 0.37f, 0.82f, 0.58f }, // erCustom { 0.00f, 0.00f, 0.00f } // erMixed -}}; +} }; const std::vector GCodeViewer::Options_Colors {{ { 0.803f, 0.135f, 0.839f }, // Retractions From acdd5716bd55dc08b5bf7b03c956f732f727c9b3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 17 Sep 2020 15:25:05 +0200 Subject: [PATCH 524/826] SplashScreen: Fixed message text UnsavedChangesDialog: Disabled "Move changes to selected preset" button, when printer technology is changed PresetComboBox: Fixed color of the filament, if it is modified --- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 3 ++- src/slic3r/GUI/UnsavedChangesDialog.cpp | 9 ++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 40c8f96e59..7b6b38b5db 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -183,7 +183,7 @@ public: copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" + - _L("Splash screen could be desabled from the \"Preferences\""); + _L("Splash screen can be disabled from the \"Preferences\""); word_wrap_string(copyright_string, banner_width, screen_scale); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 8bc9393873..8c0fefc760 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -741,7 +741,8 @@ void PlaterPresetComboBox::update() if (m_type == Preset::TYPE_FILAMENT) { // Assign an extruder color to the selected item if the extruder color is defined. - filament_rgb = preset.config.opt_string("filament_colour", 0); + filament_rgb = is_selected ? selected_filament_preset->config.opt_string("filament_colour", 0) : + preset.config.opt_string("filament_colour", 0); extruder_rgb = (is_selected && !extruder_color.empty()) ? extruder_color : filament_rgb; single_bar = filament_rgb == extruder_rgb; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 5a0d23a209..e7dec9fa81 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -590,8 +590,11 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ int btn_idx = 0; add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, btn_idx++); - if (dependent_presets && (type != dependent_presets->type() ? true : - dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology())) + + const PresetCollection& printers = wxGetApp().preset_bundle->printers; + if (dependent_presets && (type == dependent_presets->type() ? + dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() : + printers.get_edited_preset().printer_technology() == printers.find_preset(new_selected_preset)->printer_technology() ) ) add_btn(&m_move_btn, m_move_btn_id, "paste_menu", Action::Move, btn_idx++); add_btn(&m_continue_btn, m_continue_btn_id, "cross", Action::Continue, btn_idx, false); @@ -935,7 +938,7 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres } - for (const std::string& opt_key : /*presets->current_dirty_options()*/dirty_options) { + for (const std::string& opt_key : dirty_options) { const Search::Option& option = searcher.get_option(opt_key); ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), type }; From 37c5fe9923c1bd577c6e9871a04a460a13a2f1f8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 17 Sep 2020 18:39:28 +0200 Subject: [PATCH 525/826] Refactoring of adaptive cubic / support cubic: 1) Octree is built directly from the triangle mesh by checking overlap of a triangle with an octree cell. This shall produce a tighter octree with less dense cells. 2) The same method is used for both the adaptive / support cubic infill, where for the support cubic infill the non-overhang triangles are ignored. The AABB tree is no more used. 3) Optimized extraction of continuous infill lines in O(1) instead of O(n^2) --- src/admesh/stl.h | 12 +- src/libslic3r/AABBTreeIndirect.hpp | 8 +- src/libslic3r/BoundingBox.cpp | 8 + src/libslic3r/BoundingBox.hpp | 12 + src/libslic3r/Fill/Fill.cpp | 3 +- src/libslic3r/Fill/FillAdaptive.cpp | 904 ++++++++++++++++------------ src/libslic3r/Fill/FillAdaptive.hpp | 130 ++-- src/libslic3r/Fill/FillBase.cpp | 2 +- src/libslic3r/Fill/FillBase.hpp | 2 - src/libslic3r/Geometry.hpp | 2 +- src/libslic3r/Model.cpp | 32 + src/libslic3r/Model.hpp | 2 + src/libslic3r/Point.cpp | 10 - src/libslic3r/Point.hpp | 10 +- src/libslic3r/Print.hpp | 4 +- src/libslic3r/PrintObject.cpp | 71 +-- 16 files changed, 658 insertions(+), 554 deletions(-) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 9224b04594..e0f2865f0d 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -255,18 +255,24 @@ extern void its_transform(indexed_triangle_set &its, T *trafo3x4) } template -inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t) +inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t, bool fix_left_handed = false) { //const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); for (stl_vertex &v : its.vertices) v = (t * v.template cast()).template cast().eval(); + if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) + for (stl_triangle_vertex_indices &i : its.indices) + std::swap(i[0], i[1]); } template -inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m) +inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m, bool fix_left_handed = false) { - for (stl_vertex &v : its.vertices) + for (stl_vertex &v : its.vertices) v = (m * v.template cast()).template cast().eval(); + if (fix_left_handed && m.determinant() < 0.) + for (stl_triangle_vertex_indices &i : its.indices) + std::swap(i[0], i[1]); } extern void its_rotate_x(indexed_triangle_set &its, float angle); diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 17d918aeb3..964133faae 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -283,7 +283,7 @@ namespace detail { template std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); @@ -291,7 +291,7 @@ namespace detail { template std::enable_if_t::value && !std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector w0 = v0.template cast(); Vector w1 = v1.template cast(); @@ -302,7 +302,7 @@ namespace detail { template std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); @@ -311,7 +311,7 @@ namespace detail { template std::enable_if_t::value && ! std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index e3f9395092..eb4e042a08 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -75,6 +75,7 @@ BoundingBoxBase::merge(const PointClass &point) } } template void BoundingBoxBase::merge(const Point &point); +template void BoundingBoxBase::merge(const Vec2f &point); template void BoundingBoxBase::merge(const Vec2d &point); template void @@ -101,6 +102,7 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb) } } template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void @@ -115,6 +117,7 @@ BoundingBox3Base::merge(const PointClass &point) this->defined = true; } } +template void BoundingBox3Base::merge(const Vec3f &point); template void BoundingBox3Base::merge(const Vec3d &point); template void @@ -147,6 +150,7 @@ BoundingBoxBase::size() const return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1)); } template Point BoundingBoxBase::size() const; +template Vec2f BoundingBoxBase::size() const; template Vec2d BoundingBoxBase::size() const; template PointClass @@ -154,6 +158,7 @@ BoundingBox3Base::size() const { return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2)); } +template Vec3f BoundingBox3Base::size() const; template Vec3d BoundingBox3Base::size() const; template double BoundingBoxBase::radius() const @@ -200,6 +205,7 @@ BoundingBoxBase::center() const return (this->min + this->max) / 2; } template Point BoundingBoxBase::center() const; +template Vec2f BoundingBoxBase::center() const; template Vec2d BoundingBoxBase::center() const; template PointClass @@ -207,6 +213,7 @@ BoundingBox3Base::center() const { return (this->min + this->max) / 2; } +template Vec3f BoundingBox3Base::center() const; template Vec3d BoundingBox3Base::center() const; template coordf_t @@ -215,6 +222,7 @@ BoundingBox3Base::max_size() const PointClass s = size(); return std::max(s(0), std::max(s(1), s(2))); } +template coordf_t BoundingBox3Base::max_size() const; template coordf_t BoundingBox3Base::max_size() const; // Align a coordinate to a grid. The coordinate may be negative, diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 71746f32ed..065476cb28 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -19,6 +19,8 @@ public: BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {} BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} + BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : + min(p1), max(p1), defined(false) { merge(p2); merge(p3); } BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) { if (points.empty()) { @@ -66,6 +68,8 @@ public: BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase(pmin, pmax) { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } + BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : + BoundingBoxBase(p1, p1) { merge(p2); merge(p3); } BoundingBox3Base(const std::vector& points) { if (points.empty()) @@ -110,24 +114,32 @@ extern template void BoundingBoxBase::scale(double factor); extern template void BoundingBoxBase::offset(coordf_t delta); extern template void BoundingBoxBase::offset(coordf_t delta); extern template void BoundingBoxBase::merge(const Point &point); +extern template void BoundingBoxBase::merge(const Vec2f &point); extern template void BoundingBoxBase::merge(const Vec2d &point); extern template void BoundingBoxBase::merge(const Points &points); extern template void BoundingBoxBase::merge(const Pointfs &points); extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); extern template Point BoundingBoxBase::size() const; +extern template Vec2f BoundingBoxBase::size() const; extern template Vec2d BoundingBoxBase::size() const; extern template double BoundingBoxBase::radius() const; extern template double BoundingBoxBase::radius() const; extern template Point BoundingBoxBase::center() const; +extern template Vec2f BoundingBoxBase::center() const; extern template Vec2d BoundingBoxBase::center() const; +extern template void BoundingBox3Base::merge(const Vec3f &point); extern template void BoundingBox3Base::merge(const Vec3d &point); extern template void BoundingBox3Base::merge(const Pointf3s &points); extern template void BoundingBox3Base::merge(const BoundingBox3Base &bb); +extern template Vec3f BoundingBox3Base::size() const; extern template Vec3d BoundingBox3Base::size() const; extern template double BoundingBox3Base::radius() const; extern template void BoundingBox3Base::offset(coordf_t delta); +extern template Vec3f BoundingBox3Base::center() const; extern template Vec3d BoundingBox3Base::center() const; +extern template coordf_t BoundingBox3Base::max_size() const; extern template coordf_t BoundingBox3Base::max_size() const; class BoundingBox : public BoundingBoxBase diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index d68bc7afb3..70792b823f 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -345,8 +345,7 @@ void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, Fill f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; - f->adapt_fill_octree = adaptive_fill_octree; - f->support_fill_octree = support_fill_octree; + f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 3b9212230d..6c5d7c0c47 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -2,16 +2,211 @@ #include "../ExPolygon.hpp" #include "../Surface.hpp" #include "../Geometry.hpp" -#include "../AABBTreeIndirect.hpp" #include "../Layer.hpp" #include "../Print.hpp" #include "../ShortestPath.hpp" #include "FillAdaptive.hpp" +// for indexed_triangle_set +#include + +#include +#include + +// Boost pool: Don't use mutexes to synchronize memory allocation. +#define BOOST_POOL_NO_MT +#include + namespace Slic3r { -std::pair adaptive_fill_line_spacing(const PrintObject &print_object) +// Derived from https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp +// The AABB-Triangle test implementation is based on the pseudo-code in +// Christer Ericson's Real-Time Collision Detection, pp. 169-172. It is +// practically a standard SAT test. +// +// Original MathGeoLib benchmark: +// Best: 17.282 nsecs / 46.496 ticks, Avg: 17.804 nsecs, Worst: 18.434 nsecs +// +//FIXME Vojtech: The MathGeoLib contains a vectorized implementation. +template +bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, const BoundingBoxBase &aabb) +{ + using Scalar = typename Vector::Scalar; + + Vector tMin = a.cwiseMin(b.cwiseMin(c)); + Vector tMax = a.cwiseMax(b.cwiseMax(c)); + + if (tMin.x() >= aabb.max.x() || tMax.x() <= aabb.min.x() + || tMin.y() >= aabb.max.y() || tMax.y() <= aabb.min.y() + || tMin.z() >= aabb.max.z() || tMax.z() <= aabb.min.z()) + return false; + + Vector center = (aabb.min + aabb.max) * 0.5f; + Vector h = aabb.max - center; + + const Vector t[3] { b-a, c-a, c-b }; + + Vector ac = a - center; + + Vector n = t[0].cross(t[1]); + Scalar s = n.dot(ac); + Scalar r = std::abs(h.dot(n.cwiseAbs())); + if (abs(s) >= r) + return false; + + const Vector at[3] = { t[0].cwiseAbs(), t[1].cwiseAbs(), t[2].cwiseAbs() }; + + Vector bc = b - center; + Vector cc = c - center; + + // SAT test all cross-axes. + // The following is a fully unrolled loop of this code, stored here for reference: + /* + Scalar d1, d2, a1, a2; + const Vector e[3] = { DIR_VEC(1, 0, 0), DIR_VEC(0, 1, 0), DIR_VEC(0, 0, 1) }; + for(int i = 0; i < 3; ++i) + for(int j = 0; j < 3; ++j) + { + Vector axis = Cross(e[i], t[j]); + ProjectToAxis(axis, d1, d2); + aabb.ProjectToAxis(axis, a1, a2); + if (d2 <= a1 || d1 >= a2) return false; + } + */ + + // eX t[0] + Scalar d1 = t[0].y() * ac.z() - t[0].z() * ac.y(); + Scalar d2 = t[0].y() * cc.z() - t[0].z() * cc.y(); + Scalar tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[0].z() + h.z() * at[0].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eX t[1] + d1 = t[1].y() * ac.z() - t[1].z() * ac.y(); + d2 = t[1].y() * bc.z() - t[1].z() * bc.y(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[1].z() + h.z() * at[1].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eX t[2] + d1 = t[2].y() * ac.z() - t[2].z() * ac.y(); + d2 = t[2].y() * bc.z() - t[2].z() * bc.y(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[2].z() + h.z() * at[2].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[0] + d1 = t[0].z() * ac.x() - t[0].x() * ac.z(); + d2 = t[0].z() * cc.x() - t[0].x() * cc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[0].z() + h.z() * at[0].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[1] + d1 = t[1].z() * ac.x() - t[1].x() * ac.z(); + d2 = t[1].z() * bc.x() - t[1].x() * bc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[1].z() + h.z() * at[1].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[2] + d1 = t[2].z() * ac.x() - t[2].x() * ac.z(); + d2 = t[2].z() * bc.x() - t[2].x() * bc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[2].z() + h.z() * at[2].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[0] + d1 = t[0].x() * ac.y() - t[0].y() * ac.x(); + d2 = t[0].x() * cc.y() - t[0].y() * cc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[0].x() + h.x() * at[0].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[1] + d1 = t[1].x() * ac.y() - t[1].y() * ac.x(); + d2 = t[1].x() * bc.y() - t[1].y() * bc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[1].x() + h.x() * at[1].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[2] + d1 = t[2].x() * ac.y() - t[2].y() * ac.x(); + d2 = t[2].x() * bc.y() - t[2].y() * bc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[2].x() + h.x() * at[2].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // No separating axis exists, the AABB and triangle intersect. + return true; +} + +// Ordering of children cubes. +static const std::array child_centers { + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), + Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) +}; + +// Traversal order of octree children cells for three infill directions, +// so that a single line will be discretized in a strictly monotonous order. +static constexpr std::array, 3> child_traversal_order { + std::array{ 2, 3, 0, 1, 6, 7, 4, 5 }, + std::array{ 4, 0, 6, 2, 5, 1, 7, 3 }, + std::array{ 1, 5, 0, 4, 3, 7, 2, 6 }, +}; + +namespace FillAdaptive_Internal +{ + struct Cube + { + Vec3d center; +#ifndef NDEBUG + Vec3d center_octree; +#endif // NDEBUG + std::array children {}; // initialized to nullptrs + Cube(const Vec3d ¢er) : center(center) {} + }; + + struct CubeProperties + { + double edge_length; // Lenght of edge of a cube + double height; // Height of rotated cube (standing on the corner) + double diagonal_length; // Length of diagonal of a cube a face + double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created + double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created + }; + + struct Octree + { + // Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool, + // perfect for building up our octree. + boost::object_pool pool; + Cube* root_cube { nullptr }; + Vec3d origin; + std::vector cubes_properties; + + Octree(const Vec3d &origin, const std::vector &cubes_properties) + : root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {} + + void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth); + }; + + void OctreeDeleter::operator()(Octree *p) { + delete p; + } +}; // namespace FillAdaptive_Internal + +std::pair FillAdaptive_Internal::adaptive_fill_line_spacing(const PrintObject &print_object) { // Output, spacing for icAdaptiveCubic and icSupportCubic double adaptive_line_spacing = 0.; @@ -90,431 +285,392 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob return std::make_pair(adaptive_line_spacing, support_line_spacing); } -void FillAdaptive::_fill_surface_single(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out) +// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion +// (Digital Differential Analyzer). +struct FillContext { - if(this->adapt_fill_octree != nullptr) - this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->adapt_fill_octree); + // The angles have to agree with child_traversal_order. + static constexpr double direction_angles[3] { + 0., + (2.0 * M_PI) / 3.0, + -(2.0 * M_PI) / 3.0 + }; + + FillContext(const FillAdaptive_Internal::Octree &octree, double z_position, int direction_idx) : + origin_world(octree.origin), + cubes_properties(octree.cubes_properties), + z_position(z_position), + traversal_order(child_traversal_order[direction_idx]), + cos_a(cos(direction_angles[direction_idx])), + sin_a(sin(direction_angles[direction_idx])) + { + static constexpr auto unused = std::numeric_limits::max(); + temp_lines.assign((1 << octree.cubes_properties.size()) - 1, Line(Point(unused, unused), Point(unused, unused))); + } + + // Rotate the point, uses the same convention as Point::rotate(). + Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); } + + // Center of the root cube in the Octree coordinate system. + const Vec3d origin_world; + const std::vector &cubes_properties; + // Top of the current layer. + const double z_position; + // Order of traversal for this line direction. + const std::array traversal_order; + // Rotation of the generated line for this line direction. + const double cos_a; + const double sin_a; + + // Linearized tree spanning a single Octree wall, used to connect lines spanning + // neighboring Octree cells. Unused lines have the Line::a::x set to infinity. + std::vector temp_lines; + // Final output + std::vector output_lines; +}; + +static constexpr double octree_rot[3] = { 5.0 * M_PI / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0 }; + +Eigen::Quaterniond FillAdaptive_Internal::adaptive_fill_octree_transform_to_world() +{ + return Eigen::AngleAxisd(octree_rot[2], Vec3d::UnitZ()) * Eigen::AngleAxisd(octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(octree_rot[0], Vec3d::UnitX()); } -void FillAdaptive::generate_infill(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out, - FillAdaptive_Internal::Octree *octree) +Eigen::Quaterniond FillAdaptive_Internal::adaptive_fill_octree_transform_to_octree() { - Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); - Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); + return Eigen::AngleAxisd(- octree_rot[0], Vec3d::UnitX()) * Eigen::AngleAxisd(- octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(- octree_rot[2], Vec3d::UnitZ()); +} - // Store grouped lines by its direction (multiple of 120°) - std::vector infill_lines_dir(3); - this->generate_infill_lines(octree->root_cube.get(), - this->z, octree->origin, rotation_matrix, - infill_lines_dir, octree->cubes_properties, - int(octree->cubes_properties.size()) - 1); +#ifndef NDEBUG +// Verify that the traversal order of the octree children matches the line direction, +// therefore the infill line may get extended with O(1) time & space complexity. +static bool verify_traversal_order( + FillContext &context, + const FillAdaptive_Internal::Cube *cube, + int depth, + const Vec2d &line_from, + const Vec2d &line_to) +{ + std::array c; + Eigen::Quaterniond to_world = FillAdaptive_Internal::adaptive_fill_octree_transform_to_world(); + for (int i = 0; i < 8; ++i) { + int j = context.traversal_order[i]; + Vec3d cntr = to_world * (cube->center_octree + (child_centers[j] * (context.cubes_properties[depth].edge_length / 4.))); + assert(!cube->children[j] || cube->children[j]->center.isApprox(cntr)); + c[i] = cntr; + } + std::array dirs = { + c[1] - c[0], c[2] - c[0], c[3] - c[1], c[3] - c[2], c[3] - c[0], + c[5] - c[4], c[6] - c[4], c[7] - c[5], c[7] - c[6], c[7] - c[4] + }; + assert(std::abs(dirs[4].z()) < 0.001); + assert(std::abs(dirs[9].z()) < 0.001); + assert(dirs[0].isApprox(dirs[3])); + assert(dirs[1].isApprox(dirs[2])); + assert(dirs[5].isApprox(dirs[8])); + assert(dirs[6].isApprox(dirs[7])); + Vec3d line_dir = Vec3d(line_to.x() - line_from.x(), line_to.y() - line_from.y(), 0.).normalized(); + for (auto& dir : dirs) { + double d = dir.normalized().dot(line_dir); + assert(d > 0.7); + } + return true; +} +#endif // NDEBUG + +static void generate_infill_lines_recursive( + FillContext &context, + const FillAdaptive_Internal::Cube *cube, + // Address of this wall in the octree, used to address context.temp_lines. + int address, + int depth) +{ + assert(cube != nullptr); + + const std::vector &cubes_properties = context.cubes_properties; + const double z_diff = context.z_position - cube->center.z(); + const double z_diff_abs = std::abs(z_diff); + + if (z_diff_abs > cubes_properties[depth].height / 2.) + return; + + if (z_diff_abs < cubes_properties[depth].line_z_distance) { + // Discretize a single wall splitting the cube into two. + const double zdist = cubes_properties[depth].line_z_distance; + Vec2d from( + 0.5 * cubes_properties[depth].diagonal_length * (zdist - z_diff_abs) / zdist, + cubes_properties[depth].line_xy_distance - (zdist + z_diff) / sqrt(2.)); + Vec2d to(-from.x(), from.y()); + from = context.rotate(from); + to = context.rotate(to); + // Relative to cube center + Vec2d offset(cube->center.x() - context.origin_world.x(), cube->center.y() - context.origin_world.y()); + from += offset; + to += offset; + // Verify that the traversal order of the octree children matches the line direction, + // therefore the infill line may get extended with O(1) time & space complexity. + assert(verify_traversal_order(context, cube, depth, from, to)); + // Either extend an existing line or start a new one. + Line &last_line = context.temp_lines[address]; + Line new_line(Point::new_scale(from), Point::new_scale(to)); + if (last_line.a.x() == std::numeric_limits::max()) { + last_line.a = new_line.a; + } else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough) { + context.output_lines.emplace_back(last_line); + last_line.a = new_line.a; + } + last_line.b = new_line.b; + } + + // left child index + address = address * 2 + 1; + -- depth; + size_t i = 0; + for (const int child_idx : context.traversal_order) { + const FillAdaptive_Internal::Cube *child = cube->children[child_idx]; + if (child != nullptr) + generate_infill_lines_recursive(context, child, address, depth); + if (++ i == 4) + // right child index + ++ address; + } +} + +#ifndef NDEBUG +// #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT +#endif + +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT +static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path) +{ + BoundingBox bbox = get_extents(expoly); + bbox.offset(scale_(3.)); + + ::Slic3r::SVG svg(path, bbox); + svg.draw(expoly); + svg.draw_outline(expoly, "green"); + svg.draw(polylines, "red"); + static constexpr double trim_length = scale_(0.4); + for (Polyline polyline : polylines) { + Vec2d a = polyline.points.front().cast(); + Vec2d d = polyline.points.back().cast(); + if (polyline.size() == 2) { + Vec2d v = d - a; + double l = v.norm(); + if (l > 2. * trim_length) { + a += v * trim_length / l; + d -= v * trim_length / l; + polyline.points.front() = a.cast(); + polyline.points.back() = d.cast(); + } else + polyline.points.clear(); + } else if (polyline.size() > 2) { + Vec2d b = polyline.points[1].cast(); + Vec2d c = polyline.points[polyline.points.size() - 2].cast(); + Vec2d v = b - a; + double l = v.norm(); + if (l > trim_length) { + a += v * trim_length / l; + polyline.points.front() = a.cast(); + } else + polyline.points.erase(polyline.points.begin()); + v = d - c; + l = v.norm(); + if (l > trim_length) + polyline.points.back() = (d - v * trim_length / l).cast(); + else + polyline.points.pop_back(); + } + svg.draw(polyline, "black"); + } +} +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ + +void FillAdaptive::_fill_surface_single( + const FillParams & params, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out) +{ + assert (this->adapt_fill_octree); Polylines all_polylines; - all_polylines.reserve(infill_lines_dir[0].size() * 3); - for (Lines &infill_lines : infill_lines_dir) { - for (const Line &line : infill_lines) - { - all_polylines.emplace_back(line.a, line.b); + // 3 contexts for three directions of infill lines + std::array contexts { + FillContext { *adapt_fill_octree, this->z, 0 }, + FillContext { *adapt_fill_octree, this->z, 1 }, + FillContext { *adapt_fill_octree, this->z, 2 } + }; + // Generate the infill lines along the octree cells, merge touching lines of the same direction. + size_t num_lines = 0; + for (auto &context : contexts) { + generate_infill_lines_recursive(context, adapt_fill_octree->root_cube, 0, int(adapt_fill_octree->cubes_properties.size()) - 1); + num_lines += context.output_lines.size() + context.temp_lines.size(); } + // Collect the lines. + std::vector lines; + lines.reserve(num_lines); + for (auto &context : contexts) { + append(lines, context.output_lines); + for (const Line &line : context.temp_lines) + if (line.a.x() != std::numeric_limits::max()) + lines.emplace_back(line); + } + // Convert lines to polylines. + all_polylines.reserve(lines.size()); + std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); } + // Crop all polylines + all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon)); + +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + { + static int iRun = 0; + export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++)); + } +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ + if (params.dont_connect) - { - // Crop all polylines - polylines_out = intersection_pl(all_polylines, to_polygons(expolygon)); - } - else - { - // Crop all polylines - all_polylines = intersection_pl(all_polylines, to_polygons(expolygon)); - + append(polylines_out, std::move(all_polylines)); + else { Polylines boundary_polylines; Polylines non_boundary_polylines; for (const Polyline &polyline : all_polylines) - { // connect_infill required all polylines to touch the boundary. - if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) - { + if (polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) boundary_polylines.push_back(polyline); - } else - { non_boundary_polylines.push_back(polyline); - } - } - if(!boundary_polylines.empty()) - { + if (!boundary_polylines.empty()) { boundary_polylines = chain_polylines(boundary_polylines); FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); } - polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end()); + append(polylines_out, std::move(non_boundary_polylines)); } -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT { - static int iRuna = 0; - BoundingBox bbox_svg = this->bounding_box; - { - ::Slic3r::SVG svg(debug_out_path("FillAdaptive-%d.svg", iRuna), bbox_svg); - for (const Polyline &polyline : polylines_out) - { - for (const Line &line : polyline.lines()) - { - Point from = line.a; - Point to = line.b; - Point diff = to - from; - - float shrink_length = scale_(0.4); - float line_slope = (float)diff.y() / diff.x(); - float shrink_x = shrink_length / (float)std::sqrt(1.0 + (line_slope * line_slope)); - float shrink_y = line_slope * shrink_x; - - to.x() -= shrink_x; - to.y() -= shrink_y; - from.x() += shrink_x; - from.y() += shrink_y; - - svg.draw(Line(from, to)); - } - } - } - - iRuna++; + static int iRun = 0; + export_infill_lines_to_svg(expolygon, polylines_out, debug_out_path("FillAdaptive-final-%d.svg", iRun ++)); } -#endif /* SLIC3R_DEBUG */ +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ } -void FillAdaptive::generate_infill_lines( - FillAdaptive_Internal::Cube *cube, - double z_position, - const Vec3d &origin, - const Transform3d &rotation_matrix, - std::vector &dir_lines_out, - const std::vector &cubes_properties, - int depth) +static double bbox_max_radius(const BoundingBoxf3 &bbox, const Vec3d ¢er) { - using namespace FillAdaptive_Internal; - - if(cube == nullptr) - { - return; - } - - Vec3d cube_center_tranformed = rotation_matrix * cube->center; - double z_diff = std::abs(z_position - cube_center_tranformed.z()); - - if (z_diff > cubes_properties[depth].height / 2) - { - return; - } - - if (z_diff < cubes_properties[depth].line_z_distance) - { - Point from( - scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance), - scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube_center_tranformed.z() - cubes_properties[depth].line_z_distance)) / sqrt(2)))); - Point to(-from.x(), from.y()); - // Relative to cube center - - double rotation_angle = (2.0 * M_PI) / 3.0; - for (Lines &lines : dir_lines_out) - { - Vec3d offset = cube_center_tranformed - (rotation_matrix * origin); - Point from_abs(from), to_abs(to); - - from_abs.x() += int(scale_(offset.x())); - from_abs.y() += int(scale_(offset.y())); - to_abs.x() += int(scale_(offset.x())); - to_abs.y() += int(scale_(offset.y())); - -// lines.emplace_back(from_abs, to_abs); - this->connect_lines(lines, Line(from_abs, to_abs)); - - from.rotate(rotation_angle); - to.rotate(rotation_angle); - } - } - - for(const std::unique_ptr &child : cube->children) - { - if(child != nullptr) - { - generate_infill_lines(child.get(), z_position, origin, rotation_matrix, dir_lines_out, cubes_properties, depth - 1); - } - } + const auto p = (bbox.min - center); + const auto s = bbox.size(); + double r2max = 0.; + for (int i = 0; i < 8; ++ i) + r2max = std::max(r2max, (p + Vec3d(s.x() * double(i & 1), s.y() * double(i & 2), s.z() * double(i & 4))).squaredNorm()); + return sqrt(r2max); } -void FillAdaptive::connect_lines(Lines &lines, Line new_line) +static std::vector make_cubes_properties(double max_cube_edge_length, double line_spacing) { - auto eps = int(scale_(0.10)); - for (size_t i = 0; i < lines.size(); ++i) + max_cube_edge_length += EPSILON; + + std::vector cubes_properties; + for (double edge_length = line_spacing * 2.;; edge_length *= 2.) { - if (std::abs(new_line.a.x() - lines[i].b.x()) < eps && std::abs(new_line.a.y() - lines[i].b.y()) < eps) - { - new_line.a = lines[i].a; - lines.erase(lines.begin() + i); - --i; - continue; - } - - if (std::abs(new_line.b.x() - lines[i].a.x()) < eps && std::abs(new_line.b.y() - lines[i].a.y()) < eps) - { - new_line.b = lines[i].b; - lines.erase(lines.begin() + i); - --i; - continue; - } - } - - lines.emplace_back(new_line.a, new_line.b); -} - -std::unique_ptr FillAdaptive::build_octree( - TriangleMesh &triangle_mesh, - coordf_t line_spacing, - const Vec3d &cube_center) -{ - using namespace FillAdaptive_Internal; - - if(line_spacing <= 0 || std::isnan(line_spacing)) - { - return nullptr; - } - - Vec3d bb_size = triangle_mesh.bounding_box().size(); - // The furthest point from the center of the bottom of the mesh bounding box. - double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + - ((bb_size.y() * bb_size.y()) / 4.0) + - (bb_size.z() * bb_size.z())); - double max_cube_edge_length = furthest_point * 2; - - std::vector cubes_properties; - for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) - { - CubeProperties props{}; + FillAdaptive_Internal::CubeProperties props{}; props.edge_length = edge_length; props.height = edge_length * sqrt(3); props.diagonal_length = edge_length * sqrt(2); props.line_z_distance = edge_length / sqrt(3); props.line_xy_distance = edge_length / sqrt(6); - cubes_properties.push_back(props); + cubes_properties.emplace_back(props); + if (edge_length > max_cube_edge_length) + break; } + return cubes_properties; +} - if (triangle_mesh.its.vertices.empty()) - { - triangle_mesh.require_shared_vertices(); +static inline bool is_overhang_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &up) +{ + // Calculate triangle normal. + auto n = (b - a).cross(c - b); + return n.dot(up) > 0.707 * n.norm(); +} + +static void transform_center(FillAdaptive_Internal::Cube *current_cube, const Eigen::Matrix3d &rot) +{ +#ifndef NDEBUG + current_cube->center_octree = current_cube->center; +#endif // NDEBUG + current_cube->center = rot * current_cube->center; + for (auto *child : current_cube->children) + if (child) + transform_center(child, rot); +} + +FillAdaptive_Internal::OctreePtr FillAdaptive_Internal::build_octree(const indexed_triangle_set &triangle_mesh, const Vec3d &up_vector, coordf_t line_spacing, bool support_overhangs_only) +{ + assert(line_spacing > 0); + assert(! std::isnan(line_spacing)); + + BoundingBox3Base bbox(triangle_mesh.vertices); + Vec3d cube_center = bbox.center().cast(); + std::vector cubes_properties = make_cubes_properties(double(bbox.size().maxCoeff()), line_spacing); + auto octree = OctreePtr(new Octree(cube_center, cubes_properties)); + + if (cubes_properties.size() > 1) { + for (auto &tri : triangle_mesh.indices) { + auto a = triangle_mesh.vertices[tri[0]].cast(); + auto b = triangle_mesh.vertices[tri[1]].cast(); + auto c = triangle_mesh.vertices[tri[2]].cast(); + if (support_overhangs_only && ! is_overhang_triangle(a, b, c, up_vector)) + continue; + double edge_length_half = 0.5 * cubes_properties.back().edge_length; + Vec3d diag_half(edge_length_half, edge_length_half, edge_length_half); + octree->insert_triangle( + a, b, c, + octree->root_cube, + BoundingBoxf3(octree->root_cube->center - diag_half, octree->root_cube->center + diag_half), + int(cubes_properties.size()) - 1); + } + { + // Transform the octree to world coordinates to reduce computation when extracting infill lines. + auto rot = adaptive_fill_octree_transform_to_world().toRotationMatrix(); + transform_center(octree->root_cube, rot); + octree->origin = rot * octree->origin; + } } - AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - triangle_mesh.its.vertices, triangle_mesh.its.indices); - auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1); - return octree; } -void FillAdaptive::expand_cube( - FillAdaptive_Internal::Cube *cube, - const std::vector &cubes_properties, - const AABBTreeIndirect::Tree3f &distance_tree, - const TriangleMesh &triangle_mesh, int depth) +void FillAdaptive_Internal::Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth) { - using namespace FillAdaptive_Internal; + assert(current_cube); + assert(depth > 0); - if (cube == nullptr || depth == 0) - { - return; - } - - std::vector child_centers = { - Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), - Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) - }; - - double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16; - - for (size_t i = 0; i < 8; ++i) - { + for (size_t i = 0; i < 8; ++ i) { const Vec3d &child_center = child_centers[i]; - Vec3d child_center_transformed = cube->center + (child_center * (cubes_properties[depth].edge_length / 4)); - - if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices, - distance_tree, child_center_transformed, cube_radius_squared)) - { - cube->children[i] = std::make_unique(child_center_transformed); - FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, distance_tree, triangle_mesh, depth - 1); - } - } -} - -void FillAdaptive_Internal::Octree::propagate_point( - Vec3d point, - FillAdaptive_Internal::Cube * current, - int depth, - const std::vector &cubes_properties) -{ - using namespace FillAdaptive_Internal; - - if(depth <= 0) - { - return; - } - - size_t octant_idx = Octree::find_octant(point, current->center); - Cube * child = current->children[octant_idx].get(); - - // Octant not exists, then create it - if(child == nullptr) { - std::vector child_centers = { - Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), - Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) - }; - - const Vec3d &child_center = child_centers[octant_idx]; - Vec3d child_center_transformed = current->center + (child_center * (cubes_properties[depth].edge_length / 4)); - - current->children[octant_idx] = std::make_unique(child_center_transformed); - child = current->children[octant_idx].get(); - } - - Octree::propagate_point(point, child, (depth - 1), cubes_properties); -} - -std::unique_ptr FillSupportCubic::build_octree( - TriangleMesh & triangle_mesh, - coordf_t line_spacing, - const Vec3d & cube_center, - const Transform3d &rotation_matrix) -{ - using namespace FillAdaptive_Internal; - - if(line_spacing <= 0 || std::isnan(line_spacing)) - { - return nullptr; - } - - Vec3d bb_size = triangle_mesh.bounding_box().size(); - // The furthest point from the center of the bottom of the mesh bounding box. - double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + - ((bb_size.y() * bb_size.y()) / 4.0) + - (bb_size.z() * bb_size.z())); - double max_cube_edge_length = furthest_point * 2; - - std::vector cubes_properties; - for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) - { - CubeProperties props{}; - props.edge_length = edge_length; - props.height = edge_length * sqrt(3); - props.diagonal_length = edge_length * sqrt(2); - props.line_z_distance = edge_length / sqrt(3); - props.line_xy_distance = edge_length / sqrt(6); - cubes_properties.push_back(props); - } - - if (triangle_mesh.its.vertices.empty()) - { - triangle_mesh.require_shared_vertices(); - } - - AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - triangle_mesh.its.vertices, triangle_mesh.its.indices); - - auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - - double cube_edge_length = line_spacing / 2.0; - int max_depth = int(octree->cubes_properties.size()) - 1; - BoundingBoxf3 mesh_bb = triangle_mesh.bounding_box(); - Vec3f vertical(0, 0, 1); - - for (size_t facet_idx = 0; facet_idx < triangle_mesh.stl.facet_start.size(); ++facet_idx) - { - if(triangle_mesh.stl.facet_start[facet_idx].normal.dot(vertical) <= 0.707) - { - // The angle is smaller than PI/4, than infill don't to be there - continue; - } - - stl_vertex v_1 = triangle_mesh.stl.facet_start[facet_idx].vertex[0]; - stl_vertex v_2 = triangle_mesh.stl.facet_start[facet_idx].vertex[1]; - stl_vertex v_3 = triangle_mesh.stl.facet_start[facet_idx].vertex[2]; - - std::vector triangle_vertices = - {Vec3d(v_1.x(), v_1.y(), v_1.z()), - Vec3d(v_2.x(), v_2.y(), v_2.z()), - Vec3d(v_3.x(), v_3.y(), v_3.z())}; - - BoundingBoxf3 triangle_bb(triangle_vertices); - - Vec3d triangle_start_relative = triangle_bb.min - mesh_bb.min; - Vec3d triangle_end_relative = triangle_bb.max - mesh_bb.min; - - Vec3crd triangle_start_idx = Vec3crd( - int(std::floor(triangle_start_relative.x() / cube_edge_length)), - int(std::floor(triangle_start_relative.y() / cube_edge_length)), - int(std::floor(triangle_start_relative.z() / cube_edge_length))); - Vec3crd triangle_end_idx = Vec3crd( - int(std::floor(triangle_end_relative.x() / cube_edge_length)), - int(std::floor(triangle_end_relative.y() / cube_edge_length)), - int(std::floor(triangle_end_relative.z() / cube_edge_length))); - - for (int z = triangle_start_idx.z(); z <= triangle_end_idx.z(); ++z) - { - for (int y = triangle_start_idx.y(); y <= triangle_end_idx.y(); ++y) - { - for (int x = triangle_start_idx.x(); x <= triangle_end_idx.x(); ++x) - { - Vec3d cube_center_relative(x * cube_edge_length + (cube_edge_length / 2.0), y * cube_edge_length + (cube_edge_length / 2.0), z * cube_edge_length); - Vec3d cube_center_absolute = cube_center_relative + mesh_bb.min; - - double cube_center_absolute_arr[3] = {cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z()}; - double distance = 0, cord_u = 0, cord_v = 0; - - double dir[3] = {0.0, 0.0, 1.0}; - - double vert_0[3] = {triangle_vertices[0].x(), - triangle_vertices[0].y(), - triangle_vertices[0].z()}; - double vert_1[3] = {triangle_vertices[1].x(), - triangle_vertices[1].y(), - triangle_vertices[1].z()}; - double vert_2[3] = {triangle_vertices[2].x(), - triangle_vertices[2].y(), - triangle_vertices[2].z()}; - - if(intersect_triangle(cube_center_absolute_arr, dir, vert_0, vert_1, vert_2, &distance, &cord_u, &cord_v) && distance > 0 && distance <= cube_edge_length) - { - Vec3d cube_center_transformed(cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z() + (cube_edge_length / 2.0)); - Octree::propagate_point(rotation_matrix * cube_center_transformed, octree->root_cube.get(), max_depth, octree->cubes_properties); - } - } + // Calculate a slightly expanded bounding box of a child cube to cope with triangles touching a cube wall and other numeric errors. + // We will rather densify the octree a bit more than necessary instead of missing a triangle. + BoundingBoxf3 bbox; + for (int k = 0; k < 3; ++ k) { + if (child_center[k] == -1.) { + bbox.min[k] = current_bbox.min[k]; + bbox.max[k] = current_cube->center[k] + EPSILON; + } else { + bbox.min[k] = current_cube->center[k] - EPSILON; + bbox.max[k] = current_bbox.max[k]; } } + if (triangle_AABB_intersects(a, b, c, bbox)) { + if (! current_cube->children[i]) + current_cube->children[i] = this->pool.construct(current_cube->center + (child_center * (this->cubes_properties[depth].edge_length / 4))); + if (depth > 1) + this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth - 1); + } } - - return octree; -} - -void FillSupportCubic::_fill_surface_single(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out) -{ - if (this->support_fill_octree != nullptr) - this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->support_fill_octree); } } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index b24f206da2..906414747d 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -1,3 +1,13 @@ +// Adaptive cubic infill was inspired by the work of @mboerwinkle +// as implemented for Cura. +// https://github.com/Ultimaker/CuraEngine/issues/381 +// https://github.com/Ultimaker/CuraEngine/pull/401 +// +// Our implementation is more accurate (discretizes a bit less cubes than Cura's) +// by splitting only such cubes which contain a triangle. +// Our line extraction is time optimal instead of O(n^2) when connecting extracted lines, +// and we also implemented adaptivity for supporting internal overhangs only. + #ifndef slic3r_FillAdaptive_hpp_ #define slic3r_FillAdaptive_hpp_ @@ -5,49 +15,39 @@ #include "FillBase.hpp" +struct indexed_triangle_set; + namespace Slic3r { class PrintObject; -class TriangleMesh; - namespace FillAdaptive_Internal { - struct CubeProperties - { - double edge_length; // Lenght of edge of a cube - double height; // Height of rotated cube (standing on the corner) - double diagonal_length; // Length of diagonal of a cube a face - double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created - double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created + struct Octree; + // To keep the definition of Octree opaque, we have to define a custom deleter. + struct OctreeDeleter { + void operator()(Octree *p); }; + using OctreePtr = std::unique_ptr; - struct Cube - { - Vec3d center; - std::unique_ptr children[8] = {}; - Cube(const Vec3d ¢er) : center(center) {} - }; + // Calculate line spacing for + // 1) adaptive cubic infill + // 2) adaptive internal support cubic infill + // Returns zero for a particular infill type if no such infill is to be generated. + std::pair adaptive_fill_line_spacing(const PrintObject &print_object); - struct Octree - { - std::unique_ptr root_cube; - Vec3d origin; - std::vector cubes_properties; + // Rotation of the octree to stand on one of its corners. + Eigen::Quaterniond adaptive_fill_octree_transform_to_world(); + // Inverse roation of the above. + Eigen::Quaterniond adaptive_fill_octree_transform_to_octree(); - Octree(std::unique_ptr rootCube, const Vec3d &origin, const std::vector &cubes_properties) - : root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {} - - inline static int find_octant(const Vec3d &i_cube, const Vec3d ¤t) - { - return (i_cube.z() > current.z()) * 4 + (i_cube.y() > current.y()) * 2 + (i_cube.x() > current.x()); - } - - static void propagate_point( - Vec3d point, - FillAdaptive_Internal::Cube *current_cube, - int depth, - const std::vector &cubes_properties); - }; + FillAdaptive_Internal::OctreePtr build_octree( + // Mesh is rotated to the coordinate system of the octree. + const indexed_triangle_set &triangle_mesh, + // Up vector of the mesh rotated to the coordinate system of the octree. + const Vec3d &up_vector, + coordf_t line_spacing, + // If true, octree is densified below internal overhangs only. + bool support_overhangs_only); }; // namespace FillAdaptive_Internal // @@ -70,70 +70,8 @@ protected: Polylines &polylines_out); virtual bool no_sort() const { return true; } - - void generate_infill_lines( - FillAdaptive_Internal::Cube *cube, - double z_position, - const Vec3d & origin, - const Transform3d & rotation_matrix, - std::vector & dir_lines_out, - const std::vector &cubes_properties, - int depth); - - static void connect_lines(Lines &lines, Line new_line); - - void generate_infill(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out, - FillAdaptive_Internal::Octree *octree); - -public: - static std::unique_ptr build_octree( - TriangleMesh &triangle_mesh, - coordf_t line_spacing, - const Vec3d & cube_center); - - static void expand_cube( - FillAdaptive_Internal::Cube *cube, - const std::vector &cubes_properties, - const AABBTreeIndirect::Tree3f &distance_tree, - const TriangleMesh & triangle_mesh, - int depth); }; -class FillSupportCubic : public FillAdaptive -{ -public: - virtual ~FillSupportCubic() = default; - -protected: - virtual Fill* clone() const { return new FillSupportCubic(*this); }; - - virtual bool no_sort() const { return true; } - - virtual void _fill_surface_single( - const FillParams ¶ms, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon &expolygon, - Polylines &polylines_out); - -public: - static std::unique_ptr build_octree( - TriangleMesh & triangle_mesh, - coordf_t line_spacing, - const Vec3d & cube_center, - const Transform3d &rotation_matrix); -}; - -// Calculate line spacing for -// 1) adaptive cubic infill -// 2) adaptive internal support cubic infill -// Returns zero for a particular infill type if no such infill is to be generated. -std::pair adaptive_fill_line_spacing(const PrintObject &print_object); - } // namespace Slic3r #endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 077555d2ca..5866330b94 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -39,7 +39,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive(); - case ipSupportCubic: return new FillSupportCubic(); + case ipSupportCubic: return new FillAdaptive(); default: throw Slic3r::InvalidArgument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 77620e1181..4d822ddeac 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -77,8 +77,6 @@ public: // Octree builds on mesh for usage in the adaptive cubic infill FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr; - // Octree builds on mesh for usage in the support cubic infill - FillAdaptive_Internal::Octree* support_fill_octree = nullptr; public: virtual ~Fill() {} diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 75f3708d25..b690b478d9 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -281,7 +281,7 @@ bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } double rad2deg_dir(double angle); -template T deg2rad(T angle) { return T(PI) * angle / T(180.0); } +template constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); } template T angle_to_0_2PI(T angle) { static const T TWO_PI = T(2) * T(PI); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index dc06e3102d..3539cc0faa 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -777,6 +777,38 @@ TriangleMesh ModelObject::raw_mesh() const return mesh; } +// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. +// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater +// and to display the object statistics at ModelObject::print_info(). +indexed_triangle_set ModelObject::raw_indexed_triangle_set() const +{ + size_t num_vertices = 0; + size_t num_faces = 0; + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + num_vertices += v->mesh().its.vertices.size(); + num_faces += v->mesh().its.indices.size(); + } + indexed_triangle_set out; + out.vertices.reserve(num_vertices); + out.indices.reserve(num_faces); + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + size_t i = out.vertices.size(); + size_t j = out.indices.size(); + append(out.vertices, v->mesh().its.vertices); + append(out.indices, v->mesh().its.indices); + auto m = v->get_matrix(); + for (; i < out.vertices.size(); ++ i) + out.vertices[i] = (m * out.vertices[i].cast()).cast().eval(); + if (v->is_left_handed()) { + for (; j < out.indices.size(); ++ j) + std::swap(out.indices[j][0], out.indices[j][1]); + } + } + return out; +} + // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh ModelObject::full_raw_mesh() const { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a623f5cca0..b9045e28bf 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -244,6 +244,8 @@ public: // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. TriangleMesh raw_mesh() const; + // The same as above, but producing a lightweight indexed_triangle_set. + indexed_triangle_set raw_indexed_triangle_set() const; // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh full_raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index c2417d0dc9..f3ed413421 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -44,16 +44,6 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t) return ret_points; } -void Point::rotate(double angle) -{ - double cur_x = (double)(*this)(0); - double cur_y = (double)(*this)(1); - double s = ::sin(angle); - double c = ::cos(angle); - (*this)(0) = (coord_t)round(c * cur_x - s * cur_y); - (*this)(1) = (coord_t)round(c * cur_y + s * cur_x); -} - void Point::rotate(double angle, const Point ¢er) { double cur_x = (double)(*this)(0); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 30a1a4942c..5082bb746f 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -105,6 +105,7 @@ public: template Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } + static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } // This method allows you to assign Eigen expressions to MyVectorType template @@ -121,7 +122,14 @@ public: Point& operator*=(const double &rhs) { (*this)(0) = coord_t((*this)(0) * rhs); (*this)(1) = coord_t((*this)(1) * rhs); return *this; } Point operator*(const double &rhs) { return Point((*this)(0) * rhs, (*this)(1) * rhs); } - void rotate(double angle); + void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } + void rotate(double cos_a, double sin_a) { + double cur_x = (double)(*this)(0); + double cur_y = (double)(*this)(1); + (*this)(0) = (coord_t)round(cos_a * cur_x - sin_a * cur_y); + (*this)(1) = (coord_t)round(cos_a * cur_y + sin_a * cur_x); + } + void rotate(double angle, const Point ¢er); Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 98a1314112..471484005b 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -32,6 +32,8 @@ class SupportLayer; namespace FillAdaptive_Internal { struct Octree; + struct OctreeDeleter; + using OctreePtr = std::unique_ptr; }; // Print step IDs for keeping track of the print state. @@ -239,7 +241,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::pair, std::unique_ptr> prepare_adaptive_infill_data(); + std::pair prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ddd41af012..0968f6cfc8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,74 +434,27 @@ void PrintObject::generate_support_material() } } -//#define ADAPTIVE_SUPPORT_SIMPLE - -std::pair, std::unique_ptr> PrintObject::prepare_adaptive_infill_data() +std::pair PrintObject::prepare_adaptive_infill_data() { using namespace FillAdaptive_Internal; auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); - - std::unique_ptr adaptive_fill_octree = {}, support_fill_octree = {}; - if (adaptive_line_spacing == 0. && support_line_spacing == 0.) - return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree)); + return std::make_pair(OctreePtr(), OctreePtr()); - TriangleMesh mesh = this->model_object()->raw_mesh(); - mesh.transform(m_trafo, true); - // Apply XY shift - mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); - // Center of the first cube in octree - Vec3d mesh_origin = mesh.bounding_box().center(); - -#ifdef ADAPTIVE_SUPPORT_SIMPLE - if (mesh.its.vertices.empty()) + indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); + Vec3d up; { - mesh.require_shared_vertices(); - } - - Vec3f vertical(0, 0, 1); - - indexed_triangle_set its_set; - its_set.vertices = mesh.its.vertices; - - // Filter out non overhanging faces - for (size_t i = 0; i < mesh.its.indices.size(); ++i) { - stl_triangle_vertex_indices vertex_idx = mesh.its.indices[i]; - - auto its_calculate_normal = [](const stl_triangle_vertex_indices &index, const std::vector &vertices) { - stl_normal normal = (vertices[index.y()] - vertices[index.x()]).cross(vertices[index.z()] - vertices[index.x()]); - return normal; - }; - - stl_normal normal = its_calculate_normal(vertex_idx, mesh.its.vertices); - stl_normalize_vector(normal); - - if(normal.dot(vertical) >= 0.707) { - its_set.indices.push_back(vertex_idx); - } - } - - mesh = TriangleMesh(its_set); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - Slic3r::store_stl(debug_out_path("overhangs.stl").c_str(), &mesh, false); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#endif /* ADAPTIVE_SUPPORT_SIMPLE */ - - Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); - Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()).inverse(); - - if (adaptive_line_spacing != 0.) { + auto m = adaptive_fill_octree_transform_to_octree().toRotationMatrix(); + up = m * Vec3d(0., 0., 1.); // Rotate mesh and build octree on it with axis-aligned (standart base) cubes - mesh.transform(rotation_matrix); - adaptive_fill_octree = FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin); + Transform3d m2 = m_trafo; + m2.translate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); + its_transform(mesh, m * m2, true); } - - if (support_line_spacing != 0.) - support_fill_octree = FillSupportCubic::build_octree(mesh, support_line_spacing, rotation_matrix * mesh_origin, rotation_matrix); - - return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree)); + return std::make_pair( + adaptive_line_spacing ? build_octree(mesh, up, adaptive_line_spacing, false) : OctreePtr(), + support_line_spacing ? build_octree(mesh, up, support_line_spacing, true) : OctreePtr()); } void PrintObject::clear_layers() From 7c7f5ebdda38f7db6b35a1a1b3dde0254682be06 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 18 Sep 2020 08:36:29 +0200 Subject: [PATCH 526/826] Fixed sliced info panel not hiding when changing printer type --- src/slic3r/GUI/Plater.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8e27381768..8bec7a7a27 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5398,6 +5398,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->set_printer_technology(config.opt_enum(opt_key)); // print technology is changed, so we should to update a search list p->sidebar->update_searcher(); + p->sidebar->show_sliced_info_sizer(false); #if ENABLE_GCODE_VIEWER p->reset_gcode_toolpaths(); #endif // ENABLE_GCODE_VIEWER From 7e756b20e61609659864e135ff3780304fd1a4ff Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 18 Sep 2020 10:53:50 +0200 Subject: [PATCH 527/826] Adaptive infill: Reshuffled the namespaces. --- src/libslic3r/Fill/Fill.cpp | 2 +- src/libslic3r/Fill/FillAdaptive.cpp | 130 ++++++++++++++-------------- src/libslic3r/Fill/FillAdaptive.hpp | 57 ++++++------ src/libslic3r/Fill/FillBase.cpp | 4 +- src/libslic3r/Fill/FillBase.hpp | 4 +- src/libslic3r/Layer.hpp | 4 +- src/libslic3r/Print.hpp | 4 +- src/libslic3r/PrintObject.cpp | 21 ++--- 8 files changed, 108 insertions(+), 118 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 70792b823f..03aa798dc4 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree) +void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 6c5d7c0c47..b016242f48 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -19,6 +19,7 @@ #include namespace Slic3r { +namespace FillAdaptive { // Derived from https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp // The AABB-Triangle test implementation is based on the pseudo-code in @@ -165,48 +166,45 @@ static constexpr std::array, 3> child_traversal_order { std::array{ 1, 5, 0, 4, 3, 7, 2, 6 }, }; -namespace FillAdaptive_Internal +struct Cube { - struct Cube - { - Vec3d center; + Vec3d center; #ifndef NDEBUG - Vec3d center_octree; + Vec3d center_octree; #endif // NDEBUG - std::array children {}; // initialized to nullptrs - Cube(const Vec3d ¢er) : center(center) {} - }; + std::array children {}; // initialized to nullptrs + Cube(const Vec3d ¢er) : center(center) {} +}; - struct CubeProperties - { - double edge_length; // Lenght of edge of a cube - double height; // Height of rotated cube (standing on the corner) - double diagonal_length; // Length of diagonal of a cube a face - double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created - double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created - }; +struct CubeProperties +{ + double edge_length; // Lenght of edge of a cube + double height; // Height of rotated cube (standing on the corner) + double diagonal_length; // Length of diagonal of a cube a face + double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created + double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created +}; - struct Octree - { - // Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool, - // perfect for building up our octree. - boost::object_pool pool; - Cube* root_cube { nullptr }; - Vec3d origin; - std::vector cubes_properties; +struct Octree +{ + // Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool, + // perfect for building up our octree. + boost::object_pool pool; + Cube* root_cube { nullptr }; + Vec3d origin; + std::vector cubes_properties; - Octree(const Vec3d &origin, const std::vector &cubes_properties) - : root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {} + Octree(const Vec3d &origin, const std::vector &cubes_properties) + : root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {} - void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth); - }; + void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth); +}; - void OctreeDeleter::operator()(Octree *p) { - delete p; - } -}; // namespace FillAdaptive_Internal +void OctreeDeleter::operator()(Octree *p) { + delete p; +} -std::pair FillAdaptive_Internal::adaptive_fill_line_spacing(const PrintObject &print_object) +std::pair FillAdaptive::adaptive_fill_line_spacing(const PrintObject &print_object) { // Output, spacing for icAdaptiveCubic and icSupportCubic double adaptive_line_spacing = 0.; @@ -296,7 +294,7 @@ struct FillContext -(2.0 * M_PI) / 3.0 }; - FillContext(const FillAdaptive_Internal::Octree &octree, double z_position, int direction_idx) : + FillContext(const Octree &octree, double z_position, int direction_idx) : origin_world(octree.origin), cubes_properties(octree.cubes_properties), z_position(z_position), @@ -312,31 +310,31 @@ struct FillContext Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); } // Center of the root cube in the Octree coordinate system. - const Vec3d origin_world; - const std::vector &cubes_properties; + const Vec3d origin_world; + const std::vector &cubes_properties; // Top of the current layer. - const double z_position; + const double z_position; // Order of traversal for this line direction. - const std::array traversal_order; + const std::array traversal_order; // Rotation of the generated line for this line direction. - const double cos_a; - const double sin_a; + const double cos_a; + const double sin_a; // Linearized tree spanning a single Octree wall, used to connect lines spanning // neighboring Octree cells. Unused lines have the Line::a::x set to infinity. - std::vector temp_lines; + std::vector temp_lines; // Final output - std::vector output_lines; + std::vector output_lines; }; static constexpr double octree_rot[3] = { 5.0 * M_PI / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0 }; -Eigen::Quaterniond FillAdaptive_Internal::adaptive_fill_octree_transform_to_world() +Eigen::Quaterniond transform_to_world() { return Eigen::AngleAxisd(octree_rot[2], Vec3d::UnitZ()) * Eigen::AngleAxisd(octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(octree_rot[0], Vec3d::UnitX()); } -Eigen::Quaterniond FillAdaptive_Internal::adaptive_fill_octree_transform_to_octree() +Eigen::Quaterniond transform_to_octree() { return Eigen::AngleAxisd(- octree_rot[0], Vec3d::UnitX()) * Eigen::AngleAxisd(- octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(- octree_rot[2], Vec3d::UnitZ()); } @@ -345,14 +343,14 @@ Eigen::Quaterniond FillAdaptive_Internal::adaptive_fill_octree_transform_to_octr // Verify that the traversal order of the octree children matches the line direction, // therefore the infill line may get extended with O(1) time & space complexity. static bool verify_traversal_order( - FillContext &context, - const FillAdaptive_Internal::Cube *cube, - int depth, - const Vec2d &line_from, - const Vec2d &line_to) + FillContext &context, + const Cube *cube, + int depth, + const Vec2d &line_from, + const Vec2d &line_to) { std::array c; - Eigen::Quaterniond to_world = FillAdaptive_Internal::adaptive_fill_octree_transform_to_world(); + Eigen::Quaterniond to_world = transform_to_world(); for (int i = 0; i < 8; ++i) { int j = context.traversal_order[i]; Vec3d cntr = to_world * (cube->center_octree + (child_centers[j] * (context.cubes_properties[depth].edge_length / 4.))); @@ -379,15 +377,15 @@ static bool verify_traversal_order( #endif // NDEBUG static void generate_infill_lines_recursive( - FillContext &context, - const FillAdaptive_Internal::Cube *cube, + FillContext &context, + const Cube *cube, // Address of this wall in the octree, used to address context.temp_lines. - int address, - int depth) + int address, + int depth) { assert(cube != nullptr); - const std::vector &cubes_properties = context.cubes_properties; + const std::vector &cubes_properties = context.cubes_properties; const double z_diff = context.z_position - cube->center.z(); const double z_diff_abs = std::abs(z_diff); @@ -427,7 +425,7 @@ static void generate_infill_lines_recursive( -- depth; size_t i = 0; for (const int child_idx : context.traversal_order) { - const FillAdaptive_Internal::Cube *child = cube->children[child_idx]; + const Cube *child = cube->children[child_idx]; if (child != nullptr) generate_infill_lines_recursive(context, child, address, depth); if (++ i == 4) @@ -486,7 +484,7 @@ static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ -void FillAdaptive::_fill_surface_single( +void Filler::_fill_surface_single( const FillParams & params, unsigned int thickness_layers, const std::pair &direction, @@ -547,7 +545,7 @@ void FillAdaptive::_fill_surface_single( if (!boundary_polylines.empty()) { boundary_polylines = chain_polylines(boundary_polylines); - FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); } append(polylines_out, std::move(non_boundary_polylines)); @@ -571,14 +569,14 @@ static double bbox_max_radius(const BoundingBoxf3 &bbox, const Vec3d ¢er) return sqrt(r2max); } -static std::vector make_cubes_properties(double max_cube_edge_length, double line_spacing) +static std::vector make_cubes_properties(double max_cube_edge_length, double line_spacing) { max_cube_edge_length += EPSILON; - std::vector cubes_properties; + std::vector cubes_properties; for (double edge_length = line_spacing * 2.;; edge_length *= 2.) { - FillAdaptive_Internal::CubeProperties props{}; + CubeProperties props{}; props.edge_length = edge_length; props.height = edge_length * sqrt(3); props.diagonal_length = edge_length * sqrt(2); @@ -598,7 +596,7 @@ static inline bool is_overhang_triangle(const Vec3d &a, const Vec3d &b, const Ve return n.dot(up) > 0.707 * n.norm(); } -static void transform_center(FillAdaptive_Internal::Cube *current_cube, const Eigen::Matrix3d &rot) +static void transform_center(Cube *current_cube, const Eigen::Matrix3d &rot) { #ifndef NDEBUG current_cube->center_octree = current_cube->center; @@ -609,7 +607,7 @@ static void transform_center(FillAdaptive_Internal::Cube *current_cube, const Ei transform_center(child, rot); } -FillAdaptive_Internal::OctreePtr FillAdaptive_Internal::build_octree(const indexed_triangle_set &triangle_mesh, const Vec3d &up_vector, coordf_t line_spacing, bool support_overhangs_only) +OctreePtr build_octree(const indexed_triangle_set &triangle_mesh, coordf_t line_spacing, bool support_overhangs_only) { assert(line_spacing > 0); assert(! std::isnan(line_spacing)); @@ -620,6 +618,7 @@ FillAdaptive_Internal::OctreePtr FillAdaptive_Internal::build_octree(const index auto octree = OctreePtr(new Octree(cube_center, cubes_properties)); if (cubes_properties.size() > 1) { + auto up_vector = support_overhangs_only ? transform_to_octree() * Vec3d(0., 0., 1.) : Vec3d(); for (auto &tri : triangle_mesh.indices) { auto a = triangle_mesh.vertices[tri[0]].cast(); auto b = triangle_mesh.vertices[tri[1]].cast(); @@ -636,7 +635,7 @@ FillAdaptive_Internal::OctreePtr FillAdaptive_Internal::build_octree(const index } { // Transform the octree to world coordinates to reduce computation when extracting infill lines. - auto rot = adaptive_fill_octree_transform_to_world().toRotationMatrix(); + auto rot = transform_to_world().toRotationMatrix(); transform_center(octree->root_cube, rot); octree->origin = rot * octree->origin; } @@ -645,7 +644,7 @@ FillAdaptive_Internal::OctreePtr FillAdaptive_Internal::build_octree(const index return octree; } -void FillAdaptive_Internal::Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth) +void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth) { assert(current_cube); assert(depth > 0); @@ -673,4 +672,5 @@ void FillAdaptive_Internal::Octree::insert_triangle(const Vec3d &a, const Vec3d } } +} // namespace FillAdaptive } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 906414747d..aca8d1d7b5 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -11,8 +11,6 @@ #ifndef slic3r_FillAdaptive_hpp_ #define slic3r_FillAdaptive_hpp_ -#include "../AABBTreeIndirect.hpp" - #include "FillBase.hpp" struct indexed_triangle_set; @@ -20,58 +18,55 @@ struct indexed_triangle_set; namespace Slic3r { class PrintObject; -namespace FillAdaptive_Internal + +namespace FillAdaptive { - struct Octree; - // To keep the definition of Octree opaque, we have to define a custom deleter. - struct OctreeDeleter { - void operator()(Octree *p); - }; - using OctreePtr = std::unique_ptr; - // Calculate line spacing for - // 1) adaptive cubic infill - // 2) adaptive internal support cubic infill - // Returns zero for a particular infill type if no such infill is to be generated. - std::pair adaptive_fill_line_spacing(const PrintObject &print_object); +struct Octree; +// To keep the definition of Octree opaque, we have to define a custom deleter. +struct OctreeDeleter { void operator()(Octree *p); }; +using OctreePtr = std::unique_ptr; - // Rotation of the octree to stand on one of its corners. - Eigen::Quaterniond adaptive_fill_octree_transform_to_world(); - // Inverse roation of the above. - Eigen::Quaterniond adaptive_fill_octree_transform_to_octree(); +// Calculate line spacing for +// 1) adaptive cubic infill +// 2) adaptive internal support cubic infill +// Returns zero for a particular infill type if no such infill is to be generated. +std::pair adaptive_fill_line_spacing(const PrintObject &print_object); - FillAdaptive_Internal::OctreePtr build_octree( - // Mesh is rotated to the coordinate system of the octree. - const indexed_triangle_set &triangle_mesh, - // Up vector of the mesh rotated to the coordinate system of the octree. - const Vec3d &up_vector, - coordf_t line_spacing, - // If true, octree is densified below internal overhangs only. - bool support_overhangs_only); -}; // namespace FillAdaptive_Internal +// Rotation of the octree to stand on one of its corners. +Eigen::Quaterniond transform_to_world(); +// Inverse roation of the above. +Eigen::Quaterniond transform_to_octree(); + +FillAdaptive::OctreePtr build_octree( + // Mesh is rotated to the coordinate system of the octree. + const indexed_triangle_set &triangle_mesh, + coordf_t line_spacing, + // If true, octree is densified below internal overhangs only. + bool support_overhangs_only); // // Some of the algorithms used by class FillAdaptive were inspired by // Cura Engine's class SubDivCube // https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h // -class FillAdaptive : public Fill +class Filler : public Slic3r::Fill { public: - virtual ~FillAdaptive() {} + virtual ~Filler() {} protected: - virtual Fill* clone() const { return new FillAdaptive(*this); }; + virtual Fill* clone() const { return new Filler(*this); }; virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out); - virtual bool no_sort() const { return true; } }; +}; // namespace FillAdaptive } // namespace Slic3r #endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 5866330b94..43b5d464ab 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -38,8 +38,8 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipArchimedeanChords: return new FillArchimedeanChords(); case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); - case ipAdaptiveCubic: return new FillAdaptive(); - case ipSupportCubic: return new FillAdaptive(); + case ipAdaptiveCubic: return new FillAdaptive::Filler(); + case ipSupportCubic: return new FillAdaptive::Filler(); default: throw Slic3r::InvalidArgument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 4d822ddeac..0779117ebc 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -20,7 +20,7 @@ class ExPolygon; class Surface; enum InfillPattern : int; -namespace FillAdaptive_Internal { +namespace FillAdaptive { struct Octree; }; @@ -76,7 +76,7 @@ public: BoundingBox bounding_box; // Octree builds on mesh for usage in the adaptive cubic infill - FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr; + FillAdaptive::Octree* adapt_fill_octree = nullptr; public: virtual ~Fill() {} diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 8d5db42fc0..8285b5493f 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -13,7 +13,7 @@ class Layer; class PrintRegion; class PrintObject; -namespace FillAdaptive_Internal { +namespace FillAdaptive { struct Octree; }; @@ -139,7 +139,7 @@ public: } void make_perimeters(); void make_fills() { this->make_fills(nullptr, nullptr); }; - void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree); + void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree); void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 471484005b..a389ef00da 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -30,7 +30,7 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; -namespace FillAdaptive_Internal { +namespace FillAdaptive { struct Octree; struct OctreeDeleter; using OctreePtr = std::unique_ptr; @@ -241,7 +241,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::pair prepare_adaptive_infill_data(); + std::pair prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 0968f6cfc8..ac47e1d102 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -434,27 +434,22 @@ void PrintObject::generate_support_material() } } -std::pair PrintObject::prepare_adaptive_infill_data() +std::pair PrintObject::prepare_adaptive_infill_data() { - using namespace FillAdaptive_Internal; + using namespace FillAdaptive; auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); if (adaptive_line_spacing == 0. && support_line_spacing == 0.) return std::make_pair(OctreePtr(), OctreePtr()); indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); - Vec3d up; - { - auto m = adaptive_fill_octree_transform_to_octree().toRotationMatrix(); - up = m * Vec3d(0., 0., 1.); - // Rotate mesh and build octree on it with axis-aligned (standart base) cubes - Transform3d m2 = m_trafo; - m2.translate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); - its_transform(mesh, m * m2, true); - } + // Rotate mesh and build octree on it with axis-aligned (standart base) cubes. + Transform3d m = m_trafo; + m.translate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); + its_transform(mesh, transform_to_octree().toRotationMatrix() * m, true); return std::make_pair( - adaptive_line_spacing ? build_octree(mesh, up, adaptive_line_spacing, false) : OctreePtr(), - support_line_spacing ? build_octree(mesh, up, support_line_spacing, true) : OctreePtr()); + adaptive_line_spacing ? build_octree(mesh, adaptive_line_spacing, false) : OctreePtr(), + support_line_spacing ? build_octree(mesh, support_line_spacing, true) : OctreePtr()); } void PrintObject::clear_layers() From a1fadaf955be836fe3a446cddb9fae9e9c3ff051 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 31 Aug 2020 07:25:24 +0200 Subject: [PATCH 528/826] Partially working implementation of custom seam backend --- src/libslic3r/GCode.cpp | 225 +++++++++++++++++++++++++++++--------- src/libslic3r/GCode.hpp | 18 ++- src/libslic3r/Polygon.cpp | 38 +++++++ src/libslic3r/Polygon.hpp | 2 + 4 files changed, 227 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b4196dc5fc..63dde66064 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -176,27 +176,34 @@ namespace Slic3r { } - int CustomSeam::get_point_status(const Point& pt, size_t layer_id) const + void CustomSeam::get_indices(size_t layer_id, + const Polygon& polygon, + std::vector& enforcers_idxs, + std::vector& blockers_idxs) const { - // TEMPORARY - WILL BE IMPROVED - // - quadratic algorithm - // - does not support variable layer height + enforcers_idxs.clear(); + blockers_idxs.clear(); - if (! enforcers.empty()) { - assert(layer_id < enforcers.size()); - for (const ExPolygon& explg : enforcers[layer_id]) { - if (explg.contains(pt)) - return 1; + // FIXME: This is quadratic and it should be improved, maybe by building + // an AABB tree (or at least utilize bounding boxes). + for (size_t i=0; iproject_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, custom_seam.enforcers); po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, custom_seam.blockers); } - for (ExPolygons& explgs : custom_seam.enforcers) { - explgs = Slic3r::offset_ex(explgs, scale_(0.5)); - } - for (ExPolygons& explgs : custom_seam.blockers) { - explgs = Slic3r::offset_ex(explgs, scale_(0.5)); - } + const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; + float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); + for (ExPolygons& explgs : custom_seam.enforcers) + explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); + for (ExPolygons& explgs : custom_seam.blockers) + explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); } @@ -2696,15 +2703,7 @@ static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polyg return polygon.points.begin() + i_min; } -std::vector polygon_parameter_by_length(const Polygon &polygon) -{ - // Parametrize the polygon by its length. - std::vector lengths(polygon.points.size()+1, 0.); - for (size_t i = 1; i < polygon.points.size(); ++ i) - lengths[i] = lengths[i-1] + (polygon.points[i] - polygon.points[i-1]).cast().norm(); - lengths.back() = lengths[lengths.size()-2] + (polygon.points.front() - polygon.points.back()).cast().norm(); - return lengths; -} + std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) { @@ -2761,6 +2760,136 @@ std::vector polygon_angles_at_vertices(const Polygon &polygon, const std: return angles; } + + + +// Go through the polygon, identify points inside support enforcers and return +// indices of points in the middle of each enforcer (measured along the contour). +static std::vector find_enforcer_centers(const Polygon& polygon, + const std::vector& lengths, + const std::vector& enforcers_idxs) +{ + std::vector out; + assert(polygon.points.size()+1 == lengths.size()); + assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end())); + if (polygon.size() < 2 || enforcers_idxs.empty()) + return out; + + auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t { + assert(end_idx >= start_idx); + if (start_idx == end_idx) + return start_idx; + float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]); + auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c); + int ret = it - lengths.begin(); + return ret; + }; + + int last_enforcer_start_idx = enforcers_idxs.front(); + bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1; + + for (size_t i=0; i t_e) ? t_s + half_dist : t_e - half_dist; + + auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); + out[0] = it - lengths.begin(); + if (out[0] == lengths.size() - 1) + --out[0]; + assert(out[0] < lengths.size() - 1); + } + } + return out; +} + + +void CustomSeam::penalize_polygon(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id) const +{ + std::vector enforcers_idxs; + std::vector blockers_idxs; + this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs); + + for (size_t i : enforcers_idxs) { + assert(i < penalties.size()); + penalties[i] -= float(ENFORCER_BLOCKER_PENALTY); + } + for (size_t i : blockers_idxs) { + assert(i < penalties.size()); + penalties[i] += float(ENFORCER_BLOCKER_PENALTY); + } + std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); + for (size_t idx : enf_centers) { + assert(idx < penalties.size()); + penalties[idx] -= 1000.f; + } + +// ////////////////////// +// std::ostringstream os; +// os << std::setw(3) << std::setfill('0') << layer_id; +// int a = scale_(15.); +// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); +// /*if (! m_custom_seam.enforcers.empty()) +// svg.draw(m_custom_seam.enforcers[layer_id], "blue"); +// if (! m_custom_seam.blockers.empty()) +// svg.draw(m_custom_seam.blockers[layer_id], "red");*/ + +// size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); + +// //svg.draw(polygon.points[idx_min], "red", 6e5); +// for (size_t i=0; i *lower_layer_edge_grid) { // get a copy; don't modify the orientation of the original loop object otherwise @@ -2804,6 +2933,12 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); + if (m_custom_seam.is_on_layer(m_layer->id())) { + // Seam enf/blockers can begin and end in between the original vertices. + // Let add extra points in between and update the leghths. + polygon.densify(scale_(0.2f)); + } + // Retrieve the last start position for this object. float last_pos_weight = 1.f; @@ -2829,7 +2964,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou } // Parametrize the polygon by its length. - std::vector lengths = polygon_parameter_by_length(polygon); + std::vector lengths = polygon.parameter_by_length(); // For each polygon point, store a penalty. // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. @@ -2870,8 +3005,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Penalty for overhangs. if (lower_layer_edge_grid && (*lower_layer_edge_grid)) { // Use the edge grid distance field structure over the lower layer to calculate overhangs. - coord_t nozzle_r = coord_t(floor(scale_(0.5 * nozzle_dmr) + 0.5)); - coord_t search_r = coord_t(floor(scale_(0.8 * nozzle_dmr) + 0.5)); + coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); + coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); for (size_t i = 0; i < polygon.points.size(); ++ i) { const Point &p = polygon.points[i]; coordf_t dist; @@ -2888,10 +3023,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Penalty according to custom seam selection. This one is huge compared to // the others so that points outside enforcers/inside blockers never win. - for (size_t i = 0; i < polygon.points.size(); ++ i) { - const Point &p = polygon.points[i]; - penalties[i] -= float(100000 * m_custom_seam.get_point_status(p, m_layer->id())); - } + m_custom_seam.penalize_polygon(polygon, penalties, lengths, m_layer->id()); // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); @@ -2906,7 +3038,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou float penalty_max = std::max(penalty_min, penalty_aligned); float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); - if (penalty_diff_rel < 0.05) { + if (std::abs(penalty_diff_rel) < 0.05) { // Penalty of the aligned point is very close to the minimum penalty. // Align the seams as accurately as possible. idx_min = last_pos_proj_idx; @@ -2914,19 +3046,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou m_seam_position[m_layer->object()] = polygon.points[idx_min]; } -////////////////////// -// int layer_id = m_layer->id(); -// std::ostringstream os; -// os << std::setw(3) << std::setfill('0') << layer_id; -// int a = scale_(15.); -// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); -// if (! m_custom_seam.enforcers.empty()) -// svg.draw(m_custom_seam.enforcers[layer_id], "blue"); -// if (! m_custom_seam.blockers.empty()) -// svg.draw(m_custom_seam.blockers[layer_id], "red"); -// svg.draw(polygon.points, "black"); -//////////////////// - // Export the contour into a SVG file. #if 0 diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 1004a8efcd..43f3382350 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -74,9 +74,21 @@ struct CustomSeam { std::vector enforcers; std::vector blockers; - // Finds whether the point is inside an enforcer/blockers. - // Returns +1, 0 or -1. - int get_point_status(const Point& pt, size_t layer_id) const; + // Get indices of points inside enforcers and blockers. + void get_indices(size_t layer_id, + const Polygon& polygon, + std::vector& enforcers_idxs, + std::vector& blockers_idxs) const; + bool is_on_layer(size_t layer_id) const { + return ! ((enforcers.empty() || enforcers[layer_id].empty()) + && (blockers.empty() || blockers[layer_id].empty())); + } + void penalize_polygon(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id) const; + + static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; }; class OozePrevention { diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 48e63dab31..5a6ce107be 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -259,6 +259,44 @@ Point Polygon::point_projection(const Point &point) const return proj; } +std::vector Polygon::parameter_by_length() const +{ + // Parametrize the polygon by its length. + std::vector lengths(points.size()+1, 0.); + for (size_t i = 1; i < points.size(); ++ i) + lengths[i] = lengths[i-1] + (points[i] - points[i-1]).cast().norm(); + lengths.back() = lengths[lengths.size()-2] + (points.front() - points.back()).cast().norm(); + return lengths; +} + +void Polygon::densify(float min_length, std::vector* lengths_ptr) +{ + std::vector lengths_local; + std::vector& lengths = lengths_ptr ? *lengths_ptr : lengths_local; + + if (! lengths_ptr) { + // Length parametrization has not been provided. Calculate our own. + lengths = this->parameter_by_length(); + } + + assert(points.size() == lengths.size() - 1); + + for (size_t j=1; j<=points.size(); ++j) { + bool last = j == points.size(); + int i = last ? 0 : j; + + if (lengths[j] - lengths[j-1] > min_length) { + Point diff = points[i] - points[j-1]; + float diff_len = lengths[j] - lengths[j-1]; + float r = (min_length/diff_len); + Point new_pt = points[j-1] + Point(r*diff[0], r*diff[1]); + points.insert(points.begin() + j, new_pt); + lengths.insert(lengths.begin() + j, lengths[j-1] + min_length); + } + } + assert(points.size() == lengths.size() - 1); +} + BoundingBox get_extents(const Points &points) { return BoundingBox(points); diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index ab7c171e3c..ae3a71d416 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -61,12 +61,14 @@ public: bool contains(const Point &point) const; Polygons simplify(double tolerance) const; void simplify(double tolerance, Polygons &polygons) const; + void densify(float min_length, std::vector* lengths = nullptr); void triangulate_convex(Polygons* polygons) const; Point centroid() const; Points concave_points(double angle = PI) const; Points convex_points(double angle = PI) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; + std::vector parameter_by_length() const; }; inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; } From e78221409a1127ef30fe24fae80ff84150449671 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 9 Sep 2020 13:21:39 +0200 Subject: [PATCH 529/826] Renamed CustomSeam to SeamPlacer, move to a separate file --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 505 +--------------------------- src/libslic3r/GCode.hpp | 25 +- src/libslic3r/GCode/SeamPlacer.cpp | 520 +++++++++++++++++++++++++++++ src/libslic3r/GCode/SeamPlacer.hpp | 51 +++ 5 files changed, 586 insertions(+), 517 deletions(-) create mode 100644 src/libslic3r/GCode/SeamPlacer.cpp create mode 100644 src/libslic3r/GCode/SeamPlacer.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3d241dd371..cde2e9eab5 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -95,6 +95,8 @@ add_library(libslic3r STATIC GCode/PrintExtents.hpp GCode/SpiralVase.cpp GCode/SpiralVase.hpp + GCode/SeamPlacer.cpp + GCode/SeamPlacer.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp GCode/WipeTower.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 63dde66064..4da1edd7fc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -176,38 +176,6 @@ namespace Slic3r { } - void CustomSeam::get_indices(size_t layer_id, - const Polygon& polygon, - std::vector& enforcers_idxs, - std::vector& blockers_idxs) const - { - enforcers_idxs.clear(); - blockers_idxs.clear(); - - // FIXME: This is quadratic and it should be improved, maybe by building - // an AABB tree (or at least utilize bounding boxes). - for (size_t i=0; iproject_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, custom_seam.enforcers); - po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, custom_seam.blockers); - } - const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; - float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); - for (ExPolygons& explgs : custom_seam.enforcers) - explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); - for (ExPolygons& explgs : custom_seam.blockers) - explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); - } - - static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) { // Calculate wiping points if needed @@ -1487,7 +1439,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu print.throw_if_canceled(); // Collect custom seam data from all objects. - DoExport::collect_custom_seam(print, m_custom_seam); + m_seam_placer.init(print); if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. @@ -2602,293 +2554,7 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } -// Return a value in <0, 1> of a cubic B-spline kernel centered around zero. -// The B-spline is re-scaled so it has value 1 at zero. -static inline float bspline_kernel(float x) -{ - x = std::abs(x); - if (x < 1.f) { - return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; - } - else if (x < 2.f) { - x -= 1.f; - float x2 = x * x; - float x3 = x2 * x; - return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; - } - else - return 0; -} -static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) -{ - // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. - // Solved by sympy package: -/* -from sympy import * -(x,a,b,c,d,r,z)=symbols('x a b c d r z') -p = a + b*x + c*x*x + d*x*x*x -p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) -from sympy.plotting import plot -plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) -*/ - if (overlap_distance < - nozzle_r) { - // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. - return 0.f; - } else { - float x = overlap_distance / nozzle_r; - float x2 = x * x; - float x3 = x2 * x; - return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); - } -} - -static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) -{ - assert(polygon.points.size() >= 2); - if (polygon.points.size() <= 1) - if (polygon.points.size() == 1) - return polygon.points.begin(); - - Point pt_min; - double d_min = std::numeric_limits::max(); - size_t i_min = size_t(-1); - - for (size_t i = 0; i < polygon.points.size(); ++ i) { - size_t j = i + 1; - if (j == polygon.points.size()) - j = 0; - const Point &p1 = polygon.points[i]; - const Point &p2 = polygon.points[j]; - const Slic3r::Point v_seg = p2 - p1; - const Slic3r::Point v_pt = pt - p1; - const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1)); - int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1)); - if (t_pt < 0) { - // Closest to p1. - double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1))); - if (dabs < d_min) { - d_min = dabs; - i_min = i; - pt_min = p1; - } - } - else if (t_pt > l2_seg) { - // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. - continue; - } else { - // Closest to the segment. - assert(t_pt >= 0 && t_pt <= l2_seg); - int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1)); - double d = double(d_seg) / sqrt(double(l2_seg)); - double dabs = std::abs(d); - if (dabs < d_min) { - d_min = dabs; - i_min = i; - // Evaluate the foot point. - pt_min = p1; - double linv = double(d_seg) / double(l2_seg); - pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5)); - pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); - assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); - } - } - } - - assert(i_min != size_t(-1)); - if ((pt_min - polygon.points[i_min]).cast().norm() > eps) { - // Insert a new point on the segment i_min, i_min+1. - return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); - } - return polygon.points.begin() + i_min; -} - - - -std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) -{ - assert(polygon.points.size() + 1 == lengths.size()); - if (min_arm_length > 0.25f * lengths.back()) - min_arm_length = 0.25f * lengths.back(); - - // Find the initial prev / next point span. - size_t idx_prev = polygon.points.size(); - size_t idx_curr = 0; - size_t idx_next = 1; - while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) - -- idx_prev; - while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) - ++ idx_next; - - std::vector angles(polygon.points.size(), 0.f); - for (; idx_curr < polygon.points.size(); ++ idx_curr) { - // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. - if (idx_prev >= idx_curr) { - while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) - ++ idx_prev; - if (idx_prev == polygon.points.size()) - idx_prev = 0; - } - while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) - ++ idx_prev; - // Move idx_prev one step back. - if (idx_prev == 0) - idx_prev = polygon.points.size() - 1; - else - -- idx_prev; - // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. - if (idx_curr <= idx_next) { - while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) - ++ idx_next; - if (idx_next == polygon.points.size()) - idx_next = 0; - } - while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) - ++ idx_next; - // Calculate angle between idx_prev, idx_curr, idx_next. - const Point &p0 = polygon.points[idx_prev]; - const Point &p1 = polygon.points[idx_curr]; - const Point &p2 = polygon.points[idx_next]; - const Point v1 = p1 - p0; - const Point v2 = p2 - p1; - int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1)); - int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0)); - float angle = float(atan2(double(cross), double(dot))); - angles[idx_curr] = angle; - } - - return angles; -} - - - - -// Go through the polygon, identify points inside support enforcers and return -// indices of points in the middle of each enforcer (measured along the contour). -static std::vector find_enforcer_centers(const Polygon& polygon, - const std::vector& lengths, - const std::vector& enforcers_idxs) -{ - std::vector out; - assert(polygon.points.size()+1 == lengths.size()); - assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end())); - if (polygon.size() < 2 || enforcers_idxs.empty()) - return out; - - auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t { - assert(end_idx >= start_idx); - if (start_idx == end_idx) - return start_idx; - float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]); - auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c); - int ret = it - lengths.begin(); - return ret; - }; - - int last_enforcer_start_idx = enforcers_idxs.front(); - bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1; - - for (size_t i=0; i t_e) ? t_s + half_dist : t_e - half_dist; - - auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); - out[0] = it - lengths.begin(); - if (out[0] == lengths.size() - 1) - --out[0]; - assert(out[0] < lengths.size() - 1); - } - } - return out; -} - - -void CustomSeam::penalize_polygon(const Polygon& polygon, - std::vector& penalties, - const std::vector& lengths, - int layer_id) const -{ - std::vector enforcers_idxs; - std::vector blockers_idxs; - this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs); - - for (size_t i : enforcers_idxs) { - assert(i < penalties.size()); - penalties[i] -= float(ENFORCER_BLOCKER_PENALTY); - } - for (size_t i : blockers_idxs) { - assert(i < penalties.size()); - penalties[i] += float(ENFORCER_BLOCKER_PENALTY); - } - std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); - for (size_t idx : enf_centers) { - assert(idx < penalties.size()); - penalties[idx] -= 1000.f; - } - -// ////////////////////// -// std::ostringstream os; -// os << std::setw(3) << std::setfill('0') << layer_id; -// int a = scale_(15.); -// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); -// /*if (! m_custom_seam.enforcers.empty()) -// svg.draw(m_custom_seam.enforcers[layer_id], "blue"); -// if (! m_custom_seam.blockers.empty()) -// svg.draw(m_custom_seam.blockers[layer_id], "red");*/ - -// size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - -// //svg.draw(polygon.points[idx_min], "red", 6e5); -// for (size_t i=0; i *lower_layer_edge_grid) { @@ -2928,167 +2594,18 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou Point last_pos = this->last_pos(); if (m_config.spiral_vase) { loop.split_at(last_pos, false); - } else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) { - Polygon polygon = loop.polygon(); - const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); - const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); - - if (m_custom_seam.is_on_layer(m_layer->id())) { - // Seam enf/blockers can begin and end in between the original vertices. - // Let add extra points in between and update the leghths. - polygon.densify(scale_(0.2f)); - } - - // Retrieve the last start position for this object. - float last_pos_weight = 1.f; - - if (seam_position == spAligned) { - // Seam is aligned to the seam at the preceding layer. - if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) { - last_pos = m_seam_position[m_layer->object()]; - last_pos_weight = 1.f; - } - } - else if (seam_position == spRear) { - // Object is centered around (0,0) in its current coordinate system. - last_pos.x() = 0; - last_pos.y() += coord_t(3. * m_layer->object()->bounding_box().radius()); - last_pos_weight = 5.f; - } - - // Insert a projection of last_pos into the polygon. - size_t last_pos_proj_idx; - { - auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); - last_pos_proj_idx = it - polygon.points.begin(); - } - - // Parametrize the polygon by its length. - std::vector lengths = polygon.parameter_by_length(); - - // For each polygon point, store a penalty. - // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. - std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); - // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. - const float penaltyConvexVertex = 1.f; - const float penaltyFlatSurface = 5.f; - const float penaltyOverhangHalf = 10.f; - // Penalty for visible seams. - for (size_t i = 0; i < polygon.points.size(); ++ i) { - float ccwAngle = penalties[i]; - if (was_clockwise) - ccwAngle = - ccwAngle; - float penalty = 0; - if (ccwAngle <- float(0.6 * PI)) - // Sharp reflex vertex. We love that, it hides the seam perfectly. - penalty = 0.f; - else if (ccwAngle > float(0.6 * PI)) - // Seams on sharp convex vertices are more visible than on reflex vertices. - penalty = penaltyConvexVertex; - else if (ccwAngle < 0.f) { - // Interpolate penalty between maximum and zero. - penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } else { - assert(ccwAngle >= 0.f); - // Interpolate penalty between maximum and the penalty for a convex vertex. - penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } - // Give a negative penalty for points close to the last point or the prefered seam location. - float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? - std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : - std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); - float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr - penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); - penalties[i] = std::max(0.f, penalty); - } - - // Penalty for overhangs. - if (lower_layer_edge_grid && (*lower_layer_edge_grid)) { - // Use the edge grid distance field structure over the lower layer to calculate overhangs. - coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); - coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); - for (size_t i = 0; i < polygon.points.size(); ++ i) { - const Point &p = polygon.points[i]; - coordf_t dist; - // Signed distance is positive outside the object, negative inside the object. - // The point is considered at an overhang, if it is more than nozzle radius - // outside of the lower layer contour. - [[maybe_unused]] bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); - // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, - // then the signed distnace shall always be known. - assert(found); - penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); - } - } - - // Penalty according to custom seam selection. This one is huge compared to - // the others so that points outside enforcers/inside blockers never win. - m_custom_seam.penalize_polygon(polygon, penalties, lengths, m_layer->id()); - - // Find a point with a minimum penalty. - size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - - // For all (aligned, nearest, rear) seams: - { - // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. - // In that case use last_pos_proj_idx instead. - float penalty_aligned = penalties[last_pos_proj_idx]; - float penalty_min = penalties[idx_min]; - float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); - float penalty_max = std::max(penalty_min, penalty_aligned); - float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; - // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); - if (std::abs(penalty_diff_rel) < 0.05) { - // Penalty of the aligned point is very close to the minimum penalty. - // Align the seams as accurately as possible. - idx_min = last_pos_proj_idx; - } - m_seam_position[m_layer->object()] = polygon.points[idx_min]; - } - - - // Export the contour into a SVG file. - #if 0 - { - static int iRun = 0; - SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); - if (m_layer->lower_layer != NULL) - svg.draw(m_layer->lower_layer->slices); - for (size_t i = 0; i < loop.paths.size(); ++ i) - svg.draw(loop.paths[i].as_polyline(), "red"); - Polylines polylines; - for (size_t i = 0; i < loop.paths.size(); ++ i) - polylines.push_back(loop.paths[i].as_polyline()); - Slic3r::Polygons polygons; - coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); - coord_t delta = scale_(0.5*nozzle_dmr); - Slic3r::offset(polylines, &polygons, delta); -// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); - svg.draw(last_pos, "green", 3); - svg.draw(polygon.points[idx_min], "yellow", 3); - svg.Close(); - } - #endif - + } else { + const EdgeGrid::Grid* edge_grid_ptr = (lower_layer_edge_grid && *lower_layer_edge_grid) + ? lower_layer_edge_grid->get() + : nullptr; + Point seam = m_seam_placer.get_seam(m_layer->id(), seam_position, loop, + last_pos, EXTRUDER_CONFIG(nozzle_diameter), + (m_layer == NULL ? nullptr : m_layer->object()), + was_clockwise, edge_grid_ptr); // Split the loop at the point with a minium penalty. - if (!loop.split_at_vertex(polygon.points[idx_min])) + if (!loop.split_at_vertex(seam)) // The point is not in the original loop. Insert it. - loop.split_at(polygon.points[idx_min], true); - - } else if (seam_position == spRandom) { - if (loop.loop_role() == elrContourInternalPerimeter) { - // This loop does not contain any other loop. Set a random position. - // The other loops will get a seam close to the random point chosen - // on the inner most contour. - //FIXME This works correctly for inner contours first only. - //FIXME Better parametrize the loop by its length. - Polygon polygon = loop.polygon(); - Point centroid = polygon.centroid(); - last_pos = Point(polygon.bounding_box().max(0), centroid(1)); - last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid); - } - // Find the closest point, avoid overhangs. - loop.split_at(last_pos, true); + loop.split_at(seam, true); } // clip the path to avoid the extruder to get exactly on the first point of the loop; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 43f3382350..01650b6eeb 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -13,6 +13,7 @@ #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" +#include "GCode/SeamPlacer.hpp" #if ENABLE_GCODE_VIEWER #include "GCode/GCodeProcessor.hpp" #else @@ -70,27 +71,6 @@ private: }; -struct CustomSeam { - std::vector enforcers; - std::vector blockers; - - // Get indices of points inside enforcers and blockers. - void get_indices(size_t layer_id, - const Polygon& polygon, - std::vector& enforcers_idxs, - std::vector& blockers_idxs) const; - bool is_on_layer(size_t layer_id) const { - return ! ((enforcers.empty() || enforcers[layer_id].empty()) - && (blockers.empty() || blockers[layer_id].empty())); - } - void penalize_polygon(const Polygon& polygon, - std::vector& penalties, - const std::vector& lengths, - int layer_id) const; - - static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; -}; - class OozePrevention { public: bool enable; @@ -361,7 +341,7 @@ private: std::string set_extruder(unsigned int extruder_id, double print_z); // Cache for custom seam enforcers/blockers for each layer. - CustomSeam m_custom_seam; + SeamPlacer m_seam_placer; /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() @@ -401,7 +381,6 @@ private: // Current layer processed. Insequential printing mode, only a single copy will be printed. // In non-sequential mode, all its copies will be printed. const Layer* m_layer; - std::map m_seam_position; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? ExtrusionRole m_last_extrusion_role; diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp new file mode 100644 index 0000000000..da5289fe27 --- /dev/null +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -0,0 +1,520 @@ +#include "SeamPlacer.hpp" + +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/ClipperUtils.hpp" + +namespace Slic3r { + + + +static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) +{ + // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. + // Solved by sympy package: +/* +from sympy import * +(x,a,b,c,d,r,z)=symbols('x a b c d r z') +p = a + b*x + c*x*x + d*x*x*x +p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) +from sympy.plotting import plot +plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) +*/ + if (overlap_distance < - nozzle_r) { + // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. + return 0.f; + } else { + float x = overlap_distance / nozzle_r; + float x2 = x * x; + float x3 = x2 * x; + return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); + } +} + + + +// Return a value in <0, 1> of a cubic B-spline kernel centered around zero. +// The B-spline is re-scaled so it has value 1 at zero. +static inline float bspline_kernel(float x) +{ + x = std::abs(x); + if (x < 1.f) { + return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; + } + else if (x < 2.f) { + x -= 1.f; + float x2 = x * x; + float x3 = x2 * x; + return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; + } + else + return 0; +} + + + +static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) +{ + assert(polygon.points.size() >= 2); + if (polygon.points.size() <= 1) + if (polygon.points.size() == 1) + return polygon.points.begin(); + + Point pt_min; + double d_min = std::numeric_limits::max(); + size_t i_min = size_t(-1); + + for (size_t i = 0; i < polygon.points.size(); ++ i) { + size_t j = i + 1; + if (j == polygon.points.size()) + j = 0; + const Point &p1 = polygon.points[i]; + const Point &p2 = polygon.points[j]; + const Slic3r::Point v_seg = p2 - p1; + const Slic3r::Point v_pt = pt - p1; + const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1)); + int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1)); + if (t_pt < 0) { + // Closest to p1. + double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1))); + if (dabs < d_min) { + d_min = dabs; + i_min = i; + pt_min = p1; + } + } + else if (t_pt > l2_seg) { + // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. + continue; + } else { + // Closest to the segment. + assert(t_pt >= 0 && t_pt <= l2_seg); + int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1)); + double d = double(d_seg) / sqrt(double(l2_seg)); + double dabs = std::abs(d); + if (dabs < d_min) { + d_min = dabs; + i_min = i; + // Evaluate the foot point. + pt_min = p1; + double linv = double(d_seg) / double(l2_seg); + pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5)); + pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); + assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); + } + } + } + + assert(i_min != size_t(-1)); + if ((pt_min - polygon.points[i_min]).cast().norm() > eps) { + // Insert a new point on the segment i_min, i_min+1. + return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); + } + return polygon.points.begin() + i_min; +} + + + +static std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) +{ + assert(polygon.points.size() + 1 == lengths.size()); + if (min_arm_length > 0.25f * lengths.back()) + min_arm_length = 0.25f * lengths.back(); + + // Find the initial prev / next point span. + size_t idx_prev = polygon.points.size(); + size_t idx_curr = 0; + size_t idx_next = 1; + while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) + -- idx_prev; + while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) + ++ idx_next; + + std::vector angles(polygon.points.size(), 0.f); + for (; idx_curr < polygon.points.size(); ++ idx_curr) { + // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. + if (idx_prev >= idx_curr) { + while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) + ++ idx_prev; + if (idx_prev == polygon.points.size()) + idx_prev = 0; + } + while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) + ++ idx_prev; + // Move idx_prev one step back. + if (idx_prev == 0) + idx_prev = polygon.points.size() - 1; + else + -- idx_prev; + // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. + if (idx_curr <= idx_next) { + while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) + ++ idx_next; + if (idx_next == polygon.points.size()) + idx_next = 0; + } + while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) + ++ idx_next; + // Calculate angle between idx_prev, idx_curr, idx_next. + const Point &p0 = polygon.points[idx_prev]; + const Point &p1 = polygon.points[idx_curr]; + const Point &p2 = polygon.points[idx_next]; + const Point v1 = p1 - p0; + const Point v2 = p2 - p1; + int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1)); + int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0)); + float angle = float(atan2(double(cross), double(dot))); + angles[idx_curr] = angle; + } + + return angles; +} + + + +void SeamPlacer::init(const Print& print) +{ + m_enforcers.clear(); + m_blockers.clear(); + m_last_seam_position.clear(); + + for (const PrintObject* po : print.objects()) { + po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, m_enforcers); + po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, m_blockers); + } + const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; + float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); + for (ExPolygons& explgs : m_enforcers) + explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); + for (ExPolygons& explgs : m_blockers) + explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); +} + + + +Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_position, + const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr, + const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid) +{ + if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) { + Polygon polygon = loop.polygon(); + const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); + + if (this->is_custom(layer_idx)) { + // Seam enf/blockers can begin and end in between the original vertices. + // Let add extra points in between and update the leghths. + polygon.densify(scale_(0.2f)); + } + + // Retrieve the last start position for this object. + float last_pos_weight = 1.f; + + if (seam_position == spAligned) { + // Seam is aligned to the seam at the preceding layer. + if (po != nullptr && m_last_seam_position.count(po) > 0) { + last_pos = m_last_seam_position[po]; + last_pos_weight = 1.f; + } + } + else if (seam_position == spRear) { + // Object is centered around (0,0) in its current coordinate system. + last_pos.x() = 0; + last_pos.y() += coord_t(3. * po->bounding_box().radius()); + last_pos_weight = 5.f; + } + + // Insert a projection of last_pos into the polygon. + size_t last_pos_proj_idx; + { + auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); + last_pos_proj_idx = it - polygon.points.begin(); + } + + // Parametrize the polygon by its length. + std::vector lengths = polygon.parameter_by_length(); + + // For each polygon point, store a penalty. + // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. + std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); + // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. + const float penaltyConvexVertex = 1.f; + const float penaltyFlatSurface = 5.f; + const float penaltyOverhangHalf = 10.f; + // Penalty for visible seams. + for (size_t i = 0; i < polygon.points.size(); ++ i) { + float ccwAngle = penalties[i]; + if (was_clockwise) + ccwAngle = - ccwAngle; + float penalty = 0; + if (ccwAngle <- float(0.6 * PI)) + // Sharp reflex vertex. We love that, it hides the seam perfectly. + penalty = 0.f; + else if (ccwAngle > float(0.6 * PI)) + // Seams on sharp convex vertices are more visible than on reflex vertices. + penalty = penaltyConvexVertex; + else if (ccwAngle < 0.f) { + // Interpolate penalty between maximum and zero. + penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); + } else { + assert(ccwAngle >= 0.f); + // Interpolate penalty between maximum and the penalty for a convex vertex. + penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); + } + // Give a negative penalty for points close to the last point or the prefered seam location. + float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? + std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : + std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); + float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr + penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); + penalties[i] = std::max(0.f, penalty); + } + + // Penalty for overhangs. + if (lower_layer_edge_grid) { + // Use the edge grid distance field structure over the lower layer to calculate overhangs. + coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); + coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); + for (size_t i = 0; i < polygon.points.size(); ++ i) { + const Point &p = polygon.points[i]; + coordf_t dist; + // Signed distance is positive outside the object, negative inside the object. + // The point is considered at an overhang, if it is more than nozzle radius + // outside of the lower layer contour. + [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist); + // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, + // then the signed distnace shall always be known. + assert(found); + penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); + } + } + + // Penalty according to custom seam selection. This one is huge compared to + // the others so that points outside enforcers/inside blockers never win. + this->penalize_polygon(polygon, penalties, lengths, layer_idx); + + // Find a point with a minimum penalty. + size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); + + // For all (aligned, nearest, rear) seams: + { + // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. + // In that case use last_pos_proj_idx instead. + float penalty_aligned = penalties[last_pos_proj_idx]; + float penalty_min = penalties[idx_min]; + float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); + float penalty_max = std::max(penalty_min, penalty_aligned); + float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; + // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); + if (std::abs(penalty_diff_rel) < 0.05) { + // Penalty of the aligned point is very close to the minimum penalty. + // Align the seams as accurately as possible. + idx_min = last_pos_proj_idx; + } + m_last_seam_position[po] = polygon.points[idx_min]; + } + + + // Export the contour into a SVG file. + #if 0 + { + static int iRun = 0; + SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); + if (m_layer->lower_layer != NULL) + svg.draw(m_layer->lower_layer->slices); + for (size_t i = 0; i < loop.paths.size(); ++ i) + svg.draw(loop.paths[i].as_polyline(), "red"); + Polylines polylines; + for (size_t i = 0; i < loop.paths.size(); ++ i) + polylines.push_back(loop.paths[i].as_polyline()); + Slic3r::Polygons polygons; + coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); + coord_t delta = scale_(0.5*nozzle_dmr); + Slic3r::offset(polylines, &polygons, delta); + // for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); + svg.draw(last_pos, "green", 3); + svg.draw(polygon.points[idx_min], "yellow", 3); + svg.Close(); + } + #endif + return polygon.points[idx_min]; + + } else { // spRandom + if (loop.loop_role() == elrContourInternalPerimeter) { + // This loop does not contain any other loop. Set a random position. + // The other loops will get a seam close to the random point chosen + // on the inner most contour. + //FIXME This works correctly for inner contours first only. + //FIXME Better parametrize the loop by its length. + Polygon polygon = loop.polygon(); + Point centroid = polygon.centroid(); + last_pos = Point(polygon.bounding_box().max(0), centroid(1)); + last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid); + } + return last_pos; + } +} + + + + +void SeamPlacer::get_indices(size_t layer_id, + const Polygon& polygon, + std::vector& enforcers_idxs, + std::vector& blockers_idxs) const +{ + enforcers_idxs.clear(); + blockers_idxs.clear(); + + // FIXME: This is quadratic and it should be improved, maybe by building + // an AABB tree (or at least utilize bounding boxes). + for (size_t i=0; i find_enforcer_centers(const Polygon& polygon, + const std::vector& lengths, + const std::vector& enforcers_idxs) +{ + std::vector out; + assert(polygon.points.size()+1 == lengths.size()); + assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end())); + if (polygon.size() < 2 || enforcers_idxs.empty()) + return out; + + auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t { + assert(end_idx >= start_idx); + if (start_idx == end_idx) + return start_idx; + float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]); + auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c); + int ret = it - lengths.begin(); + return ret; + }; + + int last_enforcer_start_idx = enforcers_idxs.front(); + bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1; + + for (size_t i=0; i t_e) ? t_s + half_dist : t_e - half_dist; + + auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); + out[0] = it - lengths.begin(); + if (out[0] == lengths.size() - 1) + --out[0]; + assert(out[0] < lengths.size() - 1); + } + } + return out; +} + + + +void SeamPlacer::penalize_polygon(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id) const +{ + std::vector enforcers_idxs; + std::vector blockers_idxs; + this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs); + + for (size_t i : enforcers_idxs) { + assert(i < penalties.size()); + penalties[i] -= float(ENFORCER_BLOCKER_PENALTY); + } + for (size_t i : blockers_idxs) { + assert(i < penalties.size()); + penalties[i] += float(ENFORCER_BLOCKER_PENALTY); + } + std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); + for (size_t idx : enf_centers) { + assert(idx < penalties.size()); + penalties[idx] -= 1000.f; + } + +// ////////////////////// +// std::ostringstream os; +// os << std::setw(3) << std::setfill('0') << layer_id; +// int a = scale_(20.); +// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); +// /*if (! m_enforcers.empty()) +// svg.draw(m_enforcers[layer_id], "blue"); +// if (! m_blockers.empty()) +// svg.draw(m_blockers[layer_id], "red");*/ + +// size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); + +// //svg.draw(polygon.points[idx_min], "red", 6e5); +// for (size_t i=0; i m_enforcers; + std::vector m_blockers; + + std::map m_last_seam_position; + + // Get indices of points inside enforcers and blockers. + void get_indices(size_t layer_id, + const Polygon& polygon, + std::vector& enforcers_idxs, + std::vector& blockers_idxs) const; + + void penalize_polygon(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id) const; + + static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; +}; + + +} + +#endif // libslic3r_SeamPlacer_hpp_ From 5d6bf3261e85642611a1820240a62127965cf395 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 11 Sep 2020 08:58:59 +0200 Subject: [PATCH 530/826] fixed center-finding algorithm --- src/libslic3r/GCode/SeamPlacer.cpp | 127 +++++++++++++++-------------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index da5289fe27..0e3a99dc77 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -5,6 +5,7 @@ #include "libslic3r/BoundingBox.hpp" #include "libslic3r/EdgeGrid.hpp" #include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/SVG.hpp" namespace Slic3r { @@ -413,45 +414,47 @@ static std::vector find_enforcer_centers(const Polygon& polygon, }; int last_enforcer_start_idx = enforcers_idxs.front(); + bool first_pt_in_list = enforcers_idxs.front() != 0; bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1; + bool wrap_around = last_pt_in_list && first_pt_in_list; - for (size_t i=0; i t_e) ? t_s + half_dist : t_e - half_dist; - - auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); - out[0] = it - lengths.begin(); - if (out[0] == lengths.size() - 1) - --out[0]; - assert(out[0] < lengths.size() - 1); + if (wrap_around) { + // Update first center already found. + if (out.empty()) { + // Probably an enforcer around the whole contour. Return nothing. + return out; } + + // find last point of the enforcer at the beginning: + size_t idx = 0; + while (enforcers_idxs[idx]+1 == enforcers_idxs[idx+1]) + ++idx; + + float t_s = lengths[last_enforcer_start_idx]; + float t_e = lengths[idx]; + float half_dist = 0.5f * (t_e + lengths.back() - t_s); + float t_c = (half_dist > t_e) ? t_s + half_dist : t_e - half_dist; + + auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); + out[0] = it - lengths.begin(); + if (out[0] == lengths.size() - 1) + --out[0]; + assert(out[0] < lengths.size() - 1); } return out; } @@ -481,38 +484,38 @@ void SeamPlacer::penalize_polygon(const Polygon& polygon, penalties[idx] -= 1000.f; } -// ////////////////////// -// std::ostringstream os; -// os << std::setw(3) << std::setfill('0') << layer_id; -// int a = scale_(20.); -// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); -// /*if (! m_enforcers.empty()) -// svg.draw(m_enforcers[layer_id], "blue"); -// if (! m_blockers.empty()) -// svg.draw(m_blockers[layer_id], "red");*/ +//////////////////////// +// std::ostringstream os; +// os << std::setw(3) << std::setfill('0') << layer_id; +// int a = scale_(20.); +// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); +// /*if (! m_enforcers.empty()) +// svg.draw(m_enforcers[layer_id], "blue"); +// if (! m_blockers.empty()) +// svg.draw(m_blockers[layer_id], "red");*/ -// size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); +// size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); -// //svg.draw(polygon.points[idx_min], "red", 6e5); -// for (size_t i=0; i Date: Fri, 11 Sep 2020 14:24:15 +0200 Subject: [PATCH 531/826] Simple implementation of spRandom --- src/libslic3r/GCode/SeamPlacer.cpp | 388 +++++++++++++++++------------ src/libslic3r/GCode/SeamPlacer.hpp | 33 +-- 2 files changed, 248 insertions(+), 173 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 0e3a99dc77..73d5a89b3c 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -199,168 +199,231 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr, const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid) { - if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) { - Polygon polygon = loop.polygon(); - const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); + Polygon polygon = loop.polygon(); + const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); - if (this->is_custom(layer_idx)) { - // Seam enf/blockers can begin and end in between the original vertices. - // Let add extra points in between and update the leghths. - polygon.densify(scale_(0.2f)); + if (this->is_custom_seam_on_layer(layer_idx)) { + // Seam enf/blockers can begin and end in between the original vertices. + // Let add extra points in between and update the leghths. + polygon.densify(scale_(0.2f)); + } + + if (seam_position != spRandom) { + // Retrieve the last start position for this object. + float last_pos_weight = 1.f; + + if (seam_position == spAligned) { + // Seam is aligned to the seam at the preceding layer. + if (po != nullptr && m_last_seam_position.count(po) > 0) { + last_pos = m_last_seam_position[po]; + last_pos_weight = 1.f; } - - // Retrieve the last start position for this object. - float last_pos_weight = 1.f; - - if (seam_position == spAligned) { - // Seam is aligned to the seam at the preceding layer. - if (po != nullptr && m_last_seam_position.count(po) > 0) { - last_pos = m_last_seam_position[po]; - last_pos_weight = 1.f; - } - } - else if (seam_position == spRear) { - // Object is centered around (0,0) in its current coordinate system. - last_pos.x() = 0; - last_pos.y() += coord_t(3. * po->bounding_box().radius()); - last_pos_weight = 5.f; - } - - // Insert a projection of last_pos into the polygon. - size_t last_pos_proj_idx; - { - auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); - last_pos_proj_idx = it - polygon.points.begin(); - } - - // Parametrize the polygon by its length. - std::vector lengths = polygon.parameter_by_length(); - - // For each polygon point, store a penalty. - // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. - std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); - // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. - const float penaltyConvexVertex = 1.f; - const float penaltyFlatSurface = 5.f; - const float penaltyOverhangHalf = 10.f; - // Penalty for visible seams. - for (size_t i = 0; i < polygon.points.size(); ++ i) { - float ccwAngle = penalties[i]; - if (was_clockwise) - ccwAngle = - ccwAngle; - float penalty = 0; - if (ccwAngle <- float(0.6 * PI)) - // Sharp reflex vertex. We love that, it hides the seam perfectly. - penalty = 0.f; - else if (ccwAngle > float(0.6 * PI)) - // Seams on sharp convex vertices are more visible than on reflex vertices. - penalty = penaltyConvexVertex; - else if (ccwAngle < 0.f) { - // Interpolate penalty between maximum and zero. - penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } else { - assert(ccwAngle >= 0.f); - // Interpolate penalty between maximum and the penalty for a convex vertex. - penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } - // Give a negative penalty for points close to the last point or the prefered seam location. - float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? - std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : - std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); - float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr - penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); - penalties[i] = std::max(0.f, penalty); - } - - // Penalty for overhangs. - if (lower_layer_edge_grid) { - // Use the edge grid distance field structure over the lower layer to calculate overhangs. - coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); - coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); - for (size_t i = 0; i < polygon.points.size(); ++ i) { - const Point &p = polygon.points[i]; - coordf_t dist; - // Signed distance is positive outside the object, negative inside the object. - // The point is considered at an overhang, if it is more than nozzle radius - // outside of the lower layer contour. - [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist); - // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, - // then the signed distnace shall always be known. - assert(found); - penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); - } - } - - // Penalty according to custom seam selection. This one is huge compared to - // the others so that points outside enforcers/inside blockers never win. - this->penalize_polygon(polygon, penalties, lengths, layer_idx); - - // Find a point with a minimum penalty. - size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - - // For all (aligned, nearest, rear) seams: - { - // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. - // In that case use last_pos_proj_idx instead. - float penalty_aligned = penalties[last_pos_proj_idx]; - float penalty_min = penalties[idx_min]; - float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); - float penalty_max = std::max(penalty_min, penalty_aligned); - float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; - // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); - if (std::abs(penalty_diff_rel) < 0.05) { - // Penalty of the aligned point is very close to the minimum penalty. - // Align the seams as accurately as possible. - idx_min = last_pos_proj_idx; - } - m_last_seam_position[po] = polygon.points[idx_min]; - } - - - // Export the contour into a SVG file. - #if 0 - { - static int iRun = 0; - SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); - if (m_layer->lower_layer != NULL) - svg.draw(m_layer->lower_layer->slices); - for (size_t i = 0; i < loop.paths.size(); ++ i) - svg.draw(loop.paths[i].as_polyline(), "red"); - Polylines polylines; - for (size_t i = 0; i < loop.paths.size(); ++ i) - polylines.push_back(loop.paths[i].as_polyline()); - Slic3r::Polygons polygons; - coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); - coord_t delta = scale_(0.5*nozzle_dmr); - Slic3r::offset(polylines, &polygons, delta); - // for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); - svg.draw(last_pos, "green", 3); - svg.draw(polygon.points[idx_min], "yellow", 3); - svg.Close(); - } - #endif - return polygon.points[idx_min]; - - } else { // spRandom - if (loop.loop_role() == elrContourInternalPerimeter) { - // This loop does not contain any other loop. Set a random position. - // The other loops will get a seam close to the random point chosen - // on the inner most contour. - //FIXME This works correctly for inner contours first only. - //FIXME Better parametrize the loop by its length. - Polygon polygon = loop.polygon(); - Point centroid = polygon.centroid(); - last_pos = Point(polygon.bounding_box().max(0), centroid(1)); - last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid); - } - return last_pos; } + else if (seam_position == spRear) { + // Object is centered around (0,0) in its current coordinate system. + last_pos.x() = 0; + last_pos.y() += coord_t(3. * po->bounding_box().radius()); + last_pos_weight = 5.f; + } if (seam_position == spNearest) { + // last_pos already contains current nozzle position + } + + // Insert a projection of last_pos into the polygon. + size_t last_pos_proj_idx; + { + auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); + last_pos_proj_idx = it - polygon.points.begin(); + } + + // Parametrize the polygon by its length. + std::vector lengths = polygon.parameter_by_length(); + + // For each polygon point, store a penalty. + // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. + std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); + // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. + const float penaltyConvexVertex = 1.f; + const float penaltyFlatSurface = 5.f; + const float penaltyOverhangHalf = 10.f; + // Penalty for visible seams. + for (size_t i = 0; i < polygon.points.size(); ++ i) { + float ccwAngle = penalties[i]; + if (was_clockwise) + ccwAngle = - ccwAngle; + float penalty = 0; + if (ccwAngle <- float(0.6 * PI)) + // Sharp reflex vertex. We love that, it hides the seam perfectly. + penalty = 0.f; + else if (ccwAngle > float(0.6 * PI)) + // Seams on sharp convex vertices are more visible than on reflex vertices. + penalty = penaltyConvexVertex; + else if (ccwAngle < 0.f) { + // Interpolate penalty between maximum and zero. + penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); + } else { + assert(ccwAngle >= 0.f); + // Interpolate penalty between maximum and the penalty for a convex vertex. + penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); + } + // Give a negative penalty for points close to the last point or the prefered seam location. + float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? + std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : + std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); + float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr + penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); + penalties[i] = std::max(0.f, penalty); + } + + // Penalty for overhangs. + if (lower_layer_edge_grid) { + // Use the edge grid distance field structure over the lower layer to calculate overhangs. + coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); + coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); + for (size_t i = 0; i < polygon.points.size(); ++ i) { + const Point &p = polygon.points[i]; + coordf_t dist; + // Signed distance is positive outside the object, negative inside the object. + // The point is considered at an overhang, if it is more than nozzle radius + // outside of the lower layer contour. + [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist); + // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, + // then the signed distnace shall always be known. + assert(found); + penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); + } + } + + // Custom seam. Huge (negative) constant penalty is applied inside + // blockers (enforcers) to rule out points that should not win. + this->apply_custom_seam(polygon, penalties, lengths, layer_idx); + + // Find a point with a minimum penalty. + size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); + + // For all (aligned, nearest, rear) seams: + { + // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. + // In that case use last_pos_proj_idx instead. + float penalty_aligned = penalties[last_pos_proj_idx]; + float penalty_min = penalties[idx_min]; + float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); + float penalty_max = std::max(penalty_min, penalty_aligned); + float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; + // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); + if (std::abs(penalty_diff_rel) < 0.05) { + // Penalty of the aligned point is very close to the minimum penalty. + // Align the seams as accurately as possible. + idx_min = last_pos_proj_idx; + } + m_last_seam_position[po] = polygon.points[idx_min]; + } + + + // Export the contour into a SVG file. + #if 0 + { + static int iRun = 0; + SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); + if (m_layer->lower_layer != NULL) + svg.draw(m_layer->lower_layer->slices); + for (size_t i = 0; i < loop.paths.size(); ++ i) + svg.draw(loop.paths[i].as_polyline(), "red"); + Polylines polylines; + for (size_t i = 0; i < loop.paths.size(); ++ i) + polylines.push_back(loop.paths[i].as_polyline()); + Slic3r::Polygons polygons; + coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); + coord_t delta = scale_(0.5*nozzle_dmr); + Slic3r::offset(polylines, &polygons, delta); +// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); + svg.draw(last_pos, "green", 3); + svg.draw(polygon.points[idx_min], "yellow", 3); + svg.Close(); + } + #endif + return polygon.points[idx_min]; + + } else { // spRandom + if (loop.loop_role() == elrContourInternalPerimeter && loop.role() != erExternalPerimeter) { + // This loop does not contain any other loop. Set a random position. + // The other loops will get a seam close to the random point chosen + // on the innermost contour. + //FIXME This works correctly for inner contours first only. + last_pos = this->get_random_seam(layer_idx, polygon); + } + if (loop.role() == erExternalPerimeter && is_custom_seam_on_layer(layer_idx)) { + // There is a possibility that the loop will be influenced by custom + // seam enforcer/blocker. In this case do not inherit the seam + // from internal loops (which may conflict with the custom selection + // and generate another random one. + bool saw_custom = false; + Point candidate = this->get_random_seam(layer_idx, polygon, &saw_custom); + if (saw_custom) + last_pos = candidate; + } + return last_pos; + } +} + + +Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, + bool* saw_custom) const +{ + // Parametrize the polygon by its length. + std::vector lengths = polygon.parameter_by_length(); + + // Which of the points are inside enforcers/blockers? + std::vector enforcers_idxs; + std::vector blockers_idxs; + this->get_enforcers_and_blockers(layer_idx, polygon, enforcers_idxs, blockers_idxs); + + bool has_enforcers = ! enforcers_idxs.empty(); + bool has_blockers = ! blockers_idxs.empty(); + if (saw_custom) + *saw_custom = has_enforcers || has_blockers; + + // FIXME FIXME FIXME: This is just to test the outcome and whether it is + // reasonable. The algorithm should really sum the length of all available + // pieces, get a random length and find the respective point. + float rand_len = 0.f; + size_t pt_idx = 0; + do { + rand_len = lengths.back() * (rand()/float(RAND_MAX)); + auto it = std::lower_bound(lengths.begin(), lengths.end(), rand_len); + pt_idx = it == lengths.end() ? 0 : (it-lengths.begin()-1); + + // If there are blockers and the point is inside, repeat. + // If there are enforcers and the point is NOT inside, repeat. + } while ((has_blockers && std::binary_search(blockers_idxs.begin(), blockers_idxs.end(), pt_idx)) + || (has_enforcers && ! std::binary_search(enforcers_idxs.begin(), enforcers_idxs.end(), pt_idx))); + + if (! has_enforcers && ! has_blockers) { + // The polygon may be too coarse, calculate the point exactly. + bool last_seg = pt_idx == polygon.points.size()-1; + size_t next_idx = last_seg ? 0 : pt_idx+1; + const Point& prev = polygon.points[pt_idx]; + const Point& next = polygon.points[next_idx]; + assert(next_idx == 0 || pt_idx+1 == next_idx); + coordf_t diff_x = next.x() - prev.x(); + coordf_t diff_y = next.y() - prev.y(); + coordf_t dist = lengths[last_seg ? pt_idx+1 : next_idx] - lengths[pt_idx]; + return Point(prev.x() + (rand_len - lengths[pt_idx]) * (diff_x/dist), + prev.y() + (rand_len - lengths[pt_idx]) * (diff_y/dist)); + + } else { + // The polygon should be dense enough. + return polygon.points[pt_idx]; + } } -void SeamPlacer::get_indices(size_t layer_id, + + + + +void SeamPlacer::get_enforcers_and_blockers(size_t layer_id, const Polygon& polygon, std::vector& enforcers_idxs, std::vector& blockers_idxs) const @@ -388,6 +451,8 @@ void SeamPlacer::get_indices(size_t layer_id, } } } + + std::cout << layer_id << ": enforcers.size() = " << enforcers_idxs.size() << std::endl; } @@ -461,14 +526,19 @@ static std::vector find_enforcer_centers(const Polygon& polygon, -void SeamPlacer::penalize_polygon(const Polygon& polygon, - std::vector& penalties, - const std::vector& lengths, - int layer_id) const +void SeamPlacer::apply_custom_seam(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id) const { + if (! is_custom_seam_on_layer(layer_id)) + return; + + static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; + std::vector enforcers_idxs; std::vector blockers_idxs; - this->get_indices(layer_id, polygon, enforcers_idxs, blockers_idxs); + this->get_enforcers_and_blockers(layer_id, polygon, enforcers_idxs, blockers_idxs); for (size_t i : enforcers_idxs) { assert(i < penalties.size()); diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index af12dc9fbb..b55eef1b7e 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -15,11 +15,6 @@ class SeamPlacer { public: void init(const Print& print); - bool is_custom(size_t layer_id) const { - return ! ((m_enforcers.empty() || m_enforcers[layer_id].empty()) - && (m_blockers.empty() || m_blockers[layer_id].empty())); - } - Point get_seam(const size_t layer_idx, const SeamPosition seam_position, const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_diameter, const PrintObject* po, @@ -32,17 +27,27 @@ private: std::map m_last_seam_position; // Get indices of points inside enforcers and blockers. - void get_indices(size_t layer_id, - const Polygon& polygon, - std::vector& enforcers_idxs, - std::vector& blockers_idxs) const; + void get_enforcers_and_blockers(size_t layer_id, + const Polygon& polygon, + std::vector& enforcers_idxs, + std::vector& blockers_idxs) const; - void penalize_polygon(const Polygon& polygon, - std::vector& penalties, - const std::vector& lengths, - int layer_id) const; + // Apply penalties to points inside enforcers/blockers. + void apply_custom_seam(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id) const; - static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; + // Return random point of a polygon. The distribution will be uniform + // along the contour and account for enforcers and blockers. + Point get_random_seam(size_t layer_idx, const Polygon& polygon, + bool* saw_custom = nullptr) const; + + // Is there any enforcer/blocker on this layer? + bool is_custom_seam_on_layer(size_t layer_id) const { + return ! ((m_enforcers.empty() || m_enforcers[layer_id].empty()) + && (m_blockers.empty() || m_blockers[layer_id].empty())); + } }; From 8dd345ed4c776609e2c12d2e0fe08c65416fbaa1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 11 Sep 2020 16:27:06 +0200 Subject: [PATCH 532/826] use center of enforcer only with spAligned --- src/libslic3r/GCode/SeamPlacer.cpp | 30 +++++++++++++++++++++--------- src/libslic3r/GCode/SeamPlacer.hpp | 2 +- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 73d5a89b3c..b00b8ac430 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -9,6 +9,18 @@ namespace Slic3r { +// This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers). +static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; + +// In case there are custom enforcers/blockers, the loop polygon shall always have +// sides smaller than this (so it isn't limited to original resolution). +static constexpr float MINIMAL_POLYGON_SIDE = scale_(0.2f); + +// When spAligned is active and there is a support enforcer, +// add this penalty to its center. +static constexpr float ENFORCER_CENTER_PENALTY = -1e3; + + static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) @@ -205,7 +217,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit if (this->is_custom_seam_on_layer(layer_idx)) { // Seam enf/blockers can begin and end in between the original vertices. // Let add extra points in between and update the leghths. - polygon.densify(scale_(0.2f)); + polygon.densify(MINIMAL_POLYGON_SIDE); } if (seam_position != spRandom) { @@ -295,7 +307,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit // Custom seam. Huge (negative) constant penalty is applied inside // blockers (enforcers) to rule out points that should not win. - this->apply_custom_seam(polygon, penalties, lengths, layer_idx); + this->apply_custom_seam(polygon, penalties, lengths, layer_idx, seam_position); // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); @@ -529,13 +541,11 @@ static std::vector find_enforcer_centers(const Polygon& polygon, void SeamPlacer::apply_custom_seam(const Polygon& polygon, std::vector& penalties, const std::vector& lengths, - int layer_id) const + int layer_id, SeamPosition seam_position) const { if (! is_custom_seam_on_layer(layer_id)) return; - static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; - std::vector enforcers_idxs; std::vector blockers_idxs; this->get_enforcers_and_blockers(layer_id, polygon, enforcers_idxs, blockers_idxs); @@ -548,10 +558,12 @@ void SeamPlacer::apply_custom_seam(const Polygon& polygon, assert(i < penalties.size()); penalties[i] += float(ENFORCER_BLOCKER_PENALTY); } - std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); - for (size_t idx : enf_centers) { - assert(idx < penalties.size()); - penalties[idx] -= 1000.f; + if (seam_position == spAligned) { + std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); + for (size_t idx : enf_centers) { + assert(idx < penalties.size()); + penalties[idx] += ENFORCER_CENTER_PENALTY; + } } //////////////////////// diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index b55eef1b7e..e605e0540e 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -36,7 +36,7 @@ private: void apply_custom_seam(const Polygon& polygon, std::vector& penalties, const std::vector& lengths, - int layer_id) const; + int layer_id, SeamPosition seam_position) const; // Return random point of a polygon. The distribution will be uniform // along the contour and account for enforcers and blockers. From 5432784ed42146768e9a58d33048a11167527717 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 18 Sep 2020 12:15:38 +0200 Subject: [PATCH 533/826] Split generation of vertex and index buffers for toolpaths to reduce peak of memory used --- src/slic3r/GUI/GCodeViewer.cpp | 345 +++++++++++++++++++++++---------- 1 file changed, 238 insertions(+), 107 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 85ee1138a2..6bb62dbf05 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -237,7 +237,7 @@ void GCodeViewer::SequentialView::Marker::render() const ImGui::PopStyleVar(); } -const std::vector GCodeViewer::Extrusion_Role_Colors{ { +const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.75f, 0.75f, 0.75f }, // erNone { 1.00f, 0.90f, 0.30f }, // erPerimeter { 1.00f, 0.49f, 0.22f }, // erExternalPerimeter @@ -254,7 +254,7 @@ const std::vector GCodeViewer::Extrusion_Role_Colors{ { { 0.70f, 0.89f, 0.67f }, // erWipeTower { 0.37f, 0.82f, 0.58f }, // erCustom { 0.00f, 0.00f, 0.00f } // erMixed -} }; +}}; const std::vector GCodeViewer::Options_Colors {{ { 0.803f, 0.135f, 0.839f }, // Retractions @@ -287,10 +287,7 @@ const std::vector GCodeViewer::Range_Colors {{ bool GCodeViewer::init() { - bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - - for (size_t i = 0; i < m_buffers.size(); ++i) - { + for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; switch (buffer_type(i)) { @@ -304,7 +301,7 @@ bool GCodeViewer::init() { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; buffer.vertices.format = VBuffer::EFormat::Position; - buffer.shader = is_glsl_120 ? "options_120" : "options_110"; + buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } case EMoveType::Extrude: @@ -882,61 +879,78 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); - // format data into the buffers to be rendered as points - auto add_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); + auto log_memory_usage = [this](const std::string& label, const std::vector>& vertices, const std::vector& indices) { + long long vertices_size = 0; + for (size_t i = 0; i < vertices.size(); ++i) { + vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); + } + long long indices_size = 0; + for (size_t i = 0; i < indices.size(); ++i) { + for (size_t j = 0; j < indices[i].size(); ++j) { + indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i][j], unsigned int); } - buffer.add_path(curr, index_buffer_id, static_cast(buffer_indices.size()), static_cast(move_id)); + } + log_memory_used(label, vertices_size + indices_size); + }; + + // format data into the buffers to be rendered as points + auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, std::vector& buffer_vertices) { + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(curr.position[j]); + } + }; + auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id); buffer_indices.push_back(static_cast(buffer_indices.size())); }; // format data into the buffers to be rendered as lines - auto add_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, + TBuffer& buffer, std::vector& buffer_vertices) { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; + + auto add_vertex = [&buffer_vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { + // add position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(vertex.position[j]); + } + // add normal x component + buffer_vertices.push_back(normal_x); + }; + + // add previous vertex + add_vertex(prev); + // add current vertex + add_vertex(curr); + }; + auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { // x component of the normal to the current segment (the normal is parallel to the XY plane) float normal_x = (curr.position - prev.position).normalized()[1]; if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); - } - // add starting vertex normal x component - buffer_vertices.push_back(normal_x); // add starting index buffer_indices.push_back(static_cast(buffer_indices.size())); - buffer.add_path(curr, index_buffer_id, static_cast(buffer_indices.size() - 1), static_cast(move_id - 1)); + buffer.add_path(curr, index_buffer_id, buffer_indices.size() - 1, move_id - 1); buffer.paths.back().first.position = prev.position; } Path& last_path = buffer.paths.back(); if (last_path.first.i_id != last_path.last.i_id) { - // add previous vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); - } - // add previous vertex normal x component - buffer_vertices.push_back(normal_x); // add previous index buffer_indices.push_back(static_cast(buffer_indices.size())); } - // add current vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } - // add current vertex normal x component - buffer_vertices.push_back(normal_x); // add current index buffer_indices.push_back(static_cast(buffer_indices.size())); - last_path.last = { index_buffer_id, static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; }; // format data into the buffers to be rendered as solid - auto add_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + std::vector& buffer_vertices, size_t move_id) { static Vec3f prev_dir; static Vec3f prev_up; static float prev_length; @@ -950,11 +964,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) buffer_vertices.push_back(normal[j]); } }; - auto store_triangle = [](IndexBuffer& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { - buffer_indices.push_back(i1); - buffer_indices.push_back(i2); - buffer_indices.push_back(i3); - }; auto extract_position_at = [](const std::vector& vertices, size_t id) { return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); }; @@ -963,13 +972,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) vertices[id + 1] = position[1]; vertices[id + 2] = position[2]; }; - auto append_dummy_cap = [store_triangle](IndexBuffer& buffer_indices, unsigned int id) { - store_triangle(buffer_indices, id, id, id); - store_triangle(buffer_indices, id, id, id); - }; if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, index_buffer_id, static_cast(buffer_indices.size()), static_cast(move_id - 1)); + buffer.add_path(curr, 0, 0, move_id - 1); buffer.paths.back().first.position = prev.position; } @@ -979,7 +984,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); Vec3f left = -right; Vec3f up = right.cross(dir); - Vec3f bottom = -up; + Vec3f down = -up; Path& last_path = buffer.paths.back(); @@ -996,35 +1001,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // vertices 1st endpoint store_vertex(buffer_vertices, prev_pos + half_height * up, up); store_vertex(buffer_vertices, prev_pos + half_width * right, right); - store_vertex(buffer_vertices, prev_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, prev_pos + half_height * down, down); store_vertex(buffer_vertices, prev_pos + half_width * left, left); // vertices 2nd endpoint store_vertex(buffer_vertices, curr_pos + half_height * up, up); store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, curr_pos + half_height * down, down); store_vertex(buffer_vertices, curr_pos + half_width * left, left); - - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - - // dummy triangles outer corner cap - append_dummy_cap(buffer_indices, starting_vertices_size); - - // triangles sides - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); - - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); } else { // any other segment @@ -1101,8 +1085,105 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // vertices 2nd endpoint store_vertex(buffer_vertices, curr_pos + half_height * up, up); store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); + store_vertex(buffer_vertices, curr_pos + half_height * down, down); store_vertex(buffer_vertices, curr_pos + half_width * left, left); + } + + last_path.last = { 0, 0, move_id, curr.position }; + prev_dir = dir; + prev_up = up; + prev_length = length; + }; + auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + size_t& buffer_vertices_size, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + static Vec3f prev_dir; + static Vec3f prev_up; + static float prev_length; + auto store_triangle = [](IndexBuffer& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { + buffer_indices.push_back(i1); + buffer_indices.push_back(i2); + buffer_indices.push_back(i3); + }; + auto append_dummy_cap = [store_triangle](IndexBuffer& buffer_indices, unsigned int id) { + store_triangle(buffer_indices, id, id, id); + store_triangle(buffer_indices, id, id, id); + }; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id - 1); + buffer.paths.back().first.position = prev.position; + } + + unsigned int starting_vertices_size = static_cast(buffer_vertices_size); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f up = right.cross(dir); + + Path& last_path = buffer.paths.back(); + + float half_width = 0.5f * last_path.width; + float half_height = 0.5f * last_path.height; + + Vec3f prev_pos = prev.position - half_height * up; + Vec3f curr_pos = curr.position - half_height * up; + + float length = (curr_pos - prev_pos).norm(); + if (last_path.vertices_count() == 1) { + // 1st segment + buffer_vertices_size += 8; + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + + // dummy triangles outer corner cap + append_dummy_cap(buffer_indices, starting_vertices_size); + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); + } + else { + // any other segment + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + } + + Vec3f displacement_vec = displacement * prev_dir; + bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; + + bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + if (!is_sharp) { + if (can_displace) { + if (is_right_turn) + left_displaced = true; + else + right_displaced = true; + } + } + + buffer_vertices_size += 6; // triangles starting cap store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); @@ -1143,18 +1224,18 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); } - last_path.last = { index_buffer_id, static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; prev_dir = dir; prev_up = up; prev_length = length; }; - // toolpaths data -> extract from result + // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. + // the data are deleted as soon as they are sent to the gpu. std::vector> vertices(m_buffers.size()); std::vector indices(m_buffers.size()); - for (auto i : indices) { - i.push_back(IndexBuffer()); - } + + // toolpaths data -> extract vertices from result for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) @@ -1166,6 +1247,71 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned char id = buffer_id(curr.type); TBuffer& buffer = m_buffers[id]; std::vector& buffer_vertices = vertices[id]; + + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Point: + { + add_vertices_as_point(curr, buffer_vertices); + break; + } + case TBuffer::ERenderPrimitiveType::Line: + { + add_vertices_as_line(prev, curr, buffer, buffer_vertices); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: + { + add_vertices_as_solid(prev, curr, buffer, buffer_vertices, i); + break; + } + } + } + + log_memory_usage("Loaded G-code generated vertex buffers, ", vertices, indices); + + // toolpaths data -> send vertices data to gpu + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& buffer = m_buffers[i]; + + const std::vector& buffer_vertices = vertices[i]; + buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); +#if ENABLE_GCODE_VIEWER_STATISTICS + m_statistics.vertices_gpu_size += buffer_vertices.size() * sizeof(float); + m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast(buffer.vertices.count)); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + glsafe(::glGenBuffers(1, &buffer.vertices.id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + + // dismiss vertices data, no more needed + std::vector>().swap(vertices); + + // toolpaths data -> extract indices from result + // ensure that at least one index buffer is defined for each multibuffer + for (auto i : indices) { + i.push_back(IndexBuffer()); + } + // paths may have been filled while extracting vertices, + // so reset them, they will be filled again while extracting indices + for (TBuffer& buffer : m_buffers) { + buffer.paths.clear(); + } + // variable used to keep track of the current size (in vertices) of the vertex buffer + size_t curr_buffer_vertices_size = 0; + for (size_t i = 0; i < m_moves_count; ++i) { + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + unsigned char id = buffer_id(curr.type); + TBuffer& buffer = m_buffers[id]; MultiIndexBuffer& buffer_indices = indices[id]; if (buffer_indices.empty()) buffer_indices.push_back(IndexBuffer()); @@ -1199,40 +1345,28 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { case TBuffer::ERenderPrimitiveType::Point: { - add_as_point(curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + add_indices_as_point(curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } case TBuffer::ERenderPrimitiveType::Line: { - add_as_line(prev, curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + add_indices_as_line(prev, curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } case TBuffer::ERenderPrimitiveType::Triangle: { - add_as_solid(prev, curr, buffer, buffer_vertices, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + add_indices_as_solid(prev, curr, buffer, curr_buffer_vertices_size, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); break; } } } - // toolpaths data -> send data to gpu + log_memory_usage("Loaded G-code generated indices buffers, ", vertices, indices); + + // toolpaths data -> send indices data to gpu for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; - // vertices - const std::vector& buffer_vertices = vertices[i]; - buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.vertices_gpu_size += buffer_vertices.size() * sizeof(float); - m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast(buffer.vertices.count)); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - glsafe(::glGenBuffers(1, &buffer.vertices.id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - // indices for (size_t j = 0; j < indices[i].size(); ++j) { const IndexBuffer& buffer_indices = indices[i][j]; buffer.indices.push_back(IBuffer()); @@ -1268,6 +1402,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } #endif // ENABLE_GCODE_VIEWER_STATISTICS + // dismiss indices data, no more needed + std::vector().swap(indices); + // layers zs / roles / extruder ids / cp color ids -> extract from result for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; @@ -1291,8 +1428,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_layers_zs[k++] = (j > i + 1) ? (0.5 * (m_layers_zs[i] + m_layers_zs[j - 1])) : m_layers_zs[i]; i = j; } - if (k < n) + if (k < n) { m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); + m_layers_zs.shrink_to_fit(); + } // set layers z range m_layers_z_range = { m_layers_zs.front(), m_layers_zs.back() }; @@ -1300,22 +1439,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // roles -> remove duplicates std::sort(m_roles.begin(), m_roles.end()); m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); + m_roles.shrink_to_fit(); // extruder ids -> remove duplicates std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); + m_extruder_ids.shrink_to_fit(); - long long vertices_size = 0; - for (size_t i = 0; i < vertices.size(); ++i) { - vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); - } - long long indices_size = 0; - for (size_t i = 0; i < indices.size(); ++i) { - for (size_t j = 0; j < indices[i].size(); ++j) { - indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i][j], unsigned int); - } - } - log_memory_used("Loaded G-code extrusion paths, ", vertices_size + indices_size); + log_memory_usage("Loaded G-code generated extrusion paths, ", vertices, indices); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); From 8123930ee58bd7f7447d67aff2dcb236906b3e21 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 17 Sep 2020 15:15:53 +0200 Subject: [PATCH 534/826] Store seam history for more islands --- src/libslic3r/GCode/SeamPlacer.cpp | 89 ++++++++++++++++++++++++++---- src/libslic3r/GCode/SeamPlacer.hpp | 39 ++++++++++++- 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index b00b8ac430..a085f08ceb 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -10,7 +10,7 @@ namespace Slic3r { // This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers). -static constexpr float ENFORCER_BLOCKER_PENALTY = 1e6; +static constexpr float ENFORCER_BLOCKER_PENALTY = 100; // In case there are custom enforcers/blockers, the loop polygon shall always have // sides smaller than this (so it isn't limited to original resolution). @@ -18,7 +18,7 @@ static constexpr float MINIMAL_POLYGON_SIDE = scale_(0.2f); // When spAligned is active and there is a support enforcer, // add this penalty to its center. -static constexpr float ENFORCER_CENTER_PENALTY = -1e3; +static constexpr float ENFORCER_CENTER_PENALTY = -10.f; @@ -191,7 +191,8 @@ void SeamPlacer::init(const Print& print) { m_enforcers.clear(); m_blockers.clear(); - m_last_seam_position.clear(); + //m_last_seam_position.clear(); + m_seam_history.clear(); for (const PrintObject* po : print.objects()) { po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, m_enforcers); @@ -212,6 +213,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid) { Polygon polygon = loop.polygon(); + BoundingBox polygon_bb = polygon.bounding_box(); const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); if (this->is_custom_seam_on_layer(layer_idx)) { @@ -226,9 +228,13 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit if (seam_position == spAligned) { // Seam is aligned to the seam at the preceding layer. - if (po != nullptr && m_last_seam_position.count(po) > 0) { - last_pos = m_last_seam_position[po]; - last_pos_weight = 1.f; + if (po != nullptr) { + std::optional pos = m_seam_history.get_last_seam(po, layer_idx, polygon_bb); + if (pos.has_value()) { + //last_pos = m_last_seam_position[po]; + last_pos = pos.value(); + last_pos_weight = is_custom_enforcer_on_layer(layer_idx) ? 0.f : 1.f; + } } } else if (seam_position == spRear) { @@ -312,8 +318,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - // For all (aligned, nearest, rear) seams: - { + if (! spAligned || ! is_custom_enforcer_on_layer(layer_idx)) { // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. // In that case use last_pos_proj_idx instead. float penalty_aligned = penalties[last_pos_proj_idx]; @@ -327,9 +332,11 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit // Align the seams as accurately as possible. idx_min = last_pos_proj_idx; } - m_last_seam_position[po] = polygon.points[idx_min]; } + if (seam_position == spAligned && loop.role() == erExternalPerimeter) + m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb); + // Export the contour into a SVG file. #if 0 @@ -463,8 +470,6 @@ void SeamPlacer::get_enforcers_and_blockers(size_t layer_id, } } } - - std::cout << layer_id << ": enforcers.size() = " << enforcers_idxs.size() << std::endl; } @@ -602,4 +607,66 @@ void SeamPlacer::apply_custom_seam(const Polygon& polygon, } + +std::optional SeamHistory::get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb) +{ + assert(layer_id >= m_layer_id); + if (layer_id > m_layer_id) { + // Get seam was called for different layer than last time. + m_data_last_layer = m_data_this_layer; + m_data_this_layer.clear(); + m_layer_id = layer_id; + } + + + + std::optional out; + + auto seams_it = m_data_last_layer.find(po); + if (seams_it == m_data_last_layer.end()) + return out; + + const std::vector& seam_data_po = seams_it->second; + + // Find a bounding-box on the last layer that is close to one we see now. + double min_score = std::numeric_limits::max(); + for (const SeamPoint& sp : seam_data_po) { + const BoundingBox& bb = sp.m_island_bb; + + if (! bb.overlap(island_bb)) { + // This bb does not even overlap. It is likely unrelated. + continue; + } + + double score = std::pow(bb.min(0) - island_bb.min(0), 2.) + + std::pow(bb.min(1) - island_bb.min(1), 2.) + + std::pow(bb.max(0) - island_bb.max(0), 2.) + + std::pow(bb.max(1) - island_bb.max(1), 2.); + + if (score < min_score) { + min_score = score; + out = sp.m_pos; + } + } + + return out; +} + + + +void SeamHistory::add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb) +{ + m_data_this_layer[po].push_back({pos, island_bb});; +} + + + +void SeamHistory::clear() +{ + m_layer_id = 0; + m_data_last_layer.clear(); + m_data_this_layer.clear(); +} + + } diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index e605e0540e..e603b7d57b 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -1,8 +1,11 @@ #ifndef libslic3r_SeamPlacer_hpp_ #define libslic3r_SeamPlacer_hpp_ +#include + #include "libslic3r/ExPolygon.hpp" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/BoundingBox.hpp" namespace Slic3r { @@ -11,6 +14,27 @@ class ExtrusionLoop; class Print; namespace EdgeGrid { class Grid; } + +class SeamHistory { +public: + SeamHistory() { clear(); } + std::optional get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb); + void add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb); + void clear(); + +private: + struct SeamPoint { + Point m_pos; + BoundingBox m_island_bb; + }; + + std::map> m_data_last_layer; + std::map> m_data_this_layer; + size_t m_layer_id; +}; + + + class SeamPlacer { public: void init(const Print& print); @@ -24,7 +48,8 @@ private: std::vector m_enforcers; std::vector m_blockers; - std::map m_last_seam_position; + //std::map m_last_seam_position; + SeamHistory m_seam_history; // Get indices of points inside enforcers and blockers. void get_enforcers_and_blockers(size_t layer_id, @@ -45,8 +70,16 @@ private: // Is there any enforcer/blocker on this layer? bool is_custom_seam_on_layer(size_t layer_id) const { - return ! ((m_enforcers.empty() || m_enforcers[layer_id].empty()) - && (m_blockers.empty() || m_blockers[layer_id].empty())); + return is_custom_enforcer_on_layer(layer_id) + || is_custom_blocker_on_layer(layer_id); + } + + bool is_custom_enforcer_on_layer(size_t layer_id) const { + return (! m_enforcers.empty() && ! m_enforcers[layer_id].empty()); + } + + bool is_custom_blocker_on_layer(size_t layer_id) const { + return (! m_blockers.empty() && ! m_blockers[layer_id].empty()); } }; From 348c654c26d8eb8a7db283b85ec1787694aa5540 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 18 Sep 2020 13:35:17 +0200 Subject: [PATCH 535/826] Adaptive infill: Fixing compilation on Linux, WIP: Better chainining of infill lines. --- src/libslic3r/Fill/FillAdaptive.cpp | 80 ++++++++++++++++++++++------- src/libslic3r/Fill/FillBase.cpp | 18 ++++--- 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index b016242f48..54ac6c262a 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -434,6 +434,64 @@ static void generate_infill_lines_recursive( } } +#if 0 +// Collect the line segments. +static Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon) +{ + // Create line end point lookup. + struct LineEnd { + LineEnd(Line *line, bool start) : line(line), start(start) {} + Line *line; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? line->a : line->b; } + const Point& other_point() const { return start ? line->b : line->a; } + LineEnd other_end() const { return LineEnd(line, ! start); } + bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; } + }; + struct LineEndAccessor { + const Point* operator()(const LineEnd &pt) const { return &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(point_distance_epsilon); + for (const Line &line : lines) { + closest_end_point_lookup.insert(LineEnd(&line, true)); + closest_end_point_lookup.insert(LineEnd(&line, false)); + } + + // Chain the lines. + std::vector line_consumed(lines.size(), false); + static const double point_distance_epsilon2 = point_distance_epsilon * point_distance_epsilon; + Polylines out; + for (const Line &seed : lines) + if (! line_consumed[&seed - lines.data()]) { + line_consumed[&seed - lines.data()] = true; + closest_end_point_lookup.erase(LineEnd(&seed, false)); + closest_end_point_lookup.erase(LineEnd(&seed, true)); + Polyline pl { seed.a, seed.b }; + for (size_t round = 0; round < 2; ++ round) { + for (;;) { + auto [line_end, dist2] = closest_end_point_lookup.find(pl.last_point()); + if (line_end == nullptr || dist2 >= point_distance_epsilon2) + // Cannot extent in this direction. + break; + // Average the last point. + pl.points.back() = 0.5 * (pl.points.back() + line_end->point()); + // and extend with the new line segment. + pl.points.emplace_back(line_end->other_point()); + closest_end_point_lookup.erase(line_end); + closest_end_point_lookup.erase(line_end->other_end()); + line_consumed[line_end->line - lines.data()] = true; + } + // reverse and try the oter direction. + pl.reverse(); + } + out.emplace_back(std::move(pl)); + } + return out; +} +#endif + #ifndef NDEBUG // #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT #endif @@ -517,6 +575,7 @@ void Filler::_fill_surface_single( lines.emplace_back(line); } // Convert lines to polylines. + //FIXME chain the lines all_polylines.reserve(lines.size()); std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); } @@ -533,23 +592,8 @@ void Filler::_fill_surface_single( if (params.dont_connect) append(polylines_out, std::move(all_polylines)); - else { - Polylines boundary_polylines; - Polylines non_boundary_polylines; - for (const Polyline &polyline : all_polylines) - // connect_infill required all polylines to touch the boundary. - if (polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) - boundary_polylines.push_back(polyline); - else - non_boundary_polylines.push_back(polyline); - - if (!boundary_polylines.empty()) { - boundary_polylines = chain_polylines(boundary_polylines); - connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); - } - - append(polylines_out, std::move(non_boundary_polylines)); - } + else + connect_infill(chain_polylines(std::move(all_polylines)), expolygon, polylines_out, this->spacing, params); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT { @@ -618,7 +662,7 @@ OctreePtr build_octree(const indexed_triangle_set &triangle_mesh, coordf_t line_ auto octree = OctreePtr(new Octree(cube_center, cubes_properties)); if (cubes_properties.size() > 1) { - auto up_vector = support_overhangs_only ? transform_to_octree() * Vec3d(0., 0., 1.) : Vec3d(); + auto up_vector = support_overhangs_only ? Vec3d(transform_to_octree() * Vec3d(0., 0., 1.)) : Vec3d(); for (auto &tri : triangle_mesh.indices) { auto a = triangle_mesh.vertices[tri[0]].cast(); auto b = triangle_mesh.vertices[tri[1]].cast(); diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 43b5d464ab..fc5f548a30 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -847,8 +847,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ boundary.assign(boundary_src.holes.size() + 1, Points()); boundary_data.assign(boundary_src.holes.size() + 1, std::vector()); // Mapping the infill_ordered end point to a (contour, point) of boundary. - std::vector> map_infill_end_point_to_boundary; - map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(std::numeric_limits::max(), std::numeric_limits::max())); + std::vector> map_infill_end_point_to_boundary; + static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); + map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(boundary_idx_unconnected, boundary_idx_unconnected)); { // Project the infill_ordered end points onto boundary_src. std::vector> intersection_points; @@ -898,13 +899,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ contour_data.front().param = contour_data.back().param + (contour_dst.back().cast() - contour_dst.front().cast()).norm(); } -#ifndef NDEBUG assert(boundary.size() == boundary_src.num_contours()); - assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), +#if 0 + // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary. + assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), [&boundary](const std::pair &contour_point) { return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size(); })); -#endif /* NDEBUG */ +#endif } // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. @@ -935,9 +937,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ const Polyline &pl2 = infill_ordered[idx_chain]; const std::pair *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; const std::pair *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; - const std::vector &contour_data = boundary_data[cp1->first]; - if (cp1->first == cp2->first) { + if (cp1->first != boundary_idx_unconnected && cp1->first == cp2->first) { // End points on the same contour. Try to connect them. + const std::vector &contour_data = boundary_data[cp1->first]; float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param; float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param; float param_end = contour_data.front().param; @@ -964,7 +966,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ const std::pair *cp1prev = cp1 - 1; const std::pair *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; const std::pair *cp2next = cp2 + 1; - assert(cp1->first == cp2->first); + assert(cp1->first == cp2->first && cp1->first != boundary_idx_unconnected); std::vector &contour_data = boundary_data[cp1->first]; if (connection_cost.reversed) std::swap(cp1, cp2); From f2951b53c0f958dd443d44b2f6f0b687593153ed Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 18 Sep 2020 13:32:11 +0200 Subject: [PATCH 536/826] Fix build on Linux --- src/libslic3r/Fill/FillAdaptive.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 54ac6c262a..aed71aa723 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -204,7 +204,7 @@ void OctreeDeleter::operator()(Octree *p) { delete p; } -std::pair FillAdaptive::adaptive_fill_line_spacing(const PrintObject &print_object) +std::pair adaptive_fill_line_spacing(const PrintObject &print_object) { // Output, spacing for icAdaptiveCubic and icSupportCubic double adaptive_line_spacing = 0.; From cf50224248a50e48e84f8a763320160c951802e1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 18 Sep 2020 14:24:29 +0200 Subject: [PATCH 537/826] Fix build on macOS and one logic error --- src/libslic3r/GCode/SeamPlacer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index a085f08ceb..db31f8f67f 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -232,7 +232,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit std::optional pos = m_seam_history.get_last_seam(po, layer_idx, polygon_bb); if (pos.has_value()) { //last_pos = m_last_seam_position[po]; - last_pos = pos.value(); + last_pos = *pos; last_pos_weight = is_custom_enforcer_on_layer(layer_idx) ? 0.f : 1.f; } } @@ -318,7 +318,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - if (! spAligned || ! is_custom_enforcer_on_layer(layer_idx)) { + if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx)) { // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. // In that case use last_pos_proj_idx instead. float penalty_aligned = penalties[last_pos_proj_idx]; From 73d8bab4f8e6bbef606820f668487777145d779a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 21 Sep 2020 08:45:28 +0200 Subject: [PATCH 538/826] Fix confusing test fixes #4724 --- tests/sla_print/sla_print_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 1575ee0e6b..c1941c8d98 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -128,9 +128,9 @@ TEST_CASE("WingedPadAroundObjectIsValid", "[SLASupportGeneration]") { TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") { sla::SupportTreeConfig supportcfg; - supportcfg.object_elevation_mm = 5.; + supportcfg.object_elevation_mm = 10.; - for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname); + for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); } TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") { From 6cdb19971fa755032d40c05546c14b348caa47f9 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 21 Sep 2020 11:10:49 +0200 Subject: [PATCH 539/826] Fixed crash in Adaptive Cubic infill if just a single line was extracted. New function to chain lines, however not used by the Adaptive Cubic infill. --- src/libslic3r/Fill/FillAdaptive.cpp | 72 ++++------------------------- src/libslic3r/ShortestPath.cpp | 55 ++++++++++++++++++++++ src/libslic3r/ShortestPath.hpp | 2 + 3 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index aed71aa723..7023398867 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -361,8 +361,8 @@ static bool verify_traversal_order( c[1] - c[0], c[2] - c[0], c[3] - c[1], c[3] - c[2], c[3] - c[0], c[5] - c[4], c[6] - c[4], c[7] - c[5], c[7] - c[6], c[7] - c[4] }; - assert(std::abs(dirs[4].z()) < 0.001); - assert(std::abs(dirs[9].z()) < 0.001); + assert(std::abs(dirs[4].z()) < 0.005); + assert(std::abs(dirs[9].z()) < 0.005); assert(dirs[0].isApprox(dirs[3])); assert(dirs[1].isApprox(dirs[2])); assert(dirs[5].isApprox(dirs[8])); @@ -413,7 +413,7 @@ static void generate_infill_lines_recursive( Line new_line(Point::new_scale(from), Point::new_scale(to)); if (last_line.a.x() == std::numeric_limits::max()) { last_line.a = new_line.a; - } else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough) { + } else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough context.output_lines.emplace_back(last_line); last_line.a = new_line.a; } @@ -434,64 +434,6 @@ static void generate_infill_lines_recursive( } } -#if 0 -// Collect the line segments. -static Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon) -{ - // Create line end point lookup. - struct LineEnd { - LineEnd(Line *line, bool start) : line(line), start(start) {} - Line *line; - // Is it the start or end point? - bool start; - const Point& point() const { return start ? line->a : line->b; } - const Point& other_point() const { return start ? line->b : line->a; } - LineEnd other_end() const { return LineEnd(line, ! start); } - bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; } - }; - struct LineEndAccessor { - const Point* operator()(const LineEnd &pt) const { return &pt.point(); } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - ClosestPointLookupType closest_end_point_lookup(point_distance_epsilon); - for (const Line &line : lines) { - closest_end_point_lookup.insert(LineEnd(&line, true)); - closest_end_point_lookup.insert(LineEnd(&line, false)); - } - - // Chain the lines. - std::vector line_consumed(lines.size(), false); - static const double point_distance_epsilon2 = point_distance_epsilon * point_distance_epsilon; - Polylines out; - for (const Line &seed : lines) - if (! line_consumed[&seed - lines.data()]) { - line_consumed[&seed - lines.data()] = true; - closest_end_point_lookup.erase(LineEnd(&seed, false)); - closest_end_point_lookup.erase(LineEnd(&seed, true)); - Polyline pl { seed.a, seed.b }; - for (size_t round = 0; round < 2; ++ round) { - for (;;) { - auto [line_end, dist2] = closest_end_point_lookup.find(pl.last_point()); - if (line_end == nullptr || dist2 >= point_distance_epsilon2) - // Cannot extent in this direction. - break; - // Average the last point. - pl.points.back() = 0.5 * (pl.points.back() + line_end->point()); - // and extend with the new line segment. - pl.points.emplace_back(line_end->other_point()); - closest_end_point_lookup.erase(line_end); - closest_end_point_lookup.erase(line_end->other_end()); - line_consumed[line_end->line - lines.data()] = true; - } - // reverse and try the oter direction. - pl.reverse(); - } - out.emplace_back(std::move(pl)); - } - return out; -} -#endif - #ifndef NDEBUG // #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT #endif @@ -574,10 +516,14 @@ void Filler::_fill_surface_single( if (line.a.x() != std::numeric_limits::max()) lines.emplace_back(line); } +#if 0 + // Chain touching line segments, convert lines to polylines. + //all_polylines = chain_lines(lines, 300.); // SCALED_EPSILON is 100 and it is not enough +#else // Convert lines to polylines. - //FIXME chain the lines all_polylines.reserve(lines.size()); std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); +#endif } // Crop all polylines @@ -590,7 +536,7 @@ void Filler::_fill_surface_single( } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ - if (params.dont_connect) + if (params.dont_connect || all_polylines.size() <= 1) append(polylines_out, std::move(all_polylines)); else connect_infill(chain_polylines(std::move(all_polylines)), expolygon, polylines_out, this->spacing, params); diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 314bbf7163..3d5903df13 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1973,4 +1973,59 @@ std::vector chain_print_object_instances(const Print &prin return out; } +Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon) +{ + // Create line end point lookup. + struct LineEnd { + LineEnd(const Line *line, bool start) : line(line), start(start) {} + const Line *line; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? line->a : line->b; } + const Point& other_point() const { return start ? line->b : line->a; } + LineEnd other_end() const { return LineEnd(line, ! start); } + bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; } + }; + struct LineEndAccessor { + const Point* operator()(const LineEnd &pt) const { return &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(point_distance_epsilon); + for (const Line &line : lines) { + closest_end_point_lookup.insert(LineEnd(&line, true)); + closest_end_point_lookup.insert(LineEnd(&line, false)); + } + + // Chain the lines. + std::vector line_consumed(lines.size(), false); + static const double point_distance_epsilon2 = point_distance_epsilon * point_distance_epsilon; + Polylines out; + for (const Line &seed : lines) + if (! line_consumed[&seed - lines.data()]) { + line_consumed[&seed - lines.data()] = true; + closest_end_point_lookup.erase(LineEnd(&seed, false)); + closest_end_point_lookup.erase(LineEnd(&seed, true)); + Polyline pl { seed.a, seed.b }; + for (size_t round = 0; round < 2; ++ round) { + for (;;) { + auto [line_end, dist2] = closest_end_point_lookup.find(pl.last_point()); + if (line_end == nullptr || dist2 >= point_distance_epsilon2) + // Cannot extent in this direction. + break; + // Average the last point. + pl.points.back() = (0.5 * (pl.points.back().cast() + line_end->point().cast())).cast(); + // and extend with the new line segment. + pl.points.emplace_back(line_end->other_point()); + closest_end_point_lookup.erase(*line_end); + closest_end_point_lookup.erase(line_end->other_end()); + line_consumed[line_end->line - lines.data()] = true; + } + // reverse and try the oter direction. + pl.reverse(); + } + out.emplace_back(std::move(pl)); + } + return out; +} + } // namespace Slic3r diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp index 65d8b7f239..14912ee857 100644 --- a/src/libslic3r/ShortestPath.hpp +++ b/src/libslic3r/ShortestPath.hpp @@ -33,6 +33,8 @@ class Print; struct PrintInstance; std::vector chain_print_object_instances(const Print &print); +// Chain lines into polylines. +Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon); } // namespace Slic3r From 230dbb7394e2c2993183cc2bbb440216a8972bb6 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 22 Sep 2020 08:53:45 +0200 Subject: [PATCH 540/826] Adaptive Cubic infill: 1) Fixed a wrong offset when extracting infill lines from the octree. 2) Added a variant for testing triangle in a bounding sphere when buildind the octree. Currently not used as the box test is more tight. 3) "Bridging infill" regions are now triangulated and used to densify the octree as well to support the bridging infill correctly. --- src/libslic3r/Fill/FillAdaptive.cpp | 114 +++++++++++++++++++++++----- src/libslic3r/Fill/FillAdaptive.hpp | 5 +- src/libslic3r/PrintObject.cpp | 42 +++++++--- 3 files changed, 132 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 7023398867..eebded55bf 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -152,6 +152,67 @@ bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, return true; } +static double dist2_to_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &p) +{ + double out = std::numeric_limits::max(); + const Vec3d v1 = b - a; + auto l1 = v1.squaredNorm(); + const Vec3d v2 = c - b; + auto l2 = v2.squaredNorm(); + const Vec3d v3 = a - c; + auto l3 = v3.squaredNorm(); + + // Is the triangle valid? + if (l1 > 0. && l2 > 0. && l3 > 0.) + { + // 1) Project point into the plane of the triangle. + const Vec3d n = v1.cross(v2); + double d = (p - a).dot(n); + const Vec3d foot_pt = p - n * d / n.squaredNorm(); + + // 2) Maximum projection of n. + int proj_axis; + n.array().cwiseAbs().maxCoeff(&proj_axis); + + // 3) Test whether the foot_pt is inside the triangle. + { + auto inside_triangle = [](const Vec2d& v1, const Vec2d& v2, const Vec2d& v3, const Vec2d& pt) { + const double d1 = cross2(v1, pt); + const double d2 = cross2(v2, pt); + const double d3 = cross2(v3, pt); + // Testing both CCW and CW orientations. + return (d1 >= 0. && d2 >= 0. && d3 >= 0.) || (d1 <= 0. && d2 <= 0. && d3 <= 0.); + }; + bool inside; + switch (proj_axis) { + case 0: + inside = inside_triangle({v1.y(), v1.z()}, {v2.y(), v2.z()}, {v3.y(), v3.z()}, {foot_pt.y(), foot_pt.z()}); break; + case 1: + inside = inside_triangle({v1.z(), v1.x()}, {v2.z(), v2.x()}, {v3.z(), v3.x()}, {foot_pt.z(), foot_pt.x()}); break; + default: + assert(proj_axis == 2); + inside = inside_triangle({v1.x(), v1.y()}, {v2.x(), v2.y()}, {v3.x(), v3.y()}, {foot_pt.x(), foot_pt.y()}); break; + } + if (inside) + return (p - foot_pt).squaredNorm(); + } + + // 4) Find minimum distance to triangle vertices and edges. + out = std::min((p - a).squaredNorm(), std::min((p - b).squaredNorm(), (p - c).squaredNorm())); + auto t = (p - a).dot(v1); + if (t > 0. && t < l1) + out = std::min(out, (a + v1 * (t / l1) - p).squaredNorm()); + t = (p - b).dot(v2); + if (t > 0. && t < l2) + out = std::min(out, (b + v2 * (t / l2) - p).squaredNorm()); + t = (p - c).dot(v3); + if (t > 0. && t < l3) + out = std::min(out, (c + v3 * (t / l3) - p).squaredNorm()); + } + + return out; +} + // Ordering of children cubes. static const std::array child_centers { Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), @@ -295,7 +356,6 @@ struct FillContext }; FillContext(const Octree &octree, double z_position, int direction_idx) : - origin_world(octree.origin), cubes_properties(octree.cubes_properties), z_position(z_position), traversal_order(child_traversal_order[direction_idx]), @@ -309,8 +369,6 @@ struct FillContext // Rotate the point, uses the same convention as Point::rotate(). Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); } - // Center of the root cube in the Octree coordinate system. - const Vec3d origin_world; const std::vector &cubes_properties; // Top of the current layer. const double z_position; @@ -402,7 +460,7 @@ static void generate_infill_lines_recursive( from = context.rotate(from); to = context.rotate(to); // Relative to cube center - Vec2d offset(cube->center.x() - context.origin_world.x(), cube->center.y() - context.origin_world.y()); + const Vec2d offset(cube->center.x(), cube->center.y()); from += offset; to += offset; // Verify that the traversal order of the octree children matches the line direction, @@ -597,7 +655,14 @@ static void transform_center(Cube *current_cube, const Eigen::Matrix3d &rot) transform_center(child, rot); } -OctreePtr build_octree(const indexed_triangle_set &triangle_mesh, coordf_t line_spacing, bool support_overhangs_only) +OctreePtr build_octree( + // Mesh is rotated to the coordinate system of the octree. + const indexed_triangle_set &triangle_mesh, + // Overhang triangles extracted from fill surfaces with stInternalBridge type, + // rotated to the coordinate system of the octree. + const std::vector &overhang_triangles, + coordf_t line_spacing, + bool support_overhangs_only) { assert(line_spacing > 0); assert(! std::isnan(line_spacing)); @@ -608,21 +673,27 @@ OctreePtr build_octree(const indexed_triangle_set &triangle_mesh, coordf_t line_ auto octree = OctreePtr(new Octree(cube_center, cubes_properties)); if (cubes_properties.size() > 1) { + Octree *octree_ptr = octree.get(); + double edge_length_half = 0.5 * cubes_properties.back().edge_length; + Vec3d diag_half(edge_length_half, edge_length_half, edge_length_half); + int max_depth = int(cubes_properties.size()) - 1; + auto process_triangle = [octree_ptr, max_depth, diag_half](const Vec3d &a, const Vec3d &b, const Vec3d &c) { + octree_ptr->insert_triangle( + a, b, c, + octree_ptr->root_cube, + BoundingBoxf3(octree_ptr->root_cube->center - diag_half, octree_ptr->root_cube->center + diag_half), + max_depth); + }; auto up_vector = support_overhangs_only ? Vec3d(transform_to_octree() * Vec3d(0., 0., 1.)) : Vec3d(); for (auto &tri : triangle_mesh.indices) { auto a = triangle_mesh.vertices[tri[0]].cast(); auto b = triangle_mesh.vertices[tri[1]].cast(); auto c = triangle_mesh.vertices[tri[2]].cast(); - if (support_overhangs_only && ! is_overhang_triangle(a, b, c, up_vector)) - continue; - double edge_length_half = 0.5 * cubes_properties.back().edge_length; - Vec3d diag_half(edge_length_half, edge_length_half, edge_length_half); - octree->insert_triangle( - a, b, c, - octree->root_cube, - BoundingBoxf3(octree->root_cube->center - diag_half, octree->root_cube->center + diag_half), - int(cubes_properties.size()) - 1); + if (! support_overhangs_only || is_overhang_triangle(a, b, c, up_vector)) + process_triangle(a, b, c); } + for (size_t i = 0; i < overhang_triangles.size(); i += 3) + process_triangle(overhang_triangles[i], overhang_triangles[i + 1], overhang_triangles[i + 2]); { // Transform the octree to world coordinates to reduce computation when extracting infill lines. auto rot = transform_to_world().toRotationMatrix(); @@ -639,13 +710,16 @@ void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cub assert(current_cube); assert(depth > 0); + // Squared radius of a sphere around the child cube. + const double r2_cube = Slic3r::sqr(0.5 * this->cubes_properties[-- depth].height + EPSILON); + for (size_t i = 0; i < 8; ++ i) { - const Vec3d &child_center = child_centers[i]; + const Vec3d &child_center_dir = child_centers[i]; // Calculate a slightly expanded bounding box of a child cube to cope with triangles touching a cube wall and other numeric errors. // We will rather densify the octree a bit more than necessary instead of missing a triangle. BoundingBoxf3 bbox; for (int k = 0; k < 3; ++ k) { - if (child_center[k] == -1.) { + if (child_center_dir[k] == -1.) { bbox.min[k] = current_bbox.min[k]; bbox.max[k] = current_cube->center[k] + EPSILON; } else { @@ -653,11 +727,13 @@ void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cub bbox.max[k] = current_bbox.max[k]; } } + Vec3d child_center = current_cube->center + (child_center_dir * (this->cubes_properties[depth].edge_length / 2.)); + //if (dist2_to_triangle(a, b, c, child_center) < r2_cube) { if (triangle_AABB_intersects(a, b, c, bbox)) { if (! current_cube->children[i]) - current_cube->children[i] = this->pool.construct(current_cube->center + (child_center * (this->cubes_properties[depth].edge_length / 4))); - if (depth > 1) - this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth - 1); + current_cube->children[i] = this->pool.construct(child_center); + if (depth > 0) + this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth); } } } diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index aca8d1d7b5..f10c40b99f 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -40,7 +40,10 @@ Eigen::Quaterniond transform_to_octree(); FillAdaptive::OctreePtr build_octree( // Mesh is rotated to the coordinate system of the octree. - const indexed_triangle_set &triangle_mesh, + const indexed_triangle_set &triangle_mesh, + // Overhang triangles extracted from fill surfaces with stInternalBridge type, + // rotated to the coordinate system of the octree. + const std::vector &overhang_triangles, coordf_t line_spacing, // If true, octree is densified below internal overhangs only. bool support_overhangs_only); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ac47e1d102..4b83062a0c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -9,6 +9,7 @@ #include "SupportMaterial.hpp" #include "Surface.hpp" #include "Slicing.hpp" +#include "Tesselate.hpp" #include "Utils.hpp" #include "AABBTreeIndirect.hpp" #include "Fill/FillAdaptive.hpp" @@ -439,17 +440,40 @@ std::pair PrintObject::prepare using namespace FillAdaptive; auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); - if (adaptive_line_spacing == 0. && support_line_spacing == 0.) + if (adaptive_line_spacing == 0. && support_line_spacing == 0. || this->layers().empty()) return std::make_pair(OctreePtr(), OctreePtr()); indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); // Rotate mesh and build octree on it with axis-aligned (standart base) cubes. Transform3d m = m_trafo; - m.translate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); - its_transform(mesh, transform_to_octree().toRotationMatrix() * m, true); + m.pretranslate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); + auto to_octree = transform_to_octree().toRotationMatrix(); + its_transform(mesh, to_octree * m, true); + + // Triangulate internal bridging surfaces. + std::vector> overhangs(this->layers().size()); + tbb::parallel_for( + tbb::blocked_range(0, int(m_layers.size()) - 1), + [this, &to_octree, &overhangs](const tbb::blocked_range &range) { + std::vector &out = overhangs[range.begin()]; + for (int idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + m_print->throw_if_canceled(); + const Layer *layer = this->layers()[idx_layer]; + for (const LayerRegion *layerm : layer->regions()) + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == stInternalBridge) + append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z())); + } + for (Vec3d &p : out) + p = (to_octree * p).eval(); + }); + // and gather them. + for (size_t i = 1; i < overhangs.size(); ++ i) + append(overhangs.front(), std::move(overhangs[i])); + return std::make_pair( - adaptive_line_spacing ? build_octree(mesh, adaptive_line_spacing, false) : OctreePtr(), - support_line_spacing ? build_octree(mesh, support_line_spacing, true) : OctreePtr()); + adaptive_line_spacing ? build_octree(mesh, overhangs.front(), adaptive_line_spacing, false) : OctreePtr(), + support_line_spacing ? build_octree(mesh, overhangs.front(), support_line_spacing, true) : OctreePtr()); } void PrintObject::clear_layers() @@ -2785,8 +2809,8 @@ void PrintObject::project_and_append_custom_facets( // Calculate how to move points on triangle sides per unit z increment. Vec2f ta(trianglef[1] - trianglef[0]); Vec2f tb(trianglef[2] - trianglef[0]); - ta *= 1./(facet[1].z() - facet[0].z()); - tb *= 1./(facet[2].z() - facet[0].z()); + ta *= 1.f/(facet[1].z() - facet[0].z()); + tb *= 1.f/(facet[2].z() - facet[0].z()); // Projection on current slice will be build directly in place. LightPolygon* proj = &projections_of_triangles[idx].polygons[0]; @@ -2797,7 +2821,7 @@ void PrintObject::project_and_append_custom_facets( // Project a sub-polygon on all slices intersecting the triangle. while (it != layers().end()) { - const float z = (*it)->slice_z; + const float z = float((*it)->slice_z); // Projections of triangle sides intersections with slices. // a moves along one side, b tracks the other. @@ -2809,7 +2833,7 @@ void PrintObject::project_and_append_custom_facets( if (z > facet[1].z() && ! passed_first) { proj->add(trianglef[1]); ta = trianglef[2]-trianglef[1]; - ta *= 1./(facet[2].z() - facet[1].z()); + ta *= 1.f/(facet[2].z() - facet[1].z()); passed_first = true; } From 398ff9053d2218bde0b365503b5d39ec18d40fa8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 22 Sep 2020 11:17:43 +0200 Subject: [PATCH 541/826] Code refactoring of the OptionsGroup class: Controls are created only for the active page now --- src/slic3r/GUI/ConfigManipulation.cpp | 4 +- src/slic3r/GUI/ConfigManipulation.hpp | 8 +- src/slic3r/GUI/Field.cpp | 16 +- src/slic3r/GUI/Field.hpp | 2 +- src/slic3r/GUI/GUI_App.cpp | 22 + src/slic3r/GUI/GUI_ObjectSettings.cpp | 14 +- src/slic3r/GUI/OptionsGroup.cpp | 181 +++++--- src/slic3r/GUI/OptionsGroup.hpp | 22 +- src/slic3r/GUI/Plater.cpp | 4 +- src/slic3r/GUI/Tab.cpp | 628 ++++++++++++++------------ src/slic3r/GUI/Tab.hpp | 53 ++- 11 files changed, 568 insertions(+), 386 deletions(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5836b8a2c0..53cd71f8e6 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -27,9 +27,7 @@ void ConfigManipulation::toggle_field(const std::string& opt_key, const bool tog if (local_config->option(opt_key) == nullptr) return; } - Field* field = get_field(opt_key, opt_index); - if (field==nullptr) return; - field->toggle(toggle); + cb_toggle_field(opt_key, toggle, opt_index); } void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config) diff --git a/src/slic3r/GUI/ConfigManipulation.hpp b/src/slic3r/GUI/ConfigManipulation.hpp index 7344f758be..82aa533109 100644 --- a/src/slic3r/GUI/ConfigManipulation.hpp +++ b/src/slic3r/GUI/ConfigManipulation.hpp @@ -21,25 +21,25 @@ class ConfigManipulation // function to loading of changed configuration std::function load_config = nullptr; - std::function get_field = nullptr; + std::function cb_toggle_field = nullptr; // callback to propagation of changed value, if needed std::function cb_value_change = nullptr; DynamicPrintConfig* local_config = nullptr; public: ConfigManipulation(std::function load_config, - std::function get_field, + std::function cb_toggle_field, std::function cb_value_change, DynamicPrintConfig* local_config = nullptr) : load_config(load_config), - get_field(get_field), + cb_toggle_field(cb_toggle_field), cb_value_change(cb_value_change), local_config(local_config) {} ConfigManipulation() {} ~ConfigManipulation() { load_config = nullptr; - get_field = nullptr; + cb_toggle_field = nullptr; cb_value_change = nullptr; } diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index a21826205b..e075c7709b 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -51,6 +51,20 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/) return s; } +Field::~Field() +{ + if (m_on_kill_focus) + m_on_kill_focus = nullptr; + if (m_on_set_focus) + m_on_set_focus = nullptr; + if (m_on_change) + m_on_change = nullptr; + if (m_back_to_initial_value) + m_back_to_initial_value = nullptr; + if (m_back_to_sys_value) + m_back_to_sys_value = nullptr; +} + void Field::PostInitialize() { auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -318,7 +332,7 @@ void Field::sys_color_changed() template bool is_defined_input_value(wxWindow* win, const ConfigOptionType& type) { - if (static_cast(win)->GetValue().empty() && type != coString && type != coStrings) + if (!win || (static_cast(win)->GetValue().empty() && type != coString && type != coStrings)) return false; return true; } diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 1a49977565..d919304a92 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -145,7 +145,7 @@ public: Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; - virtual ~Field() {} + virtual ~Field(); /// If you don't know what you are getting back, check both methods for nullptr. virtual wxSizer* getSizer() { return nullptr; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index d59a83e875..26f19d0c1d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -68,6 +68,8 @@ #include #endif // __WXMSW__ +#include + #if ENABLE_THUMBNAIL_GENERATOR_DEBUG #include #include @@ -78,6 +80,25 @@ namespace GUI { class MainFrame; +class InitTimer +{ + std::chrono::milliseconds start_timer; +public: + InitTimer() + { + start_timer = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()); + } + + ~InitTimer() + { + std::chrono::milliseconds stop_timer = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()); + auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count(); + printf("on_init duration = %lld ms \n", process_duration); + } +}; + class SplashScreen : public wxSplashScreen { public: @@ -596,6 +617,7 @@ bool GUI_App::OnInit() bool GUI_App::on_init_inner() { + InitTimer local_timer; // Verify resources path const wxString resources_dir = from_u8(Slic3r::resources_dir()); wxCHECK_MSG(wxDirExists(resources_dir), false, diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 398cd51d45..e157cb385c 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -148,14 +148,15 @@ bool ObjectSettings::update_settings_list() if (is_extruders_cat) option.opt.max = wxGetApp().extruders_edited_cnt(); optgroup->append_single_option_line(option); - + } + optgroup->activate(); + for (auto& opt : cat.second) optgroup->get_field(opt)->m_on_change = [optgroup](const std::string& opt_id, const boost::any& value) { // first of all take a snapshot and then change value in configuration wxGetApp().plater()->take_snapshot(from_u8((boost::format(_utf8(L("Change Option %s"))) % opt_id).str())); optgroup->on_change_OG(opt_id, value); }; - } optgroup->reload_config(); m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); @@ -233,18 +234,19 @@ void ObjectSettings::update_config_values(DynamicPrintConfig* config) } }; - auto get_field = [this](const t_config_option_key & opt_key, int opt_index) + auto toggle_field = [this](const t_config_option_key & opt_key, bool toggle, int opt_index) { Field* field = nullptr; for (auto og : m_og_settings) { field = og->get_fieldc(opt_key, opt_index); if (field != nullptr) - return field; + break; } - return field; + if (field) + field->toggle(toggle); }; - ConfigManipulation config_manipulation(load_config, get_field, nullptr, config); + ConfigManipulation config_manipulation(load_config, toggle_field, nullptr, config); if (!is_object_settings) { diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index dd00b3d688..a5e896fa33 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -116,23 +116,9 @@ OptionsGroup::OptionsGroup( wxWindow* _parent, const wxString& title, } else stb = nullptr; sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); - auto num_columns = 1U; - if (label_width != 0) num_columns++; - if (extra_column != nullptr) num_columns++; - m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0); - static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); - static_cast(m_grid_sizer)->AddGrowableCol(label_width == 0 ? 0 : !extra_column ? 1 : 2 ); -#if 0//#ifdef __WXGTK__ - m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - sizer->Fit(m_panel); - sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); -#else - sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); -#endif /* __WXGTK__ */ - } -void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field) +void OptionsGroup::add_undo_buttons_to_sizer(wxSizer* sizer, const t_field& field) { if (!m_show_modified_btns) { field->m_Undo_btn->set_as_hidden(); @@ -146,11 +132,32 @@ void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& fiel sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); } -void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = nullptr*/) { - if ( line.full_width && ( - line.sizer != nullptr || - line.widget != nullptr || - !line.get_extra_widgets().empty() ) +void OptionsGroup::append_line(const Line& line) +{ + m_lines.emplace_back(line); + + if (line.full_width && ( + line.sizer != nullptr || + line.widget != nullptr || + !line.get_extra_widgets().empty()) + ) + return; + + auto option_set = line.get_options(); + for (auto opt : option_set) + m_options.emplace(opt.opt_id, opt); + + // add mode value for current line to m_options_mode + if (!option_set.empty()) + m_options_mode.push_back(option_set[0].opt.mode); +} + +void OptionsGroup::activate_line(Line& line) +{ + if (line.full_width && ( + line.sizer != nullptr || + line.widget != nullptr || + !line.get_extra_widgets().empty()) ) { if (line.sizer != nullptr) { sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); @@ -174,23 +181,13 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n } auto option_set = line.get_options(); - for (auto opt : option_set) - m_options.emplace(opt.opt_id, opt); // Set sidetext width for a better alignment of options in line // "m_show_modified_btns==true" means that options groups are in tabs if (option_set.size() > 1 && m_show_modified_btns) { sidetext_width = Field::def_width_thinner(); - /* Temporary commented till UI-review will be completed - if (m_show_modified_btns) // means that options groups are in tabs - sublabel_width = Field::def_width(); - */ } - // add mode value for current line to m_options_mode - if (!option_set.empty()) - m_options_mode.push_back(option_set[0].opt.mode); - // if we have a single option with no label, no sidetext just add it directly to sizer if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width && option_set.front().opt.label.empty() && @@ -209,7 +206,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n const auto& field = build_field(option); auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); - add_undo_buttuns_to_sizer(btn_sizer, field); + add_undo_buttons_to_sizer(btn_sizer, field); tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0); if (is_window_field(field)) tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); @@ -220,16 +217,16 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n auto grid_sizer = m_grid_sizer; #if 0//#ifdef __WXGTK__ - m_panel->SetSizer(m_grid_sizer); - m_panel->Layout(); + m_panel->SetSizer(m_grid_sizer); + m_panel->Layout(); #endif /* __WXGTK__ */ // if we have an extra column, build it - if (extra_column) - { - m_extra_column_item_ptrs.push_back(extra_column(this->ctrl_parent(), line)); - grid_sizer->Add(m_extra_column_item_ptrs.back(), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 3); - } + if (extra_column) + { + m_extra_column_item_ptrs.push_back(extra_column(this->ctrl_parent(), line)); + grid_sizer->Add(m_extra_column_item_ptrs.back(), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 3); + } // Build a label if we have it wxStaticText* label=nullptr; @@ -243,8 +240,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // Text is properly aligned only when Ellipsize is checked. label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END; #endif /* __WXGTK__ */ - label = new wxStaticText(this->ctrl_parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "), - wxDefaultPosition, wxSize(label_width*wxGetApp().em_unit(), -1), label_style); + label = new wxStaticText(this->ctrl_parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ": "), + wxDefaultPosition, wxSize(label_width * wxGetApp().em_unit(), -1), label_style); label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(wxGetApp().normal_font()); label->Wrap(label_width*wxGetApp().em_unit()); // avoid a Linux/GTK bug @@ -269,16 +266,16 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n label->SetToolTip(line.label_tooltip); } - if (full_Label != nullptr) - *full_Label = label; // Initiate the pointer to the control of the full label, if we need this one. - // If there's a widget, build it and add the result to the sizer. + if (line.full_Label != nullptr) + *line.full_Label = label; // Initiate the pointer to the control of the full label, if we need this one. + // If there's a widget, build it and add the result to the sizer. if (line.widget != nullptr) { auto wgt = line.widget(this->ctrl_parent()); // If widget doesn't have label, don't use border grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5); return; } - + // If we're here, we have more than one option or a single option with sidetext // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -290,11 +287,11 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n const auto& option = option_set.front(); const auto& field = build_field(option, label); - add_undo_buttuns_to_sizer(sizer, field); - if (is_window_field(field)) - sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) | - wxBOTTOM | wxTOP | (option.opt.full_width ? wxEXPAND : wxALIGN_CENTER_VERTICAL), (wxOSX || !staticbox) ? 0 : 2); - if (is_sizer_field(field)) + add_undo_buttons_to_sizer(sizer, field); + if (is_window_field(field)) + sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) | + wxBOTTOM | wxTOP | (option.opt.full_width ? wxEXPAND : wxALIGN_CENTER_VERTICAL), (wxOSX || !staticbox) ? 0 : 2); + if (is_sizer_field(field)) sizer->Add(field->getSizer(), 1, /*(*/option.opt.full_width ? wxEXPAND : /*0) |*/ wxALIGN_CENTER_VERTICAL, 0); return; } @@ -306,8 +303,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n if (!option.label.empty()) { //! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1 wxString str_label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ? - _CTX(option.label, "Layers") : - _(option.label); + _CTX(option.label, "Layers") : + _(option.label); label = new wxStaticText(this->ctrl_parent(), wxID_ANY, str_label + ": ", wxDefaultPosition, //wxDefaultSize); wxSize(sublabel_width != -1 ? sublabel_width * wxGetApp().em_unit() : -1, -1), wxALIGN_RIGHT); label->SetBackgroundStyle(wxBG_STYLE_PAINT); @@ -318,7 +315,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // add field const Option& opt_ref = opt; auto& field = build_field(opt_ref, label); - add_undo_buttuns_to_sizer(sizer_tmp, field); + add_undo_buttons_to_sizer(sizer_tmp, field); if (option_set.size() == 1 && option_set.front().opt.full_width) { const auto v_sizer = new wxBoxSizer(wxVERTICAL); @@ -329,10 +326,10 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n break;//return; } - is_sizer_field(field) ? + is_sizer_field(field) ? sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0); - + // add sidetext if any if (!option.sidetext.empty() || sidetext_width > 0) { auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, @@ -369,6 +366,56 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n } } +// create all controls for the option group from the m_lines +void OptionsGroup::activate() +{ + if (!sizer->IsEmpty()) + return; + + auto num_columns = 1U; + size_t grow_col = 1; + + if (label_width == 0) + grow_col = 0; + else + num_columns++; + + if (extra_column) { + num_columns++; + grow_col++; + } + + m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1, 0); + static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH); + static_cast(m_grid_sizer)->AddGrowableCol(grow_col); + + sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX || !staticbox ? 0 : 5); + + // activate lines + for (Line& line: m_lines) + activate_line(line); +} +// delete all controls from the option group +void OptionsGroup::clear() +{ + if (sizer->IsEmpty()) + return; + + m_grid_sizer->Clear(true); + sizer->Clear(true); + + for (Line& line : m_lines) + if(line.full_Label) + *line.full_Label = nullptr; + + //for (auto extra_col_win : m_extra_column_item_ptrs) + // destroy(extra_col_win); + m_extra_column_item_ptrs.clear(); + + m_near_label_widget_ptrs.clear(); + m_fields.clear(); +} + Line OptionsGroup::create_single_option_line(const Option& option) const { // Line retval{ _(option.opt.label), _(option.opt.tooltip) }; wxString tooltip = _(option.opt.tooltip); @@ -520,15 +567,31 @@ void ConfigOptionsGroup::Show(const bool show) #endif /* __WXGTK__ */ } -bool ConfigOptionsGroup::update_visibility(ConfigOptionMode mode) { +bool ConfigOptionsGroup::is_visible(ConfigOptionMode mode) +{ if (m_options_mode.empty()) return true; - int opt_mode_size = m_options_mode.size(); - if (m_grid_sizer->GetEffectiveRowsCount() != opt_mode_size && - opt_mode_size == 1) + if (m_options_mode.size() == 1) return m_options_mode[0] <= mode; - Show(true); + int hidden_row_cnt = 0; + for (auto opt_mode : m_options_mode) + if (opt_mode > mode) + hidden_row_cnt++; + + return hidden_row_cnt != m_options_mode.size(); +} + +bool ConfigOptionsGroup::update_visibility(ConfigOptionMode mode) +{ + if (m_options_mode.empty()) + return true; + int opt_mode_size = m_options_mode.size(); + if (m_grid_sizer->GetEffectiveRowsCount() != opt_mode_size && + opt_mode_size == 1) + return m_options_mode[0] <= mode; + + Show(true); int coef = 0; int hidden_row_cnt = 0; diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index edd4a15bc9..2a4c2cdb66 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -48,6 +48,7 @@ public: wxString label {wxString("")}; wxString label_tooltip {wxString("")}; size_t full_width {0}; + wxStaticText** full_Label {nullptr}; wxSizer* sizer {nullptr}; widget_t widget {nullptr}; std::function near_label_widget{ nullptr }; @@ -119,7 +120,15 @@ public: return this->stb ? (wxWindow*)this->stb : this->parent(); } - void append_line(const Line& line, wxStaticText** full_Label = nullptr); + void append_line(const Line& line); + // create controls for the option group + void activate_line(Line& line); + + // create all controls for the option group from the m_lines + void activate(); + // delete all controls from the option group + void clear(); + Line create_single_option_line(const Option& option) const; void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); } @@ -170,11 +179,7 @@ public: void clear_fields_except_of(const std::vector left_fields); - void hide_labels() { - label_width = 0; - m_grid_sizer->SetCols(m_grid_sizer->GetEffectiveColsCount()-1); - static_cast(m_grid_sizer)->AddGrowableCol(!extra_column ? 0 : 1); - } + void hide_labels() { label_width = 0; } OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, column_t extra_clmn = nullptr); @@ -188,6 +193,8 @@ protected: std::vector m_extra_column_item_ptrs; std::vector m_near_label_widget_ptrs; + std::vector m_lines; + /// Field list, contains unique_ptrs of the derived type. /// using types that need to know what it is beyond the public interface /// need to cast based on the related ConfigOptionDef. @@ -210,7 +217,7 @@ protected: const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr); const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr); const t_field& build_field(const Option& opt, wxStaticText* label = nullptr); - void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field); + void add_undo_buttons_to_sizer(wxSizer* sizer, const t_field& field); virtual void on_kill_focus(const std::string& opt_key) {}; virtual void on_set_focus(const std::string& opt_key); @@ -259,6 +266,7 @@ public: // return value shows visibility : false => all options are hidden void Hide(); void Show(const bool show); + bool is_visible(ConfigOptionMode mode); bool update_visibility(ConfigOptionMode mode); void msw_rescale(); void sys_color_changed(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f7fd608ba8..7d6823b974 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -439,9 +439,9 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : return sizer; }; line.append_widget(wiping_dialog_btn); - m_og->append_line(line); + m_og->activate(); // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared(parent, ""); @@ -513,6 +513,8 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : m_og_sla->append_line(line); + m_og_sla->activate(); + m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(m_og->sizer, 0, wxEXPAND); m_sizer->Add(m_og_sla->sizer, 0, wxEXPAND); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 29c9e33022..e8519cc0f6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -107,9 +107,9 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : m_config_manipulation = get_config_manipulation(); Bind(wxEVT_SIZE, ([this](wxSizeEvent &evt) { - for (auto page : m_pages) - if (! page.get()->IsShown()) - page->layout_valid = false; + //for (auto page : m_pages) + // if (! page.get()->IsShown()) + // page->layout_valid = false; evt.Skip(); })); @@ -290,7 +290,6 @@ void Tab::create_preset_tab() m_treectrl->AssignImageList(m_icons); m_treectrl->AddRoot("root"); m_treectrl->SetIndent(0); - m_disable_tree_sel_changed_event = 0; m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); @@ -316,7 +315,7 @@ void Tab::create_preset_tab() // Initialize the DynamicPrintConfig by default keys/values. build(); - rebuild_page_tree(); +// rebuild_page_tree(); m_completed = true; } @@ -450,6 +449,67 @@ void Tab::update_labels_colour() } } +void Tab::decorate() +{ + for (const auto opt : m_options_list) + { + wxStaticText* label = nullptr; + Field* field = nullptr; + + if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || + opt.first == "compatible_prints" || opt.first == "compatible_printers") + label = (m_colored_Labels.find(opt.first) == m_colored_Labels.end()) ? nullptr : m_colored_Labels.at(opt.first); + + if (!label) + field = get_field(opt.first); + if (!label && !field) + continue; + + bool is_nonsys_value = false; + bool is_modified_value = true; + const ScalableBitmap* sys_icon = &m_bmp_value_lock; + const ScalableBitmap* icon = &m_bmp_value_revert; + + const wxColour* color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; + + const wxString* sys_tt = &m_tt_value_lock; + const wxString* tt = &m_tt_value_revert; + + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0) { + is_nonsys_value = true; + sys_icon = m_bmp_non_system; + sys_tt = m_tt_non_system; + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if ((opt.second & osInitValue) != 0) + { + is_modified_value = false; + icon = &m_bmp_white_bullet; + tt = &m_tt_white_bullet; + } + + if (label) { + label->SetForegroundColour(*color); + label->Refresh(true); + continue; + } + + field->m_is_nonsys_value = is_nonsys_value; + field->m_is_modified_value = is_modified_value; + field->set_undo_bitmap(icon); + field->set_undo_to_sys_bitmap(sys_icon); + field->set_undo_tooltip(tt); + field->set_undo_to_sys_tooltip(sys_tt); + field->set_label_colour(color); + } +} + // Update UI according to changes void Tab::update_changed_ui() { @@ -473,59 +533,7 @@ void Tab::update_changed_ui() for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; -// Freeze(); - //update options "decoration" - for (const auto opt : m_options_list) - { - bool is_nonsys_value = false; - bool is_modified_value = true; - const ScalableBitmap *sys_icon = &m_bmp_value_lock; - const ScalableBitmap *icon = &m_bmp_value_revert; - - const wxColour *color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; - - const wxString *sys_tt = &m_tt_value_lock; - const wxString *tt = &m_tt_value_revert; - - // value isn't equal to system value - if ((opt.second & osSystemValue) == 0) { - is_nonsys_value = true; - sys_icon = m_bmp_non_system; - sys_tt = m_tt_non_system; - // value is equal to last saved - if ((opt.second & osInitValue) != 0) - color = &m_default_text_clr; - // value is modified - else - color = &m_modified_label_clr; - } - if ((opt.second & osInitValue) != 0) - { - is_modified_value = false; - icon = &m_bmp_white_bullet; - tt = &m_tt_white_bullet; - } - if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || - opt.first == "compatible_prints" || opt.first == "compatible_printers") { - wxStaticText* label = (m_colored_Labels.find(opt.first) == m_colored_Labels.end()) ? nullptr : m_colored_Labels.at(opt.first); - if (label) { - label->SetForegroundColour(*color); - label->Refresh(true); - } - continue; - } - - Field* field = get_field(opt.first); - if (field == nullptr) continue; - field->m_is_nonsys_value = is_nonsys_value; - field->m_is_modified_value = is_modified_value; - field->set_undo_bitmap(icon); - field->set_undo_to_sys_bitmap(sys_icon); - field->set_undo_tooltip(tt); - field->set_undo_to_sys_tooltip(sys_tt); - field->set_label_colour(color); - } -// Thaw(); + decorate(); wxTheApp->CallAfter([this]() { if (parent()) //To avoid a crash, parent should be exist for a moment of a tree updating @@ -833,16 +841,12 @@ void Tab::update_visibility() { Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout - // m_detach_preset_btn will be shown always after call page->update_visibility() - // So let save a "show state" of m_detach_preset_btn before update_visibility - bool was_shown = m_detach_preset_btn->IsShown(); - for (auto page : m_pages) - page->update_visibility(m_mode); - update_page_tree_visibility(); + page->update_visibility(m_mode, page.get() == m_active_page); + rebuild_page_tree(); - // update visibility for detach_preset_btn - m_detach_preset_btn->Show(was_shown); + if (this->m_type == Preset::TYPE_SLA_PRINT) + update_description_lines(); Layout(); Thaw(); @@ -942,6 +946,15 @@ Field* Tab::get_field(const t_config_option_key& opt_key, Page** selected_page, return field; } +void Tab::toggle_option(const std::string& opt_key, bool toggle, int opt_index/* = -1*/) +{ + if (!m_active_page) + return; + Field* field = m_active_page->get_field(opt_key, opt_index); + if (field) + field->toggle(toggle); +}; + // Set a key/value pair on this page. Return true if the value has been modified. // Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer // after a preset is loaded. @@ -1132,7 +1145,7 @@ 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(); +// 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) @@ -1540,27 +1553,46 @@ void TabPrint::reload_config() Tab::reload_config(); } +void TabPrint::update_description_lines() +{ + Tab::update_description_lines(); + + if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) + return; + + if (m_active_page && m_active_page->title() == "Layers and perimeters" && + m_recommended_thin_wall_thickness_description_line && m_top_bottom_shell_thickness_explanation) + { + m_recommended_thin_wall_thickness_description_line->SetText( + from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_top_bottom_shell_thickness_explanation->SetText( + from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); + } +} + +void TabPrint::toggle_options() +{ + if (!m_active_page) return; + + m_config_manipulation.toggle_print_fff_options(m_config); +} + void TabPrint::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) return; // ys_FIXME m_update_cnt++; -// Freeze(); m_config_manipulation.update_print_fff_config(m_config, true); - m_recommended_thin_wall_thickness_description_line->SetText( - from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); - m_top_bottom_shell_thickness_explanation->SetText( - from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); + update_description_lines(); Layout(); -// Thaw(); m_update_cnt--; if (m_update_cnt==0) { - m_config_manipulation.toggle_print_fff_options(m_config); + toggle_options(); // update() could be called during undo/redo execution // Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList) @@ -1571,13 +1603,18 @@ void TabPrint::update() } } -void TabPrint::OnActivate() +//void TabPrint::OnActivate() +//{ +// update_description_lines(); +// Tab::OnActivate(); +//} + +void TabPrint::clear_pages() { - m_recommended_thin_wall_thickness_description_line->SetText( - from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); - m_top_bottom_shell_thickness_explanation->SetText( - from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); - Tab::OnActivate(); + Tab::clear_pages(); + + m_recommended_thin_wall_thickness_description_line = nullptr; + m_top_bottom_shell_thickness_explanation = nullptr; } void TabFilament::add_filament_overrides_page() @@ -1637,10 +1674,13 @@ void TabFilament::add_filament_overrides_page() void TabFilament::update_filament_overrides_page() { - const auto page_it = std::find_if(m_pages.begin(), m_pages.end(), [](const PageShp page) { return page->title() == "Filament Overrides"; }); - if (page_it == m_pages.end()) + if (!m_active_page || m_active_page->title() != "Filament Overrides") return; - PageShp page = *page_it; + //const auto page_it = std::find_if(m_pages.begin(), m_pages.end(), [](const PageShp page) { return page->title() == "Filament Overrides"; }); + //if (page_it == m_pages.end()) + // return; + //PageShp page = *page_it; + Page* page = m_active_page; const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) { return og->title == "Retraction"; }); if (og_it == page->m_optgroups.end()) @@ -1847,6 +1887,40 @@ void TabFilament::update_volumetric_flow_preset_hints() m_volumetric_speed_description_line->SetText(text); } +void TabFilament::update_description_lines() +{ + Tab::update_description_lines(); + + if (!m_active_page) + return; + + if (m_active_page->title() == "Cooling" && m_cooling_description_line) + m_cooling_description_line->SetText(from_u8(PresetHints::cooling_description(m_presets->get_edited_preset()))); + if (m_active_page->title() == "Advanced" && m_volumetric_speed_description_line) + this->update_volumetric_flow_preset_hints(); +} + +void TabFilament::toggle_options() +{ + if (!m_active_page) + return; + + if (m_active_page->title() == "Cooling") + { + bool cooling = m_config->opt_bool("cooling", 0); + bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); + + for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) + toggle_option(el, cooling); + + for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) + toggle_option(el, fan_always_on); + } + + if (m_active_page->title() == "Filament Overrides") + update_filament_overrides_page(); +} + void TabFilament::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) @@ -1854,21 +1928,10 @@ void TabFilament::update() m_update_cnt++; - wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); - m_cooling_description_line->SetText(text); - this->update_volumetric_flow_preset_hints(); + update_description_lines(); Layout(); - bool cooling = m_config->opt_bool("cooling", 0); - bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); - - for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) - get_field(el)->toggle(cooling); - - for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) - get_field(el)->toggle(fan_always_on); - - update_filament_overrides_page(); + toggle_options(); m_update_cnt--; @@ -1876,10 +1939,18 @@ void TabFilament::update() wxGetApp().mainframe->on_config_changed(m_config); } -void TabFilament::OnActivate() +//void TabFilament::OnActivate() +//{ +// update_description_lines(); +// Tab::OnActivate(); +//} + +void TabFilament::clear_pages() { - this->update_volumetric_flow_preset_hints(); - Tab::OnActivate(); + Tab::clear_pages(); + + m_volumetric_speed_description_line = nullptr; + m_cooling_description_line = nullptr; } wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText) @@ -2695,97 +2766,73 @@ void TabPrinter::update_pages() rebuild_page_tree(); } -void TabPrinter::update() +void TabPrinter::active_selected_page() { - m_update_cnt++; - m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla(); - m_update_cnt--; + Tab::active_selected_page(); - if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); + // "extruders_count" doesn't update from the update_config(), + // so update it implicitly + if (m_active_page->title() == "General") + m_active_page->set_value("extruders_count", int(m_extruders_count)); } -void TabPrinter::update_fff() +void TabPrinter::toggle_options() { -// Freeze(); - - bool en; - auto serial_speed = get_field("serial_speed"); - if (serial_speed != nullptr) { - en = !m_config->opt_string("serial_port").empty(); - get_field("serial_speed")->toggle(en); - if (m_config->opt_int("serial_speed") != 0 && en) - m_serial_test_btn->Enable(); - else - m_serial_test_btn->Disable(); - } - - /* - { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); - m_printhost_browse_btn->Enable(host->has_auto_discovery()); - } - */ + if (!m_active_page || m_presets->get_edited_preset().printer_technology() == ptSLA) + return; bool have_multiple_extruders = m_extruders_count > 1; - get_field("toolchange_gcode")->toggle(have_multiple_extruders); - get_field("single_extruder_multi_material")->toggle(have_multiple_extruders); + if (m_active_page->title() == "Custom G-code") + toggle_option("toolchange_gcode", have_multiple_extruders); + if (m_active_page->title() == "General") { + toggle_option("single_extruder_multi_material", have_multiple_extruders); - bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + // Disable silent mode for non-marlin firmwares. + toggle_option("silent_mode", is_marlin_flavor); + } + wxString extruder_number; + long val; + if (m_active_page->title().StartsWith("Extruder ", &extruder_number) && extruder_number.ToLong(&val) && + val > 0 && val <= m_extruders_count) { - Field *sm = get_field("silent_mode"); - if (! is_marlin_flavor) - // Disable silent mode for non-marlin firmwares. - get_field("silent_mode")->toggle(false); - if (is_marlin_flavor) - sm->enable(); - else - sm->disable(); - } - - if (m_use_silent_mode != m_config->opt_bool("silent_mode")) { - m_rebuild_kinematics_page = true; - m_use_silent_mode = m_config->opt_bool("silent_mode"); - } - - for (size_t i = 0; i < m_extruders_count; ++i) { + size_t i = size_t(val - 1); bool have_retract_length = m_config->opt_float("retract_length", i) > 0; // when using firmware retraction, firmware decides retraction length bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); - get_field("retract_length", i)->toggle(!use_firmware_retraction); + toggle_option("retract_length", !use_firmware_retraction, i); // user can customize travel length if we have retraction length or we"re using // firmware retraction - get_field("retract_before_travel", i)->toggle(have_retract_length || use_firmware_retraction); + toggle_option("retract_before_travel", have_retract_length || use_firmware_retraction, i); // user can customize other retraction options if retraction is enabled bool retraction = (have_retract_length || use_firmware_retraction); std::vector vec = { "retract_lift", "retract_layer_change" }; for (auto el : vec) - get_field(el, i)->toggle(retraction); + toggle_option(el, retraction, i); // retract lift above / below only applies if using retract lift vec.resize(0); vec = { "retract_lift_above", "retract_lift_below" }; for (auto el : vec) - get_field(el, i)->toggle(retraction && m_config->opt_float("retract_lift", i) > 0); + toggle_option(el, retraction && (m_config->opt_float("retract_lift", i) > 0), i); // some options only apply when not using firmware retraction vec.resize(0); vec = { "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "wipe" }; for (auto el : vec) - get_field(el, i)->toggle(retraction && !use_firmware_retraction); + toggle_option(el, retraction && !use_firmware_retraction, i); bool wipe = m_config->opt_bool("wipe", i); - get_field("retract_before_wipe", i)->toggle(wipe); + toggle_option("retract_before_wipe", wipe, i); if (use_firmware_retraction && wipe) { wxMessageDialog dialog(parent(), _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" - "\nShall I disable it in order to enable Firmware Retraction?")), + "\nShall I disable it in order to enable Firmware Retraction?")), _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; @@ -2801,14 +2848,32 @@ void TabPrinter::update_fff() load_config(new_conf); } - get_field("retract_length_toolchange", i)->toggle(have_multiple_extruders); + toggle_option("retract_length_toolchange", have_multiple_extruders, i); bool toolchange_retraction = m_config->opt_float("retract_length_toolchange", i) > 0; - get_field("retract_restart_extra_toolchange", i)->toggle - (have_multiple_extruders && toolchange_retraction); + toggle_option("retract_restart_extra_toolchange", have_multiple_extruders && toolchange_retraction, i); } -// Thaw(); +} + +void TabPrinter::update() +{ + m_update_cnt++; + m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla(); + m_update_cnt--; + + if (m_update_cnt == 0) + wxGetApp().mainframe->on_config_changed(m_config); +} + +void TabPrinter::update_fff() +{ + if (m_use_silent_mode != m_config->opt_bool("silent_mode")) { + m_rebuild_kinematics_page = true; + m_use_silent_mode = m_config->opt_bool("silent_mode"); + } + + toggle_options(); } void TabPrinter::update_sla() @@ -2914,55 +2979,32 @@ void Tab::rebuild_page_tree() const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; const auto rootItem = m_treectrl->GetRootItem(); - auto have_selection = 0; + wxTreeItemId item; + + // Delete/Append events invoke wxEVT_TREE_SEL_CHANGED event. + // To avoid redundant clear/activate functions call + // suppress activate page before page_tree rebuilding + m_disable_tree_sel_changed_event = true; m_treectrl->DeleteChildren(rootItem); - for (auto p : m_pages) - { - auto itemId = m_treectrl->AppendItem(rootItem, _(p->title()), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - if (p->title() == selected) { - m_treectrl->SelectItem(itemId); - have_selection = 1; - } - } - if (!have_selection) { - // this is triggered on first load, so we don't disable the sel change event - auto item = m_treectrl->GetFirstVisibleItem(); - if (item) { - m_treectrl->SelectItem(item); - } - } -} - -void Tab::update_page_tree_visibility() -{ - const auto sel_item = m_treectrl->GetSelection(); - const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; - const auto rootItem = m_treectrl->GetRootItem(); - - auto have_selection = 0; - m_treectrl->DeleteChildren(rootItem); for (auto p : m_pages) { if (!p->get_show()) continue; auto itemId = m_treectrl->AppendItem(rootItem, _(p->title()), p->iconID()); m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - if (p->title() == selected) { - m_treectrl->SelectItem(itemId); - have_selection = 1; - } + if (p->title() == selected) + item = itemId; } - - if (!have_selection) { + if (!item) { // this is triggered on first load, so we don't disable the sel change event - auto item = m_treectrl->GetFirstVisibleItem(); - if (item) { - m_treectrl->SelectItem(item); - } + item = m_treectrl->GetFirstVisibleItem(); } + // allow activate page before selection of a page_tree item + m_disable_tree_sel_changed_event = false; + if (item) + m_treectrl->SelectItem(item); } void Tab::update_btns_enabling() @@ -3241,6 +3283,34 @@ bool Tab::may_switch_to_SLA_preset() return true; } +void Tab::clear_pages() +{ + // clear pages from the controlls + for (auto p : m_pages) + p->clear(); + + // nulling description lines pointers + m_parent_preset_description_line = nullptr; + m_detach_preset_btn = nullptr; +} + +void Tab::update_description_lines() +{ + if (m_active_page && m_active_page->title() == "Dependencies") + update_preset_description_line(); +} + +void Tab::active_selected_page() +{ + if (!m_active_page) + return; + + m_active_page->activate(m_mode); + update_changed_ui(); + update_description_lines(); + toggle_options(); +} + void Tab::OnTreeSelChange(wxTreeEvent& event) { if (m_disable_tree_sel_changed_event) @@ -3256,9 +3326,9 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) * so on Window is no needed to call a Freeze/Thaw functions. * But under OSX (builds compiled with MacOSX10.14.sdk) wxStaticBitmap rendering is broken without Freeze/Thaw call. */ -#ifdef __WXOSX__ +//#ifdef __WXOSX__ // Use Freeze/Thaw to avoid flickering during cleare/activate new page wxWindowUpdateLocker noUpdates(this); -#endif +//#endif #endif if (m_pages.empty()) @@ -3275,25 +3345,26 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) m_is_modified_values = page->m_is_modified_values; break; } - if (page == nullptr) return; + if (page == nullptr || m_active_page == page) return; + + // clear pages from the controls + m_active_page = page; + clear_pages(); for (auto& el : m_pages) -// if (el.get()->IsShown()) { - el.get()->Hide(); -// break; -// } + el.get()->Hide(); + + active_selected_page(); #ifdef __linux__ no_updates.reset(nullptr); #endif update_undo_buttons(); - page->Show(); -// if (! page->layout_valid) { - page->layout_valid = true; - m_hsizer->Layout(); - Refresh(); -// } + + m_active_page->Show(); + m_hsizer->Layout(); + Refresh(); } void Tab::OnKeyDown(wxKeyEvent& event) @@ -3308,7 +3379,7 @@ void Tab::OnKeyDown(wxKeyEvent& event) // This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, // and activates the new preset. // Wizard calls save_preset with a name "My Settings", otherwise no name is provided and this method -// opens a Slic3r::GUI::SavePresetWindow dialog. +// opens a Slic3r::GUI::SavePresetDialog dialog. void Tab::save_preset(std::string name /*= ""*/, bool detach) { // since buttons(and choices too) don't get focus on Mac, we set focus manually @@ -3494,7 +3565,8 @@ void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::strin line.widget = widget; m_colored_Labels[opt_key] = nullptr; - optgroup->append_line(line, &m_colored_Labels[opt_key]); + line.full_Label = &m_colored_Labels[opt_key]; + optgroup->append_line(line); } // Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. @@ -3649,10 +3721,15 @@ void TabPrinter::apply_extruder_cnt_from_cache() void Tab::compatible_widget_reload(PresetDependencies &deps) { + Field* field = this->get_field(deps.key_condition); + if (!field) + return; + bool has_any = ! m_config->option(deps.key_list)->values.empty(); has_any ? deps.btn->Enable() : deps.btn->Disable(); deps.checkbox->SetValue(! has_any); - this->get_field(deps.key_condition)->toggle(! has_any); + + field->toggle(! has_any); } void Tab::fill_icon_descriptions() @@ -3731,15 +3808,34 @@ void Page::reload_config() group->reload_config(); } -void Page::update_visibility(ConfigOptionMode mode) +void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibility) { bool ret_val = false; - for (auto group : m_optgroups) - ret_val = group->update_visibility(mode) || ret_val; + for (auto group : m_optgroups) { + ret_val = (update_contolls_visibility ? + group->update_visibility(mode) : // update visibility for all controlls in group + group->is_visible(mode) // just detect visibility for the group + ) || ret_val; + } m_show = ret_val; } +void Page::activate(ConfigOptionMode mode) +{ + for (auto group : m_optgroups) { + group->activate(); + group->update_visibility(mode); + group->reload_config(); + } +} + +void Page::clear() +{ + for (auto group : m_optgroups) + group->clear(); +} + void Page::msw_rescale() { for (auto group : m_optgroups) @@ -3838,65 +3934,6 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la return optgroup; } -void SavePresetWindow::build(const wxString& title, const std::string& default_name, std::vector &values) -{ - // TRN Preset - auto text = new wxStaticText(this, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(title)).str()), - wxDefaultPosition, wxDefaultSize); - m_combo = new wxComboBox(this, wxID_ANY, from_u8(default_name), - wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); - for (auto value : values) - m_combo->Append(from_u8(value)); - auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); - - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(text, 0, wxEXPAND | wxALL, 10); - sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); - sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); - - wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); - m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); - - SetSizer(sizer); - sizer->SetSizeHints(this); -} - -void SavePresetWindow::accept() -{ - m_chosen_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); - if (!m_chosen_name.empty()) { - const char* unusable_symbols = "<>[]:/\\|?*\""; - bool is_unusable_symbol = false; - bool is_unusable_suffix = false; - const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - is_unusable_symbol = true; - break; - } - } - if (m_chosen_name.find(unusable_suffix) != std::string::npos) - is_unusable_suffix = true; - - if (is_unusable_symbol) { - show_error(this,_(L("The supplied name is not valid;")) + "\n" + - _(L("the following characters are not allowed:")) + " " + unusable_symbols); - } - else if (is_unusable_suffix) { - show_error(this,_(L("The supplied name is not valid;")) + "\n" + - _(L("the following suffix is not allowed:")) + "\n\t" + - wxString::FromUTF8(unusable_suffix.c_str())); - } - else if (m_chosen_name == "- default -") { - show_error(this, _(L("The supplied name is not available."))); - } - else { - EndModal(wxID_OK); - } - } -} - void TabSLAMaterial::build() { m_presets = &m_preset_bundle->sla_materials; @@ -4120,6 +4157,34 @@ void TabSLAPrint::reload_config() Tab::reload_config(); } +void TabSLAPrint::update_description_lines() +{ + Tab::update_description_lines(); + + if (m_active_page && m_active_page->title() == "Supports") + { + bool is_visible = m_config->def()->get("support_object_elevation")->mode <= m_mode; + if (m_support_object_elevation_description_line) + { + m_support_object_elevation_description_line->Show(is_visible); + if (is_visible) + { + bool elev = !m_config->opt_bool("pad_enable") || !m_config->opt_bool("pad_around_object"); + m_support_object_elevation_description_line->SetText(elev ? "" : + from_u8((boost::format(_u8L("\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" + "To enable \"%1%\", please switch off \"%2%\"")) + % _L("Object elevation") % _L("Pad around object") % _L("Pad")).str())); + } + } + } +} + +void TabSLAPrint::toggle_options() +{ + if (m_active_page) + m_config_manipulation.toggle_print_sla_options(m_config); +} + void TabSLAPrint::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) @@ -4129,17 +4194,13 @@ void TabSLAPrint::update() m_config_manipulation.update_print_sla_config(m_config, true); - bool elev = !m_config->opt_bool("pad_enable") || !m_config->opt_bool("pad_around_object"); - m_support_object_elevation_description_line->SetText(elev ? "" : - from_u8((boost::format(_u8L("\"%1%\" is disabled because \"%2%\" is on in \"%3%\" category.\n" - "To enable \"%1%\", please switch off \"%2%\"")) - % _L("Object elevation") % _L("Pad around object") % _L("Pad")).str())); + update_description_lines(); Layout(); m_update_cnt--; if (m_update_cnt == 0) { - m_config_manipulation.toggle_print_sla_options(m_config); + toggle_options(); // update() could be called during undo/redo execution // Update of objectList can cause a crash in this case (because m_objects doesn't match ObjectList) @@ -4150,6 +4211,13 @@ void TabSLAPrint::update() } } +void TabSLAPrint::clear_pages() +{ + Tab::clear_pages(); + + m_support_object_elevation_description_line = nullptr; +} + ConfigManipulation Tab::get_config_manipulation() { auto load_config = [this]() @@ -4160,15 +4228,15 @@ ConfigManipulation Tab::get_config_manipulation() update(); }; - auto get_field_ = [this](const t_config_option_key& opt_key, int opt_index) { - return get_field(opt_key, opt_index); + auto cb_toggle_field = [this](const t_config_option_key& opt_key, bool toggle, int opt_index) { + return toggle_option(opt_key, toggle, opt_index); }; auto cb_value_change = [this](const std::string& opt_key, const boost::any& value) { return on_value_change(opt_key, value); }; - return ConfigManipulation(load_config, get_field_, cb_value_change); + return ConfigManipulation(load_config, cb_toggle_field, cb_value_change); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index f0b2e97b3d..e8bc2dbb51 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -60,7 +60,6 @@ public: bool m_is_nonsys_values{ true }; // Delayed layout after resizing the main window. - bool layout_valid = false; const std::vector& m_mode_bitmap_cache; public: @@ -73,7 +72,9 @@ public: size_t iconID() const { return m_iconID; } void set_config(DynamicPrintConfig* config_in) { m_config = config_in; } void reload_config(); - void update_visibility(ConfigOptionMode mode); + void update_visibility(ConfigOptionMode mode, bool update_contolls_visibility); + void activate(ConfigOptionMode mode); + void clear(); void msw_rescale(); void sys_color_changed(); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; @@ -195,7 +196,8 @@ protected: int m_icon_count; std::map m_icon_index; // Map from an icon file name to its index std::vector m_pages; - bool m_disable_tree_sel_changed_event; + Page* m_active_page {nullptr}; + bool m_disable_tree_sel_changed_event {false}; bool m_show_incompatible_presets; std::vector m_dependent_tabs; @@ -238,7 +240,7 @@ public: bool m_show_btn_incompatible_presets = false; PresetCollection* m_presets; DynamicPrintConfig* m_config; - ogStaticText* m_parent_preset_description_line; + ogStaticText* m_parent_preset_description_line = nullptr; ScalableButton* m_detach_preset_btn = nullptr; // map of option name -> wxStaticText (colored label, associated with option) @@ -277,7 +279,6 @@ public: void update_ui_items_related_on_parent_preset(const Preset* selected_preset_parent); void load_current_preset(); void rebuild_page_tree(); - void update_page_tree_visibility(); void update_btns_enabling(); void update_preset_choice(); // Select a new preset, possibly delete the current one. @@ -285,6 +286,10 @@ public: bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); bool may_switch_to_SLA_preset(); + virtual void clear_pages(); + virtual void update_description_lines(); + virtual void active_selected_page(); + void OnTreeSelChange(wxTreeEvent& event); void OnKeyDown(wxKeyEvent& event); @@ -294,6 +299,7 @@ public: void update_show_hide_incompatible_button(); void update_ui_from_settings(); void update_labels_colour(); + void decorate(); void update_changed_ui(); void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page); void update_changed_tree_ui(); @@ -307,6 +313,7 @@ public: virtual void on_preset_loaded() {} virtual void build() = 0; virtual void update() = 0; + virtual void toggle_options() = 0; virtual void init_options_list(); void load_initial_data(); void update_dirty(); @@ -319,6 +326,7 @@ public: virtual void sys_color_changed(); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); + void toggle_option(const std::string& opt_key, bool toggle, int opt_index = -1); bool set_value(const t_config_option_key& opt_key, const boost::any& value); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText); bool current_preset_is_dirty(); @@ -365,15 +373,18 @@ public: void build() override; void reload_config() override; + void update_description_lines() override; + void toggle_options() override; void update() override; - void OnActivate() override; +// void OnActivate() override; + void clear_pages() override; bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } }; class TabFilament : public Tab { - ogStaticText* m_volumetric_speed_description_line; - ogStaticText* m_cooling_description_line; + ogStaticText* m_volumetric_speed_description_line {nullptr}; + ogStaticText* m_cooling_description_line {nullptr}; void add_filament_overrides_page(); void update_filament_overrides_page(); @@ -388,8 +399,11 @@ public: void build() override; void reload_config() override; + void update_description_lines() override; + void toggle_options() override; void update() override; - void OnActivate() override; +// void OnActivate() override; + void clear_pages() override; bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } }; @@ -426,6 +440,8 @@ public: void build() override; void build_fff(); void build_sla(); + void active_selected_page() override; + void toggle_options() override; void update() override; void update_fff(); void update_sla(); @@ -455,6 +471,7 @@ public: void build() override; void reload_config() override; + void toggle_options() override {}; void update() override; void init_options_list() override; bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } @@ -472,25 +489,13 @@ public: void build() override; void reload_config() override; + void update_description_lines() override; + void toggle_options() override; void update() override; -// void init_options_list() override; + void clear_pages() override; bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } }; -class SavePresetWindow :public wxDialog -{ -public: - SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))) {} - ~SavePresetWindow() {} - - std::string m_chosen_name; - wxComboBox* m_combo; - - void build(const wxString& title, const std::string& default_name, std::vector &values); - void accept(); - std::string get_name() { return m_chosen_name; } -}; - } // GUI } // Slic3r From e7ae26fb8a2de049986ea8dc72310e4e77b9fc37 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 21 Sep 2020 13:43:47 +0200 Subject: [PATCH 542/826] Fix of #2834 (unretracted wipes on wipe tower) Wiping moves performed before moving away from the wipe tower were replaced by scheduling a regular wipe that is performed after normal gcode generator regains control. This makes it consistent with wipes on the model and gets rid of the unretracted wipes. --- src/libslic3r/GCode.cpp | 20 ++++++--- src/libslic3r/GCode/WipeTower.cpp | 71 +++++++++++++++++++++++++------ src/libslic3r/GCode/WipeTower.hpp | 7 +++ 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 431ad38304..0334720ff5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -294,13 +294,18 @@ namespace Slic3r { // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); + + auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { + Vec2f out = Eigen::Rotation2Df(alpha) * pt; + out += m_wipe_tower_pos; + return out; + }; + Vec2f start_pos = tcr.start_pos; Vec2f end_pos = tcr.end_pos; if (!tcr.priming) { - start_pos = Eigen::Rotation2Df(alpha) * start_pos; - start_pos += m_wipe_tower_pos; - end_pos = Eigen::Rotation2Df(alpha) * end_pos; - end_pos += m_wipe_tower_pos; + start_pos = transform_wt_pt(start_pos); + end_pos = transform_wt_pt(end_pos); } Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; @@ -403,7 +408,7 @@ namespace Slic3r { else { // Prepare a future wipe. - gcodegen.m_wipe.path.points.clear(); + /*gcodegen.m_wipe.path.points.clear(); if (new_extruder_id >= 0) { // Start the wipe at the current position. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); @@ -411,7 +416,10 @@ namespace Slic3r { gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, end_pos.y()))); - } + }*/ + gcodegen.m_wipe.reset_path(); + for (const Vec2f& wipe_pt : tcr.wipe_path) + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); } // Let the planner know we are traveling between objects. diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 0752b6dfcb..c5178eefa6 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -441,9 +441,26 @@ public: WipeTowerWriter& append(const std::string& text) { m_gcode += text; return *this; } + std::vector wipe_path() const + { + return m_wipe_path; + } + + WipeTowerWriter& add_wipe_point(const Vec2f& pt) + { + m_wipe_path.push_back(rotate(pt)); + return *this; + } + + WipeTowerWriter& add_wipe_point(float x, float y) + { + return add_wipe_point(Vec2f(x, y)); + } + private: Vec2f m_start_pos; Vec2f m_current_pos; + std::vector m_wipe_path; float m_current_z; float m_current_feedrate; size_t m_current_tool; @@ -790,7 +807,10 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_lay else { writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle - writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); + //writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); + } } } @@ -820,6 +840,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_lay result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos_rotated(); result.end_pos = writer.pos_rotated(); + result.wipe_path = writer.wipe_path(); return result; } @@ -853,13 +874,20 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of for (size_t i = 0; i < 4; ++ i) { box.expand(spacing); writer.travel (box.ld, 7000) - .extrude(box.lu, 2100).extrude(box.ru) - .extrude(box.rd ).extrude(box.ld); + .extrude(box.lu, 2100).extrude(box.ru) + .extrude(box.rd ).extrude(box.ld); } - writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. - writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - .travel(wipeTower_box.ld); + //writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. + //writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. + //.travel(wipeTower_box.ld); + + box.expand(-spacing); + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(box.ld) + .add_wipe_point(box.rd); + + writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" ";-----------------------------------\n"); @@ -884,6 +912,7 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos_rotated(); result.end_pos = writer.pos_rotated(); + result.wipe_path = writer.wipe_path(); return result; } @@ -1170,11 +1199,18 @@ void WipeTower::toolchange_Wipe( m_left_to_right = !m_left_to_right; } - // this is neither priming nor not the last toolchange on this layer - we are going back to the model - wipe the nozzle + // this is neither priming nor not the last toolchange on this layer - we are + // going back to the model - wipe the nozzle. if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { m_left_to_right = !m_left_to_right; - writer.travel(writer.x(), writer.y() - dy) - .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y()); + //writer.comment_with_value("starting wipe tower wipe ", 0) + // .travel(writer.x(), writer.y() - dy) + // .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y()) + // .comment_with_value("finished wipe tower wipe ", 0); + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x(), writer.y() - dy) + .add_wipe_point(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); + } writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. @@ -1238,7 +1274,9 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); } - writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + //writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(box.rd.x()-m_perimeter_width/2.f,writer.y()); } else { // Extrude a sparse infill to support the material to be printed above. const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); @@ -1257,10 +1295,15 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.travel(x,writer.y()); writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y()); } - writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower + writer.add_wipe_point(Vec2f(writer.x(), writer.y())) + .add_wipe_point(Vec2f(left, writer.y())); + //writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower + } + else { + writer.add_wipe_point(Vec2f(writer.x(), writer.y())) + .add_wipe_point(Vec2f(right, writer.y())); + // writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } - else - writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } writer.append("; CP EMPTY GRID END\n" ";------------------\n\n\n\n\n\n\n"); @@ -1285,6 +1328,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos_rotated(); result.end_pos = writer.pos_rotated(); + result.wipe_path = writer.wipe_path(); return result; } @@ -1432,6 +1476,7 @@ void WipeTower::generate(std::vector> & last_toolchange.gcode += finish_layer_toolchange.gcode; last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end()); last_toolchange.end_pos = finish_layer_toolchange.end_pos; + last_toolchange.wipe_path = finish_layer_toolchange.wipe_path; } else layer_result.emplace_back(std::move(finish_layer_toolchange)); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index f353151575..e4b44e2bbd 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -57,6 +57,13 @@ public: // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) bool priming; + // Pass a polyline so that normal G-code generator can do a wipe for us. + // The wipe cannot be done by the wipe tower because it has to pass back + // a loaded extruder, so it would have to either do a wipe with no retraction + // (leading to https://github.com/prusa3d/PrusaSlicer/issues/2834) or do + // an extra retraction-unretraction pair. + std::vector wipe_path; + // Initial tool int initial_tool; From a13fc805d78eeba64456493c4ab61d7265378857 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 21 Sep 2020 14:25:53 +0200 Subject: [PATCH 543/826] Removed obsolete wipe tower related code Most of the code is already commented out for a long time, it should be safe to remove now. --- src/libslic3r/GCode.cpp | 81 +++++++++---------------------- src/libslic3r/GCode/WipeTower.cpp | 79 ++++++------------------------ src/libslic3r/GCode/WipeTower.hpp | 2 +- src/libslic3r/Print.cpp | 2 +- 4 files changed, 38 insertions(+), 126 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0334720ff5..107d56da52 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -303,7 +303,7 @@ namespace Slic3r { Vec2f start_pos = tcr.start_pos; Vec2f end_pos = tcr.end_pos; - if (!tcr.priming) { + if (! tcr.priming) { start_pos = transform_wt_pt(start_pos); end_pos = transform_wt_pt(end_pos); } @@ -313,7 +313,7 @@ namespace Slic3r { std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); - if (!tcr.priming) { + if (! tcr.priming) { // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); @@ -328,7 +328,7 @@ namespace Slic3r { double current_z = gcodegen.writer().get_position().z(); if (z == -1.) // in case no specific z was provided, print at current_z pos z = current_z; - if (!is_approx(z, current_z)) { + if (! is_approx(z, current_z)) { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); gcode += gcodegen.writer().unretract(); @@ -350,27 +350,25 @@ namespace Slic3r { // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. // Otherwise, leave control to the user completely. std::string toolchange_gcode_str; - if (true /*gcodegen.writer().extruder() != nullptr*/) { - const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; - if (!toolchange_gcode.empty()) { - DynamicConfig config; - int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; - config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); - toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); - check_add_eol(toolchange_gcode_str); - } + const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; + if (! toolchange_gcode.empty()) { + DynamicConfig config; + int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + } - std::string toolchange_command; - if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) - toolchange_command = gcodegen.writer().toolchange(new_extruder_id); - if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) - toolchange_gcode_str += toolchange_command; - else { - // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. - } + std::string toolchange_command; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) + toolchange_gcode_str += toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. } gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); @@ -408,15 +406,6 @@ namespace Slic3r { else { // Prepare a future wipe. - /*gcodegen.m_wipe.path.points.clear(); - if (new_extruder_id >= 0) { - // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); - // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, - end_pos.y()))); - }*/ gcodegen.m_wipe.reset_path(); for (const Vec2f& wipe_pt : tcr.wipe_path) gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); @@ -507,37 +496,11 @@ namespace Slic3r { assert(m_layer_idx == 0); std::string gcode; - - // Disable linear advance for the wipe tower operations. - //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - for (const WipeTower::ToolChangeResult& tcr : m_priming) { - if (!tcr.extrusions.empty()) + if (! tcr.extrusions.empty()) gcode += append_tcr(gcodegen, tcr, tcr.new_tool); - - - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - //gcode += tcr.gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - // unsigned int current_extruder_id = tcr.extrusions.back().tool; - // gcodegen.writer().toolchange(current_extruder_id); - // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); - } - // A phony move to the end position at the wipe tower. - /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); - // Prepare a future wipe. - gcodegen.m_wipe.path.points.clear(); - // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); - // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, - m_priming.back().end_pos.y)));*/ - return gcode; } diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index c5178eefa6..944bea5c1f 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1,22 +1,6 @@ -/* - -TODO LIST ---------- - -1. cooling moves - DONE -2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE -3. priming extrusions (last wipe must clear the color) - DONE -4. Peter's wipe tower - layer's are not exactly square -5. Peter's wipe tower - variable width for higher levels -6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) -7. Peter's wipe tower - enable enhanced first layer adhesion - -*/ - #include "WipeTower.hpp" -#include -#include +#include #include #include #include @@ -28,13 +12,16 @@ TODO LIST #endif // ENABLE_GCODE_VIEWER #include "BoundingBox.hpp" -#if defined(__linux) || defined(__GNUC__ ) -#include -#endif /* __linux */ -#ifdef _MSC_VER -#define strcasecmp _stricmp -#endif +// Experimental "Peter's wipe tower" feature was partially implemented, inspired by +// PJR's idea of alternating two perpendicular wiping directions on a square tower. +// It is probably never going to be finished, there are multiple remaining issues +// and there is probably no need to go down this way. m_peters_wipe_tower variable +// turns this on, maybe it should just be removed. Anyway, the issues are +// - layer's are not exactly square +// - variable width for higher levels +// - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) +// - enable enhanced first layer adhesion namespace Slic3r @@ -730,7 +717,7 @@ std::vector WipeTower::prime( return results; } -WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_layer) +WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) { if ( m_print_brim ) return toolchange_Brim(); @@ -807,7 +794,6 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_lay else { writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle - //writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); writer.add_wipe_point(writer.x(), writer.y()) .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); @@ -878,16 +864,11 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of .extrude(box.rd ).extrude(box.ld); } - //writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. - //writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - //.travel(wipeTower_box.ld); - box.expand(-spacing); writer.add_wipe_point(writer.x(), writer.y()) .add_wipe_point(box.ld) .add_wipe_point(box.rd); - writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" ";-----------------------------------\n"); @@ -956,13 +937,6 @@ void WipeTower::toolchange_Unload( else sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width; - //debugging: - /* float oldx = writer.x(); - float oldy = writer.y(); - writer.travel(xr,sparse_beginning_y); - writer.extrude(xr+5,writer.y()); - writer.travel(oldx,oldy);*/ - float sum_of_depths = 0.f; for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange if (tch.old_tool == m_current_tool) { @@ -970,13 +944,6 @@ void WipeTower::toolchange_Unload( float ramming_end_y = sum_of_depths; ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line - // debugging: - /*float oldx = writer.x(); - float oldy = writer.y(); - writer.travel(xl,ramming_end_y); - writer.extrude(xl-15,writer.y()); - writer.travel(oldx,oldy);*/ - if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) || (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) ) { @@ -1027,12 +994,6 @@ void WipeTower::toolchange_Unload( .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) - - /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed - .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) - .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ .resume_preview(); } // Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should @@ -1125,11 +1086,6 @@ void WipeTower::toolchange_Load( writer.append("; CP TOOLCHANGE LOAD\n") .suppress_preview() - /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration - .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ @@ -1203,10 +1159,6 @@ void WipeTower::toolchange_Wipe( // going back to the model - wipe the nozzle. if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { m_left_to_right = !m_left_to_right; - //writer.comment_with_value("starting wipe tower wipe ", 0) - // .travel(writer.x(), writer.y() - dy) - // .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y()) - // .comment_with_value("finished wipe tower wipe ", 0); writer.add_wipe_point(writer.x(), writer.y()) .add_wipe_point(writer.x(), writer.y() - dy) .add_wipe_point(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); @@ -1274,7 +1226,6 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); } - //writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle writer.add_wipe_point(writer.x(), writer.y()) .add_wipe_point(box.rd.x()-m_perimeter_width/2.f,writer.y()); } @@ -1297,12 +1248,10 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() } writer.add_wipe_point(Vec2f(writer.x(), writer.y())) .add_wipe_point(Vec2f(left, writer.y())); - //writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } else { writer.add_wipe_point(Vec2f(writer.x(), writer.y())) .add_wipe_point(Vec2f(right, writer.y())); - // writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } } writer.append("; CP EMPTY GRID END\n" @@ -1400,7 +1349,7 @@ void WipeTower::save_on_last_wipe() continue; for (const auto &toolchange : m_layer_info->tool_changes) - tool_change(toolchange.new_tool, false); + tool_change(toolchange.new_tool); float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); @@ -1462,7 +1411,7 @@ void WipeTower::generate(std::vector> & m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; for (const auto &toolchange : layer.tool_changes) - layer_result.emplace_back(tool_change(toolchange.new_tool, false)); + layer_result.emplace_back(tool_change(toolchange.new_tool)); if (! layer_finished()) { auto finish_layer_toolchange = finish_layer(); @@ -1512,4 +1461,4 @@ void WipeTower::make_wipe_tower_square() lay.extra_spacing = lay.depth / lay.toolchanges_depth(); } -}; // namespace Slic3r +} // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index e4b44e2bbd..26f48785a9 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -161,7 +161,7 @@ public: // Returns gcode for a toolchange and a final print head position. // On the first layer, extrude a brim around the future wipe tower first. - ToolChangeResult tool_change(size_t new_tool, bool last_in_layer); + ToolChangeResult tool_change(size_t new_tool); // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a82ab3dddc..a360d840f4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2174,7 +2174,7 @@ void Print::_make_wipe_tower() wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true); } m_wipe_tower_data.final_purge = Slic3r::make_unique( - wipe_tower.tool_change((unsigned int)-1, false)); + wipe_tower.tool_change((unsigned int)(-1))); m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); From 35d225d673de4a937db27aaf79080b1053c2a06a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 22 Sep 2020 16:16:35 +0200 Subject: [PATCH 544/826] Fixed scaling on MSW for new OptionsGroup --- src/slic3r/GUI/BedShapeDialog.cpp | 3 ++ src/slic3r/GUI/GUI_App.cpp | 17 +++++++---- src/slic3r/GUI/MainFrame.cpp | 5 +-- src/slic3r/GUI/OptionsGroup.cpp | 2 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 2 ++ src/slic3r/GUI/Preferences.cpp | 8 +++++ src/slic3r/GUI/Tab.cpp | 39 ++++++++++++++++++------ src/slic3r/GUI/Tab.hpp | 1 + 8 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 2fc7d10366..29acdff007 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -252,6 +252,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf return sizer; }; optgroup->append_line(line); + optgroup->activate(); wxPanel* texture_panel = init_texture_panel(); wxPanel* model_panel = init_model_panel(); @@ -373,6 +374,7 @@ wxPanel* BedShapePanel::init_texture_panel() return sizer; }; optgroup->append_line(line); + optgroup->activate(); panel->SetSizerAndFit(optgroup->sizer); @@ -452,6 +454,7 @@ wxPanel* BedShapePanel::init_model_panel() return sizer; }; optgroup->append_line(line); + optgroup->activate(); panel->SetSizerAndFit(optgroup->sizer); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 26f19d0c1d..0e4e1c8977 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -80,22 +80,27 @@ namespace GUI { class MainFrame; -class InitTimer +class TaskTimer { - std::chrono::milliseconds start_timer; + std::chrono::milliseconds start_timer; + std::string task_name; public: - InitTimer() + TaskTimer(std::string task_name): + task_name(task_name.empty() ? "task" : task_name) { start_timer = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()); } - ~InitTimer() + ~TaskTimer() { std::chrono::milliseconds stop_timer = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()); auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count(); - printf("on_init duration = %lld ms \n", process_duration); + std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str(); + printf(out.c_str()); + std::wstring stemp = std::wstring(out.begin(), out.end()); + OutputDebugString(stemp.c_str()); } }; @@ -617,7 +622,7 @@ bool GUI_App::OnInit() bool GUI_App::on_init_inner() { - InitTimer local_timer; + TaskTimer timer("on_init"); // Verify resources path const wxString resources_dir = from_u8(Slic3r::resources_dir()); wxCHECK_MSG(wxDirExists(resources_dir), false, diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index fbb7a190f0..941841f6f1 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -345,8 +345,9 @@ void MainFrame::update_layout() fromDlg, toDlg }; - State update_scaling_state = m_layout == ESettingsLayout::Dlg ? State::fromDlg : - layout == ESettingsLayout::Dlg ? State::toDlg : State::noUpdate; + State update_scaling_state = m_layout == ESettingsLayout::Unknown ? State::noUpdate : // don't scale settings dialog from the application start + m_layout == ESettingsLayout::Dlg ? State::fromDlg : + layout == ESettingsLayout::Dlg ? State::toDlg : State::noUpdate; #endif //__WXMSW__ m_layout = layout; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index a5e896fa33..6e653c2f81 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -632,7 +632,7 @@ void ConfigOptionsGroup::msw_rescale() const int em = em_unit(parent()); // rescale width of label column - if (!m_options_mode.empty() && label_width > 1) + if (m_grid_sizer && !m_options_mode.empty() && label_width > 1) { const int cols = m_grid_sizer->GetCols(); const int rows = m_grid_sizer->GetEffectiveRowsCount(); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 3d832ae569..aa8a3811ad 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -374,6 +374,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line(option); } + m_optgroup->activate(); update(); } @@ -434,6 +435,7 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); + m_add_preset_btn->msw_rescale(); m_printhost_browse_btn->msw_rescale(); m_printhost_test_btn->msw_rescale(); if (m_printhost_cafile_browse_btn) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index a3a23fd851..c5fb766376 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -140,6 +140,8 @@ void PreferencesDialog::build() option = Option(def, "show_splash_screen"); m_optgroup_general->append_single_option_line(option); + m_optgroup_general->activate(); + m_optgroup_camera = std::make_shared(this, _(L("Camera"))); m_optgroup_camera->label_width = 40; m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -160,6 +162,8 @@ void PreferencesDialog::build() option = Option(def, "use_free_camera"); m_optgroup_camera->append_single_option_line(option); + m_optgroup_camera->activate(); + m_optgroup_gui = std::make_shared(this, _(L("GUI"))); m_optgroup_gui->label_width = 40; m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -184,6 +188,8 @@ void PreferencesDialog::build() option = Option(def, "use_custom_toolbar_size"); m_optgroup_gui->append_single_option_line(option); + m_optgroup_gui->activate(); + create_icon_size_slider(); m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); @@ -202,6 +208,8 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("use_environment_map") == "1" }); option = Option(def, "use_environment_map"); m_optgroup_render->append_single_option_line(option); + + m_optgroup_render->activate(); #endif // ENABLE_ENVIRONMENT_MAP auto sizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e8519cc0f6..47c4cf8f95 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -882,8 +882,10 @@ void Tab::msw_rescale() m_treectrl->AssignImageList(m_icons); // rescale options_groups - for (auto page : m_pages) - page->msw_rescale(); + if (m_active_page) + m_active_page->msw_rescale(); + //for (auto page : m_pages) + // page->msw_rescale(); Layout(); } @@ -1169,7 +1171,9 @@ void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) }; auto detach_preset_btn = [this](wxWindow* parent) { - add_scaled_button(parent, &m_detach_preset_btn, "lock_open_sys", _(L("Detach from system preset")), wxBU_LEFT | wxBU_EXACTFIT); + //add_scaled_button(parent, &m_detach_preset_btn, "lock_open_sys", _(L("Detach from system preset")), wxBU_LEFT | wxBU_EXACTFIT); + m_detach_preset_btn = new ScalableButton(parent, wxID_ANY, "lock_open_sys", _L("Detach from system preset"), + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); ScalableButton* btn = m_detach_preset_btn; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -2668,8 +2672,10 @@ void TabPrinter::build_unregular_pages() optgroup = page->new_optgroup(L("Preview")); auto reset_to_filament_color = [this, extruder_idx](wxWindow* parent) { - add_scaled_button(parent, &m_reset_to_filament_color, "undo", - _(L("Reset to Filament Color")), wxBU_LEFT | wxBU_EXACTFIT); + //add_scaled_button(parent, &m_reset_to_filament_color, "undo", + // _(L("Reset to Filament Color")), wxBU_LEFT | wxBU_EXACTFIT); + m_reset_to_filament_color = new ScalableButton(parent, wxID_ANY, "undo", _L("Reset to Filament Color"), + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); ScalableButton* btn = m_reset_to_filament_color; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -2776,6 +2782,12 @@ void TabPrinter::active_selected_page() m_active_page->set_value("extruders_count", int(m_extruders_count)); } +void TabPrinter::clear_pages() +{ + Tab::clear_pages(); + m_reset_to_filament_color = nullptr; +} + void TabPrinter::toggle_options() { if (!m_active_page || m_presets->get_edited_preset().printer_technology() == ptSLA) @@ -3289,9 +3301,15 @@ void Tab::clear_pages() for (auto p : m_pages) p->clear(); - // nulling description lines pointers + // nulling pointers m_parent_preset_description_line = nullptr; m_detach_preset_btn = nullptr; + + m_compatible_printers.checkbox = nullptr; + m_compatible_printers.btn = nullptr; + + m_compatible_prints.checkbox = nullptr; + m_compatible_prints.btn = nullptr; } void Tab::update_description_lines() @@ -3574,7 +3592,9 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep { deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - add_scaled_button(parent, &deps.btn, "printer_white", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), wxBU_LEFT | wxBU_EXACTFIT); +// add_scaled_button(parent, &deps.btn, "printer_white", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), wxBU_LEFT | wxBU_EXACTFIT); + deps.btn = new ScalableButton(parent, wxID_ANY, "printer_white", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); BlinkingBitmap* bbmp = new BlinkingBitmap(parent); @@ -3652,8 +3672,9 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep // Return a callback to create a TabPrinter widget to edit bed shape wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) { - ScalableButton* btn; - add_scaled_button(parent, &btn, "printer_white", " " + _(L("Set")) + " " + dots, wxBU_LEFT | wxBU_EXACTFIT); + ScalableButton* btn = new ScalableButton(parent, wxID_ANY, "printer_white", " " + _(L("Set")) + " " + dots, + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); +// add_scaled_button(parent, &btn, "printer_white", " " + _(L("Set")) + " " + dots, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().normal_font()); BlinkingBitmap* bbmp = new BlinkingBitmap(parent); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index e8bc2dbb51..89dc2d1316 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -441,6 +441,7 @@ public: void build_fff(); void build_sla(); void active_selected_page() override; + void clear_pages() override; void toggle_options() override; void update() override; void update_fff(); From 67bdf9687d7c1105db51869a1c1bfa8cb20475b5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 22 Sep 2020 20:45:59 +0200 Subject: [PATCH 545/826] Alessandro's name spelling fix (README, manifests) --- README.md | 2 +- src/platform/msw/PrusaSlicer-gcodeviewer.rc.in | 2 +- src/platform/msw/PrusaSlicer.rc.in | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6fd1af4e20..2b93a47b01 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ compatible with any modern printer based on the RepRap toolchain, including all those based on the Marlin, Prusa, Sprinter and Repetier firmware. It also works with Mach3, LinuxCNC and Machinekit controllers. -PrusaSlicer is based on [Slic3r](https://github.com/Slic3r/Slic3r) by Alessandro Ranelucci and the RepRap community. +PrusaSlicer is based on [Slic3r](https://github.com/Slic3r/Slic3r) by Alessandro Ranellucci and the RepRap community. See the [project homepage](https://www.prusa3d.com/slic3r-prusa-edition/) and the [documentation directory](doc/) for more information. diff --git a/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in b/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in index 7f4e5a15c3..eed737cb77 100644 --- a/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in +++ b/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in @@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@ VALUE "ProductName", "@SLIC3R_APP_NAME@ G-code Viewer" VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" VALUE "InternalName", "@SLIC3R_APP_NAME@ G-code Viewer" - VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranelucci" + VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranellucci" VALUE "OriginalFilename", "prusa-gcodeviewer.exe" } } diff --git a/src/platform/msw/PrusaSlicer.rc.in b/src/platform/msw/PrusaSlicer.rc.in index fb75305c8a..a4520c6d73 100644 --- a/src/platform/msw/PrusaSlicer.rc.in +++ b/src/platform/msw/PrusaSlicer.rc.in @@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@ VALUE "ProductName", "@SLIC3R_APP_NAME@" VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" VALUE "InternalName", "@SLIC3R_APP_NAME@" - VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranelucci" + VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranellucci" VALUE "OriginalFilename", "prusa-slicer.exe" } } From 9377013824e900d3344b3db057ff330ed95d82aa Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 23 Sep 2020 08:33:16 +0200 Subject: [PATCH 546/826] Fixed non-MSW builds --- src/slic3r/GUI/GUI_App.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 0e4e1c8977..14fabc3edf 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -99,8 +99,10 @@ public: auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count(); std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str(); printf(out.c_str()); +#ifdef __WXMSW__ std::wstring stemp = std::wstring(out.begin(), out.end()); OutputDebugString(stemp.c_str()); +#endif } }; From 52e605069844c02113c93d30f2306c4fb6e2970c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 23 Sep 2020 09:35:30 +0200 Subject: [PATCH 547/826] Fixed msw_scale() for Infill field --- src/slic3r/GUI/Field.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index e075c7709b..09e29caf92 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1118,8 +1118,7 @@ void Choice::msw_rescale(bool rescale_sidetext/* = false*/) Field::msw_rescale(); wxBitmapComboBox* field = dynamic_cast(window); - - const wxString selection = field->GetString(field->GetSelection()); + const wxString selection = field->GetValue();// field->GetString(index); /* To correct scaling (set new controll size) of a wxBitmapCombobox * we need to refill control with new bitmaps. So, in our case : From 056c46d01fd9a1d33d6aa9fa81a5d0e6175ca679 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 23 Sep 2020 12:18:39 +0200 Subject: [PATCH 548/826] Undo/Redo serialization extension: If an object indicates a valid timestamp, then the timestamp is relied upon to not serialize the object data if the timestamp of the same object on the undo/redo stack matches. --- src/libslic3r/ObjectID.hpp | 9 +++++-- src/slic3r/Utils/UndoRedo.cpp | 48 ++++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 920f512de3..fd5cc80ae8 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -49,7 +49,12 @@ private: class ObjectBase { public: - ObjectID id() const { return m_id; } + ObjectID id() const { return m_id; } + // Return an optional timestamp of this object. + // If the timestamp returned is non-zero, then the serialization framework will + // only save this object on the Undo/Redo stack if the timestamp is different + // from the timestmap of the object at the top of the Undo / Redo stack. + virtual uint64_t timestamp() const { return 0; } protected: // Constructors to be only called by derived classes. @@ -59,7 +64,7 @@ protected: // by an existing ID copied from elsewhere. ObjectBase(int) : m_id(ObjectID(0)) {} // The class tree will have virtual tables and type information. - virtual ~ObjectBase() {} + virtual ~ObjectBase() = default; // Use with caution! void set_new_unique_id() { m_id = generate_new_id(); } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 10b8062f7b..79cf3e82e2 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -307,7 +307,11 @@ private: size_t size; char data[1]; + // The serialized data matches the data stored here. bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; } + + // The timestamp matches the timestamp serialized in the data stored here. + bool matches_timestamp(uint64_t timestamp) { assert(timestamp > 0); assert(this->size > 8); return memcmp(this->data, ×tamp, 8) == 0; } }; Interval m_interval; @@ -350,7 +354,8 @@ public: size_t size() const { return m_data->size; } size_t refcnt() const { return m_data->refcnt; } bool matches(const std::string& data) { return m_data->matches(data); } - size_t memsize() const { + bool matches_timestamp(uint64_t timestamp) { return m_data->matches_timestamp(timestamp); } + size_t memsize() const { return m_data->refcnt == 1 ? // Count just the size of the snapshot data. m_data->size : @@ -398,6 +403,27 @@ public: return memsize; } + // If an object provides a reliable timestamp and the object serializes the timestamp first, + // then we may just check the validity of the timestamp against the last snapshot without + // having to serialize the whole object. This reduces the amount of serialization and memcmp + // when taking a snapshot. + bool try_save_timestamp(size_t active_snapshot_time, size_t current_time, uint64_t timestamp) { + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); + if (! m_history.empty() && m_history.back().matches_timestamp(timestamp)) { + if (m_history.back().end() < active_snapshot_time) + // Share the previous data by reference counting. + m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back()); + else { + assert(m_history.back().end() == active_snapshot_time); + // Just extend the last interval using the old data. + m_history.back().extend_end(current_time + 1); + } + return true; + } + // The timestamp is not valid, the caller has to call this->save() with the serialized data. + return false; + } + void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); if (m_history.empty() || m_history.back().end() < active_snapshot_time) { @@ -749,13 +775,23 @@ template ObjectID StackImpl::save_mutable_object(const T &object) if (it_object_history == m_objects.end()) it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr>(new MutableObjectHistory()))); auto *object_history = static_cast*>(it_object_history->second.get()); - // Then serialize the object into a string. - std::ostringstream oss; + bool needs_to_save = true; { - Slic3r::UndoRedo::OutputArchive archive(*this, oss); - archive(object); + // If the timestamp returned is non zero, then it is considered reliable. + // The caller is supposed to serialize the timestamp first. + uint64_t timestamp = object.timestamp(); + if (timestamp > 0) + needs_to_save = ! object_history->try_save_timestamp(m_active_snapshot_time, m_current_time, timestamp); + } + if (needs_to_save) { + // Serialize the object into a string. + std::ostringstream oss; + { + Slic3r::UndoRedo::OutputArchive archive(*this, oss); + archive(object); + } + object_history->save(m_active_snapshot_time, m_current_time, oss.str()); } - object_history->save(m_active_snapshot_time, m_current_time, oss.str()); return object.id(); } From 0dad7adfa1ecd42a429a97a583cf908078386372 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 23 Sep 2020 12:58:58 +0200 Subject: [PATCH 549/826] "There is an object with no extrusions on the first layer." should throw SlicingError, not RuntimeError. --- src/libslic3r/GCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 431ad38304..bed8b1dba6 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -629,7 +629,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Check that there are extrusions on the very first layer. if (layers_to_print.size() == 1u) { if (!has_extrusions) - throw Slic3r::RuntimeError(_(L("There is an object with no extrusions on the first layer."))); + throw Slic3r::SlicingError(_(L("There is an object with no extrusions on the first layer."))); } // In case there are extrusions on this layer, check there is a layer to lay it on. From dde64d361b58247516acc8f69f33dea33466edec Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 23 Sep 2020 12:59:15 +0200 Subject: [PATCH 550/826] Tiny polishing and documentation. --- src/libslic3r/Model.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b9045e28bf..c6b7c60306 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -448,7 +448,11 @@ public: Vec3d mesh_offset{ Vec3d::Zero() }; Geometry::Transformation transform; - template void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset, transform); } + template void serialize(Archive& ar) { + //FIXME Vojtech: Serialize / deserialize only if the Source is set. + // likely testing input_file or object_idx would be sufficient. + ar(input_file, object_idx, volume_idx, mesh_offset, transform); + } }; Source source; @@ -467,7 +471,7 @@ public: FacetsAnnotation m_supported_facets; // List of seam enforcers/blockers. - FacetsAnnotation m_seam_facets; + FacetsAnnotation m_seam_facets; // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; } From 0974d2a0e6347e0b399803c0f8b8a0657af7819c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 24 Sep 2020 14:14:22 +0200 Subject: [PATCH 551/826] Added missing include to fix build against wx3.0 (Linux) --- src/slic3r/GUI/ExtraRenderers.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index 4c1fb09dec..de013e9d23 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_GUI_ExtraRenderers_hpp_ #define slic3r_GUI_ExtraRenderers_hpp_ +#include + #include #if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) From 54976e29bbecae75d6b5bd65454d9b7d65e9d461 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 24 Sep 2020 15:34:13 +0200 Subject: [PATCH 552/826] New class ModelConfig wrapping DynamicPrintConfig and a timestamp to help with detecting "not changed" event when taking Undo/Redo snapshot or synchronizing with the back-end. Converted layer height profile and supports / seam painted areas to the same timestamp controlled structure. --- src/libslic3r/Format/3mf.cpp | 20 +++-- src/libslic3r/Format/AMF.cpp | 10 ++- src/libslic3r/Model.cpp | 33 +++---- src/libslic3r/Model.hpp | 93 +++++++++++-------- src/libslic3r/ObjectID.cpp | 2 + src/libslic3r/ObjectID.hpp | 40 ++++++++- src/libslic3r/Print.cpp | 22 ++--- src/libslic3r/PrintConfig.cpp | 2 + src/libslic3r/PrintConfig.hpp | 90 +++++++++++++++++++ src/libslic3r/PrintObject.cpp | 8 +- src/libslic3r/SLAPrint.cpp | 4 +- src/libslic3r/Slicing.cpp | 13 +-- src/libslic3r/Slicing.hpp | 5 +- src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/ConfigManipulation.hpp | 8 +- src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/GUI_ObjectLayers.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 95 ++++++++++---------- src/slic3r/GUI/GUI_ObjectList.hpp | 13 +-- src/slic3r/GUI/GUI_ObjectSettings.cpp | 12 ++- src/slic3r/GUI/GUI_ObjectSettings.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 16 ++-- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 14 +-- src/slic3r/GUI/OptionsGroup.cpp | 23 +++-- src/slic3r/GUI/OptionsGroup.hpp | 32 +++++-- src/slic3r/GUI/Selection.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 8 +- src/slic3r/Utils/UndoRedo.cpp | 2 +- 28 files changed, 366 insertions(+), 215 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 46a6c02af1..436a2b29f2 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -683,23 +683,23 @@ namespace Slic3r { // m_layer_heights_profiles are indexed by a 1 based model object index. IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); if (obj_layer_heights_profile != m_layer_heights_profiles.end()) - model_object->layer_height_profile = obj_layer_heights_profile->second; + model_object->layer_height_profile.set(std::move(obj_layer_heights_profile->second)); // m_layer_config_ranges are indexed by a 1 based model object index. IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); if (obj_layer_config_ranges != m_layer_config_ranges.end()) - model_object->layer_config_ranges = obj_layer_config_ranges->second; + model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second); // m_sla_support_points are indexed by a 1 based model object index. IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { - model_object->sla_support_points = obj_sla_support_points->second; + model_object->sla_support_points = std::move(obj_sla_support_points->second); model_object->sla_points_status = sla::PointsStatus::UserModified; } IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1); if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) { - model_object->sla_drain_holes = obj_drain_holes->second; + model_object->sla_drain_holes = std::move(obj_drain_holes->second); } IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); @@ -934,7 +934,7 @@ namespace Slic3r { double max_z = range_tree.get(".max_z"); // get Z range information - DynamicPrintConfig& config = config_ranges[{ min_z, max_z }]; + DynamicPrintConfig config; for (const auto& option : range_tree) { @@ -945,10 +945,12 @@ namespace Slic3r { config.set_deserialize(opt_key, value); } + + config_ranges[{ min_z, max_z }].assign_config(std::move(config)); } if (!config_ranges.empty()) - m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(obj_idx, config_ranges)); + m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(obj_idx, std::move(config_ranges))); } } } @@ -2407,7 +2409,7 @@ namespace Slic3r { triangles_count += (int)its.indices.size(); volume_it->second.last_triangle_id = triangles_count - 1; - for (size_t i = 0; i < its.indices.size(); ++ i) + for (int i = 0; i < int(its.indices.size()); ++ i) { stream << " <" << TRIANGLE_TAG << " "; for (int j = 0; j < 3; ++j) @@ -2472,7 +2474,7 @@ namespace Slic3r { for (const ModelObject* object : model.objects) { ++count; - const std::vector &layer_height_profile = object->layer_height_profile; + const std::vector& layer_height_profile = object->layer_height_profile.get(); if ((layer_height_profile.size() >= 4) && ((layer_height_profile.size() % 2) == 0)) { sprintf(buffer, "object_id=%d|", count); @@ -2527,7 +2529,7 @@ namespace Slic3r { range_tree.put(".max_z", range.first.second); // store range configuration - const DynamicPrintConfig& config = range.second; + const ModelConfig& config = range.second; for (const std::string& opt_key : config.keys()) { pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 1a706afa92..a2117d63bf 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -688,7 +688,7 @@ void AMFParserContext::endElement(const char * /* name */) else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) { const char *opt_key = m_value[0].c_str() + 7; if (print_config_def.options.find(opt_key) != print_config_def.options.end()) { - DynamicPrintConfig *config = nullptr; + ModelConfig *config = nullptr; if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL && m_material) config = &m_material->config; @@ -706,15 +706,17 @@ void AMFParserContext::endElement(const char * /* name */) } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { // Parse object's layer height profile, a semicolon separated list of floats. char *p = m_value[1].data(); + std::vector data; for (;;) { char *end = strchr(p, ';'); if (end != nullptr) *end = 0; - m_object->layer_height_profile.push_back(float(atof(p))); + data.emplace_back(float(atof(p))); if (end == nullptr) break; p = end + 1; } + m_object->layer_height_profile.set(std::move(data)); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "sla_support_points") == 0) { // Parse object's layer height profile, a semicolon separated list of floats. @@ -1095,7 +1097,7 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " " << object->config.opt_serialize(key) << "\n"; if (!object->name.empty()) stream << " " << xml_escape(object->name) << "\n"; - const std::vector &layer_height_profile = object->layer_height_profile; + const std::vector &layer_height_profile = object->layer_height_profile.get(); if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) { // Store the layer height profile as a single semicolon separated list. stream << " "; @@ -1112,7 +1114,7 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, // Store the layer config range as a single semicolon separated list. stream << " \n"; size_t layer_counter = 0; - for (auto range : config_ranges) { + for (const auto &range : config_ranges) { stream << " \n"; stream << " "; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3539cc0faa..a4541eeecc 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1050,7 +1050,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial ModelVolume* vol = new_object->add_volume(mesh); vol->name = volume->name; // Don't copy the config's ID. - static_cast(vol->config) = static_cast(volume->config); + vol->config.assign_config(volume->config); assert(vol->config.id().valid()); assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); @@ -1193,7 +1193,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; // Don't copy the config's ID. - static_cast(vol->config) = static_cast(volume->config); + vol->config.assign_config(volume->config); assert(vol->config.id().valid()); assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); @@ -1202,8 +1202,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; // Don't copy the config's ID. - static_cast(vol->config) = static_cast(volume->config); - assert(vol->config.id().valid()); + vol->config.assign_config(volume->config); + assert(vol->config.id().valid()); assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); @@ -1280,7 +1280,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) ModelObject* new_object = m_model->add_object(); new_object->name = this->name; // Don't copy the config's ID. - static_cast(new_object->config) = static_cast(this->config); + new_object->config.assign_config(this->config); assert(new_object->config.id().valid()); assert(new_object->config.id() != this->config.id()); new_object->instances.reserve(this->instances.size()); @@ -1867,7 +1867,6 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const return ret; } - indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const { TriangleSelector selector(mv.mesh()); @@ -1876,29 +1875,23 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce return out; } - - bool FacetsAnnotation::set(const TriangleSelector& selector) { std::map> sel_map = selector.serialize(); if (sel_map != m_data) { m_data = sel_map; - update_timestamp(); + this->touch(); return true; } return false; } - - void FacetsAnnotation::clear() { m_data.clear(); - update_timestamp(); + this->reset_timestamp(); } - - // Following function takes data from a triangle and encodes it as string // of hexadecimal numbers (one digit per triangle). Used for 3MF export, // changing it may break backwards compatibility !!!!! @@ -1926,8 +1919,6 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const return out; } - - // Recover triangle splitting & state from string of hexadecimal values previously // generated by get_triangle_as_string. Used to load from 3MF. void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) @@ -1951,12 +1942,8 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri code.insert(code.end(), bool(dec & (1 << i))); } } - - } - - // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) @@ -2024,7 +2011,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART)); assert(mo.volumes.size() == mo_new.volumes.size()); for (size_t i=0; im_supported_facets.is_same_as(mo.volumes[i]->m_supported_facets)) + if (! mo_new.volumes[i]->m_supported_facets.timestamp_matches(mo.volumes[i]->m_supported_facets)) return true; } return false; @@ -2034,7 +2021,7 @@ bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART)); assert(mo.volumes.size() == mo_new.volumes.size()); for (size_t i=0; im_seam_facets.is_same_as(mo.volumes[i]->m_seam_facets)) + if (! mo_new.volumes[i]->m_seam_facets.timestamp_matches(mo.volumes[i]->m_seam_facets)) return true; } return false; @@ -2050,7 +2037,7 @@ extern bool model_has_multi_part_objects(const Model &model) extern bool model_has_advanced_features(const Model &model) { - auto config_is_advanced = [](const DynamicPrintConfig &config) { + auto config_is_advanced = [](const ModelConfig &config) { return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder")); }; for (const ModelObject *model_object : model.objects) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c6b7c60306..003c4ed0f8 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -18,7 +18,6 @@ #include #include #include -#include namespace cereal { class BinaryInputArchive; @@ -45,7 +44,7 @@ namespace UndoRedo { class StackImpl; } -class ModelConfig : public ObjectBase, public DynamicPrintConfig +class ModelConfigObject : public ObjectBase, public ModelConfig { private: friend class cereal::access; @@ -56,21 +55,25 @@ private: // Constructors to be only called by derived classes. // Default constructor to assign a unique ID. - explicit ModelConfig() {} + explicit ModelConfigObject() {} // Constructor with ignored int parameter to assign an invalid ID, to be replaced // by an existing ID copied from elsewhere. - explicit ModelConfig(int) : ObjectBase(-1) {} + explicit ModelConfigObject(int) : ObjectBase(-1) {} // Copy constructor copies the ID. - explicit ModelConfig(const ModelConfig &cfg) : ObjectBase(-1), DynamicPrintConfig(cfg) { this->copy_id(cfg); } + explicit ModelConfigObject(const ModelConfigObject &cfg) : ObjectBase(-1), ModelConfig(cfg) { this->copy_id(cfg); } // Move constructor copies the ID. - explicit ModelConfig(ModelConfig &&cfg) : ObjectBase(-1), DynamicPrintConfig(std::move(cfg)) { this->copy_id(cfg); } + explicit ModelConfigObject(ModelConfigObject &&cfg) : ObjectBase(-1), ModelConfig(std::move(cfg)) { this->copy_id(cfg); } - ModelConfig& operator=(const ModelConfig &rhs) = default; - ModelConfig& operator=(ModelConfig &&rhs) = default; + Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); } + bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); } - template void serialize(Archive &ar) { - ar(cereal::base_class(this)); - } + // called by ModelObject::assign_copy() + ModelConfigObject& operator=(const ModelConfigObject &rhs) = default; + ModelConfigObject& operator=(ModelConfigObject &&rhs) = default; + + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + } }; namespace Internal { @@ -136,7 +139,7 @@ public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. t_model_material_attributes attributes; // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. - ModelConfig config; + ModelConfigObject config; Model* get_model() const { return m_model; } void apply(const t_model_material_attributes &attributes) @@ -162,7 +165,7 @@ private: ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } template void serialize(Archive &ar) { assert(this->id().invalid()); assert(this->config.id().invalid()); - Internal::StaticSerializationWrapper config_wrapper(config); + Internal::StaticSerializationWrapper config_wrapper(config); ar(attributes, config_wrapper); // assert(this->id().valid()); assert(this->config.id().valid()); } @@ -173,6 +176,23 @@ private: ModelMaterial& operator=(ModelMaterial &&rhs) = delete; }; +class LayerHeightProfile final : public ObjectWithTimestamp { +public: + std::vector get() const throw() { return m_data; } + bool empty() const throw() { return m_data.empty(); } + void set(const std::vector &data) { if (m_data != data) { m_data = data; this->touch(); } } + void set(std::vector &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } } + void clear() { m_data.clear(); this->touch(); } + + template void serialize(Archive &ar) + { + ar(cereal::base_class(this), m_data); + } + +private: + std::vector m_data; +}; + // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, @@ -189,12 +209,12 @@ public: // ModelVolumes are owned by this ModelObject. ModelVolumePtrs volumes; // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. - ModelConfig config; + ModelConfigObject config; // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. t_layer_config_ranges layer_config_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array. - std::vector layer_height_profile; + LayerHeightProfile layer_height_profile; // Whether or not this object is printable bool printable; @@ -381,8 +401,10 @@ private: } template void serialize(Archive &ar) { ar(cereal::base_class(this)); - Internal::StaticSerializationWrapper config_wrapper(config); - ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, + Internal::StaticSerializationWrapper config_wrapper(config); + Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); + ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, + sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); } }; @@ -403,34 +425,27 @@ enum class EnforcerBlockerType : int8_t { BLOCKER = 2 }; -class FacetsAnnotation { +class FacetsAnnotation final : public ObjectWithTimestamp { public: - using ClockType = std::chrono::steady_clock; - - const std::map>& get_data() const { return m_data; } + void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) this->m_data = rhs.m_data; } + void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) this->m_data = rhs.m_data; } + const std::map>& get_data() const throw() { return m_data; } bool set(const TriangleSelector& selector); indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; void clear(); std::string get_triangle_as_string(int i) const; void set_triangle_from_string(int triangle_id, const std::string& str); - ClockType::time_point get_timestamp() const { return timestamp; } - bool is_same_as(const FacetsAnnotation& other) const { - return timestamp == other.get_timestamp(); - } +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; template void serialize(Archive &ar) { - ar(m_data); + ar(cereal::base_class(this), m_data); } -private: std::map> m_data; - - ClockType::time_point timestamp; - void update_timestamp() { - timestamp = ClockType::now(); - } }; // An object STL, or a modifier volume, over which a different set of parameters shall be applied. @@ -465,7 +480,7 @@ public: void reset_mesh() { m_mesh = std::make_shared(); } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. - ModelConfig config; + ModelConfigObject config; // List of mesh facets to be supported/unsupported. FacetsAnnotation m_supported_facets; @@ -634,8 +649,9 @@ private: } template void load(Archive &ar) { bool has_convex_hull; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, - m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::load_by_value(ar, m_supported_facets); + cereal::load_by_value(ar, m_seam_facets); cereal::load_by_value(ar, config); assert(m_mesh); if (has_convex_hull) { @@ -648,8 +664,9 @@ private: } template void save(Archive &ar) const { bool has_convex_hull = m_convex_hull.get() != nullptr; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, - m_is_splittable, has_convex_hull, m_supported_facets, m_seam_facets); + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::save_by_value(ar, m_supported_facets); + cereal::save_by_value(ar, m_seam_facets); cereal::save_by_value(ar, config); if (has_convex_hull) cereal::save_optional(ar, m_convex_hull); @@ -935,7 +952,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2); namespace cereal { template struct specialize {}; - template struct specialize {}; + template struct specialize {}; } #endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/ObjectID.cpp b/src/libslic3r/ObjectID.cpp index b188d84c06..7188f05fd6 100644 --- a/src/libslic3r/ObjectID.cpp +++ b/src/libslic3r/ObjectID.cpp @@ -17,6 +17,8 @@ ObjectID wipe_tower_instance_id() return mine.id(); } +size_t ObjectWithTimestamp::s_last_timestamp = 1; + } // namespace Slic3r // CEREAL_REGISTER_TYPE(Slic3r::ObjectBase) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index fd5cc80ae8..bd2f6b86ec 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -49,12 +49,14 @@ private: class ObjectBase { public: + using Timestamp = uint64_t; + ObjectID id() const { return m_id; } // Return an optional timestamp of this object. // If the timestamp returned is non-zero, then the serialization framework will // only save this object on the Undo/Redo stack if the timestamp is different // from the timestmap of the object at the top of the Undo / Redo stack. - virtual uint64_t timestamp() const { return 0; } + virtual Timestamp timestamp() const { return 0; } protected: // Constructors to be only called by derived classes. @@ -91,6 +93,42 @@ private: template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } }; +class ObjectWithTimestamp : public ObjectBase +{ +protected: + // Constructors to be only called by derived classes. + // Default constructor to assign a new timestamp unique to this object's history. + ObjectWithTimestamp() = default; + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + ObjectWithTimestamp(int) : ObjectBase(-1) {} + // The class tree will have virtual tables and type information. + virtual ~ObjectWithTimestamp() = default; + + // Resetting timestamp to 1 indicates the object is in its initial (cleared) state. + // To be called by the derived class's clear() method. + void reset_timestamp() { m_timestamp = 1; } + +public: + // Return an optional timestamp of this object. + // If the timestamp returned is non-zero, then the serialization framework will + // only save this object on the Undo/Redo stack if the timestamp is different + // from the timestmap of the object at the top of the Undo / Redo stack. + Timestamp timestamp() const throw() override { return m_timestamp; } + bool timestamp_matches(const ObjectWithTimestamp &rhs) const throw() { return m_timestamp == rhs.m_timestamp; } + bool object_id_and_timestamp_match(const ObjectWithTimestamp &rhs) const throw() { return this->id() == rhs.id() && m_timestamp == rhs.m_timestamp; } + void touch() { m_timestamp = ++ s_last_timestamp; } + +private: + // The first timestamp is non-zero, as zero timestamp means the timestamp is not reliable. + Timestamp m_timestamp { 1 }; + static Timestamp s_last_timestamp; + + friend class cereal::access; + friend class Slic3r::UndoRedo::StackImpl; + template void serialize(Archive &ar) { ar(m_timestamp); } +}; + // Unique object / instance ID for the wipe tower. extern ObjectID wipe_tower_object_id(); extern ObjectID wipe_tower_instance_id(); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a360d840f4..fb276a7c29 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -403,9 +403,11 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, assert(mv_src.id() == mv_dst.id()); // Copy the ModelVolume data. mv_dst.name = mv_src.name; - static_cast(mv_dst.config) = static_cast(mv_src.config); - mv_dst.m_supported_facets = mv_src.m_supported_facets; - mv_dst.m_seam_facets = mv_src.m_seam_facets; + mv_dst.config.assign_config(mv_src.config); + if (! mv_dst.m_supported_facets.timestamp_matches(mv_src.m_supported_facets)) + mv_dst.m_supported_facets = mv_src.m_supported_facets; + if (! mv_dst.m_seam_facets.timestamp_matches(mv_src.m_seam_facets)) + mv_dst.m_seam_facets = mv_src.m_seam_facets; //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -644,7 +646,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_ranges.reserve(in.size()); // Input ranges are sorted lexicographically. First range trims the other ranges. coordf_t last_z = 0; - for (const std::pair &range : in) + for (const std::pair &range : in) if (range.first.second > last_z) { coordf_t min_z = std::max(range.first.first, 0.); if (min_z > last_z + EPSILON) { @@ -652,7 +654,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ last_z = min_z; } if (range.first.second > last_z + EPSILON) { - const DynamicPrintConfig* cfg = &range.second; + const DynamicPrintConfig *cfg = &range.second.get(); m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); last_z = range.first.second; } @@ -845,8 +847,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || - model_object.origin_translation != model_object_new.origin_translation || - model_object.layer_height_profile != model_object_new.layer_height_profile || + model_object.origin_translation != model_object_new.origin_translation || + ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile) || ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); @@ -874,9 +876,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } if (! model_parts_differ && ! modifiers_differ) { // Synchronize Object's config. - bool object_config_changed = model_object.config != model_object_new.config; + bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); if (object_config_changed) - static_cast(model_object.config) = static_cast(model_object_new.config); + model_object.config.assign_config(model_object_new.config); if (! object_diff.empty() || object_config_changed || num_extruders_changed) { PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); @@ -1577,7 +1579,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const ModelVolume *volume = model_object->volumes[volume_id]; //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder")) - volume->config.opt("extruder", true)->value = int(volume_id + 1); + volume->config.set("extruder", int(volume_id + 1)); } } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 72393a3f5a..a8eb68521d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3718,6 +3718,8 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std:: } } +uint64_t ModelConfig::s_last_timestamp = 1; + static Points to_points(const std::vector &dpts) { Points pts; pts.reserve(dpts.size()); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index fa7edd10e1..bb4428ab45 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1369,6 +1369,96 @@ Points get_bed_shape(const DynamicPrintConfig &cfg); Points get_bed_shape(const PrintConfig &cfg); Points get_bed_shape(const SLAPrinterConfig &cfg); +// ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. +// Each change of ModelConfig is tracked by assigning a new timestamp from a global counter. +// The counter is used for faster synchronization of the background slicing thread +// with the front end by skipping synchronization of equal config dictionaries. +// The global counter is also used for avoiding unnecessary serialization of config +// dictionaries when taking an Undo snapshot. +// +// The global counter is NOT thread safe, therefore it is recommended to use ModelConfig from +// the main thread only. +// +// As there is a global counter and it is being increased with each change to any ModelConfig, +// if two ModelConfig dictionaries differ, they should differ with their timestamp as well. +// Therefore copying the ModelConfig including its timestamp is safe as there is no harm +// in having multiple ModelConfig with equal timestamps as long as their dictionaries are equal. +// +// The timestamp is used by the Undo/Redo stack. As zero timestamp means invalid timestamp +// to the Undo/Redo stack (zero timestamp means the Undo/Redo stack needs to serialize and +// compare serialized data for differences), zero timestamp shall never be used. +// Timestamp==1 shall only be used for empty dictionaries. +class ModelConfig +{ +public: + void clear() { m_data.clear(); m_timestamp = 1; } + + // Modification of the ModelConfig is not thread safe due to the global timestamp counter! + // Don't call modification methods from the back-end! + void assign_config(const ModelConfig &rhs) { + if (m_timestamp != rhs.m_timestamp) { + m_data = rhs.m_data; + m_timestamp = rhs.m_timestamp; + } + } + void assign_config(ModelConfig &&rhs) { + if (m_timestamp != rhs.m_timestamp) { + m_data = std::move(rhs.m_data); + m_timestamp = rhs.m_timestamp; + rhs.clear(); + } + } + // Assign methods don't assign if src==dst to not having to bump the timestamp in case they are equal. + void assign_config(const DynamicPrintConfig &rhs) { if (m_data != rhs) { m_data = rhs; this->touch(); } } + void assign_config(DynamicPrintConfig &&rhs) { if (m_data != rhs) { m_data = std::move(rhs); this->touch(); } } + void apply(const ModelConfig &other, bool ignore_nonexistent = false) { this->apply(other.get(), ignore_nonexistent); } + void apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_data.apply_only(other, other.keys(), ignore_nonexistent); this->touch(); } + void apply_only(const ModelConfig &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->apply_only(other.get(), keys, ignore_nonexistent); } + void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { m_data.apply_only(other, keys, ignore_nonexistent); this->touch(); } + bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; } + template + void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); } + void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false) + { m_data.set_deserialize(opt_key, str, append); this->touch(); } + bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; } + + // Getters are thread safe. + // The following implicit conversion breaks the Cereal serialization. +// operator const DynamicPrintConfig&() const throw() { return this->get(); } + const DynamicPrintConfig& get() const throw() { return m_data; } + bool empty() const throw() { return m_data.empty(); } + size_t size() const throw() { return m_data.size(); } + auto cbegin() const { return m_data.cbegin(); } + auto cend() const { return m_data.cend(); } + t_config_option_keys keys() const { return m_data.keys(); } + bool has(const t_config_option_key &opt_key) const { return m_data.has(opt_key); } + const ConfigOption* option(const t_config_option_key &opt_key) const { return m_data.option(opt_key); } + int opt_int(const t_config_option_key &opt_key) const { return m_data.opt_int(opt_key); } + int extruder() const { return opt_int("extruder"); } + double opt_float(const t_config_option_key &opt_key) const { return m_data.opt_float(opt_key); } + std::string opt_serialize(const t_config_option_key &opt_key) const { return m_data.opt_serialize(opt_key); } + + // Return an optional timestamp of this object. + // If the timestamp returned is non-zero, then the serialization framework will + // only save this object on the Undo/Redo stack if the timestamp is different + // from the timestmap of the object at the top of the Undo / Redo stack. + virtual uint64_t timestamp() const throw() { return m_timestamp; } + bool timestamp_matches(const ModelConfig &rhs) const throw() { return m_timestamp == rhs.m_timestamp; } + // Not thread safe! Should not be called from other than the main thread! + void touch() { m_timestamp = ++ s_last_timestamp; } + +private: + friend class cereal::access; + template void serialize(Archive& ar) { ar(m_timestamp); ar(m_data); } + + uint64_t m_timestamp { 1 }; + DynamicPrintConfig m_data; + + static uint64_t s_last_timestamp; +}; + +template void normalize_and_apply_config(CONFIG& dst, const ModelConfig& src) { normalize_and_apply_config(dst, src.get()); } + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4b83062a0c..c0425ada4d 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1592,13 +1592,13 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full print_config, region_config_from_model_volume(default_region_config, nullptr, *model_volume, num_extruders), object_extruders); - for (const std::pair &range_and_config : model_object.layer_config_ranges) + for (const std::pair &range_and_config : model_object.layer_config_ranges) if (range_and_config.second.has("perimeter_extruder") || range_and_config.second.has("infill_extruder") || range_and_config.second.has("solid_infill_extruder")) PrintRegion::collect_object_printing_extruders( print_config, - region_config_from_model_volume(default_region_config, &range_and_config.second, *model_volume, num_extruders), + region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, num_extruders), object_extruders); } sort_remove_duplicates(object_extruders); @@ -1626,7 +1626,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c if (layer_height_profile.empty()) { // use the constructor because the assignement is crashing on ASAN OsX - layer_height_profile = std::vector(model_object.layer_height_profile); + layer_height_profile = std::vector(model_object.layer_height_profile.get()); // layer_height_profile = model_object.layer_height_profile; updated = true; } @@ -2872,7 +2872,7 @@ void PrintObject::project_and_append_custom_facets( // Now append the collected polygons to respective layers. for (auto& trg : projections_of_triangles) { - int layer_id = trg.first_layer_id; + int layer_id = int(trg.first_layer_id); for (const LightPolygon& poly : trg.polygons) { if (layer_id >= int(expolys.size())) break; // part of triangle could be projected above top layer diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 07ec380160..30d1df6c1a 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -395,9 +395,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con model_object.assign_copy(model_object_new); } else { // Synchronize Object's config. - bool object_config_changed = model_object.config != model_object_new.config; + bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); if (object_config_changed) - static_cast(model_object.config) = static_cast(model_object_new.config); + model_object.config.assign_config(model_object_new.config); if (! object_diff.empty() || object_config_changed) { SLAPrintObjectConfig new_config = m_default_object_config; normalize_and_apply_config(new_config, model_object.config); diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 82d2d19890..16068dde44 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -170,24 +170,15 @@ SlicingParameters SlicingParameters::create_from_config( return params; } -std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges) -{ - std::vector> out; - out.reserve(config_ranges.size()); - for (const auto &kvp : config_ranges) - out.emplace_back(kvp.first, kvp.second.option("layer_height")->getFloat()); - return out; -} - // Convert layer_config_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_config_ranges &layer_config_ranges) // #ys_FIXME_experiment + const t_layer_config_ranges &layer_config_ranges) { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector> ranges_non_overlapping; - ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); // #ys_FIXME_experiment + ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair( t_layer_height_range(0., slicing_params.first_object_layer_height), diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 2fd609b2c5..e151b208f4 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -17,6 +17,7 @@ namespace Slic3r class PrintConfig; class PrintObjectConfig; +class ModelConfig; class ModelObject; class DynamicPrintConfig; @@ -128,9 +129,7 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters } typedef std::pair t_layer_height_range; -typedef std::map t_layer_config_ranges; - -extern std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges); +typedef std::map t_layer_config_ranges; extern std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5836b8a2c0..5fdfac8b4d 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -2,6 +2,7 @@ #include "ConfigManipulation.hpp" #include "I18N.hpp" #include "GUI_App.hpp" +#include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" #include diff --git a/src/slic3r/GUI/ConfigManipulation.hpp b/src/slic3r/GUI/ConfigManipulation.hpp index 7344f758be..ecf8494470 100644 --- a/src/slic3r/GUI/ConfigManipulation.hpp +++ b/src/slic3r/GUI/ConfigManipulation.hpp @@ -10,9 +10,11 @@ #include "libslic3r/PrintConfig.hpp" #include "Field.hpp" -//#include namespace Slic3r { + +class ModelConfig; + namespace GUI { class ConfigManipulation @@ -24,13 +26,13 @@ class ConfigManipulation std::function get_field = nullptr; // callback to propagation of changed value, if needed std::function cb_value_change = nullptr; - DynamicPrintConfig* local_config = nullptr; + ModelConfig* local_config = nullptr; public: ConfigManipulation(std::function load_config, std::function get_field, std::function cb_value_change, - DynamicPrintConfig* local_config = nullptr) : + ModelConfig* local_config = nullptr) : load_config(load_config), get_field(get_field), cb_value_change(cb_value_change), diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e0c8c4c5bd..3e5712b731 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -511,7 +511,7 @@ void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas { this->update_slicing_parameters(); m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor); - const_cast(m_model_object)->layer_height_profile = m_layer_height_profile; + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } @@ -520,7 +520,7 @@ void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, { this->update_slicing_parameters(); m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params); - const_cast(m_model_object)->layer_height_profile = m_layer_height_profile; + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } @@ -560,7 +560,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) if (last_object_id >= 0) { if (m_layer_height_profile_modified) { wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Manual edit"))); - const_cast(m_model_object)->layer_height_profile = m_layer_height_profile; + const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } } diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 90a725fbfd..e8fab2a79f 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -153,7 +153,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus void ObjectLayers::create_layers_list() { - for (const auto layer : m_object->layer_config_ranges) + for (const auto &layer : m_object->layer_config_ranges) { const t_layer_height_range& range = layer.first; auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4973abca57..1925401b8d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -468,7 +468,7 @@ int ObjectList::get_selected_obj_idx() const return -1; } -DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) const +ModelConfig& ObjectList::get_item_config(const wxDataViewItem& item) const { assert(item); const ItemType type = m_objects_model->GetItemType(item); @@ -492,10 +492,10 @@ void ObjectList::update_extruder_values_for_items(const size_t max_extruder) auto object = (*m_objects)[i]; wxString extruder; if (!object->config.has("extruder") || - size_t(object->config.option("extruder")->value) > max_extruder) + size_t(object->config.extruder()) > max_extruder) extruder = _(L("default")); else - extruder = wxString::Format("%d", object->config.option("extruder")->value); + extruder = wxString::Format("%d", object->config.extruder()); m_objects_model->SetExtruder(extruder, item); @@ -504,10 +504,10 @@ void ObjectList::update_extruder_values_for_items(const size_t max_extruder) item = m_objects_model->GetItemByVolumeId(i, id); if (!item) continue; if (!object->volumes[id]->config.has("extruder") || - size_t(object->volumes[id]->config.option("extruder")->value) > max_extruder) + size_t(object->volumes[id]->config.extruder()) > max_extruder) extruder = _(L("default")); else - extruder = wxString::Format("%d", object->volumes[id]->config.option("extruder")->value); + extruder = wxString::Format("%d", object->volumes[id]->config.extruder()); m_objects_model->SetExtruder(extruder, item); } @@ -767,8 +767,7 @@ void ObjectList::copy_settings_to_clipboard() if (m_objects_model->GetItemType(item) & itSettings) item = m_objects_model->GetParent(item); - DynamicPrintConfig& config_cache = m_clipboard.get_config_cache(); - config_cache = get_item_config(item); + m_clipboard.get_config_cache() = get_item_config(item).get(); } void ObjectList::paste_settings_into_list() @@ -799,7 +798,7 @@ void ObjectList::paste_settings_into_list() } // Add settings item for object/sub-object and show them - show_settings(add_settings_item(item, m_config)); + show_settings(add_settings_item(item, &m_config->get())); } void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) @@ -818,8 +817,8 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol { const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, wxString::FromUTF8(volume->name.c_str()), volume->type(), volume->get_mesh_errors_count()>0 , - volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); - add_settings_item(vol_item, &volume->config); + volume->config.has("extruder") ? volume->config.extruder() : 0); + add_settings_item(vol_item, &volume->config.get()); items.Add(vol_item); } @@ -1480,7 +1479,7 @@ void ObjectList::get_settings_choice(const wxString& category_name) // Add settings item for object/sub-object and show them if (!(item_type & (itObject | itVolume | itLayer))) item = m_objects_model->GetTopParent(item); - show_settings(add_settings_item(item, m_config)); + show_settings(add_settings_item(item, &m_config->get())); } void ObjectList::get_freq_settings_choice(const wxString& bundle_name) @@ -1537,7 +1536,7 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) // Add settings item for object/sub-object and show them if (!(item_type & (itObject | itVolume | itLayer))) item = m_objects_model->GetTopParent(item); - show_settings(add_settings_item(item, m_config)); + show_settings(add_settings_item(item, &m_config->get())); } void ObjectList::show_settings(const wxDataViewItem settings_item) @@ -1821,9 +1820,8 @@ void ObjectList::append_menu_item_change_extruder(wxMenu* menu) int initial_extruder = -1; // negative value for multiple object/part selection if (sels.Count()==1) { - DynamicPrintConfig& config = get_item_config(sels[0]); - initial_extruder = !config.has("extruder") ? 0 : - config.option("extruder")->value; + const ModelConfig &config = get_item_config(sels[0]); + initial_extruder = config.has("extruder") ? config.extruder() : 0; } for (int i = 0; i <= extruders_cnt; i++) @@ -2320,9 +2318,7 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) take_snapshot(_(L("Delete Settings"))); - int extruder = -1; - if (m_config->has("extruder")) - extruder = m_config->option("extruder")->value; + int extruder = m_config->has("extruder") ? m_config->extruder() : -1; coordf_t layer_height = 0.0; if (is_layer_settings) @@ -2459,11 +2455,10 @@ void ObjectList::split() const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name), volume->is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, volume->get_mesh_errors_count()>0, - volume->config.has("extruder") ? - volume->config.option("extruder")->value : 0, + volume->config.has("extruder") ? volume->config.extruder() : 0, false); // add settings to the part, if it has those - add_settings_item(vol_item, &volume->config); + add_settings_item(vol_item, &volume->config.get()); } model_object->input_file.clear(); @@ -2579,7 +2574,7 @@ void ObjectList::merge(bool to_multipart_object) Model* model = (*m_objects)[0]->get_model(); ModelObject* new_object = model->add_object(); new_object->name = _u8L("Merged"); - DynamicPrintConfig* config = &new_object->config; + ModelConfig &config = new_object->config; for (int obj_idx : obj_idxs) { @@ -2616,8 +2611,8 @@ void ObjectList::merge(bool to_multipart_object) } // merge settings - auto new_opt_keys = config->keys(); - const DynamicPrintConfig& from_config = object->config; + auto new_opt_keys = config.keys(); + const ModelConfig& from_config = object->config; auto opt_keys = from_config.keys(); for (auto& opt_key : opt_keys) { @@ -2628,7 +2623,7 @@ void ObjectList::merge(bool to_multipart_object) // get it from default config values option = DynamicPrintConfig::new_from_defaults_keys({ opt_key })->option(opt_key); } - config->set_key_value(opt_key, option->clone()); + config.set_key_value(opt_key, option->clone()); } } // save extruder value if it was set @@ -2695,7 +2690,7 @@ void ObjectList::layers_editing() // set some default value if (ranges.empty()) { take_snapshot(_(L("Add Layers"))); - ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx); + ranges[{ 0.0f, 2.0f }].assign_config(get_default_layer_config(obj_idx)); } // create layer root item @@ -3011,8 +3006,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) auto model_object = (*m_objects)[obj_idx]; const wxString& item_name = from_u8(model_object->name); const auto item = m_objects_model->Add(item_name, - !model_object->config.has("extruder") ? 0 : - model_object->config.option("extruder")->value, + model_object->config.has("extruder") ? model_object->config.extruder() : 0, get_mesh_errors_count(obj_idx) > 0); // add volumes to the object @@ -3022,10 +3016,9 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) from_u8(volume->name), volume->type(), volume->get_mesh_errors_count()>0, - !volume->config.has("extruder") ? 0 : - volume->config.option("extruder")->value, + volume->config.has("extruder") ? volume->config.extruder() : 0, false); - add_settings_item(vol_item, &volume->config); + add_settings_item(vol_item, &volume->config.get()); } Expand(item); } @@ -3045,7 +3038,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) m_objects_model->SetPrintableState(model_object->instances[0]->printable ? piPrintable : piUnprintable, obj_idx); // add settings to the object, if it has those - add_settings_item(item, &model_object->config); + add_settings_item(item, &model_object->config.get()); // Add layers if it has add_layer_root_item(item); @@ -3123,7 +3116,7 @@ void ObjectList::delete_from_model_and_list(const std::vector& it if ((*m_objects)[item->obj_idx]->volumes.size() == 1 && (*m_objects)[item->obj_idx]->config.has("extruder")) { - const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option("extruder")->value); + const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.extruder()); m_objects_model->SetExtruder(extruder, m_objects_model->GetItemById(item->obj_idx)); } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); @@ -3289,7 +3282,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range curren const wxDataViewItem layers_item = GetSelection(); - t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + auto& ranges = object(obj_idx)->layer_config_ranges; auto it_range = ranges.find(current_range); assert(it_range != ranges.end()); if (it_range == ranges.end()) @@ -3305,7 +3298,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range curren changed = true; const t_layer_height_range new_range = { current_range.second, current_range.second + 2. }; - ranges[new_range] = get_default_layer_config(obj_idx); + ranges[new_range].assign_config(get_default_layer_config(obj_idx)); add_layer_item(new_range, layers_item); } else if (const std::pair &next_range = it_next_range->first; current_range.second <= next_range.first) @@ -3342,7 +3335,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range curren add_layer_item(new_range, layers_item, layer_idx); new_range = { current_range.second, middle_layer_z }; - ranges[new_range] = get_default_layer_config(obj_idx); + ranges[new_range].assign_config(get_default_layer_config(obj_idx)); add_layer_item(new_range, layers_item, layer_idx); } } @@ -3353,7 +3346,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range curren changed = true; const t_layer_height_range new_range = { current_range.second, next_range.first }; - ranges[new_range] = get_default_layer_config(obj_idx); + ranges[new_range].assign_config(get_default_layer_config(obj_idx)); add_layer_item(new_range, layers_item, layer_idx); } } @@ -3379,7 +3372,7 @@ wxString ObjectList::can_add_new_range_after_current(const t_layer_height_range // This should not happen. return "ObjectList assert"; - t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + auto& ranges = object(obj_idx)->layer_config_ranges; auto it_range = ranges.find(current_range); assert(it_range != ranges.end()); if (it_range == ranges.end()) @@ -3418,7 +3411,7 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, const int obj_idx = m_objects_model->GetObjectIdByItem(layers_item); if (obj_idx < 0) return; - const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; + const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range].get(); if (!config.has("extruder")) return; @@ -3438,7 +3431,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t la if (obj_idx < 0) return false; - DynamicPrintConfig* config = &object(obj_idx)->layer_config_ranges[range]; + ModelConfig* config = &object(obj_idx)->layer_config_ranges[range]; if (fabs(layer_height - config->opt_float("layer_height")) < EPSILON) return false; @@ -3467,12 +3460,14 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); - t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + auto& ranges = object(obj_idx)->layer_config_ranges; - const DynamicPrintConfig config = ranges[range]; + { + ModelConfig config = std::move(ranges[range]); + ranges.erase(range); + ranges[new_range] = std::move(config); + } - ranges.erase(range); - ranges[new_range] = config; changed_object(obj_idx); wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx)); @@ -4040,7 +4035,7 @@ void ObjectList::change_part_type() } else if (!settings_item && (new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) { - add_settings_item(item, &volume->config); + add_settings_item(item, &volume->config.get()); } } @@ -4065,14 +4060,14 @@ void ObjectList::update_and_show_object_settings_item() if (!item) return; const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item; - select_item(add_settings_item(obj_item, &get_item_config(obj_item))); + select_item(add_settings_item(obj_item, &get_item_config(obj_item).get())); } // Update settings item for item had it void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections) { const wxDataViewItem old_settings_item = m_objects_model->GetSettingsItem(item); - const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(item)); + const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(item).get()); if (!new_settings_item && old_settings_item) m_objects_model->Delete(old_settings_item); @@ -4489,19 +4484,19 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const for (const wxDataViewItem& item : sels) { - DynamicPrintConfig& config = get_item_config(item); + ModelConfig& config = get_item_config(item); if (config.has("extruder")) { if (extruder == 0) config.erase("extruder"); else - config.option("extruder")->value = extruder; + config.set("extruder", extruder); } else if (extruder > 0) config.set_key_value("extruder", new ConfigOptionInt(extruder)); const wxString extruder_str = extruder == 0 ? wxString (_(L("default"))) : - wxString::Format("%d", config.option("extruder")->value); + wxString::Format("%d", config.extruder()); auto const type = m_objects_model->GetItemType(item); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 9f7dcd2475..df27fcbdb1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -24,6 +24,7 @@ class MenuWithSeparators; namespace Slic3r { class ConfigOptionsGroup; class DynamicPrintConfig; +class ModelConfig; class ModelObject; class ModelVolume; class TriangleMesh; @@ -39,9 +40,9 @@ typedef std::map< std::string, std::vector< std::pair typedef std::vector ModelVolumePtrs; -typedef double coordf_t; -typedef std::pair t_layer_height_range; -typedef std::map t_layer_config_ranges; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; namespace GUI { @@ -165,7 +166,7 @@ private: wxMenuItem* m_menu_item_split_instances { nullptr }; ObjectDataViewModel *m_objects_model{ nullptr }; - DynamicPrintConfig *m_config {nullptr}; + ModelConfig *m_config {nullptr}; std::vector *m_objects{ nullptr }; wxBitmapComboBox *m_extruder_editor { nullptr }; @@ -210,7 +211,7 @@ public: std::map CATEGORY_ICON; ObjectDataViewModel* GetModel() const { return m_objects_model; } - DynamicPrintConfig* config() const { return m_config; } + ModelConfig* config() const { return m_config; } std::vector* objects() const { return m_objects; } ModelObject* object(const int obj_idx) const ; @@ -320,7 +321,7 @@ public: wxPoint get_mouse_position_in_control() const { return wxGetMousePosition() - this->GetScreenPosition(); } wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; - DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const; + ModelConfig& get_item_config(const wxDataViewItem& item) const; SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings); void changed_object(const int obj_idx = -1) const; diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 398cd51d45..481d9f4dc0 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -82,7 +82,7 @@ bool ObjectSettings::update_settings_list() return false; const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject; - SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_object_settings); + SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(&config->get(), is_object_settings); if (!cat_options.empty()) { @@ -176,7 +176,7 @@ bool ObjectSettings::update_settings_list() return true; } -bool ObjectSettings::add_missed_options(DynamicPrintConfig* config_to, const DynamicPrintConfig& config_from) +bool ObjectSettings::add_missed_options(ModelConfig* config_to, const DynamicPrintConfig& config_from) { bool is_added = false; if (wxGetApp().plater()->printer_technology() == ptFFF) @@ -193,7 +193,7 @@ bool ObjectSettings::add_missed_options(DynamicPrintConfig* config_to, const Dyn return is_added; } -void ObjectSettings::update_config_values(DynamicPrintConfig* config) +void ObjectSettings::update_config_values(ModelConfig* config) { const auto objects_model = wxGetApp().obj_list()->GetModel(); const auto item = wxGetApp().obj_list()->GetSelection(); @@ -250,14 +250,12 @@ void ObjectSettings::update_config_values(DynamicPrintConfig* config) { const int obj_idx = objects_model->GetObjectIdByItem(item); assert(obj_idx >= 0); - DynamicPrintConfig* obj_config = &wxGetApp().model().objects[obj_idx]->config; - - main_config.apply(*obj_config, true); + main_config.apply(wxGetApp().model().objects[obj_idx]->config.get(), true); printer_technology == ptFFF ? config_manipulation.update_print_fff_config(&main_config) : config_manipulation.update_print_sla_config(&main_config) ; } - main_config.apply(*config, true); + main_config.apply(config->get(), true); printer_technology == ptFFF ? config_manipulation.update_print_fff_config(&main_config) : config_manipulation.update_print_sla_config(&main_config) ; diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index ff187eddcf..91cfe1dda4 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -10,6 +10,7 @@ class wxBoxSizer; namespace Slic3r { class DynamicPrintConfig; +class ModelConfig; namespace GUI { class ConfigOptionsGroup; @@ -52,8 +53,8 @@ public: * Example: if Infill is set to 100%, and Fill Pattern is missed in config_to, * we should add fill_pattern to avoid endless loop in update */ - bool add_missed_options(DynamicPrintConfig *config_to, const DynamicPrintConfig &config_from); - void update_config_values(DynamicPrintConfig*config); + bool add_missed_options(ModelConfig *config_to, const DynamicPrintConfig &config_from); + void update_config_values(ModelConfig *config); void UpdateAndShow(const bool show) override; void msw_rescale(); }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 04ada52536..3d0d9c79ad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -466,7 +466,7 @@ GLGizmoHollow::get_config_options(const std::vector& keys) const if (! mo) return out; - const DynamicPrintConfig& object_cfg = mo->config; + const DynamicPrintConfig& object_cfg = mo->config.get(); const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; std::unique_ptr default_cfg = nullptr; @@ -556,7 +556,7 @@ RENDER_AGAIN: auto opts = get_config_options({"hollowing_enable"}); m_enable_hollowing = static_cast(opts[0].first)->value; if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { - mo->config.opt("hollowing_enable", true)->value = m_enable_hollowing; + mo->config.set("hollowing_enable", m_enable_hollowing); wxGetApp().obj_list()->update_and_show_object_settings_item(); config_changed = true; } @@ -618,14 +618,14 @@ RENDER_AGAIN: } if (slider_edited || slider_released) { if (slider_released) { - mo->config.opt("hollowing_min_thickness", true)->value = m_offset_stash; - mo->config.opt("hollowing_quality", true)->value = m_quality_stash; - mo->config.opt("hollowing_closing_distance", true)->value = m_closing_d_stash; + mo->config.set("hollowing_min_thickness", m_offset_stash); + mo->config.set("hollowing_quality", m_quality_stash); + mo->config.set("hollowing_closing_distance", m_closing_d_stash); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); } - mo->config.opt("hollowing_min_thickness", true)->value = offset; - mo->config.opt("hollowing_quality", true)->value = quality; - mo->config.opt("hollowing_closing_distance", true)->value = closing_d; + mo->config.set("hollowing_min_thickness", offset); + mo->config.set("hollowing_quality", quality); + mo->config.set("hollowing_closing_distance", closing_d); if (slider_released) { wxGetApp().obj_list()->update_and_show_object_settings_item(); config_changed = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index bc29da6d2b..c9db14968e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -546,7 +546,7 @@ std::vector GLGizmoSlaSupports::get_config_options(const st if (! mo) return out; - const DynamicPrintConfig& object_cfg = mo->config; + const DynamicPrintConfig& object_cfg = mo->config.get(); const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; std::unique_ptr default_cfg = nullptr; @@ -753,15 +753,15 @@ RENDER_AGAIN: m_density_stash = density; } if (slider_edited) { - mo->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; - mo->config.opt("support_points_density_relative", true)->value = (int)density; + mo->config.set("support_points_minimal_distance", minimal_point_distance); + mo->config.set("support_points_density_relative", (int)density); } if (slider_released) { - mo->config.opt("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash; - mo->config.opt("support_points_density_relative", true)->value = (int)m_density_stash; + mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); + mo->config.set("support_points_density_relative", (int)m_density_stash); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change"))); - mo->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; - mo->config.opt("support_points_density_relative", true)->value = (int)density; + mo->config.set("support_points_minimal_distance", minimal_point_distance); + mo->config.set("support_points_density_relative", (int)density); wxGetApp().obj_list()->update_and_show_object_settings_item(); } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 14defd9a96..e1c1e9d13d 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -414,7 +414,7 @@ Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index m_opt_map.emplace(opt_id, pair); if (m_show_modified_btns) // fill group and category values just fro options from Settings Tab - wxGetApp().sidebar().get_searcher().add_key(opt_id, title, config_category); + wxGetApp().sidebar().get_searcher().add_key(opt_id, title, this->config_category()); return Option(*m_config->def()->get(opt_key), opt_id); } @@ -430,13 +430,11 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b return; } - auto itOption = it->second; - std::string opt_key = itOption.first; - int opt_index = itOption.second; + auto itOption = it->second; + const std::string &opt_key = itOption.first; + int opt_index = itOption.second; - auto option = m_options.at(opt_id).opt; - - change_opt_value(*m_config, opt_key, value, opt_index == -1 ? 0 : opt_index); + this->change_opt_value(opt_key, value, opt_index == -1 ? 0 : opt_index); } OptionsGroup::on_change_OG(opt_id, value); @@ -470,7 +468,7 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, opt_key == "bed_shape" || opt_key == "filament_ramming_parameters" || opt_key == "compatible_printers" || opt_key == "compatible_prints" ) { value = get_config_value(config, opt_key); - change_opt_value(*m_config, opt_key, value); + this->change_opt_value(opt_key, value); return; } else @@ -789,6 +787,15 @@ Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int op return opt_id.empty() ? nullptr : get_field(opt_id); } +// Change an option on m_config, possibly call ModelConfig::touch(). +void ConfigOptionsGroup::change_opt_value(const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) + +{ + Slic3r::GUI::change_opt_value(const_cast(*m_config), opt_key, value, opt_index); + if (m_modelconfig) + m_modelconfig->touch(); +} + void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/) { SetLabel(value); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index edd4a15bc9..78434c03fc 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -221,18 +221,18 @@ protected: class ConfigOptionsGroup: public OptionsGroup { public: - ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr, + ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* config = nullptr, bool is_tab_opt = false, column_t extra_clmn = nullptr) : - OptionsGroup(parent, title, is_tab_opt, extra_clmn), m_config(_config) {} + OptionsGroup(parent, title, is_tab_opt, extra_clmn), m_config(config) {} + ConfigOptionsGroup( wxWindow* parent, const wxString& title, ModelConfig* config, + bool is_tab_opt = false, column_t extra_clmn = nullptr) : + OptionsGroup(parent, title, is_tab_opt, extra_clmn), m_config(&config->get()), m_modelconfig(config) {} - /// reference to libslic3r config, non-owning pointer (?). - DynamicPrintConfig* m_config {nullptr}; - bool m_full_labels {0}; - t_opt_map m_opt_map; + const std::string& config_category() const throw() { return m_config_category; } + const t_opt_map& opt_map() const throw() { return m_opt_map; } - std::string config_category; - - void set_config(DynamicPrintConfig* config) { m_config = config; } + void set_config_category(const std::string &category) { this->m_config_category = category; } + void set_config(DynamicPrintConfig* config) { m_config = config; m_modelconfig = nullptr; } Option get_option(const std::string& opt_key, int opt_index = -1); Line create_single_option_line(const std::string& title, int idx = -1) /*const*/{ Option option = get_option(title, idx); @@ -266,6 +266,20 @@ public: // return option value from config boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1); Field* get_fieldc(const t_config_option_key& opt_key, int opt_index); + +private: + // Reference to libslic3r config or ModelConfig::get(), non-owning pointer. + // The reference is const, so that the spots which modify m_config are clearly + // demarcated by const_cast and m_config_changed_callback is called afterwards. + const DynamicPrintConfig* m_config {nullptr}; + // If the config is modelconfig, then ModelConfig::touch() has to be called after value change. + ModelConfig* m_modelconfig { nullptr }; + bool m_full_labels{ 0 }; + t_opt_map m_opt_map; + std::string m_config_category; + + // Change an option on m_config, possibly call ModelConfig::touch(). + void change_opt_value(const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); }; // Static text shown among the options. diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index bef0fd5d75..736be100d5 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1364,7 +1364,7 @@ void Selection::copy_to_clipboard() ModelObject* dst_object = m_clipboard.add_object(); dst_object->name = src_object->name; dst_object->input_file = src_object->input_file; - static_cast(dst_object->config) = static_cast(src_object->config); + dst_object->config.assign_config(src_object->config); dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; dst_object->sla_drain_holes = src_object->sla_drain_holes; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 29c9e33022..de268fc7b3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -675,8 +675,8 @@ void Tab::update_changed_tree_ui() { if (!sys_page && modified_page) break; - for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { - const std::string& opt_key = it->first; + for (const auto &kvp : group->opt_map()) { + const std::string& opt_key = kvp.first; get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } @@ -764,7 +764,7 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); } } - for (auto kvp : group->m_opt_map) { + for (const auto &kvp : group->opt_map()) { const std::string& opt_key = kvp.first; if ((m_options_list[opt_key] & os) == 0) to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); @@ -3791,7 +3791,7 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la //! config_ have to be "right" ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true, extra_column); - optgroup->config_category = m_title.ToStdString(); + optgroup->set_config_category(m_title.ToStdString()); if (noncommon_label_width >= 0) optgroup->label_width = noncommon_label_width; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 79cf3e82e2..b6d22d1e62 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -17,7 +17,7 @@ #define CEREAL_FUTURE_EXPERIMENTAL #include -#include +#include #include #include From 94aac4cf97441908c3409f98e4a105ae96864c76 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 24 Sep 2020 15:45:04 +0200 Subject: [PATCH 553/826] What MSVC could process I always wonder. --- src/libslic3r/ObjectID.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/ObjectID.cpp b/src/libslic3r/ObjectID.cpp index 7188f05fd6..7177c47fec 100644 --- a/src/libslic3r/ObjectID.cpp +++ b/src/libslic3r/ObjectID.cpp @@ -17,7 +17,7 @@ ObjectID wipe_tower_instance_id() return mine.id(); } -size_t ObjectWithTimestamp::s_last_timestamp = 1; +ObjectWithTimestamp::Timestamp ObjectWithTimestamp::s_last_timestamp = 1; } // namespace Slic3r From aa0335a750cf3703b79820bb2b02d51956d7bb0e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 24 Sep 2020 16:18:56 +0200 Subject: [PATCH 554/826] Trying to fix perl bindings --- xs/xsp/Model.xsp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 844b7c95ec..93067ebe38 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -118,7 +118,7 @@ load_stl(CLASS, path, object_name) %code%{ RETVAL = THIS->get_model(); %}; Ref config() - %code%{ RETVAL = &THIS->config; %}; + %code%{ RETVAL = &const_cast(THIS->config.get()); %}; std::string get_attribute(std::string name) %code%{ if (THIS->attributes.find(name) != THIS->attributes.end()) RETVAL = THIS->attributes[name]; %}; @@ -202,7 +202,7 @@ ModelMaterial::attributes() void set_input_file(std::string value) %code%{ THIS->input_file = value; %}; Ref config() - %code%{ RETVAL = &THIS->config; %}; + %code%{ RETVAL = &const_cast(THIS->config.get()); %}; Ref model() %code%{ RETVAL = THIS->get_model(); %}; @@ -248,7 +248,7 @@ ModelMaterial::attributes() Ref material(); Ref config() - %code%{ RETVAL = &THIS->config; %}; + %code%{ RETVAL = &const_cast(THIS->config.get()); %}; Ref mesh() %code%{ RETVAL = &THIS->mesh(); %}; From 7cdc61b67abe04e14c5c2a44db5259cec4901f30 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 24 Sep 2020 16:41:47 +0200 Subject: [PATCH 555/826] Trying to patch the Perl bindings. --- xs/xsp/Print.xsp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index e4957c042f..478b1faf87 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -139,7 +139,19 @@ _constant() %}; bool apply(Model *model, DynamicPrintConfig* config) - %code%{ RETVAL = THIS->apply(*model, *config); %}; + %code%{ + // Touching every config as the Perl bindings does not correctly export ModelConfig, + // therefore the configs have often invalid timestamps. + model->config.touch(); + for (auto obj : model->objects) { + obj->config.touch(); + for (auto vol : obj->volumes) + vol->config.touch(); + } + for (auto mat : model->materials) + mat->config.touch(); + RETVAL = THIS->apply(*model, *config); + %}; bool has_infinite_skirt(); std::vector extruders() const; int validate() %code%{ From 8f04a76337d8b29c375fb4b8de240bcd6e8e279d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 24 Sep 2020 14:52:05 +0200 Subject: [PATCH 556/826] Final fix of Perl bindings --- xs/xsp/Print.xsp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 478b1faf87..d9872aa7e3 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -142,14 +142,13 @@ _constant() %code%{ // Touching every config as the Perl bindings does not correctly export ModelConfig, // therefore the configs have often invalid timestamps. - model->config.touch(); for (auto obj : model->objects) { obj->config.touch(); for (auto vol : obj->volumes) vol->config.touch(); } for (auto mat : model->materials) - mat->config.touch(); + mat.second->config.touch(); RETVAL = THIS->apply(*model, *config); %}; bool has_infinite_skirt(); From 8fb3a44a4e293ca01eb897a5706ef8283c8c9770 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 24 Sep 2020 15:41:48 +0200 Subject: [PATCH 557/826] Fixed highlighting of the searched option + Create controls only on the shown and active tab + Line class : deleted unused sizer + In GUI_Utils added TaskTimer class for the print a time of some task duration + BedShapeDialog:: activated options_groups + commented some unused code --- src/slic3r/GUI/BedShapeDialog.cpp | 2 + src/slic3r/GUI/GUI_App.cpp | 36 ++------- src/slic3r/GUI/GUI_Utils.hpp | 28 +++++++ src/slic3r/GUI/MainFrame.cpp | 75 +++++++++++++++--- src/slic3r/GUI/MainFrame.hpp | 2 + src/slic3r/GUI/OptionsGroup.cpp | 8 +- src/slic3r/GUI/OptionsGroup.hpp | 1 - src/slic3r/GUI/Plater.cpp | 11 ++- src/slic3r/GUI/Tab.cpp | 121 ++++++++++++++++++------------ src/slic3r/GUI/Tab.hpp | 8 +- 10 files changed, 186 insertions(+), 106 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 29acdff007..3caf168b51 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -229,9 +229,11 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); + optgroup->activate(); optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); + optgroup->activate(); optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a9d75e5df7..f99e87caf1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -68,8 +68,6 @@ #include #endif // __WXMSW__ -#include - #if ENABLE_THUMBNAIL_GENERATOR_DEBUG #include #include @@ -80,32 +78,6 @@ namespace GUI { class MainFrame; -class TaskTimer -{ - std::chrono::milliseconds start_timer; - std::string task_name; -public: - TaskTimer(std::string task_name): - task_name(task_name.empty() ? "task" : task_name) - { - start_timer = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } - - ~TaskTimer() - { - std::chrono::milliseconds stop_timer = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count(); - std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str(); - printf(out.c_str()); -#ifdef __WXMSW__ - std::wstring stemp = std::wstring(out.begin(), out.end()); - OutputDebugString(stemp.c_str()); -#endif - } -}; - class SplashScreen : public wxSplashScreen { public: @@ -753,9 +725,11 @@ bool GUI_App::on_init_inner() #endif // ENABLE_GCODE_VIEWER scrn->SetText(_L("Creating settings tabs...")); + TaskTimer timer2("Creating settings tabs"); + mainframe = new MainFrame(); // hide settings tabs after first Layout - mainframe->select_tab(0); + mainframe->select_tab(size_t(0)); sidebar().obj_list()->init_objects(); // propagate model objects to object list // update_mode(); // !!! do that later @@ -1007,7 +981,7 @@ void GUI_App::recreate_GUI(const wxString& msg_name) MainFrame *old_main_frame = mainframe; mainframe = new MainFrame(); // hide settings tabs after first Layout - mainframe->select_tab(0); + mainframe->select_tab(size_t(0)); // Propagate model objects to object list. sidebar().obj_list()->init_objects(); SetTopWindow(mainframe); @@ -1456,7 +1430,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) // hide full main_sizer for mainFrame mainframe->GetSizer()->Show(false); mainframe->update_layout(); - mainframe->select_tab(0); + mainframe->select_tab(size_t(0)); } break; } diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 1c88de5707..ea2f0e7827 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -18,6 +18,8 @@ #include #include +#include + #include "Event.hpp" class wxCheckBox; @@ -396,6 +398,32 @@ inline int hex_digit_to_int(const char c) } #endif // ENABLE_GCODE_VIEWER +class TaskTimer +{ + std::chrono::milliseconds start_timer; + std::string task_name; +public: + TaskTimer(std::string task_name): + task_name(task_name.empty() ? "task" : task_name) + { + start_timer = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()); + } + + ~TaskTimer() + { + std::chrono::milliseconds stop_timer = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()); + auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count(); + std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str(); + printf(out.c_str()); +#ifdef __WXMSW__ + std::wstring stemp = std::wstring(out.begin(), out.end()); + OutputDebugString(stemp.c_str()); +#endif + } +}; + }} #endif diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 941841f6f1..3165b625b3 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -595,7 +595,7 @@ void MainFrame::init_tabpanel() m_last_selected_tab = m_tabpanel->GetSelection(); } else - select_tab(0); // select Plater + select_tab(size_t(0)); // select Plater }); m_plater = new Plater(this, this); @@ -650,6 +650,24 @@ void MainFrame::add_created_tab(Tab* panel) m_tabpanel->AddPage(panel, panel->title()); } +bool MainFrame::is_active_and_shown_tab(Tab* tab) +{ + if (!this) + return false; + int page_id = m_tabpanel->FindPage(tab); + + if (m_tabpanel->GetSelection() != page_id) + return false; + + if (m_layout == ESettingsLayout::Dlg) + return m_settings_dialog.IsShown(); + + if (m_layout == ESettingsLayout::New) + return m_main_sizer->IsShown(m_tabpanel); + + return true; +} + bool MainFrame::can_start_new_project() const { return (m_plater != nullptr) && !m_plater->model().objects.empty(); @@ -1167,7 +1185,7 @@ void MainFrame::init_menubar() { if (m_plater) { append_menu_item(windowMenu, wxID_HIGHEST + 1, _L("&Plater Tab") + "\tCtrl+1", _L("Show the plater"), - [this](wxCommandEvent&) { select_tab(0); }, "plater", nullptr, + [this](wxCommandEvent&) { select_tab(size_t(0)); }, "plater", nullptr, [this]() {return true; }, this); windowMenu->AppendSeparator(); } @@ -1723,9 +1741,35 @@ void MainFrame::load_config(const DynamicPrintConfig& config) #endif } +void MainFrame::select_tab(Tab* tab) +{ + if (!tab) + return; + int page_idx = m_tabpanel->FindPage(tab); + if (page_idx != wxNOT_FOUND && m_layout == ESettingsLayout::Dlg) + page_idx++; + select_tab(size_t(page_idx)); +} + void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { bool tabpanel_was_hidden = false; + + // Controls on page are created on active page of active tab now. + // We should select/activate tab before its showing to avoid an UI-flickering + auto select = [this, tab](bool was_hidden) { + // when tab == -1, it means we should show the last selected tab + size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab; + + if (m_tabpanel->GetSelection() != new_selection) + m_tabpanel->SetSelection(new_selection); + else if (was_hidden) { + Tab* cur_tab = dynamic_cast(m_tabpanel->GetPage(new_selection)); + if (cur_tab) + cur_tab->OnActivate(); + } + }; + if (m_layout == ESettingsLayout::Dlg) { if (tab==0) { if (m_settings_dialog.IsShown()) @@ -1739,14 +1783,20 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) #ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList if (m_settings_dialog.IsShown()) m_settings_dialog.Hide(); - + else + tabpanel_was_hidden = true; + + select(tabpanel_was_hidden); m_tabpanel->Show(); m_settings_dialog.Show(); #else - if (m_settings_dialog.IsShown()) + if (m_settings_dialog.IsShown()) { + select(false); m_settings_dialog.SetFocus(); + } else { tabpanel_was_hidden = true; + select(tabpanel_was_hidden); m_tabpanel->Show(); m_settings_dialog.Show(); } @@ -1755,6 +1805,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) else if (m_layout == ESettingsLayout::New) { m_main_sizer->Show(m_plater, tab == 0); tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel); + select(tabpanel_was_hidden); m_main_sizer->Show(m_tabpanel, tab != 0); // plater should be focused for correct navigation inside search window @@ -1762,17 +1813,23 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) m_plater->SetFocus(); Layout(); } + else + select(false); // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, // which are used for update TreeCtrl and "revert_buttons". // So, force the call of this function for Tabs, if tab panel was hidden if (tabpanel_was_hidden) - for (auto tab : wxGetApp().tabs_list) - tab->update_changed_tree_ui(); + for (auto cur_tab : wxGetApp().tabs_list) + cur_tab->update_changed_tree_ui(); - // when tab == -1, it means we should show the last selected tab - m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab); + //// when tab == -1, it means we should show the last selected tab + //size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab; + //if (m_tabpanel->GetSelection() != new_selection) + // m_tabpanel->SetSelection(new_selection); + //if (tabpanel_was_hidden) + // static_cast(m_tabpanel->GetPage(new_selection))->OnActivate(); } // Set a camera direction, zoom to all objects. @@ -1919,7 +1976,7 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) auto key_up_handker = [this](wxKeyEvent& evt) { if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) { switch (evt.GetKeyCode()) { - case '1': { m_main_frame->select_tab(0); break; } + case '1': { m_main_frame->select_tab(size_t(0)); break; } case '2': { m_main_frame->select_tab(1); break; } case '3': { m_main_frame->select_tab(2); break; } case '4': { m_main_frame->select_tab(3); break; } diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 868a684925..18d2a73bd8 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -159,6 +159,7 @@ public: void init_tabpanel(); void create_preset_tabs(); void add_created_tab(Tab* panel); + bool is_active_and_shown_tab(Tab* tab); #if ENABLE_GCODE_VIEWER void init_menubar_as_editor(); void init_menubar_as_gcodeviewer(); @@ -184,6 +185,7 @@ public: void load_config(const DynamicPrintConfig& config); // Select tab in m_tabpanel // When tab == -1, will be selected last selected tab + void select_tab(Tab* tab); void select_tab(size_t tab = size_t(-1)); void select_view(const std::string& direction); // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index d96b0565ad..13c5776625 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -138,7 +138,6 @@ void OptionsGroup::append_line(const Line& line) m_lines.emplace_back(line); if (line.full_width && ( - line.sizer != nullptr || line.widget != nullptr || !line.get_extra_widgets().empty()) ) @@ -156,14 +155,9 @@ void OptionsGroup::append_line(const Line& line) void OptionsGroup::activate_line(Line& line) { if (line.full_width && ( - line.sizer != nullptr || line.widget != nullptr || !line.get_extra_widgets().empty()) ) { - if (line.sizer != nullptr) { - sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); - return; - } if (line.widget != nullptr) { sizer->Add(line.widget(this->ctrl_parent()), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); return; @@ -585,7 +579,7 @@ bool ConfigOptionsGroup::is_visible(ConfigOptionMode mode) bool ConfigOptionsGroup::update_visibility(ConfigOptionMode mode) { - if (m_options_mode.empty()) + if (m_options_mode.empty() || !m_grid_sizer) return true; int opt_mode_size = m_options_mode.size(); if (m_grid_sizer->GetEffectiveRowsCount() != opt_mode_size && diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 2a4c2cdb66..b3198137b2 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -49,7 +49,6 @@ public: wxString label_tooltip {wxString("")}; size_t full_width {0}; wxStaticText** full_Label {nullptr}; - wxSizer* sizer {nullptr}; widget_t widget {nullptr}; std::function near_label_widget{ nullptr }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 19f735714c..6607bd5752 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -305,11 +305,12 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : if (!tab_print) return; if (opt_key == "fill_density") { - value = m_og->get_config_value(*config, opt_key); - tab_print->set_value(opt_key, value); + tab_print->update_dirty(); + tab_print->reload_config(); tab_print->update(); } - else{ + else + { DynamicPrintConfig new_conf = *config; if (opt_key == "brim") { double new_val; @@ -350,8 +351,6 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : } tab_print->load_config(new_conf); } - - tab_print->update_dirty(); }; @@ -982,7 +981,7 @@ void Sidebar::jump_to_option(size_t selected) wxGetApp().get_tab(opt.type)->activate_option(boost::nowide::narrow(opt.opt_key), boost::nowide::narrow(opt.category)); // Switch to the Settings NotePad - wxGetApp().mainframe->select_tab(); +// wxGetApp().mainframe->select_tab(); } ObjectManipulation* Sidebar::obj_manipul() diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 47c4cf8f95..8d15604482 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -67,8 +67,10 @@ void Tab::Highlighter::invalidate() { timer.Stop(); - bbmp->invalidate(); - bbmp = nullptr; + if (bbmp) { + bbmp->invalidate(); + bbmp = nullptr; + } blink_counter = 0; } @@ -385,19 +387,24 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str void Tab::OnActivate() { -#ifdef __WXOSX__ wxWindowUpdateLocker noUpdates(this); - +#ifdef __WXOSX__ +// wxWindowUpdateLocker noUpdates(this); auto size = GetSizer()->GetSize(); m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); Fit(); m_size_move *= -1; #endif // __WXOSX__ + + // create controls on active page + active_selected_page(); + m_active_page->Show(); + m_hsizer->Layout(); + Refresh(); } void Tab::update_labels_colour() { -// Freeze(); //update options "decoration" for (const auto opt : m_options_list) { @@ -426,7 +433,6 @@ void Tab::update_labels_colour() if (field == nullptr) continue; field->set_label_colour_force(color); } -// Thaw(); auto cur_item = m_treectrl->GetFirstVisibleItem(); if (!cur_item || !m_treectrl->IsVisible(cur_item)) @@ -722,6 +728,8 @@ void Tab::update_undo_buttons() void Tab::on_roll_back_value(const bool to_sys /*= true*/) { + if (!m_active_page) return; + int os; if (to_sys) { if (!m_is_nonsys_values) return; @@ -734,10 +742,10 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) m_postpone_update_ui = true; - auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto page : m_pages) - if (_(page->title()) == selection) { - for (auto group : page->m_optgroups) { + //auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + //for (auto page : m_pages) + // if (_(page->title()) == selection) { + for (auto group : /*page*/m_active_page->m_optgroups) { if (group->title == "Capabilities") { if ((m_options_list["extruders_count"] & os) == 0) to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); @@ -778,8 +786,8 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); } } - break; - } + // break; + //} m_postpone_update_ui = false; update_changed_ui(); @@ -819,10 +827,10 @@ void Tab::load_config(const DynamicPrintConfig& config) // Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. void Tab::reload_config() { -// Freeze(); - for (auto page : m_pages) - page->reload_config(); -// Thaw(); + //for (auto page : m_pages) + // page->reload_config(); + if (m_active_page) + m_active_page->reload_config(); } void Tab::update_mode() @@ -884,8 +892,6 @@ void Tab::msw_rescale() // rescale options_groups if (m_active_page) m_active_page->msw_rescale(); - //for (auto page : m_pages) - // page->msw_rescale(); Layout(); } @@ -918,14 +924,16 @@ void Tab::sys_color_changed() update_labels_colour(); // update options_groups - for (auto page : m_pages) - page->sys_color_changed(); + if (m_active_page) + m_active_page->msw_rescale(); Layout(); } Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { + return m_active_page ? m_active_page->get_field(opt_key, opt_index) : nullptr; + Field* field = nullptr; for (auto page : m_pages) { field = page->get_field(opt_key, opt_index); @@ -960,14 +968,14 @@ void Tab::toggle_option(const std::string& opt_key, bool toggle, int opt_index/* // Set a key/value pair on this page. Return true if the value has been modified. // Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer // after a preset is loaded. -bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) { - bool changed = false; - for(auto page: m_pages) { - if (page->set_value(opt_key, value)) - changed = true; - } - return changed; -} +//bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) { +// bool changed = false; +// for(auto page: m_pages) { +// if (page->set_value(opt_key, value)) +// changed = true; +// } +// return changed; +//} // To be called by custom widgets, load a value into a config, // update the preset selection boxes (the dirty flags) @@ -1020,7 +1028,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "pad_around_object") { for (PageShp &pg : m_pages) { - Field * fld = pg->get_field(opt_key); + Field * fld = pg->get_field(opt_key); /// !!! ysFIXME ???? if (fld) fld->set_value(value, false); } } @@ -1064,11 +1072,20 @@ void Tab::update_wiping_button_visibility() { void Tab::activate_option(const std::string& opt_key, const wxString& category) { - Page* page {nullptr}; - Field* field = get_field(opt_key, &page); +// wxWindowUpdateLocker noUpdates(this); + + // we should to activate a tab with searched option, if it doesn't. + //if (!wxGetApp().mainframe->is_active_tab(this)) { + // wxNotebook* tap_panel = wxGetApp().tab_panel(); + // tap_panel->SetSelection(tap_panel->FindPage(this)); + //} +// Page* page {nullptr}; +// Field* field = get_field(opt_key, &page); // for option, which doesn't have field but just a text or button - wxString page_title = (!field || !page) ? category : page->title(); +// wxString page_title = (!field || !page) ? category : page->title(); + + wxString page_title = _(category); auto cur_item = m_treectrl->GetFirstVisibleItem(); if (!cur_item || !m_treectrl->IsVisible(cur_item)) @@ -1076,7 +1093,7 @@ void Tab::activate_option(const std::string& opt_key, const wxString& category) while (cur_item) { auto title = m_treectrl->GetItemText(cur_item); - if (_(page_title) != title) { + if (page_title != title) { cur_item = m_treectrl->GetNextVisible(cur_item); continue; } @@ -1086,10 +1103,14 @@ void Tab::activate_option(const std::string& opt_key, const wxString& category) } // we should to activate a tab with searched option, if it doesn't. - wxNotebook* tap_panel = wxGetApp().tab_panel(); - int page_id = tap_panel->FindPage(this); - if (tap_panel->GetSelection() != page_id) - tap_panel->SetSelection(page_id); + wxGetApp().mainframe->select_tab(this); + Field* field = get_field(opt_key); + + // we should to activate a tab with searched option, if it doesn't. + //wxNotebook* tap_panel = wxGetApp().tab_panel(); + //int page_id = tap_panel->FindPage(this); + //if (tap_panel->GetSelection() != page_id) + // tap_panel->SetSelection(page_id); // focused selected field if (field) { @@ -1973,7 +1994,7 @@ bool Tab::current_preset_is_dirty() { return m_presets->current_is_dirty(); } - +/* void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) { const PrinterTechnology tech = m_presets->get_selected_preset().printer_technology(); @@ -2090,7 +2111,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) \tOn this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.\n\ \tTo use a custom CA file, please import your CA file into Certificate Store / Keychain."))) % SLIC3R_APP_NAME).str() % std::string(ca_file_hint.ToUTF8())).str())); -*/ txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); +* / txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(txt, 1, wxEXPAND); return sizer; @@ -2099,7 +2120,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) optgroup->append_line(line); } } - +*/ void TabPrinter::build() { m_presets = &m_preset_bundle->printers; @@ -2423,14 +2444,14 @@ void TabPrinter::build_sla() build_preset_description_line(optgroup.get()); } - +/* void TabPrinter::update_serial_ports() { Field *field = get_field("serial_port"); Choice *choice = static_cast(field); choice->set_values(Utils::scan_serial_ports()); } - +*/ void TabPrinter::extruders_count_changed(size_t extruders_count) { bool is_count_changed = false; @@ -2728,7 +2749,7 @@ void TabPrinter::on_preset_loaded() // update the extruders count field auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); size_t extruders_count = nozzle_diameter->values.size(); - set_value("extruders_count", int(extruders_count)); +// set_value("extruders_count", int(extruders_count)); // update the GUI field according to the number of nozzle diameters supplied extruders_count_changed(extruders_count); } @@ -3297,6 +3318,8 @@ bool Tab::may_switch_to_SLA_preset() void Tab::clear_pages() { + // invalidated highlighter, if any exists + m_highlighter.invalidate(); // clear pages from the controlls for (auto p : m_pages) p->clear(); @@ -3310,6 +3333,8 @@ void Tab::clear_pages() m_compatible_prints.checkbox = nullptr; m_compatible_prints.btn = nullptr; + + m_blinking_ikons.clear(); } void Tab::update_description_lines() @@ -3372,7 +3397,10 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) for (auto& el : m_pages) el.get()->Hide(); - active_selected_page(); + if (wxGetApp().mainframe->is_active_and_shown_tab(this)) { + active_selected_page(); + m_active_page->Show(); + } #ifdef __linux__ no_updates.reset(nullptr); @@ -3380,7 +3408,7 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) update_undo_buttons(); - m_active_page->Show(); +// m_active_page->Show(); m_hsizer->Layout(); Refresh(); } @@ -3592,7 +3620,6 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep { deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); -// add_scaled_button(parent, &deps.btn, "printer_white", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), wxBU_LEFT | wxBU_EXACTFIT); deps.btn = new ScalableButton(parent, wxID_ANY, "printer_white", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -3674,7 +3701,6 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) { ScalableButton* btn = new ScalableButton(parent, wxID_ANY, "printer_white", " " + _(L("Set")) + " " + dots, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); -// add_scaled_button(parent, &btn, "printer_white", " " + _(L("Set")) + " " + dots, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().normal_font()); BlinkingBitmap* bbmp = new BlinkingBitmap(parent); @@ -4103,7 +4129,6 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_base_safety_distance"); // Mirrored parameter from Pad page for toggling elevation on the same page -// optgroup->append_single_option_line("pad_around_object"); optgroup->append_single_option_line("support_object_elevation"); Line line{ "", "" }; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 89dc2d1316..0a9d17c0e4 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -223,9 +223,9 @@ protected: void set_timer_owner(wxEvtHandler* owner, int timerid = wxID_ANY); void init(BlinkingBitmap* bmp); void blink(); + void invalidate(); private: - void invalidate(); BlinkingBitmap* bbmp {nullptr}; int blink_counter {0}; @@ -327,7 +327,7 @@ public: Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); void toggle_option(const std::string& opt_key, bool toggle, int opt_index = -1); - bool set_value(const t_config_option_key& opt_key, const boost::any& value); +// bool set_value(const t_config_option_key& opt_key, const boost::any& value); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText); bool current_preset_is_dirty(); @@ -417,7 +417,7 @@ class TabPrinter : public Tab std::vector m_pages_fff; std::vector m_pages_sla; - void build_printhost(ConfigOptionsGroup *optgroup); +// void build_printhost(ConfigOptionsGroup *optgroup); public: wxButton* m_serial_test_btn = nullptr; ScalableButton* m_print_host_test_btn = nullptr; @@ -447,7 +447,7 @@ public: void update_fff(); void update_sla(); void update_pages(); // update m_pages according to printer technology - void update_serial_ports(); +// void update_serial_ports(); void extruders_count_changed(size_t extruders_count); PageShp build_kinematics_page(); void build_unregular_pages(); From 8ea4b5fd78c159759a4ed6e0bb9e0e9ef792c29c Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 24 Sep 2020 17:17:35 +0200 Subject: [PATCH 558/826] instance check - bug fixes and refactoring based on code review. --- src/slic3r/GUI/InstanceCheck.cpp | 37 ++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index d5ecbcd0fc..cabae3dd56 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -64,21 +64,26 @@ namespace instance_check_internal //BOOST_LOG_TRIVIAL(error) << "ewp: version: " << l_version_wstring; TCHAR wndText[1000]; TCHAR className[1000]; - GetClassName(hwnd, className, 1000); - GetWindowText(hwnd, wndText, 1000); + int err; + err = GetClassName(hwnd, className, 1000); + if (err == 0) + return true; + err = GetWindowText(hwnd, wndText, 1000); + if (err == 0) + return true; std::wstring classNameString(className); std::wstring wndTextString(wndText); - if (wndTextString.find(L"PrusaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") { + if (wndTextString.find(L"PrusaSlicer") == 0 && classNameString == L"wxWindowNR") { //check if other instances has same instance hash //if not it is not same version(binary) as this version - HANDLE handle = GetProp(hwnd, L"Instance_Hash_Minor"); - size_t other_instance_hash = PtrToUint(handle); - size_t other_instance_hash_major; + HANDLE handle = GetProp(hwnd, L"Instance_Hash_Minor"); + unsigned long long other_instance_hash = PtrToUint(handle); + unsigned long long other_instance_hash_major; + unsigned long long my_instance_hash = GUI::wxGetApp().get_instance_hash_int(); handle = GetProp(hwnd, L"Instance_Hash_Major"); other_instance_hash_major = PtrToUint(handle); other_instance_hash_major = other_instance_hash_major << 32; other_instance_hash += other_instance_hash_major; - size_t my_instance_hash = GUI::wxGetApp().get_instance_hash_int(); if(my_instance_hash == other_instance_hash) { BOOST_LOG_TRIVIAL(debug) << "win enum - found correct instance"; @@ -95,19 +100,19 @@ namespace instance_check_internal { if (!EnumWindows(EnumWindowsProc, 0)) { std::wstring wstr = boost::nowide::widen(message); - //LPWSTR command_line_args = wstr.c_str();//GetCommandLine(); - LPWSTR command_line_args = new wchar_t[wstr.size() + 1]; + std::unique_ptr command_line_args = std::make_unique(const_cast(wstr.c_str())); + /*LPWSTR command_line_args = new wchar_t[wstr.size() + 1]; copy(wstr.begin(), wstr.end(), command_line_args); - command_line_args[wstr.size()] = 0; + command_line_args[wstr.size()] = 0;*/ + //Create a COPYDATASTRUCT to send the information //cbData represents the size of the information we want to send. //lpData represents the information we want to send. //dwData is an ID defined by us(this is a type of ID different than WM_COPYDATA). COPYDATASTRUCT data_to_send = { 0 }; data_to_send.dwData = 1; - data_to_send.cbData = sizeof(TCHAR) * (wcslen(command_line_args) + 1); - data_to_send.lpData = command_line_args; - + data_to_send.cbData = sizeof(TCHAR) * (wcslen(*command_line_args.get()) + 1); + data_to_send.lpData = *command_line_args.get(); SendMessage(l_prusa_slicer_hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send); return true; } @@ -242,7 +247,7 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance) GUI::wxGetApp().init_single_instance_checker(lock_name + ".lock", data_dir() + "/cache/"); if ((cla.should_send || app_config_single_instance) && GUI::wxGetApp().single_instance_checker()->IsAnotherRunning()) { #else // mac & linx - if (instance_check_internal::get_lock(lock_name + ".lock", data_dir() + "/cache/") && (cla.should_send || app_config_single_instance)) { + if ((cla.should_send || app_config_single_instance) && instance_check_internal::get_lock(lock_name + ".lock", data_dir() + "/cache/")) { #endif instance_check_internal::send_message(cla.cl_string, lock_name); BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate."; @@ -345,7 +350,7 @@ void OtherInstanceMessageHandler::print_window_info(HWND hwnd) namespace MessageHandlerInternal { // returns ::path to possible model or empty ::path if input string is not existing path - static boost::filesystem::path get_path(std::string possible_path) + static boost::filesystem::path get_path(const std::string& possible_path) { BOOST_LOG_TRIVIAL(debug) << "message part:" << possible_path; @@ -434,7 +439,7 @@ namespace MessageHandlerDBusInternal static void handle_method_another_instance(DBusConnection *connection, DBusMessage *request) { DBusError err; - char* text= ""; + char* text = nullptr; wxEvtHandler* evt_handler; dbus_error_init(&err); From d5bd76776f1960c3d69797ceb956228c7bee7b63 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 24 Sep 2020 18:54:54 +0200 Subject: [PATCH 559/826] Page class is used as a container of option groups and doesn't inherited from wxScrolledWindow now --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 1 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 1 + src/slic3r/GUI/GUI_ObjectSettings.cpp | 1 + src/slic3r/GUI/OptionsGroup.cpp | 37 ++++++++---- src/slic3r/GUI/OptionsGroup.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 71 +++++++++++++++-------- src/slic3r/GUI/Tab.hpp | 5 +- 7 files changed, 80 insertions(+), 38 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 90a725fbfd..09b4f36ab7 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -34,6 +34,7 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : m_grid_sizer->Add(temp); } + m_og->activate(); m_og->sizer->Clear(true); m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 7243e8c73a..4b1197a752 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -431,6 +431,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_main_grid_sizer->Add(m_check_inch, 1, wxEXPAND); + m_og->activate(); m_og->sizer->Clear(true); m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border); } diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index e157cb385c..1fbf32e118 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -58,6 +58,7 @@ wxSizer* OG_Settings::get_sizer() ObjectSettings::ObjectSettings(wxWindow* parent) : OG_Settings(parent, true) { + m_og->activate(); m_og->set_name(_(L("Additional Settings"))); m_settings_list_sizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 13c5776625..310dc95443 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -110,13 +110,13 @@ OptionsGroup::OptionsGroup( wxWindow* _parent, const wxString& title, m_show_modified_btns(is_tab_opt), staticbox(title!=""), extra_column(extra_clmn) { - if (staticbox) { + /*if (staticbox) { stb = new wxStaticBox(_parent, wxID_ANY, _(title)); if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); stb->SetFont(wxOSX ? wxGetApp().normal_font() : wxGetApp().bold_font()); } else stb = nullptr; - sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); + sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL));*/ } void OptionsGroup::add_undo_buttons_to_sizer(wxSizer* sizer, const t_field& field) @@ -362,10 +362,19 @@ void OptionsGroup::activate_line(Line& line) } // create all controls for the option group from the m_lines -void OptionsGroup::activate() +bool OptionsGroup::activate() { - if (!sizer->IsEmpty()) - return; + if (sizer)//(!sizer->IsEmpty()) + return false; + + if (staticbox) { + stb = new wxStaticBox(m_parent, wxID_ANY, _(title)); + if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); + stb->SetFont(wxOSX ? wxGetApp().normal_font() : wxGetApp().bold_font()); + } + else + stb = nullptr; + sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; size_t grow_col = 1; @@ -389,24 +398,30 @@ void OptionsGroup::activate() // activate lines for (Line& line: m_lines) activate_line(line); + + return true; } // delete all controls from the option group void OptionsGroup::clear() { - if (sizer->IsEmpty()) + if (!sizer)//(sizer->IsEmpty()) return; - m_grid_sizer->Clear(true); - sizer->Clear(true); +// m_grid_sizer->Clear(true); + m_grid_sizer = nullptr; + +// sizer->Clear(true); + //if (stb) { + // stb->SetContainingSizer(NULL); + // stb->Destroy(); + //} + sizer = nullptr; for (Line& line : m_lines) if(line.full_Label) *line.full_Label = nullptr; - //for (auto extra_col_win : m_extra_column_item_ptrs) - // destroy(extra_col_win); m_extra_column_item_ptrs.clear(); - m_near_label_widget_ptrs.clear(); m_fields.clear(); } diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index b3198137b2..4d70347aa4 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -124,7 +124,7 @@ public: void activate_line(Line& line); // create all controls for the option group from the m_lines - void activate(); + bool activate(); // delete all controls from the option group void clear(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8d15604482..b0dfe44bcd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -296,6 +296,19 @@ void Tab::create_preset_tab() m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); + // Initialize the page. +#ifdef __WXOSX__ + auto page_parent = m_tmp_panel; +#else + auto page_parent = this; +#endif + + m_page_view = new wxScrolledWindow(page_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_page_sizer = new wxBoxSizer(wxVERTICAL); + m_page_view->SetSizer(m_page_sizer); + m_page_view->SetScrollbars(1, 20, 1, 2); + m_hsizer->Add(m_page_view, 1, wxEXPAND | wxLEFT, 5); + m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { @@ -368,15 +381,16 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str #else auto panel = this; #endif - PageShp page(new Page(panel, title, icon_idx, m_mode_bitmap_cache)); + PageShp page(new Page(/*panel*/m_page_view, title, icon_idx, m_mode_bitmap_cache)); // page->SetBackgroundStyle(wxBG_STYLE_SYSTEM); #ifdef __WINDOWS__ // page->SetDoubleBuffered(true); #endif //__WINDOWS__ - page->SetScrollbars(1, 20, 1, 2); - page->Hide(); - m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); + //page->SetScrollbars(1, 20, 1, 2); + //page->Hide(); + //m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); +// m_hsizer->Add(page->vsizer(), 1, wxEXPAND | wxLEFT, 5); if (!is_extruder_pages) m_pages.push_back(page); @@ -398,7 +412,7 @@ void Tab::OnActivate() // create controls on active page active_selected_page(); - m_active_page->Show(); +// m_active_page->Show(); m_hsizer->Layout(); Refresh(); } @@ -2562,12 +2576,12 @@ void TabPrinter::build_unregular_pages() /* Workaround for correct layout of controls inside the created page: * In some _strange_ way we should we should imitate page resizing. */ - auto layout_page = [this](PageShp page) +/* auto layout_page = [this](PageShp page) { const wxSize& sz = page->GetSize(); page->SetSize(sz.x + 1, sz.y + 1); page->SetSize(sz); - }; + };*/ #endif //__WXMSW__ // Add/delete Kinematics page according to is_marlin_flavor @@ -2584,7 +2598,7 @@ void TabPrinter::build_unregular_pages() if (existed_page < n_before_extruders && is_marlin_flavor) { auto page = build_kinematics_page(); #ifdef __WXMSW__ - layout_page(page); +// layout_page(page); #endif m_pages.insert(m_pages.begin() + n_before_extruders, page); } @@ -2722,7 +2736,7 @@ void TabPrinter::build_unregular_pages() optgroup->append_line(line); #ifdef __WXMSW__ - layout_page(page); +// layout_page(page); #endif } @@ -2762,8 +2776,8 @@ void TabPrinter::update_pages() return; // hide all old pages - for (auto& el : m_pages) - el.get()->Hide(); + //for (auto& el : m_pages) + // el.get()->Hide(); // set m_pages to m_pages_(technology before changing) m_printer_technology == ptFFF ? m_pages.swap(m_pages_fff) : m_pages.swap(m_pages_sla); @@ -3320,9 +3334,11 @@ void Tab::clear_pages() { // invalidated highlighter, if any exists m_highlighter.invalidate(); + m_page_sizer->Clear(true); // clear pages from the controlls for (auto p : m_pages) - p->clear(); + p->clear(); + int i = m_page_sizer->GetItemCount(); // nulling pointers m_parent_preset_description_line = nullptr; @@ -3369,7 +3385,7 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) * so on Window is no needed to call a Freeze/Thaw functions. * But under OSX (builds compiled with MacOSX10.14.sdk) wxStaticBitmap rendering is broken without Freeze/Thaw call. */ -//#ifdef __WXOSX__ // Use Freeze/Thaw to avoid flickering during cleare/activate new page +//#ifdef __WXOSX__ // Use Freeze/Thaw to avoid flickering during clear/activate new page wxWindowUpdateLocker noUpdates(this); //#endif #endif @@ -3394,12 +3410,12 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) m_active_page = page; clear_pages(); - for (auto& el : m_pages) - el.get()->Hide(); + //for (auto& el : m_pages) + // el.get()->Hide(); if (wxGetApp().mainframe->is_active_and_shown_tab(this)) { active_selected_page(); - m_active_page->Show(); +// m_active_page->Show(); } #ifdef __linux__ @@ -3843,10 +3859,11 @@ Page::Page(wxWindow* parent, const wxString& title, const int iconID, const std: m_iconID(iconID), m_mode_bitmap_cache(mode_bmp_cache) { - Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - m_vsizer = new wxBoxSizer(wxVERTICAL); +// Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); +// m_vsizer = new wxBoxSizer(wxVERTICAL); + m_vsizer = (wxBoxSizer*)parent->GetSizer(); m_item_color = &wxGetApp().get_label_clr_default(); - SetSizer(m_vsizer); +// SetSizer(m_vsizer); } void Page::reload_config() @@ -3870,8 +3887,12 @@ void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibil void Page::activate(ConfigOptionMode mode) { + //if (m_parent) + //m_parent->SetSizer(m_vsizer); for (auto group : m_optgroups) { - group->activate(); + if (!group->activate()) + continue; + m_vsizer->Add(group->sizer, 0, wxEXPAND | wxALL, 10); group->update_visibility(mode); group->reload_config(); } @@ -3910,7 +3931,7 @@ bool Page::set_value(const t_config_option_key& opt_key, const boost::any& value bool changed = false; for(auto optgroup: m_optgroups) { if (optgroup->set_value(opt_key, value)) - changed = 1 ; + changed = true ; } return changed; } @@ -3933,15 +3954,15 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la }; //! config_ have to be "right" - ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true, extra_column); + ConfigOptionsGroupShp optgroup = std::make_shared(/*this*/m_parent, title, m_config, true, extra_column); optgroup->config_category = m_title.ToStdString(); if (noncommon_label_width >= 0) optgroup->label_width = noncommon_label_width; #ifdef __WXOSX__ - auto tab = GetParent()->GetParent(); + auto tab = parent()->GetParent()->GetParent();// GetParent()->GetParent(); #else - auto tab = GetParent(); + auto tab = parent()->GetParent();// GetParent(); #endif optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value) { //! This function will be called from OptionGroup. @@ -3975,7 +3996,7 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la ctrl->SetBitmap(reinterpret_cast(ctrl->GetClientData())->bmp()); }; - vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); +// vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); m_optgroups.push_back(optgroup); return optgroup; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 0a9d17c0e4..9df68592b5 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -44,7 +44,7 @@ class TabPresetComboBox; // Single Tab page containing a{ vsizer } of{ optgroups } // package Slic3r::GUI::Tab::Page; using ConfigOptionsGroupShp = std::shared_ptr; -class Page : public wxScrolledWindow +class Page// : public wxScrolledWindow { wxWindow* m_parent; wxString m_title; @@ -127,6 +127,9 @@ protected: wxTreeCtrl* m_treectrl; wxImageList* m_icons; + wxScrolledWindow* m_page_view {nullptr}; + wxBoxSizer* m_page_sizer {nullptr}; + ModeSizer* m_mode_sizer; struct PresetDependencies { From 7a799be426d00c70fe181efb454065925f87c34a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 24 Sep 2020 19:03:09 +0200 Subject: [PATCH 560/826] DynamicPrintConfig::normalize() renamed to normalize_fdm(), optimization of Print::apply() --- src/PrusaSlicer.cpp | 6 +++--- src/libslic3r/Print.cpp | 32 ++++++++++++++++++++------------ src/libslic3r/PrintConfig.cpp | 2 +- src/libslic3r/PrintConfig.hpp | 12 +----------- src/libslic3r/PrintObject.cpp | 34 +++++++++++++++++++++++++++++----- src/libslic3r/SLAPrint.cpp | 8 +++----- src/libslic3r/SLAPrint.hpp | 2 +- 7 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index a12ad8bb73..d79196cfa1 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -93,7 +93,7 @@ int CLI::run(int argc, char **argv) return 1; m_extra_config.apply(m_config, true); - m_extra_config.normalize(); + m_extra_config.normalize_fdm(); PrinterTechnology printer_technology = Slic3r::printer_technology(m_config); @@ -129,7 +129,7 @@ int CLI::run(int argc, char **argv) boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl; return 1; } - config.normalize(); + config.normalize_fdm(); PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; @@ -196,7 +196,7 @@ int CLI::run(int argc, char **argv) // (command line options override --load files) m_print_config.apply(m_extra_config, true); // Normalizing after importing the 3MFs / AMFs - m_print_config.normalize(); + m_print_config.normalize_fdm(); // Initialize full print configs for both the FFF and SLA technologies. FullPrintConfig fff_print_config; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index fb276a7c29..34a3d8b237 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -587,7 +587,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ new_full_config.option("print_settings_id", true); new_full_config.option("filament_settings_id", true); new_full_config.option("printer_settings_id", true); - new_full_config.normalize(); + new_full_config.normalize_fdm(); // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. t_config_option_keys print_diff, object_diff, region_diff, full_config_diff; @@ -844,8 +844,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); - bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); - bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); + bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || + model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile) || @@ -858,20 +858,21 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } // Copy content of the ModelObject including its ID, do not change the parent. model_object.assign_copy(model_object_new); - } else if (support_blockers_differ || support_enforcers_differ || model_custom_supports_data_changed(model_object, model_object_new)) { + } else if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. - this->call_cancel_callback(); - update_apply_status(false); + if (supports_differ) { + this->call_cancel_callback(); + update_apply_status(false); + } // Invalidate just the supports step. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); for (auto it = range.first; it != range.second; ++ it) update_apply_status(it->print_object->invalidate_step(posSupportMaterial)); - if (support_enforcers_differ || support_blockers_differ) { + if (supports_differ) { // Copy just the support volumes. model_volume_list_update_supports(model_object, model_object_new); } - } - if (model_custom_seam_data_changed(model_object, model_object_new)) { + } else if (model_custom_seam_data_changed(model_object, model_object_new)) { update_apply_status(this->invalidate_step(psGCodeExport)); } if (! model_parts_differ && ! modifiers_differ) { @@ -942,13 +943,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ old.emplace_back(&(*it)); } // Generate a list of trafos and XY offsets for instances of a ModelObject - PrintObjectConfig config = PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders); + // Producing the config for PrintObject on demand, caching it at print_object_last. + const PrintObject *print_object_last = nullptr; + auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject* print_object) { + print_object->config_apply(print_object_last ? + print_object_last->config() : + PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); + print_object_last = print_object; + }; std::vector new_print_instances = print_objects_from_model_object(*model_object); if (old.empty()) { // Simple case, just generate new instances. for (PrintObjectTrafoAndInstances &print_instances : new_print_instances) { PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); - print_object->config_apply(config); + print_object_apply_config(print_object); print_objects_new.emplace_back(print_object); // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); new_objects = true; @@ -965,7 +973,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { // This is a new instance (or a set of instances with the same trafo). Just add it. PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); - print_object->config_apply(config); + print_object_apply_config(print_object); print_objects_new.emplace_back(print_object); // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); new_objects = true; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index a8eb68521d..93d9cfbcff 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3233,7 +3233,7 @@ PrinterTechnology printer_technology(const ConfigBase &cfg) return ptUnknown; } -void DynamicPrintConfig::normalize() +void DynamicPrintConfig::normalize_fdm() { if (this->has("extruder")) { int extruder = this->option("extruder")->getInt(); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index bb4428ab45..52e3bc38cf 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -254,7 +254,7 @@ public: // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &print_config_def; } - void normalize(); + void normalize_fdm(); void set_num_extruders(unsigned int num_extruders); @@ -269,14 +269,6 @@ public: { PrintConfigDef::handle_legacy(opt_key, value); } }; -template -void normalize_and_apply_config(CONFIG &dst, const DynamicPrintConfig &src) -{ - DynamicPrintConfig src_normalized(src); - src_normalized.normalize(); - dst.apply(src_normalized, true); -} - class StaticPrintConfig : public StaticConfig { public: @@ -1457,8 +1449,6 @@ private: static uint64_t s_last_timestamp; }; -template void normalize_and_apply_config(CONFIG& dst, const ModelConfig& src) { normalize_and_apply_config(dst, src.get()); } - } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c0425ada4d..7bf5734bb8 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1544,22 +1544,46 @@ static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders) { PrintObjectConfig config = default_object_config; - normalize_and_apply_config(config, object.config); + { + DynamicPrintConfig src_normalized(object.config.get()); + src_normalized.normalize_fdm(); + config.apply(src_normalized, true); + } // Clamp invalid extruders to the default extruder (with index 1). clamp_exturder_to_default(config.support_material_extruder, num_extruders); clamp_exturder_to_default(config.support_material_interface_extruder, num_extruders); return config; } +static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in) +{ + // 1) Copy the "extruder key to infill_extruder and perimeter_extruder. + std::string sextruder = "extruder"; + auto *opt_extruder = in.opt(sextruder); + if (opt_extruder) { + if (opt_extruder->value != 0) { + out.infill_extruder.value = opt_extruder->value; + out.perimeter_extruder.value = opt_extruder->value; + } + } + // 2) Copy the rest of the values. + for (auto it = in.cbegin(); it != in.cend(); ++ it) + if (it->first != sextruder) { + ConfigOption *my_opt = out.option(it->first, false); + if (my_opt) + my_opt->set(it->second.get()); + } +} + PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders) { PrintRegionConfig config = default_region_config; - normalize_and_apply_config(config, volume.get_object()->config); + apply_to_print_region_config(config, volume.get_object()->config.get()); if (layer_range_config != nullptr) - normalize_and_apply_config(config, *layer_range_config); - normalize_and_apply_config(config, volume.config); + apply_to_print_region_config(config, *layer_range_config); + apply_to_print_region_config(config, volume.config.get()); if (! volume.material_id().empty()) - normalize_and_apply_config(config, volume.material()->config); + apply_to_print_region_config(config, volume.material()->config.get()); // Clamp invalid extruders to the default extruder (with index 1). clamp_exturder_to_default(config.infill_extruder, num_extruders); clamp_exturder_to_default(config.perimeter_extruder, num_extruders); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 30d1df6c1a..a2d2ff6209 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -185,7 +185,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con config.option("sla_print_settings_id", true); config.option("sla_material_settings_id", true); config.option("printer_settings_id", true); - config.normalize(); // Collect changes to print config. t_config_option_keys print_diff = m_print_config.diff(config); t_config_option_keys printer_diff = m_printer_config.diff(config); @@ -400,7 +399,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con model_object.config.assign_config(model_object_new.config); if (! object_diff.empty() || object_config_changed) { SLAPrintObjectConfig new_config = m_default_object_config; - normalize_and_apply_config(new_config, model_object.config); + new_config.apply(model_object.config.get(), true); if (it_print_object_status != print_object_status.end()) { t_config_option_keys diff = it_print_object_status->print_object->config().diff(new_config); if (! diff.empty()) { @@ -464,9 +463,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con print_object->set_instances(std::move(new_instances)); - SLAPrintObjectConfig new_config = m_default_object_config; - normalize_and_apply_config(new_config, model_object.config); - print_object->config_apply(new_config, true); + print_object->config_apply(m_default_object_config, true); + print_object->config_apply(model_object.config.get(), true); print_objects_new.emplace_back(print_object); new_objects = true; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 5fbf8346c7..5ca1b2b8e7 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -261,7 +261,7 @@ protected: SLAPrintObject(SLAPrint* print, ModelObject* model_object); ~SLAPrintObject(); - void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } + void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { m_config.apply(other, ignore_nonexistent); } void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } From e0b0a2cdcfa87be770c64a66146cd6e5e01252c8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 24 Sep 2020 20:32:52 +0200 Subject: [PATCH 561/826] Fix of previous commit. --- src/libslic3r/PrintObject.cpp | 10 ++++++---- xs/t/15_config.t | 6 +++--- xs/xsp/Config.xsp | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 7bf5734bb8..6f26d8b9de 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -440,7 +440,7 @@ std::pair PrintObject::prepare using namespace FillAdaptive; auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); - if (adaptive_line_spacing == 0. && support_line_spacing == 0. || this->layers().empty()) + if ((adaptive_line_spacing == 0. && support_line_spacing == 0.) || this->layers().empty()) return std::make_pair(OctreePtr(), OctreePtr()); indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); @@ -1561,9 +1561,11 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr std::string sextruder = "extruder"; auto *opt_extruder = in.opt(sextruder); if (opt_extruder) { - if (opt_extruder->value != 0) { - out.infill_extruder.value = opt_extruder->value; - out.perimeter_extruder.value = opt_extruder->value; + int extruder = opt_extruder->value; + if (extruder != 0) { + out.infill_extruder .value = extruder; + out.solid_infill_extruder.value = extruder; + out.perimeter_extruder .value = extruder; } } // 2) Copy the rest of the values. diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 1f9fc939bc..55b6791015 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -219,7 +219,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo my $config = Slic3r::Config->new; $config->set('extruder', 2); $config->set('perimeter_extruder', 3); - $config->normalize; + $config->normalize_fdm; ok !$config->has('extruder'), 'extruder option is removed after normalize()'; is $config->get('infill_extruder'), 2, 'undefined extruder is populated with default extruder'; is $config->get('perimeter_extruder'), 3, 'defined extruder is not overwritten by default extruder'; @@ -228,7 +228,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo { my $config = Slic3r::Config->new; $config->set('infill_extruder', 2); - $config->normalize; + $config->normalize_fdm; is $config->get('solid_infill_extruder'), 2, 'undefined solid infill extruder is populated with infill extruder'; } @@ -236,7 +236,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo my $config = Slic3r::Config->new; $config->set('spiral_vase', 1); $config->set('retract_layer_change', [1,0]); - $config->normalize; + $config->normalize_fdm; is_deeply $config->get('retract_layer_change'), [0,0], 'retract_layer_change is disabled with spiral_vase'; } diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 63dc5b312a..b8f996797b 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -48,7 +48,7 @@ %code{% THIS->apply(*other, true); %}; %name{get_keys} std::vector keys(); void erase(t_config_option_key opt_key); - void normalize(); + void normalize_fdm(); %name{setenv} void setenv_(); double min_object_distance() %code{% RETVAL = Slic3r::min_object_distance(*THIS); %}; static DynamicPrintConfig* load(char *path) From 0b0709b3d8654c4c07ac9dd0510751119c1e4ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 25 Sep 2020 09:54:27 +0200 Subject: [PATCH 562/826] Fix crash in adaptive infill when an extrusion line width is zero. When an extrusion line width is set to zero, then an extrusion line width is calculated from nozzle diameter. --- src/libslic3r/Fill/FillAdaptive.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index eebded55bf..7813d64a31 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -13,6 +13,7 @@ #include #include +#include // Boost pool: Don't use mutexes to synchronize memory allocation. #define BOOST_POOL_NO_MT @@ -284,7 +285,10 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob }; std::vector region_fill_data; region_fill_data.reserve(print_object.print()->regions().size()); - bool build_octree = false; + bool build_octree = false; + const std::vector &nozzle_diameters = print_object.print()->config().nozzle_diameter.values; + double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()); + double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, max_nozzle_diameter); for (const PrintRegion *region : print_object.print()->regions()) { const PrintRegionConfig &config = region->config(); bool nonempty = config.fill_density > 0; @@ -294,7 +298,7 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob has_adaptive_infill ? Tristate::Maybe : Tristate::No, has_support_infill ? Tristate::Maybe : Tristate::No, config.fill_density, - config.infill_extrusion_width + config.infill_extrusion_width != 0. ? config.infill_extrusion_width : default_infill_extrusion_width })); build_octree |= has_adaptive_infill || has_support_infill; } From 5243d3e53c1da2443fbb8d118f6ceae01dcd1dba Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 25 Sep 2020 10:44:22 +0200 Subject: [PATCH 563/826] Fixed crash on BedShapeDialog creation --- src/slic3r/GUI/BedShapeDialog.cpp | 14 ++++++++++---- src/slic3r/GUI/BedShapeDialog.hpp | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 3caf168b51..7818382331 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -229,11 +229,11 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); - optgroup->activate(); + activate_options_page(optgroup); optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); - optgroup->activate(); + activate_options_page(optgroup); optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); @@ -254,7 +254,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf return sizer; }; optgroup->append_line(line); - optgroup->activate(); + activate_options_page(optgroup); wxPanel* texture_panel = init_texture_panel(); wxPanel* model_panel = init_model_panel(); @@ -297,12 +297,18 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& tit }; m_optgroups.push_back(optgroup); - panel->SetSizerAndFit(optgroup->sizer); +// panel->SetSizerAndFit(optgroup->sizer); m_shape_options_book->AddPage(panel, title); return optgroup; } +void BedShapePanel::activate_options_page(ConfigOptionsGroupShp options_group) +{ + options_group->activate(); + options_group->parent()->SetSizerAndFit(options_group->sizer); +} + wxPanel* BedShapePanel::init_texture_panel() { wxPanel* panel = new wxPanel(this); diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 2cfbc73aec..370129f2ed 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -75,6 +75,7 @@ public: private: ConfigOptionsGroupShp init_shape_options_page(const wxString& title); + void activate_options_page(ConfigOptionsGroupShp options_group); wxPanel* init_texture_panel(); wxPanel* init_model_panel(); void set_shape(const ConfigOptionPoints& points); From 11a410b4e79a64d061f76a3e52e847bcf6ed1f03 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 25 Sep 2020 11:02:29 +0200 Subject: [PATCH 564/826] Updated SplashScreen + Increased gap_size for main and undo/redo toolbars --- resources/icons/splashscreen.jpg | Bin 133522 -> 104443 bytes src/slic3r/GUI/GLCanvas3D.cpp | 4 ++-- src/slic3r/GUI/GUI_App.cpp | 25 ++++++++++++++----------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/resources/icons/splashscreen.jpg b/resources/icons/splashscreen.jpg index 86c55f30f6af0fb6de922766bccd388f987d2d20..2b83ed3516ddc2ba943775da9221890564f23ed2 100644 GIT binary patch delta 87243 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

)viuk!Y^IX0%y~~LW#@X8^Y~? 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%0D0rkU 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 diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e0c8c4c5bd..67eaffe50c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4888,7 +4888,7 @@ bool GLCanvas3D::_init_main_toolbar() m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); m_main_toolbar.set_border(5.0f); m_main_toolbar.set_separator_size(5); - m_main_toolbar.set_gap_size(2); + m_main_toolbar.set_gap_size(4); GLToolbarItem::Data item; @@ -5083,7 +5083,7 @@ bool GLCanvas3D::_init_undoredo_toolbar() m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); m_undoredo_toolbar.set_border(5.0f); m_undoredo_toolbar.set_separator_size(5); - m_undoredo_toolbar.set_gap_size(2); + m_undoredo_toolbar.set_gap_size(4); GLToolbarItem::Data item; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 7b6b38b5db..7df7243b28 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -152,9 +152,9 @@ public: // this box will be 2/5 of the weight of the bitmap, and be at the left. int banner_width = (bmp.GetWidth() / 5) * 2 - 2; const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight())); - wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); - wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); - memDc.DrawRectangle(banner_rect); + //wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); + //wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); + //memDc.DrawRectangle(banner_rect); wxFont sys_font = get_scaled_sys_font(screen_sf); @@ -175,22 +175,25 @@ public: // create a copyright notice that uses the year that this file was compiled wxString year(__DATE__); wxString cr_symbol = wxString::FromUTF8("\xc2\xa9"); - wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" - "%s 2011-2018 Alessandro Ranellucci.", - cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n"; + //wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" + // "%s 2011-2018 Alessandro Ranellucci.", + // cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n"; wxFont copyright_font = sys_font.Larger(); - copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + + wxString copyright_string = //+= "Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + - _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" + - _L("Splash screen can be disabled from the \"Preferences\""); + "PrusaSlicer" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + +// _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" + +// _L("Splash screen can be disabled from the \"Preferences\""); + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + + _L("Artwork model by Nora Al-Badri and Jan Nikolai Nelles"); word_wrap_string(copyright_string, banner_width, screen_scale); wxCoord margin = int(screen_scale * 20); // draw the (orange) labels inside of our black box (at the left of the splashscreen) - memDc.SetTextForeground(wxColour(237, 107, 33)); + memDc.SetTextForeground(wxColour(255, 255, 255)); memDc.SetFont(title_font); memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT); @@ -199,7 +202,7 @@ public: memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); memDc.SetFont(copyright_font); - memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, margin), wxALIGN_BOTTOM | wxALIGN_LEFT); + memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT); return true; } From b178d0af38c786adc2c19fec5f4f2bf8f981f470 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 25 Sep 2020 12:27:56 +0200 Subject: [PATCH 565/826] Wipe tower - small refactoring and fix Collection of data from internal wipe tower gcode generator now uses move semantics. Part of gcode at the end of priming was erroneously not exported (extruder current reset etc.) --- src/libslic3r/GCode/WipeTower.cpp | 80 ++++++++++-------------------- src/libslic3r/GCode/WipeTower.hpp | 6 +++ src/libslic3r/SLA/SupportPoint.hpp | 3 +- 3 files changed, 34 insertions(+), 55 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 944bea5c1f..b56c7de9c0 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -521,6 +521,27 @@ private: +WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, + bool priming, + size_t old_tool) const +{ + ToolChangeResult result; + result.priming = priming; + result.initial_tool = int(old_tool); + result.new_tool = int(this->m_current_tool); + result.print_z = this->m_z_pos; + result.layer_height = this->m_layer_height; + result.elapsed_time = writer.elapsed_time(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = priming ? writer.pos() : writer.pos_rotated(); + result.gcode = writer.gcode(); + result.extrusions = writer.extrusions(); + result.wipe_path = writer.wipe_path(); + return result; +} + + + WipeTower::WipeTower(const PrintConfig& config, const std::vector>& wiping_matrix, size_t initial_tool) : m_semm(config.single_extruder_multi_material.value), m_wipe_tower_pos(config.wipe_tower_x, config.wipe_tower_y), @@ -679,20 +700,6 @@ std::vector WipeTower::prime( if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - ToolChangeResult result; - result.priming = true; - result.initial_tool = int(old_tool); - result.new_tool = int(m_current_tool); - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos(); - - results.push_back(std::move(result)); - // This is the last priming toolchange - finish priming if (idx_tool+1 == tools.size()) { // Reset the extruder current to a normal value. @@ -706,6 +713,8 @@ std::vector WipeTower::prime( ";------------------\n" "\n\n"); } + + results.emplace_back(construct_tcr(writer, true, old_tool)); } m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear @@ -815,19 +824,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - ToolChangeResult result; - result.priming = false; - result.initial_tool = int(old_tool); - result.new_tool = int(m_current_tool); - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos_rotated(); - result.wipe_path = writer.wipe_path(); - return result; + return construct_tcr(writer, false, old_tool); } WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_offset) @@ -882,19 +879,7 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - ToolChangeResult result; - result.priming = false; - result.initial_tool = int(old_tool); - result.new_tool = int(m_current_tool); - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos_rotated(); - result.wipe_path = writer.wipe_path(); - return result; + return construct_tcr(writer, false, old_tool); } @@ -1266,19 +1251,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - ToolChangeResult result; - result.priming = false; - result.initial_tool = int(old_tool); - result.new_tool = int(m_current_tool); - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos_rotated(); - result.wipe_path = writer.wipe_path(); - return result; + return construct_tcr(writer, false, old_tool); } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box @@ -1461,4 +1434,5 @@ void WipeTower::make_wipe_tower_square() lay.extra_spacing = lay.depth / lay.toolchanges_depth(); } + } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 26f48785a9..0f9b0b87d1 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -84,6 +84,12 @@ public: } }; + // Construct ToolChangeResult from current state of WipeTower and WipeTowerWriter. + // WipeTowerWriter is moved from ! + ToolChangeResult construct_tcr(WipeTowerWriter& writer, + bool priming, + size_t old_tool) const; + // x -- x coordinates of wipe tower in mm ( left bottom corner ) // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 2b973697bb..71849a3643 100644 --- a/src/libslic3r/SLA/SupportPoint.hpp +++ b/src/libslic3r/SLA/SupportPoint.hpp @@ -1,8 +1,7 @@ #ifndef SLA_SUPPORTPOINT_HPP #define SLA_SUPPORTPOINT_HPP -#include -#include +#include namespace Slic3r { namespace sla { From 48b0a14c4c074b7353e31142f2ee65dc95ce7564 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 25 Sep 2020 13:00:43 +0200 Subject: [PATCH 566/826] Fixup of previous commit --- src/libslic3r/GCode/WipeTower.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index b56c7de9c0..d6d336292d 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -534,9 +534,9 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, result.elapsed_time = writer.elapsed_time(); result.start_pos = writer.start_pos_rotated(); result.end_pos = priming ? writer.pos() : writer.pos_rotated(); - result.gcode = writer.gcode(); - result.extrusions = writer.extrusions(); - result.wipe_path = writer.wipe_path(); + result.gcode = std::move(writer.gcode()); + result.extrusions = std::move(writer.extrusions()); + result.wipe_path = std::move(writer.wipe_path()); return result; } From 54fbbb1a2384bdc25dede3f1e0dc84a04d01a5b8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 25 Sep 2020 15:13:01 +0200 Subject: [PATCH 567/826] InstanceCheck: typo in DBus function name --- src/slic3r/GUI/InstanceCheck.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index cabae3dd56..bbce1ed2ca 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -173,7 +173,7 @@ namespace instance_check_internal const char* sigval = message_text.c_str(); //std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck"; std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck.Object" + version; - std::string method_name = "AnotherInstace"; + std::string method_name = "AnotherInstance"; //std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck"; std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck/Object" + version; @@ -203,7 +203,7 @@ namespace instance_check_internal dbus_connection_unref(conn); return true; } - //the AnotherInstace method is not sending reply. + //the AnotherInstance method is not sending reply. dbus_message_set_no_reply(msg, TRUE); //append arguments to message @@ -424,7 +424,7 @@ namespace MessageHandlerDBusInternal " " " " " " - " " + " " " " " " " " @@ -466,7 +466,7 @@ namespace MessageHandlerDBusInternal if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) { respond_to_introspect(connection, message); return DBUS_HANDLER_RESULT_HANDLED; - } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("AnotherInstace", member_name)) { + } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("AnotherInstance", member_name)) { handle_method_another_instance(connection, message); return DBUS_HANDLER_RESULT_HANDLED; } From f890cd5b9c8b27030b16af1a9e11991fcdabec5d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 25 Sep 2020 16:04:28 +0200 Subject: [PATCH 568/826] FDM painting gizmos (support/seam) now render object in neutral color The goal is to ensure enough contrast independent on current filament color --- src/slic3r/GUI/3DScene.cpp | 12 +++++++++--- src/slic3r/GUI/3DScene.hpp | 3 +++ src/slic3r/GUI/GLCanvas3D.cpp | 14 +++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 4 ++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ca96af49c1..84a5e4d2fc 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -306,6 +306,7 @@ const float GLVolume::MODEL_COLOR[4][4] = { }; const float GLVolume::SLA_SUPPORT_COLOR[4] = { 0.75f, 0.75f, 0.75f, 1.0f }; const float GLVolume::SLA_PAD_COLOR[4] = { 0.0f, 0.2f, 0.0f, 1.0f }; +const float GLVolume::NEUTRAL_COLOR[4] = { 0.9f, 0.9f, 0.9f, 1.0f }; GLVolume::GLVolume(float r, float g, float b, float a) : m_transformed_bounding_box_dirty(true) @@ -327,6 +328,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , is_extrusion_path(false) , force_transparent(false) , force_native_color(false) + , force_neutral_color(false) , tverts_range(0, size_t(-1)) , qverts_range(0, size_t(-1)) { @@ -352,12 +354,16 @@ void GLVolume::set_render_color(const float* rgba, unsigned int size) void GLVolume::set_render_color() { - if (force_native_color) + if (force_native_color || force_neutral_color) { if (is_outside && shader_outside_printer_detection_enabled) set_render_color(OUTSIDE_COLOR, 4); - else - set_render_color(color, 4); + else { + if (force_native_color) + set_render_color(color, 4); + else + set_render_color(NEUTRAL_COLOR, 4); + } } else { if (hover == HS_Select) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 7e8ae6fe33..31e974be15 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -251,6 +251,7 @@ public: static const float MODEL_COLOR[4][4]; static const float SLA_SUPPORT_COLOR[4]; static const float SLA_PAD_COLOR[4]; + static const float NEUTRAL_COLOR[4]; enum EHoverState : unsigned char { @@ -336,6 +337,8 @@ public: bool force_transparent : 1; // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) bool force_native_color : 1; + // Whether or not render this volume in neutral + bool force_neutral_color : 1; }; // Is mouse or rectangle selection over this object to select/deselect it ? diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 67eaffe50c..a539ffc398 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1725,7 +1725,19 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) { vol->is_active = visible; - vol->force_native_color = (instance_idx != -1); + + if (instance_idx == -1) { + vol->force_native_color = false; + vol->force_neutral_color = false; + } else { + const GLGizmosManager& gm = get_gizmos_manager(); + auto gizmo_type = gm.get_current_type(); + if (gizmo_type == GLGizmosManager::FdmSupports + || gizmo_type == GLGizmosManager::Seam) + vol->force_neutral_color = true; + else + vol->force_native_color = true; + } } } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index ed98bf71d5..939d3c48a0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -504,13 +504,13 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) m_iva_blockers.finalize_geometry(true); if (m_iva_enforcers.has_VBOs()) { - ::glColor4f(0.f, 0.f, 1.f, 0.2f); + ::glColor4f(0.f, 0.f, 1.f, 0.3f); m_iva_enforcers.render(); } if (m_iva_blockers.has_VBOs()) { - ::glColor4f(1.f, 0.f, 0.f, 0.2f); + ::glColor4f(1.f, 0.f, 0.f, 0.3f); m_iva_blockers.render(); } From b82de22caaca0fb21f398ef7483e1b07624bb0e3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 28 Sep 2020 20:41:22 +0200 Subject: [PATCH 569/826] SplashScreen improvements: * Added MakeBitmap() to create a bitmap from input version image * Editor/Viewer logo is added now in SplashScreen::Decorate() --- resources/icons/splashscreen.jpg | Bin 104443 -> 128296 bytes resources/icons/splashscreen_.jpg | Bin 0 -> 104443 bytes src/slic3r/GUI/GUI_App.cpp | 92 +++++++++++++++++++----------- 3 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 resources/icons/splashscreen_.jpg diff --git a/resources/icons/splashscreen.jpg b/resources/icons/splashscreen.jpg index 2b83ed3516ddc2ba943775da9221890564f23ed2..754e2458806530847a7b15db5311f1e327ee7216 100644 GIT binary patch delta 122632 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 #include @@ -112,13 +114,40 @@ public: wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); memDC.SetFont(m_font); memDC.SetTextForeground(wxColour(237, 107, 33)); - memDC.DrawText(text, int(m_scale * 45), int(m_scale * 200)); + memDC.DrawText(text, int(m_scale * 45), int(m_scale * 240)); memDC.SelectObject(wxNullBitmap); set_bitmap(bitmap); } } + static wxBitmap MakeBitmap(wxBitmap bmp) + { + if (!bmp.IsOk()) + return wxNullBitmap; + + // create dark grey background for the splashscreen + // It will be 5/3 of the weight of the bitmap + int width = lround((double)5 / 3 * bmp.GetWidth()); + int height = bmp.GetHeight(); + + wxImage image(width, height); + unsigned char* imgdata_ = image.GetData(); + for (int i = 0; i < width * height; ++i) { + *imgdata_++ = 51; + *imgdata_++ = 51; + *imgdata_++ = 51; + } + + wxBitmap new_bmp(image); + + wxMemoryDC memDC; + memDC.SelectObject(new_bmp); + memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); + + return new_bmp; + } + static bool Decorate(wxBitmap& bmp, wxPoint screen_pos = wxDefaultPosition, bool force_decor = false) { if (!bmp.IsOk()) @@ -145,18 +174,23 @@ public: // Decoration will be continued later, from the SplashScreen constructor } - // use a memory DC to draw directly onto the bitmap - wxMemoryDC memDc(bmp); - - // draw an dark grey box at the left of the splashscreen. + // draw text to the box at the left of the splashscreen. // this box will be 2/5 of the weight of the bitmap, and be at the left. - int banner_width = (bmp.GetWidth() / 5) * 2 - 2; - const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight())); - //wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); - //wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); - //memDc.DrawRectangle(banner_rect); + int width = lround(bmp.GetWidth() * 0.4); + + // load bitmap for logo + BitmapCache bmp_cache; + int logo_size = lround(width * 0.25); +#if ENABLE_GCODE_VIEWER + wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().is_editor() ? "prusa_slicer_logo" : "add_gcode", logo_size, logo_size); +#else + wxBitmap logo_bmp = *bmp_cache.load_svg("prusa_slicer_logo", logo_size, logo_size); +#endif // ENABLE_GCODE_VIEWER wxFont sys_font = get_scaled_sys_font(screen_sf); + wxCoord margin = int(screen_scale * 20); + + const wxRect banner_rect(wxPoint(0, logo_size + margin * 2), wxPoint(width, bmp.GetHeight())); // title #if ENABLE_GCODE_VIEWER @@ -172,27 +206,23 @@ public: wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION); wxFont version_font = sys_font.Larger().Larger(); - // create a copyright notice that uses the year that this file was compiled - wxString year(__DATE__); - wxString cr_symbol = wxString::FromUTF8("\xc2\xa9"); - //wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" - // "%s 2011-2018 Alessandro Ranellucci.", - // cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n"; - wxFont copyright_font = sys_font.Larger(); - - wxString copyright_string = //+= "Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + - _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + - "PrusaSlicer" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + -// _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" + -// _L("Splash screen can be disabled from the \"Preferences\""); + // create a info notice + wxString info_string = title_string + " " + + _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + + title_string + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + _L("Artwork model by Nora Al-Badri and Jan Nikolai Nelles"); + wxFont info_font = sys_font.Larger(); - word_wrap_string(copyright_string, banner_width, screen_scale); + word_wrap_string(info_string, width, screen_scale); - wxCoord margin = int(screen_scale * 20); + // use a memory DC to draw directly onto the bitmap + wxMemoryDC memDc(bmp); - // draw the (orange) labels inside of our black box (at the left of the splashscreen) + // draw logo + memDc.DrawBitmap(logo_bmp, margin, margin, true); + + // draw the (white) labels inside of our black box (at the left of the splashscreen) memDc.SetTextForeground(wxColour(255, 255, 255)); memDc.SetFont(title_font); @@ -201,8 +231,8 @@ public: memDc.SetFont(version_font); memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); - memDc.SetFont(copyright_font); - memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT); + memDc.SetFont(info_font); + memDc.DrawLabel(info_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_BOTTOM | wxALIGN_LEFT); return true; } @@ -643,11 +673,7 @@ bool GUI_App::on_init_inner() SplashScreen* scrn = nullptr; if (app_config->get("show_splash_screen") == "1") { -#if ENABLE_GCODE_VIEWER - wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG); -#else - wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); -#endif // ENABLE_GCODE_VIEWER + wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG)); // Detect position (display) to show the splash screen // Now this position is equal to the mainframe position From 13dcc3e7a040bacfd386af096399073f9694a37f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 29 Sep 2020 08:39:57 +0200 Subject: [PATCH 570/826] OSX specific: Set SplashScreen as TopWindow --- src/slic3r/GUI/GUI_App.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 34367a6efe..c2d901bfe5 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -84,8 +84,13 @@ class SplashScreen : public wxSplashScreen { public: SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition, bool is_decorated = false) - : wxSplashScreen(bitmap, splashStyle, milliseconds, nullptr, wxID_ANY, - wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR ) + : wxSplashScreen(bitmap, splashStyle, milliseconds, nullptr, wxID_ANY, wxDefaultPosition, wxDefaultSize, +#ifdef __APPLE__ + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP +#else + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR +#endif // !__APPLE__ + ) { wxASSERT(bitmap.IsOk()); m_main_bitmap = bitmap; From ec8602f8d9b261ac8ea211337bff14fe74267dbe Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 29 Sep 2020 11:04:25 +0200 Subject: [PATCH 571/826] Faster switching of parameter pages if the page is switched by cursor keys in the tree control: The page update is delayed to idle. --- src/slic3r/GUI/Tab.cpp | 29 ++++++++++++++++++----------- src/slic3r/GUI/Tab.hpp | 6 +++++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b0dfe44bcd..7352c70376 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -293,7 +293,20 @@ void Tab::create_preset_tab() m_treectrl->AddRoot("root"); m_treectrl->SetIndent(0); - m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); + // Delay processing of the following handler until the message queue is flushed. + // This helps to process all the cursor key events on Windows in the tree control, + // so that the cursor jumps to the last item. + m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, [this](wxTreeEvent&) { + if (!m_disable_tree_sel_changed_event && !m_pages.empty()) + m_page_switch_planned = true; + }); + m_treectrl->Bind(wxEVT_IDLE, [this](wxIdleEvent&) { + if (m_page_switch_planned) { + this->tree_sel_change_delayed(); + m_page_switch_planned = false; + } + }); + m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); // Initialize the page. @@ -3370,14 +3383,11 @@ void Tab::active_selected_page() toggle_options(); } -void Tab::OnTreeSelChange(wxTreeEvent& event) +void Tab::tree_sel_change_delayed() { - if (m_disable_tree_sel_changed_event) - return; - -// There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. -// The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, -// we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. + // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. + // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, + // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. #ifdef __linux__ std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); #else @@ -3390,9 +3400,6 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) //#endif #endif - if (m_pages.empty()) - return; - Page* page = nullptr; const auto sel_item = m_treectrl->GetSelection(); const auto selection = sel_item ? m_treectrl->GetItemText(sel_item) : ""; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 9df68592b5..9d65d767a1 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -130,7 +130,7 @@ protected: wxScrolledWindow* m_page_view {nullptr}; wxBoxSizer* m_page_sizer {nullptr}; - ModeSizer* m_mode_sizer; + ModeSizer* m_mode_sizer; struct PresetDependencies { Preset::Type type = Preset::TYPE_INVALID; @@ -238,6 +238,9 @@ protected: DynamicPrintConfig m_cache_config; + + bool m_page_switch_planned = false; + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; @@ -351,6 +354,7 @@ protected: void compatible_widget_reload(PresetDependencies &deps); void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false); + void tree_sel_change_delayed(); void on_presets_changed(); void build_preset_description_line(ConfigOptionsGroup* optgroup); void update_preset_description_line(); From b15023dfa946682975a5bff8fada6fb187eab33f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 29 Sep 2020 13:36:56 +0200 Subject: [PATCH 572/826] Cancellation of parameter page build process. --- src/slic3r/GUI/OptionsGroup.cpp | 72 ++++++++++++++----------- src/slic3r/GUI/OptionsGroup.hpp | 5 +- src/slic3r/GUI/Tab.cpp | 93 +++++++++++++++++++++++---------- src/slic3r/GUI/Tab.hpp | 10 ++-- 4 files changed, 116 insertions(+), 64 deletions(-) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 310dc95443..3d55237dff 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -362,42 +362,52 @@ void OptionsGroup::activate_line(Line& line) } // create all controls for the option group from the m_lines -bool OptionsGroup::activate() +bool OptionsGroup::activate(std::function throw_if_canceled) { if (sizer)//(!sizer->IsEmpty()) return false; - if (staticbox) { - stb = new wxStaticBox(m_parent, wxID_ANY, _(title)); - if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); - stb->SetFont(wxOSX ? wxGetApp().normal_font() : wxGetApp().bold_font()); + try { + if (staticbox) { + stb = new wxStaticBox(m_parent, wxID_ANY, _(title)); + if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); + stb->SetFont(wxOSX ? wxGetApp().normal_font() : wxGetApp().bold_font()); + } + else + stb = nullptr; + sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); + + auto num_columns = 1U; + size_t grow_col = 1; + + if (label_width == 0) + grow_col = 0; + else + num_columns++; + + if (extra_column) { + num_columns++; + grow_col++; + } + + m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1, 0); + static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH); + static_cast(m_grid_sizer)->AddGrowableCol(grow_col); + + sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX || !staticbox ? 0 : 5); + + // activate lines + for (Line& line: m_lines) { + throw_if_canceled(); + activate_line(line); + } + } catch (UIBuildCanceled&) { + auto p = sizer; + this->clear(); + p->Clear(true); + delete p; + throw; } - else - stb = nullptr; - sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); - - auto num_columns = 1U; - size_t grow_col = 1; - - if (label_width == 0) - grow_col = 0; - else - num_columns++; - - if (extra_column) { - num_columns++; - grow_col++; - } - - m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1, 0); - static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH); - static_cast(m_grid_sizer)->AddGrowableCol(grow_col); - - sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX || !staticbox ? 0 : 5); - - // activate lines - for (Line& line: m_lines) - activate_line(line); return true; } diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 4d70347aa4..8dcba2213c 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -24,6 +24,9 @@ namespace Slic3r { namespace GUI { +// Thrown if the building of a parameter page is canceled. +class UIBuildCanceled : public std::exception {}; + /// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window). using widget_t = std::function;//!std::function; @@ -124,7 +127,7 @@ public: void activate_line(Line& line); // create all controls for the option group from the m_lines - bool activate(); + bool activate(std::function throw_if_canceled = [](){}); // delete all controls from the option group void clear(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7352c70376..e688034712 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -297,15 +297,28 @@ void Tab::create_preset_tab() // This helps to process all the cursor key events on Windows in the tree control, // so that the cursor jumps to the last item. m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, [this](wxTreeEvent&) { - if (!m_disable_tree_sel_changed_event && !m_pages.empty()) - m_page_switch_planned = true; + if (!m_disable_tree_sel_changed_event && !m_pages.empty()) { + if (m_page_switch_running) + m_page_switch_planned = true; + else { + m_page_switch_running = true; + do { + m_page_switch_planned = false; + } while (this->tree_sel_change_delayed()); + m_page_switch_running = false; + } + } + m_treectrl->Update(); }); +#if 0 m_treectrl->Bind(wxEVT_IDLE, [this](wxIdleEvent&) { if (m_page_switch_planned) { - this->tree_sel_change_delayed(); - m_page_switch_planned = false; + do { + m_page_switch_planned = false; + } while (this->tree_sel_change_delayed()); } }); +#endif m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); @@ -424,7 +437,7 @@ void Tab::OnActivate() #endif // __WXOSX__ // create controls on active page - active_selected_page(); + activate_selected_page(); // m_active_page->Show(); m_hsizer->Layout(); Refresh(); @@ -2820,9 +2833,9 @@ void TabPrinter::update_pages() rebuild_page_tree(); } -void TabPrinter::active_selected_page() +void TabPrinter::activate_selected_page(std::function throw_if_canceled) { - Tab::active_selected_page(); + Tab::activate_selected_page(throw_if_canceled); // "extruders_count" doesn't update from the update_config(), // so update it implicitly @@ -3372,19 +3385,20 @@ void Tab::update_description_lines() update_preset_description_line(); } -void Tab::active_selected_page() +void Tab::activate_selected_page(std::function throw_if_canceled) { if (!m_active_page) return; - m_active_page->activate(m_mode); + m_active_page->activate(m_mode, throw_if_canceled); update_changed_ui(); update_description_lines(); toggle_options(); } -void Tab::tree_sel_change_delayed() +bool Tab::tree_sel_change_delayed() { +#if 1 // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. @@ -3398,6 +3412,7 @@ void Tab::tree_sel_change_delayed() //#ifdef __WXOSX__ // Use Freeze/Thaw to avoid flickering during clear/activate new page wxWindowUpdateLocker noUpdates(this); //#endif +#endif #endif Page* page = nullptr; @@ -3411,29 +3426,50 @@ void Tab::tree_sel_change_delayed() m_is_modified_values = page->m_is_modified_values; break; } - if (page == nullptr || m_active_page == page) return; + if (page == nullptr || m_active_page == page) + return false; // clear pages from the controls m_active_page = page; - clear_pages(); + + auto throw_if_canceled = std::function([this](){ +#ifdef WIN32 + wxCheckForInterrupt(m_treectrl); + if (m_page_switch_planned) + throw UIBuildCanceled(); +#endif // WIN32 + }); - //for (auto& el : m_pages) - // el.get()->Hide(); + try { + clear_pages(); + throw_if_canceled(); - if (wxGetApp().mainframe->is_active_and_shown_tab(this)) { - active_selected_page(); -// m_active_page->Show(); + //for (auto& el : m_pages) + // el.get()->Hide(); + + if (wxGetApp().mainframe->is_active_and_shown_tab(this)) { + activate_selected_page(throw_if_canceled); + // m_active_page->Show(); + } + + #ifdef __linux__ + no_updates.reset(nullptr); + #endif + + update_undo_buttons(); + throw_if_canceled(); + + // m_active_page->Show(); + m_hsizer->Layout(); + throw_if_canceled(); + Refresh(); + } catch (const UIBuildCanceled&) { + if (m_active_page) + m_active_page->clear(); + return true; } - #ifdef __linux__ - no_updates.reset(nullptr); - #endif - - update_undo_buttons(); - -// m_active_page->Show(); - m_hsizer->Layout(); - Refresh(); + return false; } void Tab::OnKeyDown(wxKeyEvent& event) @@ -3892,16 +3928,17 @@ void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibil m_show = ret_val; } -void Page::activate(ConfigOptionMode mode) +void Page::activate(ConfigOptionMode mode, std::function throw_if_canceled) { //if (m_parent) //m_parent->SetSizer(m_vsizer); for (auto group : m_optgroups) { - if (!group->activate()) + if (!group->activate(throw_if_canceled)) continue; m_vsizer->Add(group->sizer, 0, wxEXPAND | wxALL, 10); group->update_visibility(mode); group->reload_config(); + throw_if_canceled(); } } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 9d65d767a1..a3711f453e 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -73,7 +73,7 @@ public: void set_config(DynamicPrintConfig* config_in) { m_config = config_in; } void reload_config(); void update_visibility(ConfigOptionMode mode, bool update_contolls_visibility); - void activate(ConfigOptionMode mode); + void activate(ConfigOptionMode mode, std::function throw_if_canceled); void clear(); void msw_rescale(); void sys_color_changed(); @@ -239,6 +239,7 @@ protected: DynamicPrintConfig m_cache_config; + bool m_page_switch_running = false; bool m_page_switch_planned = false; public: @@ -294,7 +295,7 @@ public: virtual void clear_pages(); virtual void update_description_lines(); - virtual void active_selected_page(); + virtual void activate_selected_page(std::function throw_if_canceled); void OnTreeSelChange(wxTreeEvent& event); void OnKeyDown(wxKeyEvent& event); @@ -354,7 +355,8 @@ protected: void compatible_widget_reload(PresetDependencies &deps); void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false); - void tree_sel_change_delayed(); + // return true if cancelled + bool tree_sel_change_delayed(); void on_presets_changed(); void build_preset_description_line(ConfigOptionsGroup* optgroup); void update_preset_description_line(); @@ -447,7 +449,7 @@ public: void build() override; void build_fff(); void build_sla(); - void active_selected_page() override; + void activate_selected_page(std::function throw_if_canceled) override; void clear_pages() override; void toggle_options() override; void update() override; From 2583522e43345d4b53d2d3a5a6ba3417b16bf04b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 29 Sep 2020 14:47:03 +0200 Subject: [PATCH 573/826] Refresh the page tree immediately after key press. --- src/slic3r/GUI/Tab.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e688034712..e6363b3d06 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -304,21 +304,12 @@ void Tab::create_preset_tab() m_page_switch_running = true; do { m_page_switch_planned = false; + m_treectrl->Update(); } while (this->tree_sel_change_delayed()); m_page_switch_running = false; } } - m_treectrl->Update(); }); -#if 0 - m_treectrl->Bind(wxEVT_IDLE, [this](wxIdleEvent&) { - if (m_page_switch_planned) { - do { - m_page_switch_planned = false; - } while (this->tree_sel_change_delayed()); - } - }); -#endif m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); @@ -437,7 +428,7 @@ void Tab::OnActivate() #endif // __WXOSX__ // create controls on active page - activate_selected_page(); + activate_selected_page([](){}); // m_active_page->Show(); m_hsizer->Layout(); Refresh(); From 82b86d2c65c7132af4a5c9649cfc91e46519aa82 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 29 Sep 2020 18:07:18 +0200 Subject: [PATCH 574/826] New 3rd party printer profiles, prepared by @rtyr Anycubic (5 printers from 3 contributors) https://github.com/prusa3d/PrusaSlicer/pull/4057 https://github.com/prusa3d/PrusaSlicer/pull/4220 https://github.com/prusa3d/PrusaSlicer/pull/4619 TriLAB https://github.com/prusa3d/PrusaSlicer-settings/pull/100 Creality based on https://github.com/prusa3d/PrusaSlicer/pull/4485 https://github.com/prusa3d/PrusaSlicer/pull/4748 --- resources/profiles/Anycubic.idx | 2 + resources/profiles/Anycubic.ini | 1098 + resources/profiles/Anycubic/AK.png | Bin 0 -> 211675 bytes resources/profiles/Anycubic/AKLP_Bed.stl | 145756 +++++++++++++++ .../profiles/Anycubic/AKLP_thumbnail.png | Bin 0 -> 44474 bytes resources/profiles/Anycubic/AK_Bed.stl | Bin 0 -> 706984 bytes resources/profiles/Anycubic/AK_thumbnail.png | Bin 0 -> 43771 bytes .../profiles/Anycubic/I3MEGAS_thumbnail.png | Bin 0 -> 59028 bytes .../profiles/Anycubic/I3MEGA_thumbnail.png | Bin 0 -> 54636 bytes .../profiles/Anycubic/MEGA0_thumbnail.png | Bin 0 -> 38993 bytes resources/profiles/Anycubic/mega0.svg | 32 + resources/profiles/Anycubic/mega0_bed.stl | Bin 0 -> 50484 bytes resources/profiles/Creality.idx | 1 + resources/profiles/Creality.ini | 27 +- .../Creality/ENDER3BLTOUCH_thumbnail.png | Bin 0 -> 59603 bytes resources/profiles/Creality/ender2.svg | 560 + resources/profiles/Creality/ender2_bed.stl | Bin 0 -> 684 bytes resources/profiles/Creality/ender3.svg | 8 - resources/profiles/TriLAB.idx | 2 + resources/profiles/TriLAB.ini | 343 + resources/profiles/TriLAB/DQL_thumbnail.png | Bin 0 -> 42593 bytes resources/profiles/TriLAB/DQM_thumbnail.png | Bin 0 -> 36481 bytes resources/profiles/TriLAB/DQXL_thumbnail.png | Bin 0 -> 43035 bytes 23 files changed, 147817 insertions(+), 12 deletions(-) create mode 100644 resources/profiles/Anycubic.idx create mode 100644 resources/profiles/Anycubic.ini create mode 100644 resources/profiles/Anycubic/AK.png create mode 100644 resources/profiles/Anycubic/AKLP_Bed.stl create mode 100644 resources/profiles/Anycubic/AKLP_thumbnail.png create mode 100644 resources/profiles/Anycubic/AK_Bed.stl create mode 100644 resources/profiles/Anycubic/AK_thumbnail.png create mode 100644 resources/profiles/Anycubic/I3MEGAS_thumbnail.png create mode 100644 resources/profiles/Anycubic/I3MEGA_thumbnail.png create mode 100644 resources/profiles/Anycubic/MEGA0_thumbnail.png create mode 100644 resources/profiles/Anycubic/mega0.svg create mode 100644 resources/profiles/Anycubic/mega0_bed.stl create mode 100644 resources/profiles/Creality/ENDER3BLTOUCH_thumbnail.png create mode 100644 resources/profiles/Creality/ender2.svg create mode 100644 resources/profiles/Creality/ender2_bed.stl create mode 100644 resources/profiles/TriLAB.idx create mode 100644 resources/profiles/TriLAB.ini create mode 100644 resources/profiles/TriLAB/DQL_thumbnail.png create mode 100644 resources/profiles/TriLAB/DQM_thumbnail.png create mode 100644 resources/profiles/TriLAB/DQXL_thumbnail.png diff --git a/resources/profiles/Anycubic.idx b/resources/profiles/Anycubic.idx new file mode 100644 index 0000000000..01a6c8f7e9 --- /dev/null +++ b/resources/profiles/Anycubic.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.3.0-alpha0 +0.0.1 Initial Version diff --git a/resources/profiles/Anycubic.ini b/resources/profiles/Anycubic.ini new file mode 100644 index 0000000000..a51e6b6d8e --- /dev/null +++ b/resources/profiles/Anycubic.ini @@ -0,0 +1,1098 @@ +# Print profiles for the Anycubic printers. + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Anycubic +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the PrusaSlicer configuration to be downgraded. +config_version = 0.0.1 +# Where to get the updates from? +config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Anycubic/ +# changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% + +# The printer models will be shown by the Configuration Wizard in this order, +# also the first model installed & the first nozzle installed will be activated after install. +# Printer model name will be shown by the installation wizard. + +[printer_model:AKLP] +name = Anycubic Kossel Linear Plus +variants = 0.4 +technology = FFF +family = KOSSEL +bed_model = AKLP_Bed.stl +bed_texture = AK.png +default_materials = Generic PLA @AKOSSEL; Generic PETG @AKOSSEL; Generic ABS @AKOSSEL + +[printer_model:AK] +name = Anycubic Kossel Pulley (Linear) +variants = 0.4 +technology = FFF +family = KOSSEL +bed_model = AK_Bed.stl +bed_texture = AK.png +default_materials = Generic PLA @AKOSSEL; Generic PETG @AKOSSEL; Generic ABS @AKOSSEL + +[printer_model:MEGA0] +name = Anycubic Mega Zero +variants = 0.4 +technology = FFF +family = MEGA +bed_model = mega0_bed.stl +bed_texture = mega0.svg +default_materials = Generic PLA @MEGA0; Generic PETG @MEGA0; Anycubic PLA @MEGA0; Prusament PLA @MEGA0; Prusament PETG @MEGA0 + +[printer_model:I3MEGA] +name = Anycubic i3 Mega +variants = 0.4 +technology = FFF +family = MEGA + +[printer_model:I3MEGAS] +name = Anycubic i3 Mega S +variants = 0.4 +technology = FFF +family = MEGA + +# All presets starting with asterisk, for example *common*, are intermediate and they will +# not make it into the user interface. + +## Anycubic KOSSEL +## Author: https://github.com/tc0fh +## Initial PR: https://github.com/prusa3d/PrusaSlicer/pull/4220 + +# Common print preset +[print:*common_akossel*] +avoid_crossing_perimeters = 0 +bottom_solid_min_thickness = 0.5 +bridge_angle = 0 +bridge_flow_ratio = 0.8 +bridge_speed = 30 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = +complete_objects = 0 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeters_first = 0 +external_perimeter_extrusion_width = 0.45 +extra_perimeters = 0 +extruder_clearance_height = 25 +extruder_clearance_radius = 45 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = grid +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 20 +gap_fill_speed = 40 +gcode_comments = 0 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +interface_shells = 0 +max_print_speed = 200 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +overhangs = 0 +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = {input_filename_base}_{print_preset}_{filament_type[0]}_{printer_model}_{print_time}.gcode +perimeters = 2 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 45 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +single_extruder_multi_material_priming = 0 +skirts = 2 +skirt_distance = 5 +skirt_height = 1 +small_perimeter_speed = 25 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.38 +support_material_interface_extruder = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_enforce_layers = 0 +support_material_contact_distance = 0.15 +support_material_interface_contact_loops = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 40 +top_solid_min_thickness = 0.6 +travel_speed = 180 +wipe_tower = 1 +wipe_tower_bridging = 10 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 170 +wipe_tower_y = 140 +xy_size_compensation = 0 +bridge_acceleration = 1000 +default_acceleration = 1500 +first_layer_acceleration = 1000 +infill_acceleration = 1500 +perimeter_acceleration = 800 + +[print:*0.08mm_akossel*] +inherits = *common_akossel* +bottom_solid_layers = 10 +bridge_acceleration = 300 +bridge_flow_ratio = 0.7 +bridge_speed = 20 +external_perimeter_speed = 20 +first_layer_acceleration = 500 +gap_fill_speed = 20 +infill_acceleration = 800 +infill_speed = 40 +layer_height = 0.08 +max_print_speed = 80 +perimeter_acceleration = 300 +perimeter_speed = 30 +perimeters = 3 +small_perimeter_speed = 20 +solid_infill_speed = 40 +support_material_extrusion_width = 0.3 +support_material_spacing = 1.5 +support_material_speed = 40 +top_solid_infill_speed = 30 +top_solid_layers = 12 + +[print:*0.16mm_akossel*] +inherits = *common_akossel* +bottom_solid_layers = 5 +layer_height = 0.16 +top_solid_layers = 6 + +[print:*0.20mm_akossel*] +inherits = *common_akossel* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +layer_height = 0.20 +top_solid_layers = 5 + +[print:*0.24mm_akossel*] +inherits = *common_akossel* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +layer_height = 0.24 +perimeter_speed = 50 +external_perimeter_speed = 35 +top_solid_layers = 4 +infill_speed = 100 +solid_infill_speed = 100 +top_solid_infill_speed = 40 + +[print:*0.30mm_akossel*] +inherits = *common_akossel* +bottom_solid_layers = 3 +bridge_flow_ratio = 0.95 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 35 +extrusion_width = 0.5 +infill_extrusion_width = 0.5 +infill_speed = 70 +layer_height = 0.30 +perimeter_extrusion_width = 0.5 +perimeter_speed = 50 +small_perimeter_speed = 30 +solid_infill_extrusion_width = 0.5 +solid_infill_speed = 70 +support_material_speed = 45 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 50 +top_solid_layers = 3 + +[print:0.08mm ULTRADETAIL @AKOSSEL] +inherits = *0.08mm_akossel* +fill_density = 15% +fill_pattern = gyroid +compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and nozzle_diameter[0]==0.4 + +[print:0.16mm QUALITY @AKOSSEL] +inherits = *0.16mm_akossel* +external_perimeter_speed = 25 +fill_density = 15% +fill_pattern = gyroid +infill_speed = 80 +compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and nozzle_diameter[0]==0.4 + +[print:0.16mm SPEED @AKOSSEL] +inherits = *0.16mm_akossel* +external_perimeter_speed = 35 +infill_speed = 120 +perimeter_speed = 60 +solid_infill_speed = 120 +top_solid_infill_speed = 50 +compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and nozzle_diameter[0]==0.4 + +[print:0.20mm QUALITY @AKOSSEL] +inherits = *0.20mm_akossel* +external_perimeter_speed = 25 +fill_density = 15% +fill_pattern = gyroid +infill_speed = 80 +compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and nozzle_diameter[0]==0.4 + +[print:0.20mm SPEED @AKOSSEL] +inherits = *0.20mm_akossel* +external_perimeter_speed = 35 +infill_speed = 120 +perimeter_speed = 60 +solid_infill_speed = 120 +top_solid_infill_speed = 50 +compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and nozzle_diameter[0]==0.4 + +[print:0.24mm DRAFT @AKOSSEL] +inherits = *0.24mm_akossel* +compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and nozzle_diameter[0]==0.4 + +[print:0.30mm FAST @AKOSSEL] +inherits = *0.30mm_akossel* +compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ and nozzle_diameter[0]==0.4 + +# Common filament preset +[filament:*common_akossel*] +cooling = 0 +compatible_printers = +extrusion_multiplier = 1 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 15 +slowdown_below_layer_time = 20 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_Anycubic.*/ and printer_notes=~/.*PRINTER_MODEL_AK(|LP).*/ + +[filament:*PLA_akossel*] +inherits = *common_akossel* +bed_temperature = 60 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_max_volumetric_speed = 10 +filament_type = PLA +filament_density = 1.24 +filament_cost = 20 +first_layer_bed_temperature = 60 +first_layer_temperature = 200 +fan_always_on = 1 +cooling = 1 +max_fan_speed = 100 +min_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +temperature = 200 + +[filament:*PET_akossel*] +inherits = *common_akossel* +bed_temperature = 70 +cooling = 1 +disable_fan_first_layers = 3 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_max_volumetric_speed = 8 +filament_type = PETG +filament_density = 1.27 +filament_cost = 30 +first_layer_bed_temperature =70 +first_layer_temperature = 240 +fan_always_on = 1 +max_fan_speed = 50 +min_fan_speed = 20 +bridge_fan_speed = 100 +temperature = 240 + +[filament:*ABS_akossel*] +inherits = *common_akossel* +bed_temperature = 100 +cooling = 0 +disable_fan_first_layers = 3 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_max_volumetric_speed = 10 +filament_type = ABS +filament_density = 1.04 +filament_cost = 20 +first_layer_bed_temperature = 100 +first_layer_temperature = 245 +fan_always_on = 0 +max_fan_speed = 0 +min_fan_speed = 0 +bridge_fan_speed = 30 +top_fan_speed = 0 +temperature = 245 + +[filament:Generic PLA @AKOSSEL] +inherits = *PLA_akossel* +filament_vendor = Generic + +[filament:Generic PETG @AKOSSEL] +inherits = *PET_akossel* +filament_vendor = Generic + +[filament:Generic ABS @AKOSSEL] +inherits = *ABS_akossel* +filament_vendor = Generic + +# Common printer preset +[printer:*common_akossel*] +printer_technology = FFF +bed_shape = +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n +between_objects_gcode = +deretract_speed = 40 +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = marlin +silent_mode = 0 +remaining_times = 0 +machine_max_acceleration_e = 3000 +machine_max_acceleration_extruding = 1000 +machine_max_acceleration_retracting = 1000 +machine_max_acceleration_x = 1500 +machine_max_acceleration_y = 1500 +machine_max_acceleration_z = 1500 +machine_max_feedrate_e = 60 +machine_max_feedrate_x = 200 +machine_max_feedrate_y = 200 +machine_max_feedrate_z = 200 +machine_max_jerk_e = 5 +machine_max_jerk_x = 5 +machine_max_jerk_y = 5 +machine_max_jerk_z = 5 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.3 +min_layer_height = 0.08 +max_print_height = 300 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 2 +retract_before_wipe = 70% +retract_layer_change = 1 +retract_length = 5 +retract_length_toolchange = 1 +retract_lift = 0 +retract_lift_above = 0 +retract_lift_below = 0 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 60 +serial_port = +serial_speed = 250000 +single_extruder_multi_material = 0 +start_gcode = +end_gcode = M104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+10, max_print_height)} F600{endif} ; Move print head up\nG1 X0 Y100 F3000 ; present print\nM84 ; disable motors +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 +default_print_profile = 0.20mm QUALITY @AKOSSEL +default_filament_profile = Generic PLA @AKOSSEL + +[printer:Anycubic Kossel Linear Plus] +inherits = *common_akossel* +printer_model = AKLP +printer_variant = 0.4 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Anycubic\nPRINTER_MODEL_AKLP\nPRINTER_HAS_BOWDEN\n +bed_shape = 114.562x10.0229,113.253x19.9695,111.081x29.7642,108.065x39.3323,104.225x48.6011,99.5929x57.5,94.2025x65.9613,88.0951x73.9206,81.3173x81.3173,73.9206x88.0951,65.9613x94.2025,57.5x99.5929,48.6011x104.225,39.3323x108.065,29.7642x111.081,19.9695x113.253,10.0229x114.562,7.04172e-15x115,-10.0229x114.562,-19.9695x113.253,-29.7642x111.081,-39.3323x108.065,-48.6011x104.225,-57.5x99.5929,-65.9613x94.2025,-73.9206x88.0951,-81.3173x81.3173,-88.0951x73.9206,-94.2025x65.9613,-99.5929x57.5,-104.225x48.6011,-108.065x39.3323,-111.081x29.7642,-113.253x19.9695,-114.562x10.0229,-115x1.40834e-14,-114.562x-10.0229,-113.253x-19.9695,-111.081x-29.7642,-108.065x-39.3323,-104.225x-48.6011,-99.5929x-57.5,-94.2025x-65.9613,-88.0951x-73.9206,-81.3173x-81.3173,-73.9206x-88.0951,-65.9613x-94.2025,-57.5x-99.5929,-48.6011x-104.225,-39.3323x-108.065,-29.7642x-111.081,-19.9695x-113.253,-10.0229x-114.562,-2.11252e-14x-115,10.0229x-114.562,19.9695x-113.253,29.7642x-111.081,39.3323x-108.065,48.6011x-104.225,57.5x-99.5929,65.9613x-94.2025,73.9206x-88.0951,81.3173x-81.3173,88.0951x-73.9206,94.2025x-65.9613,99.5929x-57.5,104.225x-48.6011,108.065x-39.3323,111.081x-29.7642,113.253x-19.9695,114.562x-10.0229,115x-2.81669e-14 +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 ; home\nG1 X-54.672 Y95.203 Z0.3 F9000\nG92 E0.0\nG1 F1000\nG1 X-52.931 Y96.185 E0.300\nG1 X-50.985 Y97.231 E0.331\nG1 X-49.018 Y98.238 E0.331\nG1 X-47.032 Y99.205 E0.331\nG1 X-45.026 Y100.132 E0.331\nG1 X-43.003 Y101.019 E0.331\nG1 X-40.961 Y101.864 E0.331\nG1 X-38.904 Y102.668 E0.331\nG1 X-36.83 Y103.431 E0.331\nG1 X-34.742 Y104.152 E0.331\nG1 X-32.639 Y104.83 E0.331\nG1 X-30.523 Y105.466 E0.331\nG1 X-28.395 Y106.06 E0.331\nG1 X-26.255 Y106.61 E0.331\nG1 X-24.105 Y107.117 E0.331\nG1 X-21.945 Y107.581 E0.331\nG1 X-19.776 Y108.001 E0.331\nG1 X-17.599 Y108.377 E0.331\nG1 X-15.415 Y108.71 E0.331\nG1 X-13.224 Y108.998 E0.331\nG1 X-11.028 Y109.242 E0.331\nG1 X-8.828 Y109.442 E0.331\nG1 X-6.624 Y109.598 E0.331\nG1 X-4.418 Y109.709 E0.331\nG1 X-2.209 Y109.776 E0.332\nG1 X0 Y109.798 E0.331\nG1 X2.209 Y109.776 E0.690\nG1 X4.418 Y109.709 E0.691\nG1 X6.624 Y109.598 E0.690\nG1 X8.828 Y109.442 E0.690\nG1 X11.028 Y109.242 E0.690\nG1 X13.224 Y108.998 E0.690\nG1 X15.415 Y108.71 E0.691\nG1 X17.599 Y108.377 E0.690\nG1 X19.776 Y108.001 E0.690\nG1 X21.945 Y107.581 E0.690\nG1 X24.105 Y107.117 E0.690\nG1 X26.255 Y106.61 E0.690\nG1 X28.395 Y106.06 E0.690\nG1 X30.523 Y105.466 E0.690\nG1 X32.639 Y104.83 E0.690\nG1 X34.742 Y104.152 E0.690\nG1 X36.83 Y103.431 E0.690\nG1 X38.904 Y102.668 E0.691\nG1 X40.961 Y101.864 E0.690\nG1 X43.003 Y101.019 E0.691\nG1 X45.026 Y100.132 E0.690\nG1 X47.032 Y99.205 E0.691\nG1 X49.018 Y98.238 E0.690\nG1 X50.985 Y97.231 E0.691\nG1 X52.931 Y96.185 E0.690\nG1 X54.672 Y95.203 E0.625\nG92 E0.0\nG1 E-5 F3000 ; retract 5mm\nG1 X52.931 Y96.185 F1000 ; wipe\nG1 X50.985 Y97.231 F1000 ; wipe\nG1 X49.018 Y98.238 F1000 ; wipe\nG1 X0 Y109.798 F1000\nG1 E4.8 F1500; de-retract\nG92 E0.0 ; reset extrusion distance\nM221 S{if layer_height<0.075}100{else}95{endif} + +[printer:Anycubic Kossel Pulley (Linear)] +inherits = *common_akossel* +printer_model = AK +printer_variant = 0.4 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_Anycubic\nPRINTER_MODEL_AK\nPRINTER_HAS_BOWDEN\n +bed_shape = 89.6575x7.84402,88.6327x15.6283,86.9333x23.2937,84.5723x30.7818,81.5677x38.0356,77.9423x45,73.7237x51.6219,68.944x57.8509,63.6396x63.6396,57.8509x68.944,51.6219x73.7237,45x77.9423,38.0356x81.5677,30.7818x84.5723,23.2937x86.9333,15.6283x88.6327,7.84402x89.6575,5.51091e-15x90,-7.84402x89.6575,-15.6283x88.6327,-23.2937x86.9333,-30.7818x84.5723,-38.0356x81.5677,-45x77.9423,-51.6219x73.7237,-57.8509x68.944,-63.6396x63.6396,-68.944x57.8509,-73.7237x51.6219,-77.9423x45,-81.5677x38.0356,-84.5723x30.7818,-86.9333x23.2937,-88.6327x15.6283,-89.6575x7.84402,-90x1.10218e-14,-89.6575x-7.84402,-88.6327x-15.6283,-86.9333x-23.2937,-84.5723x-30.7818,-81.5677x-38.0356,-77.9423x-45,-73.7237x-51.6219,-68.944x-57.8509,-63.6396x-63.6396,-57.8509x-68.944,-51.6219x-73.7237,-45x-77.9423,-38.0356x-81.5677,-30.7818x-84.5723,-23.2937x-86.9333,-15.6283x-88.6327,-7.84402x-89.6575,-1.65327e-14x-90,7.84402x-89.6575,15.6283x-88.6327,23.2937x-86.9333,30.7818x-84.5723,38.0356x-81.5677,45x-77.9423,51.6219x-73.7237,57.8509x-68.944,63.6396x-63.6396,68.944x-57.8509,73.7237x-51.6219,77.9423x-45,81.5677x-38.0356,84.5723x-30.7818,86.9333x-23.2937,88.6327x-15.6283,89.6575x-7.84402,90x-2.20436e-14 +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 ; home\nG1 X-39.672 Y69.712 Z0.3 F9000\nG92 E0.0\nG1 F1000\nG1 X-38.457 Y70.397 E0.209\nG1 X-37.043 Y71.157 E0.241\nG1 X-35.614 Y71.889 E0.241\nG1 X-34.171 Y72.591 E0.241\nG1 X-32.714 Y73.265 E0.241\nG1 X-31.244 Y73.909 E0.241\nG1 X-29.761 Y74.523 E0.241\nG1 X-28.266 Y75.108 E0.241\nG1 X-26.759 Y75.662 E0.241\nG1 X-25.242 Y76.185 E0.241\nG1 X-23.714 Y76.678 E0.241\nG1 X-22.177 Y77.14 E0.241\nG1 X-20.63 Y77.571 E0.241\nG1 X-19.076 Y77.971 E0.241\nG1 X-17.514 Y78.34 E0.241\nG1 X-15.944 Y78.677 E0.241\nG1 X-14.368 Y78.982 E0.241\nG1 X-12.786 Y79.255 E0.241\nG1 X-11.199 Y79.497 E0.241\nG1 X-9.608 Y79.706 E0.241\nG1 X-8.013 Y79.884 E0.241\nG1 X-6.414 Y80.029 E0.241\nG1 X-4.813 Y80.142 E0.241\nG1 X-3.21 Y80.223 E0.241\nG1 X-1.605 Y80.271 E0.241\nG1 X0 Y80.287 E0.241\nG1 X1.605 Y80.271 E0.502\nG1 X3.21 Y80.223 E0.502\nG1 X4.813 Y80.142 E0.502\nG1 X6.414 Y80.029 E0.502\nG1 X8.013 Y79.884 E0.502\nG1 X9.608 Y79.706 E0.502\nG1 X11.199 Y79.497 E0.501\nG1 X12.786 Y79.255 E0.502\nG1 X14.368 Y78.982 E0.502\nG1 X15.944 Y78.677 E0.502\nG1 X17.514 Y78.34 E0.502\nG1 X19.076 Y77.971 E0.502\nG1 X20.63 Y77.571 E0.501\nG1 X22.177 Y77.14 E0.502\nG1 X23.714 Y76.678 E0.502\nG1 X25.242 Y76.185 E0.502\nG1 X26.759 Y75.662 E0.501\nG1 X28.266 Y75.108 E0.502\nG1 X29.761 Y74.523 E0.502\nG1 X31.244 Y73.909 E0.502\nG1 X32.714 Y73.265 E0.502\nG1 X34.171 Y72.591 E0.502\nG1 X35.614 Y71.889 E0.501\nG1 X37.043 Y71.157 E0.502\nG1 X38.457 Y70.397 E0.502\nG1 X39.672 Y69.712 E0.436\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} + +## Anycubic MEGA ZERO +## Author: https://github.com/kad +## Initial PR: https://github.com/prusa3d/PrusaSlicer/pull/4057 + +# Common print preset +[print:*common_mega0*] +avoid_crossing_perimeters = 1 +bridge_angle = 0 +bridge_flow_ratio = 0.7 +bridge_speed = 25 +brim_width = 0 +clip_multipart_objects = 1 +compatible_printers = +complete_objects = 0 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeters_first = 0 +external_perimeter_extrusion_width = 0.45 +extra_perimeters = 0 +extruder_clearance_height = 25 +extruder_clearance_radius = 45 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = grid +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +first_layer_speed = 20 +gap_fill_speed = 30 +gcode_comments = 0 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +interface_shells = 0 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 0 +min_skirt_length = 4 +notes = +overhangs = 1 +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode +perimeters = 2 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +single_extruder_multi_material_priming = 1 +skirts = 2 +skirt_distance = 2 +skirt_height = 2 +small_perimeter_speed = 25 +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.45 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.4 +support_material_interface_extruder = 0 +support_material_angle = 0 +support_material_buildplate_only = 0 +support_material_enforce_layers = 0 +support_material_contact_distance = 0.15 +support_material_interface_contact_loops = 0 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 40 +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thin_walls = 0 +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 40 +travel_speed = 100 +wipe_tower = 0 +wipe_tower_bridging = 10 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 170 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[print:*0.10mm_mega0*] +inherits = *common_mega0* +perimeter_speed = 40 +external_perimeter_speed = 25 +infill_speed = 50 +solid_infill_speed = 40 +layer_height = 0.10 +perimeters = 3 +top_infill_extrusion_width = 0.4 +bottom_solid_layers = 6 +top_solid_layers = 7 + +[print:*0.20mm_mega0*] +inherits = *common_mega0* +perimeter_speed = 40 +external_perimeter_speed = 25 +infill_speed = 50 +solid_infill_speed = 40 +layer_height = 0.20 +top_infill_extrusion_width = 0.4 +bottom_solid_layers = 4 +top_solid_layers = 5 + +[print:*0.30mm_mega0*] +inherits = *common_mega0* +perimeter_speed = 40 +external_perimeter_speed = 25 +infill_speed = 50 +solid_infill_speed = 40 +layer_height = 0.24 +top_infill_extrusion_width = 0.45 +bottom_solid_layers = 3 +top_solid_layers = 4 + +[print:0.10mm DETAIL @MEGA0] +inherits = *0.10mm_mega0* +travel_speed = 120 +infill_speed = 50 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +support_material_extrusion_width = 0.38 +compatible_printers_condition = printer_model=="MEGA0" and nozzle_diameter[0]==0.4 + +[print:0.20mm NORMAL @MEGA0] +inherits = *0.20mm_mega0* +travel_speed = 120 +infill_speed = 50 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +support_material_extrusion_width = 0.38 +compatible_printers_condition = printer_model=="MEGA0" and nozzle_diameter[0]==0.4 + +[print:0.30mm DRAFT @MEGA0] +inherits = *0.30mm_mega0* +travel_speed = 120 +infill_speed = 50 +solid_infill_speed = 40 +top_solid_infill_speed = 30 +support_material_extrusion_width = 0.38 +compatible_printers_condition = printer_model=="MEGA0" and nozzle_diameter[0]==0.4 + +# Common filament preset +[filament:*common_mega0*] +cooling = 0 +compatible_printers = +extrusion_multiplier = 1 +filament_cost = 0 +filament_density = 0 +filament_diameter = 1.75 +filament_notes = "" +filament_settings_id = "" +filament_soluble = 0 +min_print_speed = 15 +slowdown_below_layer_time = 20 +compatible_printers_condition = printer_model=="MEGA0" + +[filament:*PLA_mega0*] +inherits = *common_mega0* +bed_temperature = 40 +fan_below_layer_time = 100 +filament_colour = #FF3232 +filament_max_volumetric_speed = 15 +filament_type = PLA +filament_density = 1.24 +filament_cost = 20 +first_layer_bed_temperature = 40 +first_layer_temperature = 215 +fan_always_on = 1 +cooling = 1 +max_fan_speed = 100 +min_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +temperature = 210 + +[filament:*PET_mega0*] +inherits = *common_mega0* +bed_temperature = 70 +cooling = 1 +disable_fan_first_layers = 3 +fan_below_layer_time = 20 +filament_colour = #FF8000 +filament_max_volumetric_speed = 8 +filament_type = PETG +filament_density = 1.27 +filament_cost = 30 +first_layer_bed_temperature =70 +first_layer_temperature = 240 +fan_always_on = 1 +max_fan_speed = 50 +min_fan_speed = 20 +bridge_fan_speed = 100 +temperature = 240 + +[filament:*ABS_mega0*] +inherits = *common_mega0* +bed_temperature = 100 +cooling = 0 +disable_fan_first_layers = 3 +fan_below_layer_time = 20 +filament_colour = #3A80CA +filament_max_volumetric_speed = 11 +filament_type = ABS +filament_density = 1.04 +filament_cost = 20 +first_layer_bed_temperature = 100 +first_layer_temperature = 245 +fan_always_on = 0 +max_fan_speed = 0 +min_fan_speed = 0 +bridge_fan_speed = 30 +top_fan_speed = 0 +temperature = 245 + +[filament:Generic PLA @MEGA0] +inherits = *PLA_mega0* +filament_vendor = Generic + +[filament:Generic PETG @MEGA0] +inherits = *PET_mega0* +filament_vendor = Generic + +[filament:Generic ABS @MEGA0] +inherits = *ABS_mega0* +filament_vendor = Generic + +[filament:Anycubic PLA @MEGA0] +inherits = *PLA_mega0* +filament_vendor = Anycubic +temperature = 190 +first_layer_temperature = 195 +filament_cost = 24.99 +filament_density = 1.25 + +[filament:Prusament PLA @MEGA0] +inherits = *PLA_mega0* +filament_vendor = Prusa Polymers +temperature = 215 +bed_temperature = 40 +first_layer_temperature = 215 +filament_cost = 24.99 +filament_density = 1.24 + +[filament:Prusament PETG @MEGA0] +inherits = *PET_mega0* +filament_vendor = Prusa Polymers +temperature = 245 +bed_temperature = 70 +first_layer_temperature = 245 +filament_cost = 24.99 +filament_density = 1.27 + +# Common printer preset +[printer:*common_mega0*] +printer_technology = FFF +bed_shape = 0x0,220x0,220x220,0x220 +before_layer_gcode = ; BEFORE_LAYER_CHANGE [layer_num] @ [layer_z]mm +between_objects_gcode = +deretract_speed = 0 +extruder_colour = #FFFF00 +extruder_offset = 0x0 +gcode_flavor = marlin +silent_mode = 0 +remaining_times = 0 +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 2000 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 3000 +machine_max_acceleration_y = 3000 +machine_max_acceleration_z = 500 +machine_max_feedrate_e = 120 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 12 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 20 +machine_max_jerk_y = 20 +machine_max_jerk_z = 0.4 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +layer_gcode = ; AFTER_LAYER_CHANGE [layer_num] @ [layer_z]mm +max_layer_height = 0.3 +min_layer_height = 0.1 +max_print_height = 200 +nozzle_diameter = 0.4 +octoprint_apikey = +octoprint_host = +printer_notes = +printer_settings_id = +retract_before_travel = 1 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 6 +retract_length_toolchange = 1 +retract_lift = 0 +retract_lift_above = 0 +retract_lift_below = 0 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 30 +serial_port = +serial_speed = 115200 +single_extruder_multi_material = 0 +start_gcode = G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nM117 Homing X/Y ...\nG28 X0 Y0 ;move X/Y to min endstops\nM117 Homing Z ...\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F240 ;move the platform down 15mm\nM117 Heating ...\nM104 S[first_layer_temperature]\n ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature]\n ; wait for extruder temp\nM117 Start cleaning ...\nG92 E0 ;zero the extruded length\nG1 F200 E10 ;extrude 10mm of feed stock\nG92 E0 ;zero the extruded length again\nM117 Intro line ...\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z[first_layer_height] F5000.0 ; Move to start position\nG1 X0.1 Y200.0 Z[first_layer_height] F1500.0 E15 ; Draw the first line\nG1 X0.4 Y200.0 Z[first_layer_height] F5000.0 ; Move to side a little\nG1 X0.4 Y20 Z0.3[first_layer_height] F1500.0 E30 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 E-1 F500 ; Retract filiment by 1 mm\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X5 Y20 Z0.3 F240 ; Move over to prevent blob squish\nG92 E0 ; Reset Extruder\nM117 Printing...\n +end_gcode = M117 Cooling down...\nM104 S0 ; turn off extruder\nM140 S0 ; turn off heatbed\nM107 ; Fan off\nM84 ; disable motors\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F240 ;move Z up a bit and retract filament even more\nG28 X0 ;move X to min endstops, so the head is out of the way\nG90 ;Absolute positionning\nG1 Y200 F3000 ;Present print\nM84 ;steppers off\nM300 P300 S4000\nM117 Finished.\n +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 1 +z_offset = 0 +printer_model = +default_print_profile = +default_filament_profile = + +[printer:Anycubic Mega Zero] +inherits = *common_mega0* +printer_model = MEGA0 +printer_variant = 0.4 +max_layer_height = 0.3 +min_layer_height = 0.1 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_MEGA0 +bed_shape = 0x0,220x0,220x220,0x220 +max_print_height = 250 +machine_max_acceleration_e = 5000 +machine_max_acceleration_extruding = 500 +machine_max_acceleration_retracting = 500 +machine_max_acceleration_x = 500 +machine_max_acceleration_y = 500 +machine_max_acceleration_z = 100 +machine_max_feedrate_e = 25 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 5 +machine_max_jerk_e = 5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.4 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +nozzle_diameter = 0.4 +retract_before_travel = 2 +retract_layer_change = 0 +retract_length = 7 +retract_speed = 30 +retract_lift = 0.2 +deretract_speed = 30 +retract_before_wipe = 70% +default_print_profile = 0.20mm NORMAL @MEGA0 +default_filament_profile = Anycubic PLA @MEGA0 +start_gcode = G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nM117 Homing X/Y ...\nG28 X0 Y0 ;move X/Y to min endstops\nM117 Homing Z ...\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F240 ;move the platform down 15mm\nM117 Heating ...\nM104 S[first_layer_temperature]\n ; set extruder temp\nM109 S[first_layer_temperature]\n ; wait for extruder temp\nM117 Start cleaning ...\nG92 E0 ;zero the extruded length\nG1 F200 E10 ;extrude 10mm of feed stock\nG92 E0 ;zero the extruded length again\nM117 Intro line ...\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z[first_layer_height] F5000.0 ; Move to start position\nG1 X0.1 Y200.0 Z[first_layer_height] F1500.0 E15 ; Draw the first line\nG1 X0.4 Y200.0 Z[first_layer_height] F5000.0 ; Move to side a little\nG1 X0.4 Y20 Z0.3[first_layer_height] F1500.0 E30 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 E-1 F500 ; Retract filiment by 1 mm\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X5 Y20 Z0.3 F240 ; Move over to prevent blob squish\nG92 E0 ; Reset Extruder\nM117 Printing...\n +end_gcode = M117 Cooling down...\nM104 S0 ; turn off extruder\nM107 ; Fan off\nM84 ; disable motors\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F240 ;move Z up a bit and retract filament even more\nG28 X0 ;move X to min endstops, so the head is out of the way\nG90 ;Absolute positionning\nG1 Y200 F3000 ;Present print\nM84 ;steppers off\nM300 P300 S4000\nM117 Finished.\n + +## Anycubic i3 Mega and i3 Mega S +## Author: https://github.com/Igami +## Initial PR: https://github.com/prusa3d/PrusaSlicer/pull/4619 + +[print:*common_mega*] +bottom_solid_min_thickness = 0.5 +bridge_acceleration = 1800 +bridge_flow_ratio = 0.8 +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_ANYCUBIC.*/ and printer_notes=~/.*PRINTER_MODEL_I3_MEGA.*/ and nozzle_diameter[0]==0.4 +default_acceleration = 1800 +ensure_vertical_shell_thickness = 1 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +extruder_clearance_height = 35 +extruder_clearance_radius = 60 +extrusion_width = 0.45 +fill_density = 15% +fill_pattern = gyroid +first_layer_acceleration = 1800 +first_layer_extrusion_width = 0.42 +first_layer_height = 0.2 +gap_fill_speed = 40 +infill_acceleration = 1800 +infill_extrusion_width = 0.45 +infill_speed = 60 +only_retract_when_crossing_perimeters = 0 +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode +perimeter_acceleration = 1800 +perimeter_extrusion_width = 0.45 +perimeters = 2 +seam_position = nearest +skirts = 0 +slice_closing_radius = 0.05 +small_perimeter_speed = 30 +solid_infill_below_area = 0 +solid_infill_speed = 60 +support_material_buildplate_only = 1 +support_material_contact_distance = 0.1 +support_material_extrusion_width = 0.35 +support_material_interface_layers = 2 +support_material_interface_spacing = 0.2 +support_material_spacing = 2 +support_material_threshold = 55 +support_material_with_sheath = 0 +thin_walls = 0 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 40 +top_solid_min_thickness = 0.6 +travel_speed = 180 + +[print:*supported_mega*] +raft_layers = 2 +support_material = 1 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.15mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[print:*0.15mm_mega*] +inherits = *common_mega* +bottom_solid_layers = 5 +layer_height = 0.15 +top_solid_layers = 7 + +[print:0.15mm QUALITY @MEGA] +inherits = *0.15mm_mega* + +[print:0.15mm QUALITY SUPPORTED @MEGA] +inherits = *0.15mm_mega*;*supported_mega* + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.20mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[print:*0.20mm_mega*] +inherits = *common_mega* +bottom_solid_layers = 4 +layer_height = 0.2 +top_solid_layers = 5 + +[print:0.20mm QUALITY @MEGA] +inherits = *0.20mm_mega* + +[print:0.20mm QUALITY SUPPORTED @MEGA] +inherits = *0.20mm_mega*;*supported_mega* + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.30mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[print:*0.30mm_mega*] +inherits = *common_mega* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +top_solid_layers = 4 + +[print:0.30mm DRAFT @MEGA] +inherits = *0.30mm_mega* + +[print:0.30mm DRAFT SUPPORTED @MEGA] +inherits = *0.30mm_mega*;*supported_mega* + +# XXXXXXXXXXXXXXXXXXXXXX +# XXX--- filament ---XXX +# XXXXXXXXXXXXXXXXXXXXXX + +[filament:*common_mega*] +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_ANYCUBIC.*/ and printer_notes=~/.*PRINTER_MODEL_I3_MEGA.*/ +end_filament_gcode = "; Filament-specific end gcode" +fan_always_on = 1 +fan_below_layer_time = 100 +filament_colour = #FF8000 +filament_vendor = Generic +min_print_speed = 15 +slowdown_below_layer_time = 20 + +[filament:*ABS_mega*] + inherits = *common_mega* + bed_temperature = 110 + bridge_fan_speed = 25 + cooling = 0 + fan_always_on = 0 + fan_below_layer_time = 20 + filament_colour = #FFF2EC + filament_cost = 27.82 + filament_density = 1.04 + filament_max_volumetric_speed = 11 + filament_ramming_parameters = "120 100 5.70968 6.03226 7 8.25806 9 9.19355 9.3871 9.77419 10.129 10.3226 10.4516 10.5161| 0.05 5.69677 0.45 6.15484 0.95 8.76774 1.45 9.20323 1.95 9.95806 2.45 10.3871 2.95 10.5677 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" + filament_type = ABS + first_layer_bed_temperature = 100 + first_layer_temperature = 255 + max_fan_speed = 30 + min_fan_speed = 20 + temperature = 255 + +[filament:Generic ABS @MEGA] +inherits = *ABS_mega* + +[filament:*FLEX_mega*] +inherits = *common_mega* +bed_temperature = 50 +bridge_fan_speed = 80 +cooling = 0 +extrusion_multiplier = 1.15 +fan_always_on = 0 +filament_colour = #008000 +filament_cost = 82.00 +filament_density = 1.22 +filament_deretract_speed = 25 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0.8 +filament_type = FLEX +first_layer_bed_temperature = 50 +first_layer_temperature = 240 +max_fan_speed = 90 +min_fan_speed = 70 +temperature = 240 + +[filament:Generic FLEX @MEGA] +inherits = *FLEX_mega* + +[filament:*PETG_mega*] +inherits = *common_mega* +bed_temperature = 90 +bridge_fan_speed = 50 +fan_below_layer_time = 20 +filament_cost = 27.82 +filament_density = 1.27 +filament_max_volumetric_speed = 8 +filament_type = PETG +first_layer_bed_temperature = 85 +first_layer_temperature = 230 +max_fan_speed = 50 +min_fan_speed = 30 +temperature = 240 + +[filament:Generic PETG @MEGA] +inherits = *PETG_mega* + +[filament:*PLA_mega*] +inherits = *common_mega* +bed_temperature = 60 +disable_fan_first_layers = 1 +filament_cost = 25.40 +filament_density = 1.24 +filament_max_volumetric_speed = 10 +first_layer_bed_temperature = 60 +first_layer_temperature = 215 +min_fan_speed = 100 +temperature = 210 + +[filament:Generic PLA @MEGA] +inherits = *PLA_mega* + +[filament:*3Dmensionals PLA_mega*] +inherits = *PLA_mega* +filament_vendor = 3Dmensionals +filament_cost = 23.35 + +[filament:3Dmensionals PLA @MEGA] +inherits = *3Dmensionals PLA_mega* + +[filament:3Dmensionals PLA blue @MEGA] +inherits = *3Dmensionals PLA_mega* +filament_colour = #4155FB + +[filament:3Dmensionals PLA silver @MEGA] +inherits = *3Dmensionals PLA_mega* +filament_colour = #B9B5B4 + +[filament:3Dmensionals PLA white @MEGA] +inherits = *3Dmensionals PLA_mega* +filament_colour = #FEFEFD + +[filament:*Verbatim PLA_mega*] +inherits = *PLA_mega* +filament_vendor = Verbatim +filament_cost = 23.88 + +[filament:Verbatim PLA @MEGA] +inherits = *Verbatim PLA_mega* + +[filament:Verbatim PLA black @MEGA] +inherits = *Verbatim PLA_mega* +filament_colour = #333333 + +[printer:*common_mega*] +printer_technology = FFF +bed_shape = 0x0,210x0,210x210,0x210 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z] +default_filament_profile = Generic PLA @MEGA +default_print_profile = 0.15mm QUALITY @MEGA +deretract_speed = 50 +end_gcode = G4 ; wait\nG92 E0\nG1{if layer_z < max_print_height} Z{z_offset+min(layer_z+30, max_print_height)}{endif} E-35 F1000 ; move print head up & retract filament\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors +extruder_colour = #808080 +gcode_flavor = marlin +layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] +max_layer_height = 0.36 +max_print_height = 205 +retract_before_wipe = 60% +retract_layer_change = 1 +retract_length = 6 +retract_lift = 0.075 +retract_lift_below = 204 +silent_mode = 0 +start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nG28 ; home all\nG1 Y0 Z1 F100 ; move print head up\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG92 E0\nG1 E38 F1000; deretract filament\nG92 E0\nG1 X60 Z0 E9 ; intro line\nG1 X100 E12.5 ; intro line\nG92 E0 +use_relative_e_distances = 1 +wipe = 1 +## Based on stock firmware limits +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 1500 +machine_max_acceleration_retracting = 1500 +machine_max_acceleration_x = 3000 +machine_max_acceleration_y = 2000 +machine_max_acceleration_z = 60 +machine_max_feedrate_e = 60 +machine_max_feedrate_x = 500 +machine_max_feedrate_y = 500 +machine_max_feedrate_z = 6 +machine_max_jerk_e = 5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.4 + +[printer:Anycubic i3 Mega] +inherits = *common_mega* +printer_model = I3MEGA +printer_variant = 0.4 +printer_notes = Do not remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_I3_MEGA\nPRINTER_HAS_BOWDEN + +[printer:Anycubic i3 Mega S] +inherits = *common_mega* +printer_model = I3MEGAS +printer_variant = 0.4 +printer_notes = Do not remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_ANYCUBIC\nPRINTER_MODEL_I3_MEGA_S\nPRINTER_HAS_BOWDEN +machine_max_feedrate_z = 8 + + diff --git a/resources/profiles/Anycubic/AK.png b/resources/profiles/Anycubic/AK.png new file mode 100644 index 0000000000000000000000000000000000000000..a3837ba7ab03fb7d7961c8e228effcd2c16266f5 GIT binary patch literal 211675 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
B!qJ%!2 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℞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

?$6< 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 A@Bjb+ literal 0 HcmV?d00001 diff --git a/resources/profiles/Anycubic/AK_Bed.stl b/resources/profiles/Anycubic/AK_Bed.stl new file mode 100644 index 0000000000000000000000000000000000000000..31daa1150ebed0c1bf67001c711a3628074fa229 GIT binary patch literal 706984 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:w=_s3p(_v9erXLea;uFk7Mex`m9#PV@&j< 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

3lBXNcOsAk) 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%0D5Fq3JGBcij`e}4J9gydNjfPSRGjW1?rH0nZ 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 VCKvM?EDtCFcKoO(o`JiR_%C{o%zFR; literal 0 HcmV?d00001 diff --git a/resources/profiles/Anycubic/MEGA0_thumbnail.png b/resources/profiles/Anycubic/MEGA0_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..cbf4482640f0562360c41bcde43df4e43f22899e GIT binary patch literal 38993 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 Z?*K_z&9ey|Pl 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 + + + + + ender3_bed_texture + + + + + + + + + + + + + + + + + + + + + + + + + MEGA ZERO + \ No newline at end of file diff --git a/resources/profiles/Anycubic/mega0_bed.stl b/resources/profiles/Anycubic/mega0_bed.stl new file mode 100644 index 0000000000000000000000000000000000000000..fb8f86d094e6cc1adf87808aefa292fb217000ea GIT binary patch literal 50484 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 Z{4XJ~5p+rtJ=FjJ002ovPDHLkV1m4dHX;B3 literal 0 HcmV?d00001 diff --git a/resources/profiles/Creality/ender2.svg b/resources/profiles/Creality/ender2.svg new file mode 100644 index 0000000000..7a5e63cb26 --- /dev/null +++ b/resources/profiles/Creality/ender2.svg @@ -0,0 +1,560 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/profiles/Creality/ender2_bed.stl b/resources/profiles/Creality/ender2_bed.stl new file mode 100644 index 0000000000000000000000000000000000000000..3cf022ed0bf2222addc3c2f206b750fb796e73d5 GIT binary patch literal 684 zcmb`ET?&Ih421J6y-Lp@2!+1*)C=V@o}zEE6+Jnb?Wgrc7f-u4zu|jG4Cscyf45yldgj?wAX)3C MN&?mDN{#L$zP}QX_5c6? literal 0 HcmV?d00001 diff --git a/resources/profiles/Creality/ender3.svg b/resources/profiles/Creality/ender3.svg index 06910afdf8..9dac7a62e6 100644 --- a/resources/profiles/Creality/ender3.svg +++ b/resources/profiles/Creality/ender3.svg @@ -1,13 +1,5 @@ ender3_bed_texture - - - - - - - - diff --git a/resources/profiles/TriLAB.idx b/resources/profiles/TriLAB.idx new file mode 100644 index 0000000000..a43bf4e004 --- /dev/null +++ b/resources/profiles/TriLAB.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.3.0-alpha0 +0.0.1 Initial TriLAB bundle diff --git a/resources/profiles/TriLAB.ini b/resources/profiles/TriLAB.ini new file mode 100644 index 0000000000..2412cf1159 --- /dev/null +++ b/resources/profiles/TriLAB.ini @@ -0,0 +1,343 @@ +# DeltiQ presets for PrusaSlicer +# https://github.com/prusa3d/PrusaSlicer-settings/pull/100 +# based on https://github.com/trilab3d/Slicer-profiles/tree/deltiq/Slic3r_PE_1_41_3 + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = TRILAB +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the PrusaSlicer configuration to be downgraded. +config_version = 0.0.1 +# Where to get the updates from? +config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/TriLAB/ +# changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% + +# The printer models will be shown by the Configuration Wizard in this order, +# also the first model installed & the first nozzle installed will be activated after install. +# Printer model name will be shown by the installation wizard. + +[printer_model:DQM] +name = TRILAB DeltiQ M +variants = 0.4 +technology = FFF +bed_model = +bed_texture = +default_materials = DeltiQ PLA; DeltiQ ASA; DeltiQ PET; DeltiQ ABS; DeltiQ CPE + +[printer_model:DQL] +name = TRILAB DeltiQ L +variants = 0.4 +technology = FFF +bed_model = +bed_texture = +default_materials = DeltiQ PLA; DeltiQ ASA; DeltiQ PET; DeltiQ ABS; DeltiQ CPE + +[printer_model:DQXL] +name = TRILAB DeltiQ XL +variants = 0.4 +technology = FFF +bed_model = +bed_texture = +default_materials = DeltiQ PLA; DeltiQ ASA; DeltiQ PET; DeltiQ ABS; DeltiQ CPE + + +# All presets starting with asterisk, for example *common*, are intermediate and they will +# not make it into the user interface. + +[print:DeltiQ 0.2mm] +avoid_crossing_perimeters = 0 +bottom_solid_layers = 3 +bridge_acceleration = 1000 +bridge_angle = 0 +bridge_flow_ratio = 0.95 +bridge_speed = 20 +brim_width = 0 +clip_multipart_objects = 0 +compatible_printers_condition = printer_notes=~/.*TRILAB.*/ +complete_objects = 0 +default_acceleration = 2000 +dont_support_bridges = 1 +elefant_foot_compensation = 0 +ensure_vertical_shell_thickness = 1 +external_fill_pattern = rectilinear +external_perimeter_extrusion_width = 0.4 +external_perimeter_speed = 30 +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 20 +extruder_clearance_radius = 20 +extrusion_width = 0.4 +fill_angle = 45 +fill_density = 15% +fill_pattern = gyroid +first_layer_acceleration = 1000 +first_layer_extrusion_width = 0.4 +first_layer_height = 0.3 +first_layer_speed = 20 +gap_fill_speed = 50 +gcode_comments = 0 +infill_acceleration = 2000 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.55 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 50 +inherits = +interface_shells = 0 +layer_height = 0.2 +max_print_speed = 100 +max_volumetric_extrusion_rate_slope_negative = 0 +max_volumetric_extrusion_rate_slope_positive = 0 +max_volumetric_speed = 10 +min_skirt_length = 4 +notes = +only_retract_when_crossing_perimeters = 1 +ooze_prevention = 0 +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode +overhangs = 0 +perimeter_acceleration = 1500 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.4 +perimeter_speed = 40 +perimeters = 2 +post_process = +print_settings_id = +raft_layers = 0 +resolution = 0 +seam_position = nearest +single_extruder_multi_material_priming = 1 +skirt_distance = 2 +skirt_height = 1 +skirts = 1 +small_perimeter_speed = 20 +solid_infill_below_area = 15 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width = 0.4 +solid_infill_speed = 50 +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_auto = 1 +support_material_buildplate_only = 0 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 1 +support_material_extrusion_width = 0 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 1 +support_material_interface_layers = 3 +support_material_interface_spacing = 0 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2.5 +support_material_speed = 50 +support_material_synchronize_layers = 0 +support_material_threshold = 55 +support_material_with_sheath = 1 +support_material_xy_spacing = 100% +thin_walls = 0 +threads = 4 +top_infill_extrusion_width = 0.4 +top_solid_infill_speed = 30 +top_solid_layers = 4 +travel_speed = 150 +wipe_tower = 0 +wipe_tower_bridging = 10 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 180 +wipe_tower_y = 140 +xy_size_compensation = 0 + +[filament:*DeltiQ common*] +bed_temperature = 90 +bridge_fan_speed = 50 +compatible_printers_condition = printer_notes=~/.*TRILAB.*/ +cooling = 1 +filament_vendor = Generic +disable_fan_first_layers = 3 +end_filament_gcode = "" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 60 +filament_colour = #FF3232 +filament_cooling_final_speed = 3.4 +filament_cooling_initial_speed = 2.2 +filament_cooling_moves = 4 +filament_cost = 0 +filament_density = 1.25 +filament_diameter = 1.75 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_volumetric_speed = 10 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "" +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_settings_id = "" +filament_soluble = 0 +filament_toolchange_delay = 0 +filament_type = PET +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 50 +min_fan_speed = 30 +min_print_speed = 10 +slowdown_below_layer_time = 5 +start_filament_gcode = "" +temperature = 245 + +[filament:DeltiQ PET] +inherits = *DeltiQ common* + +[filament:DeltiQ PLA] +inherits = *DeltiQ common* +bed_temperature = 55 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +filament_type = PLA +first_layer_bed_temperature = 55 +first_layer_temperature = 215 +max_fan_speed = 100 +min_fan_speed = 85 +slowdown_below_layer_time = 4 +temperature = 210 + +[filament:DeltiQ ABS] +inherits = *DeltiQ common* +bed_temperature = 100 +bridge_fan_speed = 20 +filament_density = 1.04 +filament_type = ABS +first_layer_bed_temperature = 100 +first_layer_temperature = 255 +max_fan_speed = 20 +min_fan_speed = 5 +slowdown_below_layer_time = 15 +temperature = 255 + +[filament:DeltiQ ASA] +inherits = DeltiQ ABS +filament_density = 1.07 +filament_type = ASA +first_layer_temperature = 265 +temperature = 265 + +[filament:DeltiQ CPE] +inherits = *DeltiQ common* +bed_temperature = 85 +filament_type = CPE +first_layer_bed_temperature = 85 +first_layer_temperature = 260 +temperature = 265 + + +[printer:*DeltiQ*] +inherits = +bed_shape = 124.315x13.0661,122.268x25.989,118.882x38.6271,114.193x50.8421,108.253x62.5,101.127x73.4732,92.8931x83.6413,83.6413x92.8931,73.4732x101.127,62.5x108.253,50.8421x114.193,38.6271x118.882,25.989x122.268,13.0661x124.315,3.54096e-014x125,-13.0661x124.315,-25.989x122.268,-38.6271x118.882,-50.8421x114.193,-62.5x108.253,-73.4732x101.127,-83.6413x92.8931,-92.8931x83.6413,-101.127x73.4732,-108.253x62.5,-114.193x50.8421,-118.882x38.6271,-122.268x25.989,-124.315x13.0661,-125x7.08192e-014,-124.315x-13.0661,-122.268x-25.989,-118.882x-38.6271,-114.193x-50.8421,-108.253x-62.5,-101.127x-73.4732,-92.8931x-83.6413,-83.6413x-92.8931,-73.4732x-101.127,-62.5x-108.253,-50.8421x-114.193,-38.6271x-118.882,-25.989x-122.268,-13.0661x-124.315,-2.29621e-014x-125,13.0661x-124.315,25.989x-122.268,38.6271x-118.882,50.8421x-114.193,62.5x-108.253,73.4732x-101.127,83.6413x-92.8931,92.8931x-83.6413,101.127x-73.4732,108.253x-62.5,114.193x-50.8421,118.882x-38.6271,122.268x-25.989,124.315x-13.0661,125x-1.41638e-013 +before_layer_gcode = ;BEFORE_LAYER_CHANGE\nG92 E0\n;[layer_z] +between_objects_gcode = +cooling_tube_length = 5 +cooling_tube_retraction = 91.5 +default_filament_profile = "" +default_print_profile = +deretract_speed = 25 +end_gcode = ;END\nM104 S0 ; Turn extruder heater off\nM140 S0 ; Turn bed heater off\nG28 ; Home all axes\nM84 S5 ; Stop all axes and hold inidle for 5 seconds\nG90 ; Absolute positioning +extra_loading_move = -2 +extruder_colour = "" +extruder_offset = 0x0 +gcode_flavor = repetier +host_type = octoprint +layer_gcode = ;AFTER_LAYER_CHANGE\nM117 layer [layer_num] at [layer_z]mm\n;[layer_z]\n +machine_max_acceleration_e = 10000,5000 +machine_max_acceleration_extruding = 1500,1250 +machine_max_acceleration_retracting = 1500,1250 +machine_max_acceleration_x = 9000,1000 +machine_max_acceleration_y = 9000,1000 +machine_max_acceleration_z = 500,200 +machine_max_feedrate_e = 120,120 +machine_max_feedrate_x = 500,200 +machine_max_feedrate_y = 500,200 +machine_max_feedrate_z = 12,12 +machine_max_jerk_e = 2.5,2.5 +machine_max_jerk_x = 10,10 +machine_max_jerk_y = 10,10 +machine_max_jerk_z = 0.2,0.4 +machine_min_extruding_rate = 0,0 +machine_min_travel_rate = 0,0 +max_layer_height = 0.25 +max_print_height = 320 +min_layer_height = 0.15 +nozzle_diameter = 0.4 +parking_pos_retraction = 92 +print_host = +printer_model = +printer_notes = TRILAB +printer_settings_id = +printer_variant = +printer_vendor = +printhost_apikey = +printhost_cafile = +remaining_times = 0 +retract_before_travel = 2 +retract_before_wipe = 100% +retract_layer_change = 1 +retract_length = 4.1 +retract_length_toolchange = 10 +retract_lift = 0.3 +retract_lift_above = 0 +retract_lift_below = 0 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 33 +serial_port = +serial_speed = 250000 +silent_mode = 1 +single_extruder_multi_material = 0 +start_gcode = ;START\nM220 S100 ; Set feedmultiply back to 100percent\nG90 ; Absolute positioning\nM83 ; Relative extruder\nM107 ; Layer fan OFF\nM190 S[first_layer_bed_temperature] ; Set bed temperature and wait\nM104 S[first_layer_temperature] ; Set extruder temperature\nG28 ; Home all axes\nG33 ; auto leveling rutine\nG1 X-62 Y-108 Z0.3 F6000 ; Go to purge position start\nG92 E0 ; Zero extruder\nM109 S[first_layer_temperature] ; Set and wait - hotend temperature\nG3 X62 Y-108 I62 J108 E10 F200 ; Go ARC to purge end\nG92 E0 ; Zero extruder +toolchange_gcode = +use_firmware_retraction = 0 +use_relative_e_distances = 1 +use_volumetric_e = 0 +variable_layer_height = 0 +wipe = 0 +z_offset = 0 + +[printer:DeltiQ L] +inherits = *DeltiQ* +printer_model = DQL +printer_variant = 0.4 +bed_shape = 124.315x13.0661,122.268x25.989,118.882x38.6271,114.193x50.8421,108.253x62.5,101.127x73.4732,92.8931x83.6413,83.6413x92.8931,73.4732x101.127,62.5x108.253,50.8421x114.193,38.6271x118.882,25.989x122.268,13.0661x124.315,3.54096e-014x125,-13.0661x124.315,-25.989x122.268,-38.6271x118.882,-50.8421x114.193,-62.5x108.253,-73.4732x101.127,-83.6413x92.8931,-92.8931x83.6413,-101.127x73.4732,-108.253x62.5,-114.193x50.8421,-118.882x38.6271,-122.268x25.989,-124.315x13.0661,-125x7.08192e-014,-124.315x-13.0661,-122.268x-25.989,-118.882x-38.6271,-114.193x-50.8421,-108.253x-62.5,-101.127x-73.4732,-92.8931x-83.6413,-83.6413x-92.8931,-73.4732x-101.127,-62.5x-108.253,-50.8421x-114.193,-38.6271x-118.882,-25.989x-122.268,-13.0661x-124.315,-2.29621e-014x-125,13.0661x-124.315,25.989x-122.268,38.6271x-118.882,50.8421x-114.193,62.5x-108.253,73.4732x-101.127,83.6413x-92.8931,92.8931x-83.6413,101.127x-73.4732,108.253x-62.5,114.193x-50.8421,118.882x-38.6271,122.268x-25.989,124.315x-13.0661,125x-1.41638e-013 +max_print_height = 320 + +[printer:DeltiQ M] +inherits = *DeltiQ* +printer_variant = 0.4 +bed_shape = 89.507x9.40756,88.0333x18.7121,85.5951x27.8115,82.2191x36.6063,77.9423x45,72.8115x52.9007,66.883x60.2218,60.2218x66.883,52.9007x72.8115,45x77.9423,36.6063x82.2191,27.8115x85.5951,18.7121x88.0333,9.40756x89.507,2.54949e-014x90,-9.40756x89.507,-18.7121x88.0333,-27.8115x85.5951,-36.6063x82.2191,-45x77.9423,-52.9007x72.8115,-60.2218x66.883,-66.883x60.2218,-72.8115x52.9007,-77.9423x45,-82.2191x36.6063,-85.5951x27.8115,-88.0333x18.7121,-89.507x9.40756,-90x5.09899e-014,-89.507x-9.40756,-88.0333x-18.7121,-85.5951x-27.8115,-82.2191x-36.6063,-77.9423x-45,-72.8115x-52.9007,-66.883x-60.2218,-60.2218x-66.883,-52.9007x-72.8115,-45x-77.9423,-36.6063x-82.2191,-27.8115x-85.5951,-18.7121x-88.0333,-9.40756x-89.507,-1.65327e-014x-90,9.40756x-89.507,18.7121x-88.0333,27.8115x-85.5951,36.6063x-82.2191,45x-77.9423,52.9007x-72.8115,60.2218x-66.883,66.883x-60.2218,72.8115x-52.9007,77.9423x-45,82.2191x-36.6063,85.5951x-27.8115,88.0333x-18.7121,89.507x-9.40756,90x-1.0198e-013 +max_print_height = 230 +printer_model = DQM +retract_length = 3.7 +retract_length_toolchange = 10 +retract_speed = 30 +start_gcode = ;START\nM220 S100 ; Set feedmultiply back to 100percent\nG90 ; Absolute positioning\nM83 ; Relative extruder\nM107 ; Layer fan OFF\nM190 S[first_layer_bed_temperature] ; Set bed temperature and wait\nM104 S[first_layer_temperature] ; Set extruder temperature\nG28 ; Home all axes\nG33 ; auto leveling rutine\nG1 X-45 Y-77 Z0.3 F6000 ; Go to purge position start\nG92 E0 ; Zero extruder\nM109 S[first_layer_temperature] ; Set Extruder Temperature and Wait\nG3 X45 Y-77 I45 J77 E10 F200 ; Go ARC to purge end\nG92 E0 ; Zero extruder + +[printer:DeltiQ XL] +inherits = *DeltiQ* +printer_model = DQXL +printer_variant = 0.4 +bed_shape = 124.315x13.0661,122.268x25.989,118.882x38.6271,114.193x50.8421,108.253x62.5,101.127x73.4732,92.8931x83.6413,83.6413x92.8931,73.4732x101.127,62.5x108.253,50.8421x114.193,38.6271x118.882,25.989x122.268,13.0661x124.315,3.54096e-014x125,-13.0661x124.315,-25.989x122.268,-38.6271x118.882,-50.8421x114.193,-62.5x108.253,-73.4732x101.127,-83.6413x92.8931,-92.8931x83.6413,-101.127x73.4732,-108.253x62.5,-114.193x50.8421,-118.882x38.6271,-122.268x25.989,-124.315x13.0661,-125x7.08192e-014,-124.315x-13.0661,-122.268x-25.989,-118.882x-38.6271,-114.193x-50.8421,-108.253x-62.5,-101.127x-73.4732,-92.8931x-83.6413,-83.6413x-92.8931,-73.4732x-101.127,-62.5x-108.253,-50.8421x-114.193,-38.6271x-118.882,-25.989x-122.268,-13.0661x-124.315,-2.29621e-014x-125,13.0661x-124.315,25.989x-122.268,38.6271x-118.882,50.8421x-114.193,62.5x-108.253,73.4732x-101.127,83.6413x-92.8931,92.8931x-83.6413,101.127x-73.4732,108.253x-62.5,114.193x-50.8421,118.882x-38.6271,122.268x-25.989,124.315x-13.0661,125x-1.41638e-013 +max_print_height = 500 +retract_length = 4.5 +retract_speed = 35 + +[presets] +print = DeltiQ 0.2mm +printer = DeltiQ L +filament = DeltiQ PLA diff --git a/resources/profiles/TriLAB/DQL_thumbnail.png b/resources/profiles/TriLAB/DQL_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..97769279da3312de5350cf8e332207c92f8ad558 GIT binary patch literal 42593 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 literal 0 HcmV?d00001 diff --git a/resources/profiles/TriLAB/DQM_thumbnail.png b/resources/profiles/TriLAB/DQM_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..0046090c8aed55bfbd2766e2459916be15a30de2 GIT binary patch literal 36481 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 Qd;kCd07*qoM6N<$f<{5qUH||9 literal 0 HcmV?d00001 diff --git a/resources/profiles/TriLAB/DQXL_thumbnail.png b/resources/profiles/TriLAB/DQXL_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..46fa300c41dce88b5ef46a9734e7609f5ae46eb2 GIT binary patch literal 43035 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 Date: Wed, 30 Sep 2020 08:34:29 +0200 Subject: [PATCH 575/826] #4806 - Fixed detection of move type for G1 lines containing Z>0 and E>0 --- src/libslic3r/GCode/GCodeProcessor.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 0a5617559d..ac67fdabe5 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1377,12 +1377,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) auto move_type = [this](const AxisCoords& delta_pos) { EMoveType type = EMoveType::Noop; - if (delta_pos[E] < 0.0f) { + if (delta_pos[E] < 0.0f) type = (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f || delta_pos[Z] != 0.0f) ? EMoveType::Travel : EMoveType::Retract; - } else if (delta_pos[E] > 0.0f) { - if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f && delta_pos[Z] == 0.0f) - type = EMoveType::Unretract; + if (delta_pos[X] == 0.0f && delta_pos[Y] == 0.0f) + type = (delta_pos[Z] == 0.0f) ? EMoveType::Unretract : EMoveType::Travel; else if (delta_pos[X] != 0.0f || delta_pos[Y] != 0.0f) type = EMoveType::Extrude; } From e55a9cceecda11eefa9c898324470e0230c73d7f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 30 Sep 2020 10:58:14 +0200 Subject: [PATCH 576/826] #4808 - Fixed incorrect detection of toolpaths outside of printbed --- src/slic3r/GUI/GCodeViewer.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 6bb62dbf05..4a5880218c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -872,9 +872,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } - // add origin - m_paths_bounding_box.merge(Vec3d::Zero()); - // max bounding box (account for tool marker) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); From 63ab60467a759f442c019c7fc30633f97bbb9f41 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 30 Sep 2020 11:56:02 +0200 Subject: [PATCH 577/826] Added progress dialog while generating toolpaths to render --- src/slic3r/GUI/GCodeViewer.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 4a5880218c..7fd44f608e 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -861,6 +861,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (m_moves_count == 0) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxProgressDialog progress_dialog(_L("Generating toolpaths"), "...", + 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (wxGetApp().is_gcode_viewer()) @@ -1227,6 +1232,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) prev_length = length; }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxBusyCursor busy; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. // the data are deleted as soon as they are sent to the gpu. std::vector> vertices(m_buffers.size()); @@ -1238,6 +1247,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + progress_dialog.Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), + _L("Generating vertex buffer") + " (" + wxNumberFormatter::ToString((long)i, wxNumberFormatter::Style_None) + "/" + + wxNumberFormatter::ToString((long)m_moves_count, wxNumberFormatter::Style_None) + ")"); + progress_dialog.Fit(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; @@ -1304,6 +1320,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + progress_dialog.Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), + _L("Generating index buffers") + " (" + wxNumberFormatter::ToString((long)i, wxNumberFormatter::Style_None) + "/" + + wxNumberFormatter::ToString((long)m_moves_count, wxNumberFormatter::Style_None) + ")"); + progress_dialog.Fit(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; From dcc5d795af708f98a36dac9f1c61ad9e2c14fb6b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 30 Sep 2020 11:59:41 +0200 Subject: [PATCH 578/826] Follow-up of 63ab60467a759f442c019c7fc30633f97bbb9f41 -> Code cleanup --- src/slic3r/GUI/GCodeViewer.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 7fd44f608e..0e3378e9a1 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -861,10 +861,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (m_moves_count == 0) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxProgressDialog progress_dialog(_L("Generating toolpaths"), "...", 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; @@ -1232,9 +1230,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) prev_length = length; }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxBusyCursor busy; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. // the data are deleted as soon as they are sent to the gpu. @@ -1247,12 +1243,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ progress_dialog.Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), _L("Generating vertex buffer") + " (" + wxNumberFormatter::ToString((long)i, wxNumberFormatter::Style_None) + "/" + wxNumberFormatter::ToString((long)m_moves_count, wxNumberFormatter::Style_None) + ")"); progress_dialog.Fit(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; @@ -1320,12 +1314,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ progress_dialog.Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), _L("Generating index buffers") + " (" + wxNumberFormatter::ToString((long)i, wxNumberFormatter::Style_None) + "/" + wxNumberFormatter::ToString((long)m_moves_count, wxNumberFormatter::Style_None) + ")"); progress_dialog.Fit(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; From 6685e78605445865dd18662124cb69870f47f7e9 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 30 Sep 2020 13:24:37 +0200 Subject: [PATCH 579/826] Fixed red background wrongly showing up when slicing with SLA printer --- src/slic3r/GUI/GLCanvas3D.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b916bfc546..a3b4be9a7a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5420,7 +5420,9 @@ void GLCanvas3D::_render_background() const #if ENABLE_GCODE_VIEWER bool use_error_color = false; if (wxGetApp().is_editor()) { - use_error_color = m_dynamic_background_enabled; + use_error_color = m_dynamic_background_enabled && + (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA || !m_volumes.empty()); + if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); else { From dad8a477416b73bddd049839958be3592a47d68e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 30 Sep 2020 13:35:19 +0200 Subject: [PATCH 580/826] Fixed splash screen info label --- src/slic3r/GUI/GUI_App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c2d901bfe5..4279435850 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -214,7 +214,7 @@ public: // create a info notice wxString info_string = title_string + " " + _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + - title_string + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + + title_string + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + _L("Artwork model by Nora Al-Badri and Jan Nikolai Nelles"); wxFont info_font = sys_font.Larger(); From e8325a8e2dca67aeb4c0989c379de125286da8fb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 30 Sep 2020 14:02:03 +0200 Subject: [PATCH 581/826] Fixes of DPI scaling on Windows. --- src/slic3r/GUI/AboutDialog.cpp | 4 ++-- src/slic3r/GUI/ConfigSnapshotDialog.cpp | 4 ++-- src/slic3r/GUI/GUI_Utils.cpp | 6 +++--- src/slic3r/GUI/GUI_Utils.hpp | 20 ++++++++------------ src/slic3r/GUI/SysInfoDialog.cpp | 4 ++-- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 8d9ea97b98..2b1bea13cf 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -59,7 +59,7 @@ CopyrightsDialog::CopyrightsDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * em_unit(), 20 * em_unit()), wxHW_SCROLLBAR_AUTO); - wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); + wxFont font = get_default_font(this); const int fs = font.GetPointSize(); const int fs2 = static_cast(1.2f*fs); int size[] = { fs, fs, fs, fs, fs2, fs2, fs2 }; @@ -269,7 +269,7 @@ AboutDialog::AboutDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/); { m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); - wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); + wxFont font = get_default_font(this); const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 4855bea81e..48b5a2b007 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -114,7 +114,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db // text html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); { - wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + wxFont font = get_default_font(this); #ifdef __WXMSW__ const int fs = font.GetPointSize(); const int fs1 = static_cast(0.8f*fs); @@ -140,7 +140,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db void ConfigSnapshotDialog::on_dpi_changed(const wxRect &suggested_rect) { - wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); + wxFont font = get_default_font(this); const int fs = font.GetPointSize(); const int fs1 = static_cast(0.8f*fs); const int fs2 = static_cast(1.1f*fs); diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 7db3d57ffc..97e66812a6 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -75,7 +75,7 @@ template typename F::FN winapi_get_function(const wchar_t *dll, const c #endif // If called with nullptr, a DPI for the primary monitor is returned. -int get_dpi_for_window(wxWindow *window) +int get_dpi_for_window(const wxWindow *window) { #ifdef _WIN32 enum MONITOR_DPI_TYPE_ { @@ -126,7 +126,7 @@ int get_dpi_for_window(wxWindow *window) #endif } -wxFont get_default_font_for_dpi(int dpi) +wxFont get_default_font_for_dpi(const wxWindow *window, int dpi) { #ifdef _WIN32 // First try to load the font with the Windows 10 specific way. @@ -137,7 +137,7 @@ wxFont get_default_font_for_dpi(int dpi) memset(&nm, 0, sizeof(NONCLIENTMETRICS)); nm.cbSize = sizeof(NONCLIENTMETRICS); if (SystemParametersInfoForDpi_fn(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &nm, 0, dpi)) - return wxFont(wxNativeFontInfo(nm.lfMessageFont)); + return wxFont(wxNativeFontInfo(nm.lfMessageFont, window)); } // Then try to guesstimate the font DPI scaling on Windows 8. // Let's hope that the font returned by the SystemParametersInfo(), which is used by wxWidgets internally, makes sense. diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 1c88de5707..a68a0a88ab 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -53,8 +53,9 @@ void on_window_geometry(wxTopLevelWindow *tlw, std::function callback); enum { DPI_DEFAULT = 96 }; -int get_dpi_for_window(wxWindow *window); -wxFont get_default_font_for_dpi(int dpi); +int get_dpi_for_window(const wxWindow *window); +wxFont get_default_font_for_dpi(const wxWindow* window, int dpi); +inline wxFont get_default_font(const wxWindow* window) { return get_default_font_for_dpi(window, get_dpi_for_window(window)); } #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) struct DpiChangedEvent : public wxEvent { @@ -84,7 +85,7 @@ public: int dpi = get_dpi_for_window(this); m_scale_factor = (float)dpi / (float)DPI_DEFAULT; m_prev_scale_factor = m_scale_factor; - m_normal_font = get_default_font_for_dpi(dpi); + m_normal_font = get_default_font_for_dpi(this, dpi); /* Because of default window font is a primary display font, * We should set correct font for window before getting em_unit value. @@ -106,15 +107,10 @@ public: #if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { - m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; - - m_new_font_point_size = get_default_font_for_dpi(evt.GetNewDPI().x).GetPointSize(); - - if (!m_can_rescale) - return; - - if (m_force_rescale || is_new_scale_factor()) - rescale(wxRect()); + m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; + m_new_font_point_size = get_default_font_for_dpi(this, evt.GetNewDPI().x).GetPointSize(); + if (m_can_rescale && (m_force_rescale || is_new_scale_factor())) + rescale(wxRect()); }); #else this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 34905fa6d4..14d4fb0b3c 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -129,7 +129,7 @@ SysInfoDialog::SysInfoDialog() } // main_info_text - wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// wxGetApp().normal_font(); + wxFont font = get_default_font(this); const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); @@ -195,7 +195,7 @@ void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect) m_logo_bmp.msw_rescale(); m_logo->SetBitmap(m_logo_bmp.bmp()); - wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); + wxFont font = get_default_font(this); const int fs = font.GetPointSize() - 1; int font_size[] = { static_cast(fs*1.5), static_cast(fs*1.4), static_cast(fs*1.3), fs, fs, fs, fs }; From 1ca872f81ebf797358abc5bc1eb758b07bbc0f0e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 30 Sep 2020 15:11:17 +0200 Subject: [PATCH 582/826] Fixed size of selected single volumes --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 53 +++++++++++------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 7243e8c73a..0d108ba7d4 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -65,8 +65,8 @@ static wxBitmapComboBox* create_word_local_combo(wxWindow *parent) temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); - temp->Append(_(L("World coordinates"))); - temp->Append(_(L("Local coordinates"))); + temp->Append(_L("World coordinates")); + temp->Append(_L("Local coordinates")); temp->SetSelection(0); temp->SetValue(temp->GetString(0)); @@ -85,7 +85,7 @@ static wxBitmapComboBox* create_word_local_combo(wxWindow *parent) temp->SetItemBitmap(0, empty_bmp); #endif - temp->SetToolTip(_(L("Select coordinate space, in which the transformation will be performed."))); + temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed.")); return temp; } @@ -108,8 +108,8 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo) // Set rescaled size combo->SetSize(size); - combo->Append(_(L("World coordinates"))); - combo->Append(_(L("Local coordinates"))); + combo->Append(_L("World coordinates")); + combo->Append(_L("Local coordinates")); wxBitmap empty_bmp(1, combo->GetFont().GetPixelSize().y + 2); empty_bmp.SetWidth(0); @@ -158,9 +158,9 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : sizer->Add(m_fix_throught_netfab_bitmap); - auto name_label = new wxStaticText(m_parent, wxID_ANY, _(L("Name"))+":"); + auto name_label = new wxStaticText(m_parent, wxID_ANY, _L("Name")+":"); set_font_and_background_style(name_label, wxGetApp().normal_font()); - name_label->SetToolTip(_(L("Object name"))); + name_label->SetToolTip(_L("Object name")); sizer->Add(name_label); m_main_grid_sizer->Add(sizer); @@ -268,7 +268,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // We will add a button to toggle mirroring to each axis: auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); - btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); + btn->SetToolTip(wxString::Format(_L("Toggle %c axis mirroring"), (int)label)); btn->SetBitmapDisabled_(m_mirror_bitmap_hidden); m_mirror_buttons[axis_idx].first = btn; @@ -342,7 +342,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add drop to bed button m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed")); - m_drop_to_bed_button->SetToolTip(_(L("Drop to bed"))); + m_drop_to_bed_button->SetToolTip(_L("Drop to bed")); m_drop_to_bed_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { // ??? GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); @@ -354,7 +354,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); change_position_value(1, diff.y()); change_position_value(2, diff.z()); @@ -369,7 +369,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add reset rotation button m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); - m_reset_rotation_button->SetToolTip(_(L("Reset rotation"))); + m_reset_rotation_button->SetToolTip(_L("Reset rotation")); m_reset_rotation_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); @@ -404,9 +404,9 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add reset scale button m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); - m_reset_scale_button->SetToolTip(_(L("Reset scale"))); + m_reset_scale_button->SetToolTip(_L("Reset scale")); m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); @@ -509,8 +509,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_world_coordinates = true; ObjectList* obj_list = wxGetApp().obj_list(); - if (selection.is_single_full_instance()) - { + if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); m_new_position = volume->get_instance_offset(); @@ -528,7 +527,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; - } else { + } + else { m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_scale = volume->get_instance_scaling_factor() * 100.; @@ -536,8 +536,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_enabled = true; } - else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) - { + else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) { const BoundingBoxf3& box = selection.get_bounding_box(); m_new_position = box.center(); m_new_rotation = Vec3d::Zero(); @@ -547,18 +546,16 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_scale_label_string = L("Scale"); m_new_enabled = true; } - else if (selection.is_single_modifier() || selection.is_single_volume()) - { + else if (selection.is_single_modifier() || selection.is_single_volume()) { // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; - m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size()); + m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size())); m_new_enabled = true; } - else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) - { + else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { reset_settings_value(); m_new_move_label_string = L("Translate"); m_new_rotate_label_string = L("Rotate"); @@ -624,7 +621,7 @@ void ObjectManipulation::update_if_dirty() if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); - m_lock_bnt->SetToolTip(_(L("You cannot use non-uniform scaling mode for multiple objects/parts selection"))); + m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); m_lock_bnt->disable(); } else { @@ -924,11 +921,11 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Cannot apply scaling in the world coordinate system. wxMessageDialog dlg(GUI::wxGetApp().mainframe, - _(L("The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" + _L("The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" "Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" - "once the rotation is embedded into the object coordinates.")) + "\n" + - _(L("This operation is irreversible.\n" - "Do you want to proceed?")), + "once the rotation is embedded into the object coordinates.") + "\n" + + _L("This operation is irreversible.\n" + "Do you want to proceed?"), SLIC3R_APP_NAME, wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) { From 8bf0f75e83846ba2588c75a3f4330175b9a87117 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 30 Sep 2020 17:33:08 +0200 Subject: [PATCH 583/826] Fixed compilation with wxWidgets 3.0 --- src/slic3r/GUI/GCodeViewer.cpp | 2 ++ src/slic3r/GUI/GUI_Utils.hpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 0e3378e9a1..8ab17717c7 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a68a0a88ab..ad6bca7208 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -116,7 +116,7 @@ public: this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; - m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize(); + m_new_font_point_size = get_default_font_for_dpi(this, evt.dpi).GetPointSize(); if (!m_can_rescale) return; From c696e6ec192cbf44e4073585999ff866d69f0e12 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 25 Sep 2020 09:07:52 +0200 Subject: [PATCH 584/826] Experiment with spherical cursor (painting gizmos) --- src/libslic3r/TriangleSelector.cpp | 8 +++++--- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 9f04374fdc..abbc3bc566 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -61,7 +61,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // add neighboring facets to list to be proccessed later for (int n=0; n<3; ++n) { int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n]; - if (neighbor_idx >=0 && faces_camera(neighbor_idx)) + if (neighbor_idx >=0 && true/*faces_camera(neighbor_idx)*/) facets_to_check.push_back(neighbor_idx); } } @@ -206,8 +206,10 @@ void TriangleSelector::split_triangle(int facet_idx) // Calculate distance of a point from a line. bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const { - Vec3f diff = m_cursor.center - point; - return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; + Vec3f diff = m_cursor.center - point; + // return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; + + return diff.squaredNorm() < m_cursor.radius_sqr; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 939d3c48a0..4b3b9e52ba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -119,6 +119,8 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const void GLGizmoPainterBase::render_cursor_circle() const { + return; + const Camera& camera = wxGetApp().plater()->get_camera(); float zoom = (float)camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; From 1ca8120398e00d37c439c32dad4994d836235750 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 30 Sep 2020 17:01:51 +0200 Subject: [PATCH 585/826] Sphere selection added as an option for painting gizmos --- src/libslic3r/TriangleSelector.cpp | 13 ++-- src/libslic3r/TriangleSelector.hpp | 7 +++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 64 +++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 7 +++ src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 20 +++++- 6 files changed, 103 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index abbc3bc566..1462b1a764 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -35,14 +35,15 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, const Vec3f& source, const Vec3f& dir, - float radius, EnforcerBlockerType new_state) + float radius, CursorType cursor_type, + EnforcerBlockerType new_state) { assert(facet_start < m_orig_size_indices); assert(is_approx(dir.norm(), 1.f)); // Save current cursor center, squared radius and camera direction, // so we don't have to pass it around. - m_cursor = {hit, source, dir, radius*radius}; + m_cursor = {hit, source, dir, radius*radius, cursor_type}; // In case user changed cursor size since last time, update triangle edge limit. if (m_old_cursor_radius != radius) { @@ -61,7 +62,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, // add neighboring facets to list to be proccessed later for (int n=0; n<3; ++n) { int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n]; - if (neighbor_idx >=0 && true/*faces_camera(neighbor_idx)*/) + if (neighbor_idx >=0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx))) facets_to_check.push_back(neighbor_idx); } } @@ -207,9 +208,11 @@ void TriangleSelector::split_triangle(int facet_idx) bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const { Vec3f diff = m_cursor.center - point; - // return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; - return diff.squaredNorm() < m_cursor.radius_sqr; + if (m_cursor.type == CIRCLE) + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; + else // SPHERE + return diff.squaredNorm() < m_cursor.radius_sqr; } diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index be1b20ed40..899539c8e7 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -17,6 +17,11 @@ enum class EnforcerBlockerType : int8_t; // to recursively subdivide the triangles and make the selection finer. class TriangleSelector { public: + enum CursorType { + CIRCLE, + SPHERE + }; + void set_edge_limit(float edge_limit); // Create new object on a TriangleMesh. The referenced mesh must @@ -29,6 +34,7 @@ public: const Vec3f& source, // camera position (mesh coords) const Vec3f& dir, // direction of the ray (mesh coords) float radius, // radius of the cursor + CursorType type, // current type of cursor EnforcerBlockerType new_state); // enforcer or blocker? // Get facets currently in the given state. @@ -127,6 +133,7 @@ protected: Vec3f source; Vec3f dir; float radius_sqr; + CursorType type; }; Cursor m_cursor; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 6b3456b60e..58346a4147 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -66,7 +66,7 @@ void GLGizmoFdmSupports::on_render() const render_triangles(selection); m_c->object_clipper()->render_cut(); - render_cursor_circle(); + render_cursor(); glsafe(::glDisable(GL_BLEND)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 4b3b9e52ba..e7d3e17c2d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -21,6 +21,14 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic : GLGizmoBase(parent, icon_filename, sprite_id) { m_clipping_plane.reset(new ClippingPlane()); + // Make sphere and save it into a vertex buffer. + const TriangleMesh sphere_mesh = make_sphere(1., (2*M_PI)/24.); + for (size_t i=0; i(), + sphere_mesh.stl.facet_start[i].normal.cast()); + for (const stl_triangle_vertex_indices& indices : sphere_mesh.its.indices) + m_vbo_sphere.push_triangle(indices(0), indices(1), indices(2)); + m_vbo_sphere.finalize_geometry(true); } @@ -117,10 +125,18 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const } +void GLGizmoPainterBase::render_cursor() const +{ + if (m_cursor_type == TriangleSelector::SPHERE) + render_cursor_sphere(); + else + render_cursor_circle(); +} + + + void GLGizmoPainterBase::render_cursor_circle() const { - return; - const Camera& camera = wxGetApp().plater()->get_camera(); float zoom = (float)camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; @@ -164,6 +180,46 @@ void GLGizmoPainterBase::render_cursor_circle() const } +void GLGizmoPainterBase::render_cursor_sphere() const +{ + int mesh_id = m_last_mesh_idx_and_hit.first; + if (mesh_id == -1) + return; + + const Vec3f hit_pos = m_last_mesh_idx_and_hit.second; + const Selection& selection = m_parent.get_selection(); + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelVolume* mv = mo->volumes[mesh_id]; + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_matrix = mi->get_transformation().get_matrix() * mv->get_matrix(); + const Transform3d instance_scaling_matrix_inverse = Geometry::Transformation(instance_matrix).get_matrix(true, true, false, true).inverse(); + const bool is_left_handed = Geometry::Transformation(instance_matrix).is_left_handed(); + + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(instance_matrix.data())); + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glTranslatef(hit_pos(0), hit_pos(1), hit_pos(2))); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); + + if (is_left_handed) + glFrontFace(GL_CW); + + float render_color[4] = { 0.f, 0.f, 0.f, 0.15f }; + if (m_button_down == Button::Left) + render_color[2] = 1.f; + else // right + render_color[0] = 1.f; + glsafe(::glColor4fv(render_color)); + + m_vbo_sphere.render(); + + if (is_left_handed) + glFrontFace(GL_CCW); + + glsafe(::glPopMatrix()); +} + bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const { @@ -354,8 +410,9 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous assert(mesh_id < int(m_triangle_selectors.size())); m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, - dir, limit, new_state); + dir, limit, m_cursor_type, new_state); m_last_mouse_position = mouse_position; + m_last_mesh_idx_and_hit = {mesh_id, closest_hit}; } return true; @@ -392,6 +449,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_button_down = Button::None; m_last_mouse_position = Vec2d::Zero(); + m_last_mesh_idx_and_hit = {-1, Vec3f::Zero()}; return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index b3e2b65f1f..47bd266086 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -67,10 +67,13 @@ public: protected: void render_triangles(const Selection& selection) const; + void render_cursor() const; void render_cursor_circle() const; + void render_cursor_sphere() const; virtual void update_model_object() const = 0; virtual void update_from_model_object() = 0; void activate_internal_undo_redo_stack(bool activate); + void set_cursor_type(TriangleSelector::CursorType); float m_cursor_radius = 2.f; static constexpr float CursorRadiusMin = 0.4f; // cannot be zero @@ -80,16 +83,20 @@ protected: // For each model-part volume, store status and division of the triangles. std::vector> m_triangle_selectors; + TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE; + private: bool is_mesh_point_clipped(const Vec3d& point) const; float m_clipping_plane_distance = 0.f; std::unique_ptr m_clipping_plane; + GLIndexedVertexArray m_vbo_sphere; bool m_internal_stack_active = false; bool m_schedule_update = false; Vec2d m_last_mouse_position = Vec2d::Zero(); + std::pair m_last_mesh_idx_and_hit = {-1, Vec3f::Zero()}; enum class Button { None, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index d0edfba131..1b1b1f10f5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -25,6 +25,7 @@ bool GLGizmoSeam::on_init() m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size"] = _L("Cursor size") + ": "; + m_desc["cursor_type"] = _L("Cursor size") + ": "; m_desc["enforce_caption"] = _L("Left mouse button") + ": "; m_desc["enforce"] = _L("Enforce seam"); m_desc["block_caption"] = _L("Right mouse button") + " "; @@ -55,7 +56,7 @@ void GLGizmoSeam::on_render() const render_triangles(selection); m_c->object_clipper()->render_cut(); - render_cursor_circle(); + render_cursor(); glsafe(::glDisable(GL_BLEND)); } @@ -134,6 +135,23 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::EndTooltip(); } + + m_imgui->text(m_desc.at("cursor_type")); + ImGui::SameLine(/*clipping_slider_left*/); + //ImGui::PushItemWidth(window_width - clipping_slider_left); + int selection = int(m_cursor_type); + m_imgui->combo(" ", {"Circle", "Sphere"}, selection); + m_cursor_type = TriangleSelector::CursorType(selection); + /*if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + }*/ + + + ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) m_imgui->text(m_desc.at("clipping_of_view")); From 6744a40cd5dfadc65d43131d13635f24744600ed Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 30 Sep 2020 22:10:03 +0200 Subject: [PATCH 586/826] Slight refactoring --- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 29 +++++++------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index e7d3e17c2d..ef4b318779 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -314,9 +314,15 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) + trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); + } + // Now "click" into all the prepared points and spill paint around them. for (const Vec2d& mp : mouse_positions) { - std::vector>> hit_positions_and_facet_ids; bool clipped_mesh_was_hit = false; Vec3f normal = Vec3f::Zero(); @@ -327,9 +333,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous size_t closest_facet = 0; int closest_hit_mesh_id = -1; - // Transformations of individual meshes - std::vector trafo_matrices; - int mesh_id = -1; // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh for (const ModelVolume* mv : mo->volumes) { @@ -338,9 +341,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous ++mesh_id; - trafo_matrices.push_back(instance_trafo * mv->get_matrix()); - hit_positions_and_facet_ids.push_back(std::vector>()); - if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( mp, trafo_matrices[mesh_id], @@ -366,18 +366,19 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } } } + mesh_id = closest_hit_mesh_id; bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); // The mouse button click detection is enabled when there is a valid hit // or when the user clicks the clipping plane. Missing the object entirely // shall not capture the mouse. - if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { + if (mesh_id != -1 || clipped_mesh_was_hit) { if (m_button_down == Button::None) m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); } - if (closest_hit_mesh_id == -1) { + if (mesh_id == -1) { // In case we have no valid hit, we can return. The event will // be stopped in following two cases: // 1. clicking the clipping plane @@ -386,16 +387,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous || dragging_while_painting; } - // Find respective mesh id. - mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++mesh_id; - if (mesh_id == closest_hit_mesh_id) - break; - } - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; // Calculate how far can a point be from the line (in mesh coords). From fac7e735acb219e5ef8fc891c7a202127e4f1e24 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 30 Sep 2020 22:28:49 +0200 Subject: [PATCH 587/826] Moved the raycasting query in painting gizmos to a separate function This way it can be called when rendering the spherical cursor and when processing the mouse clicks/drags --- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 93 +++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 6 ++ 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index ef4b318779..1c5edbb036 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -323,50 +323,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Now "click" into all the prepared points and spill paint around them. for (const Vec2d& mp : mouse_positions) { - bool clipped_mesh_was_hit = false; - Vec3f normal = Vec3f::Zero(); + bool clipped_mesh_was_hit = false; Vec3f hit = Vec3f::Zero(); size_t facet = 0; - Vec3f closest_hit = Vec3f::Zero(); - double closest_hit_squared_distance = std::numeric_limits::max(); - size_t closest_facet = 0; - int closest_hit_mesh_id = -1; - int mesh_id = -1; - // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++mesh_id; - - if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( - mp, - trafo_matrices[mesh_id], - camera, - hit, - normal, - m_clipping_plane.get(), - &facet)) - { - // In case this hit is clipped, skip it. - if (is_mesh_point_clipped(hit.cast())) { - clipped_mesh_was_hit = true; - continue; - } - - // Is this hit the closest to the camera so far? - double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); - if (hit_squared_distance < closest_hit_squared_distance) { - closest_hit_squared_distance = hit_squared_distance; - closest_facet = facet; - closest_hit_mesh_id = mesh_id; - closest_hit = hit; - } - } - } - mesh_id = closest_hit_mesh_id; + get_mesh_hit(mp, camera, trafo_matrices, mesh_id, hit, facet, clipped_mesh_was_hit); bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); @@ -397,13 +360,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); - Vec3f dir = (closest_hit - camera_pos).normalized(); + Vec3f dir = (hit - camera_pos).normalized(); assert(mesh_id < int(m_triangle_selectors.size())); - m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, + m_triangle_selectors[mesh_id]->select_patch(hit, facet, camera_pos, dir, limit, m_cursor_type, new_state); m_last_mouse_position = mouse_position; - m_last_mesh_idx_and_hit = {mesh_id, closest_hit}; + m_last_mesh_idx_and_hit = {mesh_id, hit}; } return true; @@ -448,6 +411,52 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } +void GLGizmoPainterBase::get_mesh_hit(const Vec2d& mouse_position, + const Camera& camera, + const std::vector& trafo_matrices, + int& mesh_id, Vec3f& hit, size_t& facet, + bool& clipped_mesh_was_hit) const +{ + Vec3f normal = Vec3f::Zero(); + size_t current_facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + size_t closest_facet = 0; + int closest_hit_mesh_id = -1; + + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( + mouse_position, + trafo_matrices[mesh_id], + camera, + hit, + normal, + m_clipping_plane.get(), + ¤t_facet)) + { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast())) { + clipped_mesh_was_hit = true; + continue; + } + + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_facet = current_facet; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + } + } + } + + mesh_id = closest_hit_mesh_id; + facet = closest_facet; + hit = closest_hit; +} bool GLGizmoPainterBase::on_is_activable() const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 47bd266086..177bad53dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -21,6 +21,7 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class ClippingPlane; +class Camera; enum class PainterGizmoType { FDM_SUPPORTS, @@ -88,6 +89,11 @@ protected: private: bool is_mesh_point_clipped(const Vec3d& point) const; + void get_mesh_hit(const Vec2d& mouse_position, + const Camera& camera, + const std::vector& trafo_matrices, + int& mesh_id, Vec3f& hit, size_t& facet, + bool& clipped_mesh_was_hit) const; float m_clipping_plane_distance = 0.f; std::unique_ptr m_clipping_plane; From 3ec5d9e2cfacb65fd49b707e29b28afe16abf2b2 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 1 Oct 2020 00:03:20 +0200 Subject: [PATCH 588/826] Cache raycast results so they don't have to be repeated on the same mouse pos Fixed incorrect handling of clipping plane with multiple volumes - only the first volume was correctly clipped by the painter. --- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 118 +++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 25 ++-- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 1c5edbb036..c421e63de9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -182,33 +182,41 @@ void GLGizmoPainterBase::render_cursor_circle() const void GLGizmoPainterBase::render_cursor_sphere() const { - int mesh_id = m_last_mesh_idx_and_hit.first; - if (mesh_id == -1) + Vec2d mouse_position(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1)); + + const ModelObject* mo = m_c->selection_info()->model_object(); + const Selection& selection = m_parent.get_selection(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + const Camera& camera = wxGetApp().plater()->get_camera(); + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) + trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + } + update_raycast_cache(mouse_position, camera, trafo_matrices); + if (m_rr.mesh_id == -1) return; - const Vec3f hit_pos = m_last_mesh_idx_and_hit.second; - const Selection& selection = m_parent.get_selection(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelVolume* mv = mo->volumes[mesh_id]; - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - const Transform3d instance_matrix = mi->get_transformation().get_matrix() * mv->get_matrix(); - const Transform3d instance_scaling_matrix_inverse = Geometry::Transformation(instance_matrix).get_matrix(true, true, false, true).inverse(); - const bool is_left_handed = Geometry::Transformation(instance_matrix).is_left_handed(); + const Transform3d& complete_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(complete_matrix).get_matrix(true, true, false, true).inverse(); + const bool is_left_handed = Geometry::Transformation(complete_matrix).is_left_handed(); glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(instance_matrix.data())); + glsafe(::glMultMatrixd(complete_matrix.data())); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glTranslatef(hit_pos(0), hit_pos(1), hit_pos(2))); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); + glsafe(::glTranslatef(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2))); + glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data())); glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); if (is_left_handed) glFrontFace(GL_CW); - float render_color[4] = { 0.f, 0.f, 0.f, 0.15f }; + float render_color[4] = { 0.f, 0.f, 0.f, 0.25f }; if (m_button_down == Button::Left) render_color[2] = 1.f; - else // right + else if (m_button_down == Button::Right) render_color[0] = 1.f; glsafe(::glColor4fv(render_color)); @@ -221,16 +229,12 @@ void GLGizmoPainterBase::render_cursor_sphere() const } -bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const +bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const { if (m_c->object_clipper()->get_position() == 0.) return false; auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix(); - Vec3d transformed_point = trafo * point; transformed_point(2) += sel_info->get_sla_shift(); return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); @@ -299,20 +303,20 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // add several positions from between into the list, so there // are no gaps in the painted region. { - if (m_last_mouse_position == Vec2d::Zero()) - m_last_mouse_position = mouse_position; + if (m_last_mouse_click == Vec2d::Zero()) + m_last_mouse_click = mouse_position; // resolution describes minimal distance limit using circle radius // as a unit (e.g., 2 would mean the patches will be touching). double resolution = 0.7; double diameter_px = resolution * m_cursor_radius * camera.get_zoom(); - int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px); + int patches_in_between = int(((mouse_position - m_last_mouse_click).norm() - diameter_px) / diameter_px); if (patches_in_between > 0) { - Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1); + Vec2d diff = (mouse_position - m_last_mouse_click)/(patches_in_between+1); for (int i=1; i<=patches_in_between; ++i) - mouse_positions.emplace_back(m_last_mouse_position + i*diff); + mouse_positions.emplace_back(m_last_mouse_click + i*diff); } } - m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved + m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved // Precalculate transformations of individual meshes. std::vector trafo_matrices; @@ -323,34 +327,28 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Now "click" into all the prepared points and spill paint around them. for (const Vec2d& mp : mouse_positions) { - - bool clipped_mesh_was_hit = false; - Vec3f hit = Vec3f::Zero(); - size_t facet = 0; - int mesh_id = -1; - - get_mesh_hit(mp, camera, trafo_matrices, mesh_id, hit, facet, clipped_mesh_was_hit); + update_raycast_cache(mp, camera, trafo_matrices); bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); // The mouse button click detection is enabled when there is a valid hit // or when the user clicks the clipping plane. Missing the object entirely // shall not capture the mouse. - if (mesh_id != -1 || clipped_mesh_was_hit) { + if (m_rr.mesh_id != -1 || m_rr.clipped_mesh_was_hit) { if (m_button_down == Button::None) m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); } - if (mesh_id == -1) { + if (m_rr.mesh_id == -1) { // In case we have no valid hit, we can return. The event will // be stopped in following two cases: // 1. clicking the clipping plane // 2. dragging while painting (to prevent scene rotations and moving the object) - return clipped_mesh_was_hit + return m_rr.clipped_mesh_was_hit || dragging_while_painting; } - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; + const Transform3d& trafo_matrix = trafo_matrices[m_rr.mesh_id]; // Calculate how far can a point be from the line (in mesh coords). // FIXME: The scaling of the mesh can be non-uniform. @@ -360,13 +358,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Calculate direction from camera to the hit (in mesh coords): Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); - Vec3f dir = (hit - camera_pos).normalized(); + Vec3f dir = (m_rr.hit - camera_pos).normalized(); - assert(mesh_id < int(m_triangle_selectors.size())); - m_triangle_selectors[mesh_id]->select_patch(hit, facet, camera_pos, + assert(m_rr.mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, m_rr.facet, camera_pos, dir, limit, m_cursor_type, new_state); - m_last_mouse_position = mouse_position; - m_last_mesh_idx_and_hit = {mesh_id, hit}; + m_last_mouse_click = mouse_position; } return true; @@ -402,8 +399,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous update_model_object(); m_button_down = Button::None; - m_last_mouse_position = Vec2d::Zero(); - m_last_mesh_idx_and_hit = {-1, Vec3f::Zero()}; + m_last_mouse_click = Vec2d::Zero(); return true; } @@ -411,21 +407,27 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -void GLGizmoPainterBase::get_mesh_hit(const Vec2d& mouse_position, - const Camera& camera, - const std::vector& trafo_matrices, - int& mesh_id, Vec3f& hit, size_t& facet, - bool& clipped_mesh_was_hit) const + +void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position, + const Camera& camera, + const std::vector& trafo_matrices) const { + if (m_rr.mouse_position == mouse_position) { + // Same query as last time - the answer is already in the cache. + return; + } + + bool clipped_mesh_was_hit{false}; Vec3f normal = Vec3f::Zero(); - size_t current_facet = 0; + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; Vec3f closest_hit = Vec3f::Zero(); double closest_hit_squared_distance = std::numeric_limits::max(); size_t closest_facet = 0; int closest_hit_mesh_id = -1; // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh - for (mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { + for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( mouse_position, @@ -434,10 +436,10 @@ void GLGizmoPainterBase::get_mesh_hit(const Vec2d& mouse_position, hit, normal, m_clipping_plane.get(), - ¤t_facet)) + &facet)) { // In case this hit is clipped, skip it. - if (is_mesh_point_clipped(hit.cast())) { + if (is_mesh_point_clipped(hit.cast(), trafo_matrices[mesh_id])) { clipped_mesh_was_hit = true; continue; } @@ -446,16 +448,14 @@ void GLGizmoPainterBase::get_mesh_hit(const Vec2d& mouse_position, double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); if (hit_squared_distance < closest_hit_squared_distance) { closest_hit_squared_distance = hit_squared_distance; - closest_facet = current_facet; + closest_facet = facet; closest_hit_mesh_id = mesh_id; closest_hit = hit; } } } - mesh_id = closest_hit_mesh_id; - facet = closest_facet; - hit = closest_hit; + m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet, clipped_mesh_was_hit}; } bool GLGizmoPainterBase::on_is_activable() const @@ -564,13 +564,13 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) m_iva_blockers.finalize_geometry(true); if (m_iva_enforcers.has_VBOs()) { - ::glColor4f(0.f, 0.f, 1.f, 0.3f); + ::glColor4f(0.f, 0.f, 1.f, 0.4f); m_iva_enforcers.render(); } if (m_iva_blockers.has_VBOs()) { - ::glColor4f(1.f, 0.f, 0.f, 0.3f); + ::glColor4f(1.f, 0.f, 0.f, 0.4f); m_iva_blockers.render(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 177bad53dd..02b4dd1c79 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -88,12 +88,10 @@ protected: private: - bool is_mesh_point_clipped(const Vec3d& point) const; - void get_mesh_hit(const Vec2d& mouse_position, - const Camera& camera, - const std::vector& trafo_matrices, - int& mesh_id, Vec3f& hit, size_t& facet, - bool& clipped_mesh_was_hit) const; + bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const; + void update_raycast_cache(const Vec2d& mouse_position, + const Camera& camera, + const std::vector& trafo_matrices) const; float m_clipping_plane_distance = 0.f; std::unique_ptr m_clipping_plane; @@ -101,8 +99,7 @@ private: bool m_internal_stack_active = false; bool m_schedule_update = false; - Vec2d m_last_mouse_position = Vec2d::Zero(); - std::pair m_last_mesh_idx_and_hit = {-1, Vec3f::Zero()}; + Vec2d m_last_mouse_click = Vec2d::Zero(); enum class Button { None, @@ -113,6 +110,18 @@ private: Button m_button_down = Button::None; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) + // Following cache holds result of a raycast query. The queries are asked + // during rendering the sphere cursor and painting, this saves repeated + // raycasts when the mouse position is the same as before. + struct RaycastResult { + Vec2d mouse_position; + int mesh_id; + Vec3f hit; + size_t facet; + bool clipped_mesh_was_hit; + }; + mutable RaycastResult m_rr; + protected: void on_set_state() override; void on_start_dragging() override {} From 3f7d41df15e023271054720f31dd690afaeca671 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 1 Oct 2020 00:41:19 +0200 Subject: [PATCH 589/826] Imgui dialog layout adjustments after the new combo was added --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 37 ++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 38 +++++++++++++------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 58346a4147..04288c13f6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -42,6 +42,7 @@ bool GLGizmoFdmSupports::on_init() m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size"] = _L("Cursor size") + ": "; + m_desc["cursor_type"] = _L("Cursor type") + ": "; m_desc["enforce_caption"] = _L("Left mouse button") + ": "; m_desc["enforce"] = _L("Enforce supports"); m_desc["block_caption"] = _L("Right mouse button") + " "; @@ -78,16 +79,26 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(18.0f); + const float approx_height = m_imgui->scaled(14.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); if (! m_setting_angle) { m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + std::vector cursor_types; + cursor_types.push_back(_L("Circle").ToUTF8().data()); + cursor_types.push_back(_L("Sphere").ToUTF8().data()); + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + + m_imgui->scaled(1.5f); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); + const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0])).x, + m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1])).x) + + m_imgui->scaled(2.5f); const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); @@ -103,6 +114,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); + window_width = std::max(window_width, cursor_type_combo_left + cursor_type_combo_width); auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption); @@ -139,8 +151,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SameLine(cursor_slider_left); + ImGui::PushItemWidth(window_width - cursor_slider_left); ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); @@ -150,6 +162,23 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::EndTooltip(); } + + m_imgui->text(m_desc.at("cursor_type")); + ImGui::SameLine(window_width - cursor_type_combo_width - m_imgui->scaled(0.5f)); + ImGui::PushItemWidth(cursor_type_combo_width); + int selection = int(m_cursor_type); + m_imgui->combo("", cursor_types, selection); + m_cursor_type = TriangleSelector::CursorType(selection); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(max_tooltip_width); + ImGui::TextUnformatted(_L("Sphere paints all facets inside, regardless of their orientation.\n\n" + "Circle ignores facets facing away from the camera.").ToUTF8().data()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + ImGui::Separator(); if (m_c->object_clipper()->get_position() == 0.f) m_imgui->text(m_desc.at("clipping_of_view")); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 1b1b1f10f5..0cbfaeedcf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -25,7 +25,7 @@ bool GLGizmoSeam::on_init() m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; m_desc["reset_direction"] = _L("Reset direction"); m_desc["cursor_size"] = _L("Cursor size") + ": "; - m_desc["cursor_type"] = _L("Cursor size") + ": "; + m_desc["cursor_type"] = _L("Cursor type") + ": "; m_desc["enforce_caption"] = _L("Left mouse button") + ": "; m_desc["enforce"] = _L("Enforce seam"); m_desc["block_caption"] = _L("Right mouse button") + " "; @@ -68,16 +68,26 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(18.0f); + const float approx_height = m_imgui->scaled(14.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + std::vector cursor_types; + cursor_types.push_back(_L("Circle").ToUTF8().data()); + cursor_types.push_back(_L("Sphere").ToUTF8().data()); + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + + m_imgui->scaled(1.5f); + const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); + const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0])).x, + m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1])).x) + + m_imgui->scaled(2.5f); const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); @@ -90,9 +100,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) caption_max += m_imgui->scaled(1.f); total_text_max += m_imgui->scaled(1.f); - float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left); + float window_width = minimal_slider_width + std::max(cursor_size_slider_left, clipping_slider_left); window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); + window_width = std::max(window_width, cursor_type_combo_left + cursor_type_combo_width); auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); @@ -124,8 +135,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SameLine(cursor_size_slider_left); + ImGui::PushItemWidth(window_width - cursor_size_slider_left); ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f"); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); @@ -137,18 +148,19 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) m_imgui->text(m_desc.at("cursor_type")); - ImGui::SameLine(/*clipping_slider_left*/); - //ImGui::PushItemWidth(window_width - clipping_slider_left); + ImGui::SameLine(window_width - cursor_type_combo_width - m_imgui->scaled(0.5f)); + ImGui::PushItemWidth(cursor_type_combo_width); int selection = int(m_cursor_type); - m_imgui->combo(" ", {"Circle", "Sphere"}, selection); + m_imgui->combo("", cursor_types, selection); m_cursor_type = TriangleSelector::CursorType(selection); - /*if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(max_tooltip_width); - ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data()); + ImGui::TextUnformatted(_L("Sphere paints all facets inside, regardless of their orientation.\n\n" + "Circle ignores facets facing away from the camera.").ToUTF8().data()); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); - }*/ + } From fad1f5e84f2de4970120639b369bd6fd0b99f888 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 1 Oct 2020 08:34:16 +0200 Subject: [PATCH 590/826] Fixed typo --- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 02b4dd1c79..8a9e871248 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -21,7 +21,7 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class ClippingPlane; -class Camera; +struct Camera; enum class PainterGizmoType { FDM_SUPPORTS, From 43f122b5ee506e9e9093d12b9273ff7a02d9ee7d Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 1 Oct 2020 09:25:11 +0200 Subject: [PATCH 591/826] Filament selection in configuration wizard: compatible printers in html window, bug fixes. --- src/slic3r/GUI/ConfigWizard.cpp | 362 ++++++++++++++++++------ src/slic3r/GUI/ConfigWizard_private.hpp | 50 ++-- 2 files changed, 293 insertions(+), 119 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index c98b736b7c..9c4338d863 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -566,20 +566,20 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin , list_type(new StringList(this)) , list_vendor(new StringList(this)) , list_profile(new PresetList(this)) - , compatible_printers(new wxStaticText(this, wxID_ANY, _(L("")))) { append_spacer(VERTICAL_SPACING); const int em = parent->em_unit(); const int list_h = 30*em; - list_printer->SetWindowStyle(wxLB_EXTENDED); list_printer->SetMinSize(wxSize(23*em, list_h)); list_type->SetMinSize(wxSize(8*em, list_h)); list_vendor->SetMinSize(wxSize(13*em, list_h)); list_profile->SetMinSize(wxSize(23*em, list_h)); + + grid = new wxFlexGridSizer(4, em/2, em); grid->AddGrowableCol(3, 1); grid->AddGrowableRow(1, 1); @@ -601,17 +601,19 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin btn_sizer->Add(sel_none); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); grid->Add(new wxBoxSizer(wxHORIZONTAL)); grid->Add(new wxBoxSizer(wxHORIZONTAL)); grid->Add(btn_sizer, 0, wxALIGN_RIGHT); - - auto* notes_sizer = new wxBoxSizer(wxHORIZONTAL); - notes_sizer->Add(compatible_printers); - grid->Add(notes_sizer); - append(grid, 1, wxEXPAND); + append_spacer(VERTICAL_SPACING); + + html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, + wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); + append(html_window, 0, wxEXPAND); + list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { update_lists(evt.GetInt(), list_type->GetSelection(), list_vendor->GetSelection()); }); @@ -627,28 +629,25 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); - + /* Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); - + */ reload_presets(); + set_compatible_printers_html_window(std::vector(), false); } void PageMaterials::on_paint() { - if (first_paint) { - first_paint = false; - prepare_compatible_printers_label(); - } } void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) { const wxClientDC dc(list_profile); const wxPoint pos = evt.GetLogicalPosition(dc); int item = list_profile->HitTest(pos); - BOOST_LOG_TRIVIAL(error) << "hit test: " << item; + //BOOST_LOG_TRIVIAL(debug) << "hit test: " << item; on_material_hovered(item); } void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) @@ -666,10 +665,11 @@ void PageMaterials::reload_presets() for (const Preset* printer : materials->printers) { list_printer->append(printer->name, &printer->name); } - + sort_list_data(list_printer, true, false); if (list_printer->GetCount() > 0) { list_printer->SetSelection(0); - sel_printer_prev = wxNOT_FOUND; + sel_printer_count_prev = wxNOT_FOUND; + sel_printer_item_prev = wxNOT_FOUND; sel_type_prev = wxNOT_FOUND; sel_vendor_prev = wxNOT_FOUND; update_lists(0, 0, 0); @@ -678,34 +678,105 @@ void PageMaterials::reload_presets() presets_loaded = true; } -void PageMaterials::prepare_compatible_printers_label() +void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) { - assert(grid->GetColWidths().size() == 4); - compatible_printers_width = grid->GetColWidths()[3]; - empty_printers_label = "Compatible printers:"; - for (const Preset* printer : materials->printers) { - empty_printers_label += "\n"; + //Slic3r::GUI::wxGetApp().dark_mode() + const auto bgr_clr = +#if defined(__APPLE__) + wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); +#else + wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); +#endif + const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); + wxString first_line = L"Profiles marked with * are not compatible with all installed printers."; + wxString text; + if (all_printers) { + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

All installed printers are compatible with selected profile." + "
" + "
" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + ); + } else { + wxString second_line = L"Compatible printers:"; + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line); + for (int i = 0; i < printer_names.size(); ++i) + { + text += wxString::Format("", boost::nowide::widen(printer_names[i])); + if (i % 3 == 2) { + text += wxString::Format( + "" + ""); + } + } + text += wxString::Format( + "" + "
%s
" + "
" + "
" + "" + "" + ); } - clear_compatible_printers_label(); + + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this)); + const int fs = font.GetPointSize(); + int size[] = { fs,fs,fs,fs,fs,fs,fs }; + html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html_window->SetPage(text); } void PageMaterials::clear_compatible_printers_label() { - compatible_printers->SetLabel(boost::nowide::widen(empty_printers_label)); - compatible_printers->Wrap(compatible_printers_width); - Layout(); + set_compatible_printers_html_window(std::vector(), false); } void PageMaterials::on_material_hovered(int sel_material) { - if ( sel_material == last_hovered_item) + +} + +void PageMaterials::on_material_highlighted(int sel_material) +{ + if (sel_material == last_hovered_item) return; if (sel_material == -1) { clear_compatible_printers_label(); return; } last_hovered_item = sel_material; - std::string compatible_printers_label = "compatible printers:\n"; + std::string compatible_printers_label = "Compatible printers:\n"; + std::vector tabs; + tabs.push_back(std::string()); + tabs.push_back(std::string()); + tabs.push_back(std::string()); //selected material string std::string material_name = list_profile->get_data(sel_material); // get material preset @@ -716,70 +787,16 @@ void PageMaterials::on_material_hovered(int sel_material) return; } //find matching printers - bool first = true; + std::vector names; for (const Preset* printer : materials->printers) { - bool compatible = false; for (const Preset* material : matching_materials) { if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { - if (first) - first = false; - else - compatible_printers_label += "\n";//", "; - compatible_printers_label += printer->name; - compatible = true; + names.push_back(printer->name); break; } } } - this->compatible_printers->SetLabel(boost::nowide::widen(compatible_printers_label)); - this->compatible_printers->Wrap(compatible_printers_width); -} - -void PageMaterials::on_material_highlighted(int sel_material) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - //std::string compatible_printers_label = "compatible printers:\n"; - //std::string empty_suplement = std::string(); - //unselect all printers - list_printer->SetSelection(wxNOT_FOUND); - //selected material string - std::string material_name = list_profile->get_data(sel_material); - // get material preset - const std::vector matching_materials = materials->get_presets_by_alias(material_name); - if (matching_materials.empty()) - return; - //find matching printers - //bool first = true; - for (const Preset* printer : materials->printers) { - bool compatible = false; - for (const Preset* material : matching_materials) { - if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { - //select printer - int index = list_printer->find(printer->name); - list_printer->SetSelection(index); - /*if (first) - first = false; - else - compatible_printers_label += "\n";//", "; - compatible_printers_label += printer->name; - compatible = true; - break;*/ - } - } - //if(!compatible) - // empty_suplement += std::string(printer->name.length() + 2, ' '); - } - // fill rest of label with blanks so it maintains legth - //compatible_printers_label += empty_suplement; - - update_lists(0,0,0); - list_profile->SetSelection(list_profile->find(material_name)); - - //this->compatible_printers->SetLabel(boost::nowide::widen(compatible_printers_label)); - //this->compatible_printers->Wrap(compatible_printers_width); - //Refresh(); + set_compatible_printers_html_window(names, names.size() == materials->printers.size()); } void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) @@ -790,7 +807,7 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) wxArrayInt sel_printers; int sel_printers_count = list_printer->GetSelections(sel_printers); - if (sel_printers_count != sel_printer_prev) { + if (sel_printers_count != sel_printer_count_prev || (sel_printers_count == 1 && sel_printer_item_prev != sel_printer && sel_printer != -1)) { // Refresh type list list_type->Clear(); list_type->append(_(L("(All)")), &EMPTY); @@ -827,6 +844,7 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) //clear selection except "ALL" list_printer->SetSelection(wxNOT_FOUND); list_printer->SetSelection(0); + sel_printers_count = list_printer->GetSelections(sel_printers); materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { const std::string& type = this->materials->get_type(p); @@ -835,10 +853,11 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) } }); } - + sort_list_data(list_type, true, true); } - sel_printer_prev = sel_printers_count; + sel_printer_count_prev = sel_printers_count; + sel_printer_item_prev = sel_printer; sel_type = 0; sel_type_prev = wxNOT_FOUND; list_type->SetSelection(sel_type); @@ -872,6 +891,7 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) } }); } + sort_list_data(list_vendor, true, false); } sel_type_prev = sel_type; @@ -905,7 +925,6 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) //size_t printer_counter = materials->get_printer_counter(p); int cur_i = list_profile->find(p->alias); if (cur_i == wxNOT_FOUND) - //cur_i = list_profile->append(p->alias + " " + std::to_string(printer_counter)/*+ (omnipresent ? "" : " ONLY SOME PRINTERS")*/, &p->alias); cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); else was_checked = list_profile->IsChecked(cur_i); @@ -925,12 +944,103 @@ void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor) wizard_p()->appconfig_new.set(section, p->name, "1"); }); } + sort_list_data(list_profile); } sel_vendor_prev = sel_vendor; } } +void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) +{ +// get data from list +// sort data +// first should be +// then prusa profiles +// then the rest +// in alphabetical order + + std::vector> prusa_profiles; + std::vector> other_profiles; + for (int i = 0 ; i < list->size(); ++i) { + const std::string& data = list->get_data(i); + if (data == EMPTY) // do not sort item + continue; + if (!material_type_ordering && data.find("Prusa") != std::string::npos) + prusa_profiles.push_back(data); + else + other_profiles.push_back(data); + } + if(material_type_ordering) { + + const ConfigOptionDef* def = print_config_def.get("filament_type"); + std::vectorenum_values = def->enum_values; + int end_of_sorted = 0; + for (size_t vals = 0; vals < enum_values.size(); vals++) { + for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) + { + // find instead compare because PET vs PETG + if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { + //swap + if(profs != end_of_sorted) { + std::reference_wrapper aux = other_profiles[end_of_sorted]; + other_profiles[end_of_sorted] = other_profiles[profs]; + other_profiles[profs] = aux; + } + end_of_sorted++; + break; + } + } + } + } else { + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + } + + list->Clear(); + if (add_All_item) + list->append(_(L("(All)")), &EMPTY); + for (const auto& item : prusa_profiles) + list->append(item, &const_cast(item.get())); + for (const auto& item : other_profiles) + list->append(item, &const_cast(item.get())); +} + +void PageMaterials::sort_list_data(PresetList* list) +{ + // sort data + // then prusa profiles + // then the rest + // in alphabetical order + std::vector> prusa_profiles; + std::vector> other_profiles; + for (int i = 0; i < list->size(); ++i) { + const std::string& data = list->get_data(i); + if (data == EMPTY) // do not sort item + continue; + if (data.find("Prusa") != std::string::npos) + prusa_profiles.push_back(data); + else + other_profiles.push_back(data); + } + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + list->Clear(); + for (const auto& item : prusa_profiles) + list->append(item, &const_cast(item.get())); + for (const auto& item : other_profiles) + list->append(item, &const_cast(item.get())); + +} + void PageMaterials::select_material(int i) { const bool checked = list_profile->IsChecked(i); @@ -959,7 +1069,8 @@ void PageMaterials::clear() list_type->Clear(); list_vendor->Clear(); list_profile->Clear(); - sel_printer_prev = wxNOT_FOUND; + sel_printer_count_prev = wxNOT_FOUND; + sel_printer_item_prev = wxNOT_FOUND; sel_type_prev = wxNOT_FOUND; sel_vendor_prev = wxNOT_FOUND; presets_loaded = false; @@ -1546,7 +1657,7 @@ const std::string Materials::UNKNOWN = "(Unknown)"; void Materials::push(const Preset *preset) { - presets.emplace_back(preset, 0); + presets.emplace_back(preset); types.insert(technology & T_FFF ? Materials::get_filament_type(preset) : Materials::get_material_type(preset)); @@ -1562,6 +1673,7 @@ void Materials::clear() presets.clear(); types.clear(); printers.clear(); + compatibility_counter.clear(); } const std::string& Materials::appconfig_section() const @@ -1855,13 +1967,45 @@ void ConfigWizard::priv::update_materials(Technology technology) if (!filament.alias.empty()) aliases_fff[filament.alias].insert(filament.name); } - filaments.add_printer_counter(&filament); filaments.add_printer(&printer); } } } } + // count compatible printers + for (const auto& preset : filaments.presets) { + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { + continue; + } + std::vector idx_with_same_alias; + for (size_t i = 0; i < filaments.presets.size(); ++i) { + if (preset->alias == filaments.presets[i]->alias) + idx_with_same_alias.push_back(i); + } + size_t counter = 0; + for (const auto& printer : filaments.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) + continue; + bool compatible = false; + // Test otrher materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + filaments.compatibility_counter.emplace_back(preset->alias, counter); + } } if (any_sla_selected && (technology & T_SLA)) { @@ -1887,12 +2031,44 @@ void ConfigWizard::priv::update_materials(Technology technology) if (!material.alias.empty()) aliases_sla[material.alias].insert(material.name); } - sla_materials.add_printer_counter(&material); sla_materials.add_printer(&printer); } } } } + // count compatible printers + for (const auto& preset : sla_materials.presets) { + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { + continue; + } + std::vector idx_with_same_alias; + for (size_t i = 0; i < sla_materials.presets.size(); ++i) { + if(preset->alias == sla_materials.presets[i]->alias) + idx_with_same_alias.push_back(i); + } + size_t counter = 0; + for (const auto& printer : sla_materials.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) + continue; + bool compatible = false; + // Test otrher materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + sla_materials.compatibility_counter.emplace_back(preset->alias, counter); + } } } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 260eeb22cb..850f8fd46e 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" @@ -86,9 +87,9 @@ struct Materials { Technology technology; // use vector for the presets to purpose of save of presets sorting in the bundle - // bool is true if material is present in all printers (omnipresent) - // size_t is counter of printers compatible with material - std::vector> presets; + std::vector presets; + // String is alias of material, size_t number of compatible counters + std::vector> compatibility_counter; std::set types; std::set printers; @@ -100,7 +101,7 @@ struct Materials bool containts(const Preset *preset) const { //return std::find(presets.begin(), presets.end(), preset) != presets.end(); return std::find_if(presets.begin(), presets.end(), - [preset](const std::pair& element) { return element.first == preset; }) != presets.end(); + [preset](const Preset* element) { return element == preset; }) != presets.end(); } @@ -111,42 +112,35 @@ struct Materials const std::vector get_presets_by_alias(const std::string name) { std::vector ret_vec; for (auto it = presets.begin(); it != presets.end(); ++it) { - if ((*it).first->alias == name) - ret_vec.push_back((*it).first); + if ((*it)->alias == name) + ret_vec.push_back((*it)); } return ret_vec; } - void add_printer_counter(const Preset* preset) { - for (auto it = presets.begin(); it != presets.end(); ++it) { - if ((*it).first->alias == preset->alias) - (*it).second += 1; - } - } + size_t get_printer_counter(const Preset* preset) { - size_t highest = 0; - for (auto it : presets) { - if (it.first->alias == preset->alias && it.second > highest) - highest = it.second; - } - return highest; + for (auto it : compatibility_counter) { + if (it.first == preset->alias) + return it.second; + } + return 0; } const std::string& appconfig_section() const; const std::string& get_type(const Preset *preset) const; const std::string& get_vendor(const Preset *preset) const; - template void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) { for (auto preset : presets) { - const Preset& prst = *(preset.first); + const Preset& prst = *(preset); const Preset& prntr = *printer; if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) && - (type.empty() || get_type(preset.first) == type) && - (vendor.empty() || get_vendor(preset.first) == vendor)) { + (type.empty() || get_type(preset) == type) && + (vendor.empty() || get_vendor(preset) == vendor)) { - cb(preset.first); + cb(preset); } } } @@ -325,11 +319,12 @@ struct PageMaterials: ConfigWizardPage Materials *materials; StringList *list_printer, *list_type, *list_vendor; PresetList *list_profile; - int sel_printer_prev, sel_type_prev, sel_vendor_prev; + int sel_printer_count_prev, sel_printer_item_prev, sel_type_prev, sel_vendor_prev; bool presets_loaded; wxFlexGridSizer *grid; - wxStaticText *compatible_printers; + wxHtmlWindow* html_window; + int compatible_printers_width = { 100 }; std::string empty_printers_label; bool first_paint = { false }; @@ -345,9 +340,12 @@ struct PageMaterials: ConfigWizardPage void select_material(int i); void select_all(bool select); void clear(); - void prepare_compatible_printers_label(); + void set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers = false); void clear_compatible_printers_label(); + void sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering); + void sort_list_data(PresetList* list); + void on_paint(); void on_mouse_move_on_profiles(wxMouseEvent& evt); void on_mouse_enter_profiles(wxMouseEvent& evt); From a47178557fcb6fd562ab6c6e96b008823e0945d8 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 21 Sep 2020 09:14:47 +0200 Subject: [PATCH 592/826] notification orange color for hypertext --- src/slic3r/GUI/NotificationManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 47962f4b2a..d5ab9ee1ac 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -362,7 +362,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, ImGui::PopStyleColor(); //hover color - ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button); + ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);//ImGui::GetStyleColorVec4(ImGuiCol_Button); if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) orange_color.y += 0.2f; From 661534042b683bdb8d2624eaa7a5a140da271965 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 21 Sep 2020 13:47:18 +0200 Subject: [PATCH 593/826] notifications: changed some plater warnings into errors, fixed not showing plater warnings in preview. --- src/slic3r/GUI/GLCanvas3D.cpp | 6 +++--- src/slic3r/GUI/NotificationManager.cpp | 9 +++++---- src/slic3r/GUI/NotificationManager.hpp | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a3b4be9a7a..c138b937c9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -628,8 +628,8 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool bool error = false; switch (warning) { case ObjectOutside: text = L("An object outside the print area was detected."); break; - case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break; - case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break; + case ToolpathOutside: text = L("A toolpath outside the print area was detected."); error = true; break; + case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); error = true; break; case SomethingNotShown: text = L("Some objects are not visible."); break; case ObjectClashed: text = L( "An object outside the print area was detected.\n" @@ -644,7 +644,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D())); } else { if (error) - wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); + wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(text); else wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text); } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index d5ab9ee1ac..b9e7c7a6fc 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -56,8 +56,7 @@ NotificationManager::PopNotification::~PopNotification() } NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y) { - if (!m_initialized) - { + if (!m_initialized) { init(); } if (m_finished) @@ -682,11 +681,13 @@ void NotificationManager::push_plater_error_notification(const std::string& text void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas) { push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, canvas, 0); + // dissaper if in preview + set_in_preview(m_in_preview); } -void NotificationManager::close_plater_error_notification() +void NotificationManager::close_plater_error_notification(const std::string& text) { for (PopNotification* notification : m_pop_notifications) { - if (notification->get_type() == NotificationType::PlaterError) { + if (notification->get_type() == NotificationType::PlaterError && notification->compare_text(_u8L("ERROR:") + "\n" + text)) { notification->close(); } } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index a11d08394c..c2cbc242c8 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -220,7 +220,8 @@ public: void compare_warning_oids(const std::vector& living_oids); void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas); void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas); - void close_plater_error_notification(); + // Closes error or warning of same text + void close_plater_error_notification(const std::string& text); void close_plater_warning_notification(const std::string& text); // creates special notification slicing complete // if large = true prints printing time and export button @@ -250,7 +251,7 @@ private: bool m_hovered { false }; //timestamps used for slining finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; - bool m_in_preview; + bool m_in_preview { false }; //prepared (basic) notifications const std::vector basic_notifications = { From 8ded9dc0fdb80305955cd52a81b0e4c2eaaa9af8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 1 Oct 2020 09:33:05 +0200 Subject: [PATCH 594/826] Improved performance of progress dialog shown while generating toolpaths for render --- src/slic3r/GUI/GCodeViewer.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 8ab17717c7..a6914f7681 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -863,6 +863,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (m_moves_count == 0) return; + unsigned int progress_count = 0; + static const unsigned int progress_threshold = 1000; wxProgressDialog progress_dialog(_L("Generating toolpaths"), "...", 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL); @@ -1245,10 +1247,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; - progress_dialog.Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), - _L("Generating vertex buffer") + " (" + wxNumberFormatter::ToString((long)i, wxNumberFormatter::Style_None) + "/" + - wxNumberFormatter::ToString((long)m_moves_count, wxNumberFormatter::Style_None) + ")"); - progress_dialog.Fit(); + ++progress_count; + if (progress_count % progress_threshold == 0) { + progress_dialog.Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), + _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); + progress_dialog.Fit(); + progress_count = 0; + } const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; @@ -1316,10 +1321,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (i == 0) continue; - progress_dialog.Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), - _L("Generating index buffers") + " (" + wxNumberFormatter::ToString((long)i, wxNumberFormatter::Style_None) + "/" + - wxNumberFormatter::ToString((long)m_moves_count, wxNumberFormatter::Style_None) + ")"); - progress_dialog.Fit(); + ++progress_count; + if (progress_count % progress_threshold == 0) { + progress_dialog.Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), + _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); + progress_dialog.Fit(); + progress_count = 0; + } const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; From 6a46708608a0114cfb64953f773402fe54d8de82 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 1 Oct 2020 09:45:36 +0200 Subject: [PATCH 595/826] fix in ConfigWizard.cpp --- src/slic3r/GUI/ConfigWizard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 9c4338d863..cfc81f5a7d 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -746,7 +746,7 @@ void PageMaterials::set_compatible_printers_html_window(const std::vectorSetFonts(font.GetFaceName(), font.GetFaceName(), size); From b71e5c2763e80e652a6f3bb410ed9325b91479b2 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 1 Oct 2020 15:11:56 +0200 Subject: [PATCH 596/826] Maybe one day we will be able to run PrusaGCodeViewer, but for now the Apple notarization process refuses Apps with multiple binaries and Vojtech does not know any workaround. Just run PrusaSlicer and give it a --gcodeviewer parameter. --- src/slic3r/Utils/Process.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp index 375c10a6a5..d2a326221a 100644 --- a/src/slic3r/Utils/Process.cpp +++ b/src/slic3r/Utils/Process.cpp @@ -56,11 +56,17 @@ static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); #if defined(__APPLE__) { - bin_path = bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"); + // Maybe one day we will be able to run PrusaGCodeViewer, but for now the Apple notarization + // process refuses Apps with multiple binaries and Vojtech does not know any workaround. + // ((instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"); + // Just run PrusaSlicer and give it a --gcodeviewer parameter. + bin_path = bin_path.parent_path() / "PrusaSlicer"; // On Apple the wxExecute fails, thus we use boost::process instead. BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; try { std::vector args; + if (instance_type == NewSlicerInstanceType::GCodeViewer) + args.emplace_back("--gcodeviewer"); if (path_to_open) args.emplace_back(into_u8(*path_to_open)); boost::process::spawn(bin_path, args); From b17c829c9ad3302e4960f16929fdd10698c4ad79 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 1 Oct 2020 19:16:23 +0200 Subject: [PATCH 597/826] Fixed crash on Linux on startup --- src/slic3r/GUI/Tab.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e6363b3d06..c99ddeecd8 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -298,6 +298,7 @@ void Tab::create_preset_tab() // so that the cursor jumps to the last item. m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, [this](wxTreeEvent&) { if (!m_disable_tree_sel_changed_event && !m_pages.empty()) { +#ifdef WIN32 if (m_page_switch_running) m_page_switch_planned = true; else { @@ -308,6 +309,10 @@ void Tab::create_preset_tab() } while (this->tree_sel_change_delayed()); m_page_switch_running = false; } +#else + // Crashes on Linux on start-up without CallAfter. + this->CallAfter([this]() { this->tree_sel_change_delayed(); }); +#endif } }); @@ -3389,10 +3394,9 @@ void Tab::activate_selected_page(std::function throw_if_canceled) bool Tab::tree_sel_change_delayed() { -#if 1 - // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. - // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, - // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. + // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. + // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, + // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. #ifdef __linux__ std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); #else @@ -3401,9 +3405,8 @@ bool Tab::tree_sel_change_delayed() * But under OSX (builds compiled with MacOSX10.14.sdk) wxStaticBitmap rendering is broken without Freeze/Thaw call. */ //#ifdef __WXOSX__ // Use Freeze/Thaw to avoid flickering during clear/activate new page - wxWindowUpdateLocker noUpdates(this); + wxWindowUpdateLocker noUpdates(this); //#endif -#endif #endif Page* page = nullptr; From f35efb8fe58581fcab676a032423df492e8b3b42 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 1 Oct 2020 19:58:23 +0200 Subject: [PATCH 598/826] Win32 specific: Workaround for tooltips over Tree Controls displayed over excessively long tree control items, stealing the window focus. In case the Tab was reparented from the MainFrame to the floating dialog, the tooltip created by the Tree Control before reparenting is not reparented, but it still points to the MainFrame. If the tooltip pops up, the MainFrame is incorrectly focussed, stealing focus from the floating dialog. The workaround is to delete the tooltip control. Vojtech tried to reparent the tooltip control, but it did not work, and if the Tab was later reparented back to MainFrame, the tooltip was displayed at an incorrect position, therefore it is safer to just discard the tooltip control altogether. --- src/slic3r/GUI/Tab.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index de268fc7b3..aad698e5d9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -394,6 +394,31 @@ void Tab::OnActivate() Fit(); m_size_move *= -1; #endif // __WXOSX__ + +#ifdef __WXMSW__ + // Workaround for tooltips over Tree Controls displayed over excessively long + // tree control items, stealing the window focus. + // + // In case the Tab was reparented from the MainFrame to the floating dialog, + // the tooltip created by the Tree Control before reparenting is not reparented, + // but it still points to the MainFrame. If the tooltip pops up, the MainFrame + // is incorrectly focussed, stealing focus from the floating dialog. + // + // The workaround is to delete the tooltip control. + // Vojtech tried to reparent the tooltip control, but it did not work, + // and if the Tab was later reparented back to MainFrame, the tooltip was displayed + // at an incorrect position, therefore it is safer to just discard the tooltip control + // altogether. + HWND hwnd_tt = TreeView_GetToolTips(m_treectrl->GetHandle()); + if (hwnd_tt) { + HWND hwnd_toplevel = find_toplevel_parent(m_treectrl)->GetHandle(); + HWND hwnd_parent = ::GetParent(hwnd_tt); + if (hwnd_parent != hwnd_toplevel) { + ::DestroyWindow(hwnd_tt); + TreeView_SetToolTips(m_treectrl->GetHandle(), nullptr); + } + } +#endif } void Tab::update_labels_colour() From 50293c0f86861b32ae569ef1b0d892ae64a1cf55 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 1 Oct 2020 20:15:40 +0200 Subject: [PATCH 599/826] Fixing a missing include on Windows. --- src/slic3r/GUI/Tab.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index aad698e5d9..09d151fc7b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -38,6 +38,10 @@ #include "PhysicalPrinterDialog.hpp" #include "UnsavedChangesDialog.hpp" +#ifdef WIN32 + #include +#endif // WIN32 + namespace Slic3r { namespace GUI { From 2bba0e313190518375696337f7370ec8dd43c760 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 1 Oct 2020 22:48:00 +0200 Subject: [PATCH 600/826] Physical printers: Implemented import/export to/from the ConfigBundle. + fixed a bug : Case sensitivity of printer's name wasn't check during the adding of a new printer, as a result in printers list was appeared both of printers (ex. "YuSanka" and "yusanka"), but related file was just one. --- src/libslic3r/Preset.cpp | 60 ++++++++++++++++++--- src/libslic3r/Preset.hpp | 23 ++++---- src/libslic3r/PresetBundle.cpp | 67 ++++++++++++++++++++++-- src/libslic3r/PresetBundle.hpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 7 ++- src/slic3r/GUI/MainFrame.hpp | 2 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 6 ++- 7 files changed, 140 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 18a6e387cf..ddcadf9281 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1361,7 +1361,7 @@ const std::vector& PhysicalPrinter::printer_options() s_opts = { "preset_name", "printer_technology", - "printer_model", +// "printer_model", "host_type", "print_host", "printhost_apikey", @@ -1576,6 +1576,24 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const throw Slic3r::RuntimeError(errors_cummulative); } +void PhysicalPrinterCollection::load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save/* = false*/) +{ + auto it = this->find_printer_internal(name); + if (it == m_printers.end() || it->name != name) { + // The preset was not found. Create a new preset. + it = m_printers.emplace(it, PhysicalPrinter(name, config)); + } + + it->file = path; + it->config = std::move(config); + it->loaded = true; + if (select) + this->select_printer(*it); + + if (save) + it->save(); +} + // if there is saved user presets, contains information about "Print Host upload", // Create default printers with this presets // Note! "Print Host upload" options will be cleared after physical printer creations @@ -1623,12 +1641,39 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti } } -PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found) +PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool case_sensitive_search) { - auto it = this->find_printer_internal(name); + auto it = this->find_printer_internal(name, case_sensitive_search); + // Ensure that a temporary copy is returned if the preset found is currently selected. - return (it != m_printers.end() && it->name == name) ? &this->printer(it - m_printers.begin()) : - first_visible_if_not_found ? &this->printer(0) : nullptr; + auto is_equal_name = [name, case_sensitive_search](const std::string& in_name) { + if (case_sensitive_search) + return in_name == name; + return boost::to_lower_copy(in_name) == boost::to_lower_copy(name); + }; + + if (it == m_printers.end() || !is_equal_name(it->name)) + return nullptr; + return &this->printer(it - m_printers.begin()); +} + +std::deque::iterator PhysicalPrinterCollection::find_printer_internal(const std::string& name, bool case_sensitive_search/* = true*/) +{ + if (case_sensitive_search) + return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; }); + + std::string low_name = boost::to_lower_copy(name); + + int i = 0; + for (const PhysicalPrinter& printer : m_printers) { + if (boost::to_lower_copy(printer.name) == low_name) + break; + i++; + } + if (i == m_printers.size()) + return m_printers.end(); + + return m_printers.begin() + i; } PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const DynamicPrintConfig& config) @@ -1667,10 +1712,13 @@ void PhysicalPrinterCollection::save_printer(PhysicalPrinter& edited_printer, co it->config = std::move(edited_printer.config); it->name = edited_printer.name; it->preset_names = edited_printer.preset_names; + // sort printers and get new it + std::sort(m_printers.begin(), m_printers.end()); + it = this->find_printer_internal(edited_printer.name); } else { // Creating a new printer. - it = m_printers.insert(it, edited_printer); + it = m_printers.emplace(it, edited_printer); } assert(it != m_printers.end()); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index ec11d59fae..e3e16b65dc 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -632,6 +632,8 @@ public: // Load ini files of the particular type from the provided directory path. void load_printers(const std::string& dir_path, const std::string& subdir); void load_printers_from_presets(PrinterPresetCollection &printer_presets); + // Load printer from the loaded configuration + void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false); // Save the printer under a new name. If the name is different from the old one, // a new printer is stored into the list of printers. @@ -687,10 +689,11 @@ public: // Return a preset by its name. If the preset is active, a temporary copy is returned. // If a preset is not found by its name, null is returned. - PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false); - const PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false) const + // It is possible case (in)sensitive search + PhysicalPrinter* find_printer(const std::string& name, bool case_sensitive_search = true); + const PhysicalPrinter* find_printer(const std::string& name, bool case_sensitive_search = true) const { - return const_cast(this)->find_printer(name, first_visible_if_not_found); + return const_cast(this)->find_printer(name, case_sensitive_search); } // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. @@ -701,15 +704,11 @@ public: private: PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); - // Find a preset position in the sorted list of presets. - // The "-- default -- " preset is always the first, so it needs - // to be handled differently. - // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. - std::deque::iterator find_printer_internal(const std::string& name) - { - return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; }); - } - std::deque::const_iterator find_printer_internal(const std::string& name) const + // Find a physical printer position in the sorted list of printers. + // The name of a printer should be unique and case insensitive + // Use this functions with case_sensitive_search = false, when you need case insensitive search + std::deque::iterator find_printer_internal(const std::string& name, bool case_sensitive_search = true); + std::deque::const_iterator find_printer_internal(const std::string& name, bool case_sensitive_search = true) const { return const_cast(this)->find_printer_internal(name); } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index c100e6971a..4309f07328 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1113,16 +1113,22 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla std::vector loaded_sla_prints; std::vector loaded_sla_materials; std::vector loaded_printers; + std::vector loaded_physical_printers; std::string active_print; std::vector active_filaments; std::string active_sla_print; std::string active_sla_material; std::string active_printer; + std::string active_physical_printer; size_t presets_loaded = 0; + size_t ph_printers_loaded = 0; + for (const auto §ion : tree) { PresetCollection *presets = nullptr; std::vector *loaded = nullptr; std::string preset_name; + PhysicalPrinterCollection *ph_printers = nullptr; + std::string ph_printer_name; if (boost::starts_with(section.first, "print:")) { presets = &this->prints; loaded = &loaded_prints; @@ -1143,6 +1149,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla presets = &this->printers; loaded = &loaded_printers; preset_name = section.first.substr(8); + } else if (boost::starts_with(section.first, "physical_printer:")) { + ph_printers = &this->physical_printers; + loaded = &loaded_physical_printers; + ph_printer_name = section.first.substr(17); } else if (section.first == "presets") { // Load the names of the active presets. for (auto &kvp : section.second) { @@ -1161,6 +1171,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla active_sla_material = kvp.second.data(); } else if (kvp.first == "printer") { active_printer = kvp.second.data(); + }else if (kvp.first == "physical_printer") { + active_physical_printer = kvp.second.data(); } } } else if (section.first == "obsolete_presets") { @@ -1317,9 +1329,46 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla ++ presets_loaded; } + + if (ph_printers != nullptr) { + // Load the physical printer + const DynamicPrintConfig& default_config = ph_printers->default_config(); + DynamicPrintConfig config = default_config; + + for (auto& kvp : section.second) + config.set_deserialize(kvp.first, kvp.second.data()); + + // Report configuration fields, which are misplaced into a wrong group. + std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config); + if (!incorrect_keys.empty()) + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The physical printer \"" << + section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; + + const PhysicalPrinter* ph_printer_existing = ph_printers->find_printer(ph_printer_name, false); + if (ph_printer_existing != nullptr) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The physical printer \"" << + section.first << "\" has already been loaded from another Confing Bundle."; + continue; + } + + // Decide a full path to this .ini file. + auto file_name = boost::algorithm::iends_with(ph_printer_name, ".ini") ? ph_printer_name : ph_printer_name + ".ini"; + auto file_path = (boost::filesystem::path(data_dir()) +#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR + // Store the physical printers into a "presets" directory. + / "presets" +#else + // Store the physical printers at the same location as the upstream Slic3r. +#endif + / "physical_printer" / file_name).make_preferred(); + // Load the preset into the list of presets, save it to disk. + ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags & LOAD_CFGBNDLE_SAVE); + + ++ph_printers_loaded; + } } - // 3) Activate the presets. + // 3) Activate the presets and physical printer if any exists. if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { if (! active_print.empty()) prints.select_preset_by_name(active_print, true); @@ -1329,6 +1378,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla sla_materials.select_preset_by_name(active_sla_material, true); if (! active_printer.empty()) printers.select_preset_by_name(active_printer, true); + if (! active_physical_printer.empty()) + physical_printers.select_printer(active_physical_printer +" * " + active_printer); // Activate the first filament preset. if (! active_filaments.empty() && ! active_filaments.front().empty()) filaments.select_preset_by_name(active_filaments.front(), true); @@ -1338,7 +1389,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla this->update_compatible(PresetSelectCompatibleType::Never); } - return presets_loaded; + return presets_loaded + ph_printers_loaded; } void PresetBundle::update_multi_material_filament_presets() @@ -1458,7 +1509,7 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri } } -void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings) +void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings, bool export_physical_printers/* = false*/) { boost::nowide::ofstream c; c.open(path, std::ios::out | std::ios::trunc); @@ -1482,6 +1533,14 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst } } + if (export_physical_printers) { + for (const PhysicalPrinter& ph_printer : this->physical_printers) { + c << std::endl << "[physical_printer:" << ph_printer.name << "]" << std::endl; + for (const std::string& opt_key : ph_printer.config.keys()) + c << opt_key << " = " << ph_printer.config.opt_serialize(opt_key) << std::endl; + } + } + // Export the names of the active presets. c << std::endl << "[presets]" << std::endl; c << "print = " << this->prints.get_selected_preset_name() << std::endl; @@ -1497,6 +1556,8 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; } + if (export_physical_printers && this->physical_printers.get_selected_idx() >= 0) + c << "physical_printer = " << this->physical_printers.get_selected_printer_name() << std::endl; #if 0 // Export the following setting values from the provided setting repository. static const char *settings_keys[] = { "autocenter" }; diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index ff02bbeae3..609e25e2c2 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -102,7 +102,7 @@ public: size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); // Export a config bundle file containing all the presets and the names of the active presets. - void export_configbundle(const std::string &path, bool export_system_settings = false); + void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false); // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 3165b625b3..ca9ddf512e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1071,6 +1071,9 @@ void MainFrame::init_menubar() append_menu_item(export_menu, wxID_ANY, _L("Export Config &Bundle") + dots, _L("Export all presets to file"), [this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle", nullptr, [this]() {return true; }, this); + append_menu_item(export_menu, wxID_ANY, _L("Export Config Bundle With Physical Printers") + dots, _L("Export all presets including physical printers to file"), + [this](wxCommandEvent&) { export_configbundle(true); }, "export_config_bundle", nullptr, + [this]() {return true; }, this); append_submenu(fileMenu, export_menu, wxID_ANY, _L("&Export"), ""); append_menu_item(fileMenu, wxID_ANY, _L("Ejec&t SD card / Flash drive") + dots + "\tCtrl+T", _L("Eject SD card / Flash drive after the G-code was exported to it."), @@ -1641,7 +1644,7 @@ bool MainFrame::load_config_file(const std::string &path) return true; } -void MainFrame::export_configbundle() +void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) { if (!wxGetApp().check_unsaved_changes()) return; @@ -1663,7 +1666,7 @@ void MainFrame::export_configbundle() // Export the config bundle. wxGetApp().app_config->update_config_dir(get_dir_name(file)); try { - wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data()); + wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data(), false, export_physical_printers); } catch (const std::exception &ex) { show_error(this, ex.what()); } diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 18d2a73bd8..0a6cefd9a1 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -180,7 +180,7 @@ public: void load_config_file(); // Open a config file. Return true if loaded. bool load_config_file(const std::string &path); - void export_configbundle(); + void export_configbundle(bool export_physical_printers = false); void load_configbundle(wxString file = wxEmptyString); void load_config(const DynamicPrintConfig& config); // Select tab in m_tabpanel diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index aa8a3811ad..827bff28cf 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -468,15 +468,17 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) } PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers; - const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name)); + const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name), false); if (existing && into_u8(printer_name) != printers.get_selected_printer_name()) { - wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str()); + wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % existing->name/*printer_name*/).str()); msg_text += "\n" + _L("Replace?"); wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO); if (dialog.ShowModal() == wxID_NO) return; + + m_printer.name = existing->name; } std::set repeat_presets; From f1c24e6a8ce28b913b7ca4cf87849b16b4a7ffed Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 2 Oct 2020 00:11:01 +0200 Subject: [PATCH 601/826] Fix build with wxWidgets 3.0 --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 04288c13f6..c6b9a952bc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -96,8 +96,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l + m_imgui->scaled(1.5f); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); - const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0])).x, - m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1])).x) + const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0].c_str())).x, + m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1].c_str())).x) + m_imgui->scaled(2.5f); const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 0cbfaeedcf..b137dd5c18 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -85,8 +85,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) + m_imgui->scaled(1.5f); const float cursor_size_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float cursor_type_combo_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f); - const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0])).x, - m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1])).x) + const float cursor_type_combo_width = std::max(m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[0].c_str())).x, + m_imgui->calc_text_size(wxString::FromUTF8(cursor_types[1].c_str())).x) + m_imgui->scaled(2.5f); const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); const float minimal_slider_width = m_imgui->scaled(4.f); From 1fb400a091747c142befe32ca4d8430f2d06b7f0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 2 Oct 2020 08:32:44 +0200 Subject: [PATCH 602/826] use wxYield on mac to show the splashscreen --- src/slic3r/GUI/GUI_App.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 23f79c65fc..409ca0a150 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -696,6 +696,7 @@ bool GUI_App::on_init_inner() // create splash screen with updated bmp scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("prusa_slicer_logo", nullptr, 400), wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos, is_decorated); + wxYield(); scrn->SetText(_L("Loading configuration...")); } From 3ec462e8f5f2d3c7c637997da0d82f8df77e0f93 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 2 Oct 2020 09:02:16 +0200 Subject: [PATCH 603/826] Fixed a crash when preset with "modified"suffix is selected --- src/slic3r/GUI/Tab.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index e3ee9481c8..3517381cf7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -176,7 +176,8 @@ void Tab::create_preset_tab() m_preset_bundle->physical_printers.unselect_printer(); // select preset - select_preset(m_presets_choice->GetString(selection).ToUTF8().data()); + std::string preset_name = m_presets_choice->GetString(selection).ToUTF8().data(); + select_preset(Preset::remove_suffix_modified(preset_name)); } }); From 1130778d5e29d9bdb071b050b7e0b8db995e7c59 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 2 Oct 2020 09:14:43 +0200 Subject: [PATCH 604/826] Small fix in debug tech ENABLE_GCODE_VIEWER_DATA_CHECKING --- src/libslic3r/GCode/GCodeProcessor.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index b31591ca86..a0cf5d6e5c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -305,10 +305,12 @@ namespace Slic3r { {} void update(float value, ExtrusionRole role) { - ++count; - if (last_tag_value != 0.0f) { - if (std::abs(value - last_tag_value) / last_tag_value > threshold) - errors.push_back({ value, last_tag_value, role }); + if (role != erCustom) { + ++count; + if (last_tag_value != 0.0f) { + if (std::abs(value - last_tag_value) / last_tag_value > threshold) + errors.push_back({ value, last_tag_value, role }); + } } } From dd94b34a8d092305f184cf2e5029fd19e91c8296 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 2 Oct 2020 09:30:35 +0200 Subject: [PATCH 605/826] Fixed missing include on Linux, printf format string fix boost/format.hpp was missing in the header --- src/slic3r/GUI/GUI_Utils.cpp | 21 +++++++++++++++++++++ src/slic3r/GUI/GUI_Utils.hpp | 20 ++------------------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 97e66812a6..e2a6ccb885 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -267,5 +267,26 @@ std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics) } +TaskTimer::TaskTimer(std::string task_name): + task_name(task_name.empty() ? "task" : task_name) +{ + start_timer = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()); +} + +TaskTimer::~TaskTimer() +{ + std::chrono::milliseconds stop_timer = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()); + auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count(); + std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str(); + printf("%s", out.c_str()); +#ifdef __WXMSW__ + std::wstring stemp = std::wstring(out.begin(), out.end()); + OutputDebugString(stemp.c_str()); +#endif +} + + } } diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 249ae74f69..edc9fba1fe 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -399,25 +399,9 @@ class TaskTimer std::chrono::milliseconds start_timer; std::string task_name; public: - TaskTimer(std::string task_name): - task_name(task_name.empty() ? "task" : task_name) - { - start_timer = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } + TaskTimer(std::string task_name); - ~TaskTimer() - { - std::chrono::milliseconds stop_timer = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count(); - std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str(); - printf(out.c_str()); -#ifdef __WXMSW__ - std::wstring stemp = std::wstring(out.begin(), out.end()); - OutputDebugString(stemp.c_str()); -#endif - } + ~TaskTimer(); }; }} From 3fe61cfec2fa3fa89cf47bdc4a21aeef9c95937b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 2 Oct 2020 10:26:27 +0200 Subject: [PATCH 606/826] Progress dialog while generating toolpaths to render enabled only for standalone gcode viewer --- src/slic3r/GUI/GCodeViewer.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index a6914f7681..aae0f4f071 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -865,8 +865,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned int progress_count = 0; static const unsigned int progress_threshold = 1000; - wxProgressDialog progress_dialog(_L("Generating toolpaths"), "...", - 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL); + wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? + new wxProgressDialog(_L("Generating toolpaths"), "...", + 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; @@ -1248,10 +1249,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) continue; ++progress_count; - if (progress_count % progress_threshold == 0) { - progress_dialog.Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), + if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { + progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog.Fit(); + progress_dialog->Fit(); progress_count = 0; } @@ -1322,10 +1323,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) continue; ++progress_count; - if (progress_count % progress_threshold == 0) { - progress_dialog.Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), + if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { + progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog.Fit(); + progress_dialog->Fit(); progress_count = 0; } @@ -1473,6 +1474,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS + + if (progress_dialog != nullptr) + progress_dialog->Destroy(); } void GCodeViewer::load_shells(const Print& print, bool initialized) From ec6599da98a5a478a2ca6c0f0ca5e042b0712875 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 1 Oct 2020 19:39:51 +0200 Subject: [PATCH 607/826] notifications: sla supports outside error appearing/disappearing --- src/slic3r/GUI/GLCanvas3D.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c138b937c9..60192f0a24 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2662,6 +2662,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstancePVS_Partly_Outside); _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstancePVS_Fully_Outside); + if(printer_technology != ptSLA || state == ModelInstancePVS_Inside) + _set_warning_texture(WarningTexture::SlaSupportsOutside, false); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, contained_min_one && !m_model->objects.empty() && state != ModelInstancePVS_Partly_Outside)); @@ -2670,6 +2672,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re { _set_warning_texture(WarningTexture::ObjectOutside, false); _set_warning_texture(WarningTexture::ObjectClashed, false); + _set_warning_texture(WarningTexture::SlaSupportsOutside, false); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } From 092a9f80b6828bc214ae65428ae0984acb043379 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 2 Oct 2020 10:26:11 +0200 Subject: [PATCH 608/826] notifications: avoid collision with gizmos on same position by moving to left --- src/slic3r/GUI/GLCanvas3D.cpp | 17 ++++++++++++++++- src/slic3r/GUI/GLCanvas3D.hpp | 20 ++++++++++++++++++-- src/slic3r/GUI/NotificationManager.cpp | 12 +++++++----- src/slic3r/GUI/NotificationManager.hpp | 10 ++++++++-- src/slic3r/GUI/Plater.cpp | 1 + 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 60192f0a24..b88b642f89 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -215,6 +215,8 @@ void GLCanvas3D::LayersEditing::set_enabled(bool enabled) m_enabled = is_allowed() && enabled; } +float GLCanvas3D::LayersEditing::s_overelay_window_width; + void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const { if (!m_enabled) @@ -297,6 +299,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const if (imgui.button(_L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); + GLCanvas3D::LayersEditing::s_overelay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; imgui.end(); const Rect& bar_rect = get_bar_rect_viewport(canvas); @@ -1426,6 +1429,16 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas } #if ENABLE_SLOPE_RENDERING + +float GLCanvas3D::Slope::s_window_width; + +void GLCanvas3D::Slope::show_dialog(bool show) { + if (show && is_used()) + return; use(show); + m_dialog_shown = show; + wxGetApp().plater()->get_notification_manager()->set_move_from_slope(show); +} + void GLCanvas3D::Slope::render() const { if (m_dialog_shown) { @@ -1482,6 +1495,8 @@ void GLCanvas3D::Slope::render() const if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x) m_canvas.request_extra_frame(); + s_window_width = ImGui::GetWindowSize().x; + imgui.end(); if (modified) @@ -2151,7 +2166,7 @@ void GLCanvas3D::render() wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); - wxGetApp().plater()->get_notification_manager()->render_notifications(*this); + wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overelay_window_width(), get_slope_window_width()); wxGetApp().imgui()->render(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 03d42089b8..127f822c80 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -186,6 +186,8 @@ private: mutable float m_adaptive_quality; mutable HeightProfileSmoothingParams m_smooth_params; + + static float s_overelay_window_width; class LayersTexture { @@ -241,6 +243,7 @@ private: static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); static Rect get_bar_rect_screen(const GLCanvas3D& canvas); static Rect get_bar_rect_viewport(const GLCanvas3D& canvas); + static float get_overelay_window_width() { return LayersEditing::s_overelay_window_width; } float object_max_z() const { return m_object_max_z; } @@ -254,6 +257,7 @@ private: void update_slicing_parameters(); static float thickness_bar_width(const GLCanvas3D &canvas); + }; struct Mouse @@ -425,7 +429,7 @@ private: bool m_dialog_shown{ false }; GLCanvas3D& m_canvas; GLVolumeCollection& m_volumes; - + static float s_window_width; public: Slope(GLCanvas3D& canvas, GLVolumeCollection& volumes) : m_canvas(canvas), m_volumes(volumes) {} @@ -433,12 +437,13 @@ private: bool is_enabled() const { return m_enabled; } void use(bool use) { m_volumes.set_slope_active(m_enabled ? use : false); } bool is_used() const { return m_volumes.is_slope_active(); } - void show_dialog(bool show) { if (show && is_used()) return; use(show); m_dialog_shown = show; } + void show_dialog(bool show); bool is_dialog_shown() const { return m_dialog_shown; } void render() const; void set_range(const std::array& range) const { m_volumes.set_slope_z_range({ -::cos(Geometry::deg2rad(90.0f - range[0])), -::cos(Geometry::deg2rad(90.0f - range[1])) }); } + static float get_window_width() { return s_window_width; }; }; #endif // ENABLE_SLOPE_RENDERING @@ -772,6 +777,8 @@ public: void set_slope_range(const std::array& range) { m_slope.set_range(range); } #endif // ENABLE_SLOPE_RENDERING + + private: bool _is_shown_on_screen() const; @@ -892,6 +899,15 @@ private: bool _activate_search_toolbar_item(); bool _deactivate_collapse_toolbar_items(); + float get_overelay_window_width() { return LayersEditing::get_overelay_window_width(); } + float get_slope_window_width() { +#if ENABLE_SLOPE_RENDERING + return Slope::get_window_width(); +#else + return 0.0f; +#endif + } + static std::vector _parse_colors(const std::vector& colors); public: diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b9e7c7a6fc..228e6c3e9c 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -54,7 +54,7 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, NotificationManager::PopNotification::~PopNotification() { } -NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y) +NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y, bool move_from_overlay, float overlay_width, bool move_from_slope, float slope_width) { if (!m_initialized) { init(); @@ -76,6 +76,7 @@ NotificationManager::PopNotification::RenderResult NotificationManager::PopNotif bool shown = true; std::string name; ImVec2 mouse_pos = ImGui::GetMousePos(); + float right_gap = SPACE_RIGHT_PANEL + (move_from_overlay ? overlay_width + m_line_height * 5 : (move_from_slope ? slope_width /*+ m_line_height * 0.3f*/ : 0)); if (m_line_height != ImGui::CalcTextSize("A").y) init(); @@ -85,10 +86,11 @@ NotificationManager::PopNotification::RenderResult NotificationManager::PopNotif //top y of window m_top_y = initial_y + m_window_height; //top right position - ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y); + + ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - right_gap, 1.0f * (float)cnv_size.get_height() - m_top_y); imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f); imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always); - + //find if hovered if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height) { @@ -820,7 +822,7 @@ bool NotificationManager::push_notification_data(NotificationManager::PopNotific return false; } } -void NotificationManager::render_notifications(GLCanvas3D& canvas) +void NotificationManager::render_notifications(GLCanvas3D& canvas, float overlay_width, float slope_width) { float last_x = 0.0f; float current_height = 0.0f; @@ -835,7 +837,7 @@ void NotificationManager::render_notifications(GLCanvas3D& canvas) it = m_pop_notifications.erase(it); } else { (*it)->set_paused(m_hovered); - PopNotification::RenderResult res = (*it)->render(canvas, last_x); + PopNotification::RenderResult res = (*it)->render(canvas, last_x, m_move_from_overlay, overlay_width, m_move_from_slope, slope_width); if (res != PopNotification::RenderResult::Finished) { last_x = (*it)->get_top() + GAP_WIDTH; current_height = std::max(current_height, (*it)->get_current_top()); diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index c2cbc242c8..24be35b91a 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -76,7 +76,7 @@ public: }; PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler); virtual ~PopNotification(); - RenderResult render(GLCanvas3D& canvas, const float& initial_y); + RenderResult render(GLCanvas3D& canvas, const float& initial_y, bool move_from_overlay, float overlay_width, bool move_from_slope, float slope_width); // close will dissapear notification on next render void close() { m_close_pending = true; } // data from newer notification of same type @@ -229,11 +229,15 @@ public: void set_slicing_complete_print_time(std::string info); void set_slicing_complete_large(bool large); // renders notifications in queue and deletes expired ones - void render_notifications(GLCanvas3D& canvas); + void render_notifications(GLCanvas3D& canvas, float overlay_width, float slope_width); // finds and closes all notifications of given type void close_notification_of_type(const NotificationType type); void dpi_changed(); void set_in_preview(bool preview); + // Move to left to avoid colision with variable layer height gizmo + void set_move_from_overlay(bool move) { m_move_from_overlay = move; } + // or slope visualization gizmo + void set_move_from_slope (bool move) { m_move_from_slope = move; } private: //pushes notification into the queue of notifications that are rendered //can be used to create custom notification @@ -252,6 +256,8 @@ private: //timestamps used for slining finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; bool m_in_preview { false }; + bool m_move_from_overlay { false }; + bool m_move_from_slope{ false }; //prepared (basic) notifications const std::vector basic_notifications = { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6607bd5752..8111cbcae1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3625,6 +3625,7 @@ void Plater::priv::on_action_split_volumes(SimpleEvent&) void Plater::priv::on_action_layersediting(SimpleEvent&) { view3D->enable_layers_editing(!view3D->is_layers_editing_enabled()); + notification_manager->set_move_from_overlay(view3D->is_layers_editing_enabled()); } void Plater::priv::on_object_select(SimpleEvent& evt) From 11d8a2ad8e8635a568d1ba4f6ea5e83151eb4d2f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 2 Oct 2020 11:40:21 +0200 Subject: [PATCH 609/826] Start PrusaSlicer in gcode viewer mode when dragging and dropping a .gcode file on the application icon --- src/PrusaSlicer.cpp | 11 +++++++++++ src/libslic3r/Technologies.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 19 +++++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index d79196cfa1..8f96de3edb 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -154,6 +154,17 @@ int CLI::run(int argc, char **argv) // Read input file(s) if any. #if ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_DRAG_AND_DROP_GCODE_FILES + for (const std::string& file : m_input_files) { + std::string ext = boost::filesystem::path(file).extension().string(); + if (boost::filesystem::path(file).extension().string() == ".gcode") { + if (boost::filesystem::exists(file)) { + start_as_gcodeviewer = true; + break; + } + } + } +#endif // ENABLE_GCODE_DRAG_AND_DROP_GCODE_FILES if (!start_as_gcodeviewer) { #endif // ENABLE_GCODE_VIEWER for (const std::string& file : m_input_files) { diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a0484b259c..786557cf1e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -59,5 +59,6 @@ #define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) #define ENABLE_GCODE_VIEWER_TASKBAR_ICON (0 && ENABLE_GCODE_VIEWER) +#define ENABLE_GCODE_DRAG_AND_DROP_GCODE_FILES (1 && ENABLE_GCODE_VIEWER) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ca9ddf512e..4ecd36c7f2 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -89,10 +89,26 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // Load the icon either from the exe, or from the ico file. #if _WIN32 { - +#if ENABLE_GCODE_DRAG_AND_DROP_GCODE_FILES + switch (wxGetApp().get_app_mode()) + { + default: + case GUI_App::EAppMode::Editor: + { + SetIcon(wxIcon(Slic3r::var("PrusaSlicer.ico"), wxBITMAP_TYPE_ICO)); + break; + } + case GUI_App::EAppMode::GCodeViewer: + { + SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer.ico"), wxBITMAP_TYPE_ICO)); + break; + } + } +#else TCHAR szExeFileName[MAX_PATH]; GetModuleFileName(nullptr, szExeFileName, MAX_PATH); SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); +#endif // ENABLE_GCODE_DRAG_AND_DROP_GCODE_FILES } #else #if ENABLE_GCODE_VIEWER @@ -1965,7 +1981,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) // Load the icon either from the exe, or from the ico file. #if _WIN32 { - TCHAR szExeFileName[MAX_PATH]; GetModuleFileName(nullptr, szExeFileName, MAX_PATH); SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); From 97e3100c730c68e74bd727f406da0d86cfa6afe7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 2 Oct 2020 11:42:27 +0200 Subject: [PATCH 610/826] Removed obsolete images for splash screen --- resources/icons/splashscreen-gcodeviewer.jpg | Bin 135897 -> 0 bytes resources/icons/splashscreen_.jpg | Bin 104443 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 resources/icons/splashscreen-gcodeviewer.jpg delete mode 100644 resources/icons/splashscreen_.jpg diff --git a/resources/icons/splashscreen-gcodeviewer.jpg b/resources/icons/splashscreen-gcodeviewer.jpg deleted file mode 100644 index f170f390c2b822410ef2853041ce58563410ed25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135897 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
-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 diff --git a/resources/icons/splashscreen_.jpg b/resources/icons/splashscreen_.jpg deleted file mode 100644 index 2b83ed3516ddc2ba943775da9221890564f23ed2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104443 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 Date: Fri, 2 Oct 2020 12:11:10 +0200 Subject: [PATCH 611/826] Deleted use of TaskTimer in OnInit + code cleaning in MainFrame --- src/slic3r/GUI/GUI_App.cpp | 3 --- src/slic3r/GUI/MainFrame.cpp | 2 -- src/slic3r/GUI/Tab.cpp | 4 +--- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 23f79c65fc..09dcf875be 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -634,7 +634,6 @@ bool GUI_App::OnInit() bool GUI_App::on_init_inner() { - TaskTimer timer("on_init"); // Verify resources path const wxString resources_dir = from_u8(Slic3r::resources_dir()); wxCHECK_MSG(wxDirExists(resources_dir), false, @@ -759,8 +758,6 @@ bool GUI_App::on_init_inner() #endif // ENABLE_GCODE_VIEWER scrn->SetText(_L("Creating settings tabs...")); - TaskTimer timer2("Creating settings tabs"); - mainframe = new MainFrame(); // hide settings tabs after first Layout mainframe->select_tab(size_t(0)); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 4ecd36c7f2..6ee496052c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -668,8 +668,6 @@ void MainFrame::add_created_tab(Tab* panel) bool MainFrame::is_active_and_shown_tab(Tab* tab) { - if (!this) - return false; int page_id = m_tabpanel->FindPage(tab); if (m_tabpanel->GetSelection() != page_id) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3517381cf7..924f3c8bbd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3471,10 +3471,8 @@ bool Tab::tree_sel_change_delayed() //for (auto& el : m_pages) // el.get()->Hide(); - if (wxGetApp().mainframe->is_active_and_shown_tab(this)) { + if (wxGetApp().mainframe!=nullptr && wxGetApp().mainframe->is_active_and_shown_tab(this)) activate_selected_page(throw_if_canceled); - // m_active_page->Show(); - } #ifdef __linux__ no_updates.reset(nullptr); From 1f4010ba4ed3bd306cd81381c3d8ef27de18f5f3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 2 Oct 2020 13:02:56 +0200 Subject: [PATCH 612/826] Orange background for imgui buttons --- src/slic3r/GUI/ImGuiWrapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index f9a23cb51c..d759f4b9af 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -23,7 +23,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" -#include "3DScene.hpp" +#include "3DScene.hpp"+ #include "GUI.hpp" #include "I18N.hpp" #include "Search.hpp" @@ -57,8 +57,8 @@ const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f }; const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f }; const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f }; const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.133f, 0.133f, 0.133f, 0.8f }; -const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = { 0.233f, 0.233f, 0.233f, 1.0f }; -const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = { 0.433f, 0.433f, 0.433f, 1.8f }; +const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = COL_ORANGE_DARK; +const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = COL_ORANGE_LIGHT; const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED; ImGuiWrapper::ImGuiWrapper() From 38ddc5fdc4efece8552780a6ae8257377d466736 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 2 Oct 2020 13:20:48 +0200 Subject: [PATCH 613/826] New image for gcode viewer splash screen --- resources/icons/splashscreen-gcodepreview.jpg | Bin 0 -> 127316 bytes src/slic3r/GUI/GUI_App.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 resources/icons/splashscreen-gcodepreview.jpg diff --git a/resources/icons/splashscreen-gcodepreview.jpg b/resources/icons/splashscreen-gcodepreview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3bae384935ecadff5051a4d2f06f9bef7195a7e5 GIT binary patch literal 127316 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

qKg8 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"show_splash_screen") == "1") { - wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG)); + wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); // Detect position (display) to show the splash screen // Now this position is equal to the mainframe position From 0f44caa99cd0a238b8882f75008ae0a040631361 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 2 Oct 2020 15:43:39 +0200 Subject: [PATCH 614/826] ENABLE_SLOPE_RENDERING set as default Slope rendering active only when using Gizmo FDM supports --- resources/shaders/gouraud.fs | 6 +- resources/shaders/gouraud.vs | 3 +- src/libslic3r/Technologies.hpp | 3 - src/slic3r/GUI/3DScene.cpp | 30 +----- src/slic3r/GUI/3DScene.hpp | 22 +---- src/slic3r/GUI/GLCanvas3D.cpp | 99 +------------------- src/slic3r/GUI/GLCanvas3D.hpp | 30 +----- src/slic3r/GUI/GUI_Preview.cpp | 2 - src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 25 +++-- src/slic3r/GUI/KBShortcutsDialog.cpp | 3 - src/slic3r/GUI/MainFrame.cpp | 11 --- src/slic3r/GUI/NotificationManager.hpp | 2 - src/slic3r/GUI/Plater.cpp | 10 -- src/slic3r/GUI/Plater.hpp | 5 - 14 files changed, 30 insertions(+), 221 deletions(-) diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 45175acc21..b1a8d6ac28 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -9,8 +9,7 @@ const float EPSILON = 0.0001; struct SlopeDetection { bool actived; - // x = yellow, y = red - vec2 z_range; + float normal_z; mat3 volume_world_normal_matrix; }; @@ -33,8 +32,7 @@ varying vec3 eye_normal; vec3 slope_color() { - float gradient_range = slope.z_range.x - slope.z_range.y; - return (world_normal_z > slope.z_range.x - EPSILON) ? GREEN : ((gradient_range == 0.0) ? RED : mix(RED, YELLOW, clamp((world_normal_z - slope.z_range.y) / gradient_range, 0.0, 1.0))); + return (world_normal_z > slope.normal_z - EPSILON) ? GREEN : RED; } void main() diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index d60f6eae8a..ed7e3f56ba 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -29,8 +29,7 @@ struct PrintBoxDetection struct SlopeDetection { bool actived; - // x = yellow, y = red - vec2 z_range; + float normal_z; mat3 volume_world_normal_matrix; }; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 786557cf1e..a1f503d59a 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -39,9 +39,6 @@ //=================== #define ENABLE_2_3_0_ALPHA1 1 -// Enable rendering of objects colored by facets' slope -#define ENABLE_SLOPE_RENDERING (1 && ENABLE_2_3_0_ALPHA1) - // Enable rendering of objects using environment map #define ENABLE_ENVIRONMENT_MAP (1 && ENABLE_2_3_0_ALPHA1) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 84a5e4d2fc..fc285e3356 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -506,24 +506,6 @@ void GLVolume::render() const glFrontFace(GL_CCW); } -#if !ENABLE_SLOPE_RENDERING -void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const -{ - if (color_id >= 0) - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)render_color)); - else - glsafe(::glColor4fv(render_color)); - - if (detection_id != -1) - glsafe(::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0)); - - if (worldmatrix_id != -1) - glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data())); - - render(); -} -#endif // !ENABLE_SLOPE_RENDERING - bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); } @@ -775,9 +757,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("print_box.max", m_print_box_max, 3); shader->set_uniform("z_range", m_z_range, 2); shader->set_uniform("clipping_plane", m_clipping_plane, 4); -#if ENABLE_SLOPE_RENDERING - shader->set_uniform("slope.z_range", m_slope.z_range); -#endif // ENABLE_SLOPE_RENDERING + shader->set_uniform("slope.normal_z", m_slope.normal_z); #if ENABLE_ENVIRONMENT_MAP unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id(); @@ -791,7 +771,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); -#if ENABLE_SLOPE_RENDERING shader->set_uniform("uniform_color", volume.first->render_color, 4); shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled); shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); @@ -799,9 +778,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("slope.volume_world_normal_matrix", static_cast(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast())); volume.first->render(); -#else - volume.first->render(color_id, print_box_detection_id, print_box_worldmatrix_id); -#endif // ENABLE_SLOPE_RENDERING } #if ENABLE_ENVIRONMENT_MAP @@ -2020,12 +1996,8 @@ void GLModel::render() const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); -#if ENABLE_SLOPE_RENDERING shader->set_uniform("uniform_color", m_volume.render_color, 4); m_volume.render(); -#else - m_volume.render(color_id, -1, -1); -#endif // ENABLE_SLOPE_RENDERING glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 31e974be15..a6362dadc6 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -447,9 +447,6 @@ public: void set_range(double low, double high); void render() const; -#if !ENABLE_SLOPE_RENDERING - void render(int color_id, int detection_id, int worldmatrix_id) const; -#endif // !ENABLE_SLOPE_RENDERING void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } @@ -494,26 +491,19 @@ private: // plane coeffs for clipping in shaders float m_clipping_plane[4]; -#if ENABLE_SLOPE_RENDERING struct Slope { // toggle for slope rendering bool active{ false }; - // [0] = yellow, [1] = red - std::array z_range; + float normal_z; }; Slope m_slope; -#endif // ENABLE_SLOPE_RENDERING public: GLVolumePtrs volumes; -#if ENABLE_SLOPE_RENDERING - GLVolumeCollection() { set_default_slope_z_range(); } -#else - GLVolumeCollection() = default; -#endif // ENABLE_SLOPE_RENDERING + GLVolumeCollection() { set_default_slope_normal_z(); } ~GLVolumeCollection() { clear(); } std::vector load_object( @@ -572,14 +562,12 @@ public: void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; } -#if ENABLE_SLOPE_RENDERING bool is_slope_active() const { return m_slope.active; } void set_slope_active(bool active) { m_slope.active = active; } - const std::array& get_slope_z_range() const { return m_slope.z_range; } - void set_slope_z_range(const std::array& range) { m_slope.z_range = range; } - void set_default_slope_z_range() { m_slope.z_range = { -::cos(Geometry::deg2rad(90.0f - 45.0f)), -::cos(Geometry::deg2rad(90.0f - 70.0f)) }; } -#endif // ENABLE_SLOPE_RENDERING + float get_slope_normal_z() const { return m_slope.normal_z; } + void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } + void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); } // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b88b642f89..d90ad8a871 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1414,7 +1414,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha); imgui.set_next_window_pos(position(0), position(1), ImGuiCond_Always, 0.0f, 0.0f); - imgui.begin(_(L("canvas_tooltip")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); + imgui.begin(_L("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); ImGui::TextUnformatted(m_text.c_str()); @@ -1428,83 +1428,8 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas ImGui::PopStyleVar(2); } -#if ENABLE_SLOPE_RENDERING - float GLCanvas3D::Slope::s_window_width; -void GLCanvas3D::Slope::show_dialog(bool show) { - if (show && is_used()) - return; use(show); - m_dialog_shown = show; - wxGetApp().plater()->get_notification_manager()->set_move_from_slope(show); -} - -void GLCanvas3D::Slope::render() const -{ - if (m_dialog_shown) { - const std::array& z_range = m_volumes.get_slope_z_range(); - std::array angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f }; - bool modified = false; - - ImGuiWrapper& imgui = *wxGetApp().imgui(); - const Size& cnv_size = m_canvas.get_canvas_size(); - imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f); - imgui.begin(_L("Slope visualization"), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - imgui.text(_L("Facets' slope range (degrees)") + ":"); - - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.0f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.0f, 0.0f, 1.0f)); - - // angle_range is range of normal angle, GUI should - // show facet slope angle - float slope_bound = 90.f - angle_range[1]; - bool mod = ImGui::SliderFloat("##red", &slope_bound, 0.0f, 90.0f, "%.1f"); - angle_range[1] = 90.f - slope_bound; - if (mod) { - modified = true; - if (angle_range[0] > angle_range[1]) - angle_range[0] = angle_range[1]; - } - - ImGui::PopStyleColor(4); - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f)); - - slope_bound = 90.f - angle_range[0]; - mod = ImGui::SliderFloat("##yellow", &slope_bound, 0.0f, 90.0f, "%.1f"); - angle_range[0] = 90.f - slope_bound; - if (mod) { - modified = true; - if (angle_range[1] < angle_range[0]) - angle_range[1] = angle_range[0]; - } - - ImGui::PopStyleColor(4); - - ImGui::Separator(); - - if (imgui.button(_(L("Default")))) - m_volumes.set_default_slope_z_range(); - - // to let the dialog immediately showup without waiting for a mouse move - if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x) - m_canvas.request_extra_frame(); - - s_window_width = ImGui::GetWindowSize().x; - - imgui.end(); - - if (modified) - set_range(angle_range); - } - } -#endif // ENABLE_SLOPE_RENDERING - wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); @@ -1577,9 +1502,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) #endif // ENABLE_RENDER_PICKING_PASS , m_render_sla_auxiliaries(true) , m_labels(*this) -#if ENABLE_SLOPE_RENDERING , m_slope(*this, m_volumes) -#endif // ENABLE_SLOPE_RENDERING { if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); @@ -1887,11 +1810,6 @@ bool GLCanvas3D::is_reload_delayed() const void GLCanvas3D::enable_layers_editing(bool enable) { -#if ENABLE_SLOPE_RENDERING - if (enable && m_slope.is_dialog_shown()) - m_slope.show_dialog(false); -#endif // ENABLE_SLOPE_RENDERING - m_layers_editing.set_enabled(enable); const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); for (unsigned int idx : idxs) @@ -3105,17 +3023,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } case 'B': case 'b': { zoom_to_bed(); break; } -#if ENABLE_SLOPE_RENDERING - case 'D': - case 'd': { - if (!is_layers_editing_enabled()) - { - m_slope.show_dialog(!m_slope.is_dialog_shown()); - m_dirty = true; - } - break; - } -#endif // ENABLE_SLOPE_RENDERING case 'E': case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; } case 'I': @@ -5684,10 +5591,6 @@ void GLCanvas3D::_render_overlays() const } m_labels.render(sorted_instances); -#if ENABLE_SLOPE_RENDERING - m_slope.render(); -#endif // ENABLE_SLOPE_RENDERING - glsafe(::glPopMatrix()); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 127f822c80..886944488f 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -422,7 +422,6 @@ private: bool is_in_imgui() const { return m_in_imgui; } }; -#if ENABLE_SLOPE_RENDERING class Slope { bool m_enabled{ false }; @@ -437,15 +436,11 @@ private: bool is_enabled() const { return m_enabled; } void use(bool use) { m_volumes.set_slope_active(m_enabled ? use : false); } bool is_used() const { return m_volumes.is_slope_active(); } - void show_dialog(bool show); - bool is_dialog_shown() const { return m_dialog_shown; } - void render() const; - void set_range(const std::array& range) const { - m_volumes.set_slope_z_range({ -::cos(Geometry::deg2rad(90.0f - range[0])), -::cos(Geometry::deg2rad(90.0f - range[1])) }); + void set_normal_angle(float angle_in_deg) const { + m_volumes.set_slope_normal_z(-::cos(Geometry::deg2rad(90.0f - angle_in_deg))); } static float get_window_width() { return s_window_width; }; }; -#endif // ENABLE_SLOPE_RENDERING public: enum ECursorType : unsigned char @@ -534,9 +529,7 @@ private: Labels m_labels; mutable Tooltip m_tooltip; mutable bool m_tooltip_enabled{ true }; -#if ENABLE_SLOPE_RENDERING Slope m_slope; -#endif // ENABLE_SLOPE_RENDERING public: explicit GLCanvas3D(wxGLCanvas* canvas); @@ -621,9 +614,7 @@ public: void enable_undoredo_toolbar(bool enable); void enable_dynamic_background(bool enable); void enable_labels(bool enable) { m_labels.enable(enable); } -#if ENABLE_SLOPE_RENDERING void enable_slope(bool enable) { m_slope.enable(enable); } -#endif // ENABLE_SLOPE_RENDERING void allow_multisample(bool allow); void zoom_to_bed(); @@ -770,14 +761,9 @@ public: bool are_labels_shown() const { return m_labels.is_shown(); } void show_labels(bool show) { m_labels.show(show); } -#if ENABLE_SLOPE_RENDERING - bool is_slope_shown() const { return m_slope.is_dialog_shown(); } + bool is_using_slope() const { return m_slope.is_used(); } void use_slope(bool use) { m_slope.use(use); } - void show_slope(bool show) { m_slope.show_dialog(show); } - void set_slope_range(const std::array& range) { m_slope.set_range(range); } -#endif // ENABLE_SLOPE_RENDERING - - + void set_slope_normal_angle(float angle_in_deg) { m_slope.set_normal_angle(angle_in_deg); } private: bool _is_shown_on_screen() const; @@ -900,13 +886,7 @@ private: bool _deactivate_collapse_toolbar_items(); float get_overelay_window_width() { return LayersEditing::get_overelay_window_width(); } - float get_slope_window_width() { -#if ENABLE_SLOPE_RENDERING - return Slope::get_window_width(); -#else - return 0.0f; -#endif - } + float get_slope_window_width() { return Slope::get_window_width(); } static std::vector _parse_colors(const std::vector& colors); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 8ea54c6f18..b9e0afc65a 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -74,9 +74,7 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba m_canvas->enable_main_toolbar(true); m_canvas->enable_undoredo_toolbar(true); m_canvas->enable_labels(true); -#if ENABLE_SLOPE_RENDERING m_canvas->enable_slope(true); -#endif // ENABLE_SLOPE_RENDERING wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index c6b9a952bc..3455a30d27 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -150,6 +150,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(cursor_slider_left); ImGui::PushItemWidth(window_width - cursor_slider_left); @@ -163,6 +164,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l } + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_type")); ImGui::SameLine(window_width - cursor_type_combo_width - m_imgui->scaled(0.5f)); ImGui::PushItemWidth(cursor_type_combo_width); @@ -180,8 +182,10 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("clipping_of_view")); + } else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ @@ -206,23 +210,24 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->end(); } else { - std::string name = "Autoset custom supports"; - m_imgui->begin(wxString(name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->text("Threshold:"); + m_imgui->begin(_L("Autoset custom supports"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + ImGui::AlignTextToFramePadding(); + m_imgui->text(_L("Threshold:") + " " + _L("deg")); ImGui::SameLine(); if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) - m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - if (m_imgui->button("Enforce")) + m_parent.set_slope_normal_angle(90.f - m_angle_threshold_deg); + if (m_imgui->button(_L("Enforce"))) select_facets_by_angle(m_angle_threshold_deg, false); ImGui::SameLine(); - if (m_imgui->button("Block")) + if (m_imgui->button(_L("Block"))) select_facets_by_angle(m_angle_threshold_deg, true); ImGui::SameLine(); - if (m_imgui->button("Cancel")) + if (m_imgui->button(_L("Cancel"))) m_setting_angle = false; m_imgui->end(); - if (! m_setting_angle) { - m_parent.use_slope(false); + bool needs_update = !(m_setting_angle && m_parent.is_using_slope()); + if (needs_update) { + m_parent.use_slope(m_setting_angle); m_parent.set_as_dirty(); } } diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 4affd13269..f2cc6f5a65 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -140,9 +140,6 @@ void KBShortcutsDialog::fill_shortcuts() // View { "0-6", L("Camera view") }, { "E", L("Show/Hide object/instance labels") }, -#if ENABLE_SLOPE_RENDERING - { "D", L("Turn On/Off facets' slope rendering") }, -#endif // ENABLE_SLOPE_RENDERING // Configuration { ctrl + "P", L("Preferences") }, // Help diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6ee496052c..f5570c5147 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1278,20 +1278,9 @@ void MainFrame::init_menubar() "", nullptr, [this](){return can_change_view(); }, this); #endif // ENABLE_GCODE_VIEWER viewMenu->AppendSeparator(); -#if ENABLE_SLOPE_RENDERING - wxMenu* options_menu = new wxMenu(); - append_menu_check_item(options_menu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"), - [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this, - [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); - append_menu_check_item(options_menu, wxID_ANY, _L("Show &slope") + sep + "D", _L("Objects coloring using faces' slope"), - [this](wxCommandEvent&) { m_plater->show_view3D_slope(!m_plater->is_view3D_slope_shown()); }, this, - [this]() { return m_plater->is_view3D_shown() && !m_plater->is_view3D_layers_editing_enabled(); }, [this]() { return m_plater->is_view3D_slope_shown(); }, this); - append_submenu(viewMenu, options_menu, wxID_ANY, _L("&Options"), ""); -#else append_menu_check_item(viewMenu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"), [this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this, [this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this); -#endif // ENABLE_SLOPE_RENDERING append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar"), _L("Collapse sidebar"), [this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this, [this]() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this); diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 24be35b91a..dfdf39f0ed 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -236,8 +236,6 @@ public: void set_in_preview(bool preview); // Move to left to avoid colision with variable layer height gizmo void set_move_from_overlay(bool move) { m_move_from_overlay = move; } - // or slope visualization gizmo - void set_move_from_slope (bool move) { m_move_from_slope = move; } private: //pushes notification into the queue of notifications that are rendered //can be used to create custom notification diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8111cbcae1..b9dc23ded1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1590,12 +1590,7 @@ struct Plater::priv bool is_sidebar_collapsed() const { return sidebar->is_collapsed(); } void collapse_sidebar(bool show) { sidebar->collapse(show); } -#if ENABLE_SLOPE_RENDERING - bool is_view3D_slope_shown() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_slope_shown(); } - void show_view3D_slope(bool show) { if (current_panel == view3D) view3D->get_canvas3d()->show_slope(show); } - bool is_view3D_layers_editing_enabled() const { return (current_panel == view3D) && view3D->get_canvas3d()->is_layers_editing_enabled(); } -#endif // ENABLE_SLOPE_RENDERING void set_current_canvas_as_dirty(); GLCanvas3D* get_current_canvas3D(); @@ -4698,12 +4693,7 @@ void Plater::show_view3D_labels(bool show) { p->show_view3D_labels(show); } bool Plater::is_sidebar_collapsed() const { return p->is_sidebar_collapsed(); } void Plater::collapse_sidebar(bool show) { p->collapse_sidebar(show); } -#if ENABLE_SLOPE_RENDERING -bool Plater::is_view3D_slope_shown() const { return p->is_view3D_slope_shown(); } -void Plater::show_view3D_slope(bool show) { p->show_view3D_slope(show); } - bool Plater::is_view3D_layers_editing_enabled() const { return p->is_view3D_layers_editing_enabled(); } -#endif // ENABLE_SLOPE_RENDERING void Plater::select_all() { p->select_all(); } void Plater::deselect_all() { p->deselect_all(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index cc80186202..71cd32415b 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -164,12 +164,7 @@ public: bool is_sidebar_collapsed() const; void collapse_sidebar(bool show); -#if ENABLE_SLOPE_RENDERING - bool is_view3D_slope_shown() const; - void show_view3D_slope(bool show); - bool is_view3D_layers_editing_enabled() const; -#endif // ENABLE_SLOPE_RENDERING // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. From 8df01818dd85560a76d3a33bcff1f3437cede618 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 2 Oct 2020 17:31:55 +0200 Subject: [PATCH 615/826] Limiting the application of Machine Limits https://github.com/prusa3d/PrusaSlicer/issues/1212 WIP: The hints do not rescale when switching the "usage" combo box. The new g-code time estimator needs to be updated to not read the machine limits if not enabled. --- src/libslic3r/GCode.cpp | 82 +++++++++++++------------ src/libslic3r/Preset.cpp | 24 ++++++-- src/libslic3r/Preset.hpp | 2 + src/libslic3r/PrintConfig.cpp | 15 +++++ src/libslic3r/PrintConfig.hpp | 20 ++++++ src/slic3r/GUI/Field.cpp | 2 + src/slic3r/GUI/GUI.cpp | 2 + src/slic3r/GUI/OptionsGroup.cpp | 3 + src/slic3r/GUI/Tab.cpp | 46 +++++++++++++- src/slic3r/GUI/Tab.hpp | 13 ++-- src/slic3r/GUI/UnsavedChangesDialog.cpp | 2 + 11 files changed, 161 insertions(+), 50 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index febdff7e02..5c8c454aab 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -819,48 +819,54 @@ namespace DoExport { // this->print_machine_envelope(file, print); // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor. if (config.gcode_flavor.value == gcfMarlin) { - normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]); - normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]); - normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]); - normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]); - normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]); - normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]); - normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]); - normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]); - normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]); - normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]); - normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]); - normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]); - normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]); - normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]); - normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]); - normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]); - + if (config.machine_limits_type.value != MachineLimitsUsage::Ignore) { + normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]); + normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]); + normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]); + normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]); + } + if (silent_time_estimator_enabled) { silent_time_estimator.reset(); silent_time_estimator.set_dialect(config.gcode_flavor); silent_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]); - /* "Stealth mode" values can be just a copy of "normal mode" values - * (when they aren't input for a printer preset). - * Thus, use back value from values, instead of second one, which could be absent - */ - silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back()); - silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back()); - silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back()); - silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back()); - silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back()); - silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back()); - silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back()); - silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back()); - silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back()); - silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back()); - silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back()); - silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back()); - silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back()); - silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back()); - silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back()); - silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back()); + + if (config.machine_limits_type.value != MachineLimitsUsage::Ignore) { + /* "Stealth mode" values can be just a copy of "normal mode" values + * (when they aren't input for a printer preset). + * Thus, use back value from values, instead of second one, which could be absent + */ + silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back()); + silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back()); + silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back()); + silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back()); + } + if (config.single_extruder_multi_material) { // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they // are considered to be active for the single extruder multi-material printers only. @@ -1694,7 +1700,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. void GCode::print_machine_envelope(FILE *file, Print &print) { - if (print.config().gcode_flavor.value == gcfMarlin) { + if (print.config().gcode_flavor.value == gcfMarlin && print.config().machine_limits_type.value == MachineLimitsUsage::EmitToGCode) { fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), int(print.config().machine_max_acceleration_y.values.front() + 0.5), diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ddcadf9281..182cf11930 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -454,6 +454,21 @@ const std::vector& Preset::filament_options() return s_opts; } +const std::vector& Preset::machine_limits_options() +{ + static std::vector s_opts; + if (s_opts.empty()) { + s_opts = { + "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", + "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", + "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", + "machine_min_extruding_rate", "machine_min_travel_rate", + "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e", + }; + } + return s_opts; +} + const std::vector& Preset::printer_options() { static std::vector s_opts; @@ -468,13 +483,10 @@ const std::vector& Preset::printer_options() "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", - "remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", - "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", - "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", - "machine_min_extruding_rate", "machine_min_travel_rate", - "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e", - "thumbnails" + "remaining_times", "silent_mode", + "machine_limits_usage", "thumbnails" }; + s_opts.insert(s_opts.end(), Preset::machine_limits_options().begin(), Preset::machine_limits_options().end()); s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } return s_opts; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index e3e16b65dc..5713b25c81 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -218,6 +218,8 @@ public: static const std::vector& printer_options(); // Nozzle options of the printer options. static const std::vector& nozzle_options(); + // Printer machine limits, those are contained in printer_options(). + static const std::vector& machine_limits_options(); static const std::vector& sla_printer_options(); static const std::vector& sla_material_options(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 93d9cfbcff..f4b0de707e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1201,6 +1201,21 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("machine_limits_usage", coEnum); + def->label = L("How to apply"); + def->full_label = L("Purpose of Machine Limits"); + def->category = L("Machine limits"); + def->tooltip = L("How to apply the Machine Limits"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("emit_to_gcode"); + def->enum_values.push_back("time_estimate_only"); + def->enum_values.push_back("ignore"); + def->enum_labels.push_back("Emit to G-code"); + def->enum_labels.push_back("Use for time estimate"); + def->enum_labels.push_back("Ignore"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(MachineLimitsUsage::EmitToGCode)); + { struct AxisDefault { std::string name; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 52e3bc38cf..9151fd8093 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -29,6 +29,13 @@ enum GCodeFlavor : unsigned char { gcfSmoothie, gcfNoExtrusion, }; +enum class MachineLimitsUsage { + EmitToGCode, + TimeEstimateOnly, + Ignore, + Count, +}; + enum PrintHostType { htOctoPrint, htDuet, htFlashAir, htAstroBox }; @@ -102,6 +109,16 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::get return keys_map; } +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["emit_to_gcode"] = int(MachineLimitsUsage::EmitToGCode); + keys_map["time_estimate_only"] = int(MachineLimitsUsage::TimeEstimateOnly); + keys_map["ignore"] = int(MachineLimitsUsage::Ignore); + } + return keys_map; +} + template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { @@ -597,6 +614,8 @@ class MachineEnvelopeConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(MachineEnvelopeConfig) public: + // Allowing the machine limits to be completely ignored or used just for time estimator. + ConfigOptionEnum machine_limits_type; // M201 X... Y... Z... E... [mm/sec^2] ConfigOptionFloats machine_max_acceleration_x; ConfigOptionFloats machine_max_acceleration_y; @@ -624,6 +643,7 @@ public: protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { + OPT_PTR(machine_limits_type); OPT_PTR(machine_max_acceleration_x); OPT_PTR(machine_max_acceleration_y); OPT_PTR(machine_max_acceleration_z); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 09e29caf92..5392deec99 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1084,6 +1084,8 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("gcode_flavor") == 0) m_value = static_cast(ret_enum); + else if (m_opt_id.compare("machine_limits_usage") == 0) + m_value = static_cast(ret_enum); else if (m_opt_id.compare("support_material_pattern") == 0) m_value = static_cast(ret_enum); else if (m_opt_id.compare("seam_position") == 0) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 6c76b62272..d822c98736 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -184,6 +184,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if (opt_key.compare("gcode_flavor") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); + else if (opt_key.compare("machine_limits_usage") == 0) + config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if (opt_key.compare("support_material_pattern") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if (opt_key.compare("seam_position") == 0) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 7080dc11c2..8f593f1f20 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -821,6 +821,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config else if (opt_key == "gcode_flavor") { ret = static_cast(config.option>(opt_key)->value); } + else if (opt_key == "machine_limits_usage") { + ret = static_cast(config.option>(opt_key)->value); + } else if (opt_key == "support_material_pattern") { ret = static_cast(config.option>(opt_key)->value); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 924f3c8bbd..d46413320f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2510,6 +2510,7 @@ void TabPrinter::build_sla() build_preset_description_line(optgroup.get()); } + /* void TabPrinter::update_serial_ports() { @@ -2556,7 +2557,18 @@ PageShp TabPrinter::build_kinematics_page() { auto page = add_options_page(L("Machine limits"), "cog", true); - if (m_use_silent_mode) { + auto optgroup = page->new_optgroup(L("General")); + { + optgroup->append_single_option_line("machine_limits_usage"); + Line line { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_machine_limits_description_line); + }; + optgroup->append_line(line); + } + + if (m_use_silent_mode) { // Legend for OptionsGroups auto optgroup = page->new_optgroup(""); optgroup->set_show_modified_btns_val(false); @@ -2583,7 +2595,7 @@ PageShp TabPrinter::build_kinematics_page() } std::vector axes{ "x", "y", "z", "e" }; - auto optgroup = page->new_optgroup(L("Maximum feedrates")); + optgroup = page->new_optgroup(L("Maximum feedrates")); for (const std::string &axis : axes) { append_option_line(optgroup, "machine_max_feedrate_" + axis); } @@ -2953,6 +2965,17 @@ void TabPrinter::toggle_options() toggle_option("retract_restart_extra_toolchange", have_multiple_extruders && toolchange_retraction, i); } + if (m_active_page->title() == "Machine limits") { + assert(m_config->option>("gcode_flavor")->value == gcfMarlin); + const auto *machine_limits_usage = m_config->option>("machine_limits_usage"); + bool enabled = machine_limits_usage->value != MachineLimitsUsage::Ignore; + bool silent_mode = m_config->opt_bool("silent_mode"); + int max_field = silent_mode ? 2 : 1; + for (const std::string &opt : Preset::machine_limits_options()) + for (int i = 0; i < max_field; ++ i) + toggle_option(opt, enabled, i); + update_machine_limits_description(machine_limits_usage->value); + } } void TabPrinter::update() @@ -3847,6 +3870,25 @@ void TabPrinter::apply_extruder_cnt_from_cache() } } +void TabPrinter::update_machine_limits_description(const MachineLimitsUsage usage) +{ + wxString text; + switch (usage) { + case MachineLimitsUsage::EmitToGCode: + text = _L("Machine limits will be emitted to G-code and used to estimate print time."); + break; + case MachineLimitsUsage::TimeEstimateOnly: + text = _L("Machine limits will NOT be emitted to G-code, however they will be used to estimate print time, \ + which may herefore not be accurate as the printer may apply a different set of machine limits."); + break; + case MachineLimitsUsage::Ignore: + text = _L("Machine limits are not set, therefore the print time estimate may not be accurate."); + break; + default: assert(false); + } + m_machine_limits_description_line->SetText(text); +} + void Tab::compatible_widget_reload(PresetDependencies &deps) { Field* field = this->get_field(deps.key_condition); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index a3711f453e..bc396ae304 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -376,10 +376,6 @@ public: Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {} ~TabPrint() {} - ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr; - ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr; - bool m_support_material_overhangs_queried = false; - void build() override; void reload_config() override; void update_description_lines() override; @@ -388,10 +384,16 @@ public: // void OnActivate() override; void clear_pages() override; bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } + +private: + ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr; + ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr; + bool m_support_material_overhangs_queried = false; }; class TabFilament : public Tab { +private: ogStaticText* m_volumetric_speed_description_line {nullptr}; ogStaticText* m_cooling_description_line {nullptr}; @@ -418,10 +420,13 @@ public: class TabPrinter : public Tab { +private: bool m_has_single_extruder_MM_page = false; bool m_use_silent_mode = false; void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key); bool m_rebuild_kinematics_page = false; + ogStaticText* m_machine_limits_description_line {nullptr}; + void update_machine_limits_description(const MachineLimitsUsage usage); std::vector m_pages_fff; std::vector m_pages_sla; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index e7dec9fa81..e43f738c4f 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -809,6 +809,8 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& return get_string_from_enum(opt_key, config, true); if (opt_key == "gcode_flavor") return get_string_from_enum(opt_key, config); + if (opt_key == "machine_limits_usage") + return get_string_from_enum(opt_key, config); if (opt_key == "ironing_type") return get_string_from_enum(opt_key, config); if (opt_key == "support_material_pattern") From 48f775decb0e1609ef4ab356f6a558666db2bfc0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 2 Oct 2020 22:27:20 +0200 Subject: [PATCH 616/826] A part of code related to loads after App::OnInit() call is moved from PrusaSlicer.cpp to GUI_App.cpp Splash Screen under OSX requires a call of wxYeild() for update. But wxYield() furthers a case, when CallAfter() in CLI::run() was called at the wrong time, before some of the GUI was created. So, there is workaround: Parameters needed for later loads are encapsulated to GUI_App::AFTER_INIT_LOADS structure and are used in GUI_App::AFTER_INIT_LOADS::on_loads which is called just ones after wxEVT_IDLE --- src/PrusaSlicer.cpp | 7 ++++++ src/slic3r/GUI/GUI_App.cpp | 47 +++++++++++++++++++++++++++++++++++++- src/slic3r/GUI/GUI_App.hpp | 30 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index d79196cfa1..6110a3cc7c 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -577,6 +577,12 @@ int CLI::run(int argc, char **argv) // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); +#if ENABLE_GCODE_VIEWER + gui->m_after_init_loads.set_params(load_configs, m_extra_config, m_input_files, start_as_gcodeviewer); +#else + gui->m_after_init_loads.set_params(load_configs, m_extra_config, m_input_files); +#endif // ENABLE_GCODE_VIEWER +/* #if ENABLE_GCODE_VIEWER gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] { #else @@ -614,6 +620,7 @@ int CLI::run(int argc, char **argv) } #endif // ENABLE_GCODE_VIEWER }); +*/ int result = wxEntry(argc, argv); return result; #else /* SLIC3R_GUI */ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 409ca0a150..9a32a76b6f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -123,6 +123,10 @@ public: memDC.SelectObject(wxNullBitmap); set_bitmap(bitmap); +#ifdef __WXOSX__ + // without this code splash screen wouldn't be updated under OSX + wxYield(); +#endif } } @@ -531,6 +535,41 @@ static void generic_exception_handle() } } +void GUI_App::AFTER_INIT_LOADS::on_loads(GUI_App* gui) +{ + if (!gui->initialized()) + return; + +#if ENABLE_GCODE_VIEWER + if (m_start_as_gcodeviewer) { + if (!m_input_files.empty()) + gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str())); + } + else { +#endif // ENABLE_GCODE_VIEWER_AS +#if 0 + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + gui->mainframe->load_config(m_print_config); +#endif + if (!m_load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + gui->mainframe->load_config_file(m_load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (!m_input_files.empty()) + gui->plater()->load_files(m_input_files, true, true); + if (!m_extra_config.empty()) + gui->mainframe->load_config(m_extra_config); +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER + } + IMPLEMENT_APP(GUI_App) #if ENABLE_GCODE_VIEWER @@ -696,7 +735,6 @@ bool GUI_App::on_init_inner() // create splash screen with updated bmp scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("prusa_slicer_logo", nullptr, 400), wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos, is_decorated); - wxYield(); scrn->SetText(_L("Loading configuration...")); } @@ -785,6 +823,13 @@ bool GUI_App::on_init_inner() this->obj_manipul()->update_if_dirty(); + static bool update_gui_after_init = true; + if (update_gui_after_init) + { + update_gui_after_init = false; + m_after_init_loads.on_loads(this); + } + // Preset updating & Configwizard are done after the above initializations, // and after MainFrame is created & shown. // The extra CallAfter() is needed because of Mac, where this is the only way diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 9bf470a42d..68c9b2efd8 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -140,6 +140,35 @@ private: std::string m_instance_hash_string; size_t m_instance_hash_int; + // parameters needed for the after OnInit() loads + struct AFTER_INIT_LOADS + { + std::vector m_load_configs; + DynamicPrintConfig m_extra_config; + std::vector m_input_files; +#if ENABLE_GCODE_VIEWER + bool m_start_as_gcodeviewer; +#endif // ENABLE_GCODE_VIEWER + + void set_params( + const std::vector& load_configs, + const DynamicPrintConfig& extra_config, + const std::vector& input_files, +#if ENABLE_GCODE_VIEWER + bool start_as_gcodeviewer +#endif // ENABLE_GCODE_VIEWER + ) { + m_load_configs = load_configs; + m_extra_config = extra_config; + m_input_files = input_files; +#if ENABLE_GCODE_VIEWER + m_start_as_gcodeviewer = start_as_gcodeviewer; +#endif // ENABLE_GCODE_VIEWER + } + + void on_loads(GUI_App* gui); + }; + public: bool OnInit() override; bool initialized() const { return m_initialized; } @@ -236,6 +265,7 @@ public: PresetUpdater* preset_updater{ nullptr }; MainFrame* mainframe{ nullptr }; Plater* plater_{ nullptr }; + AFTER_INIT_LOADS m_after_init_loads; PresetUpdater* get_preset_updater() { return preset_updater; } From 62557921814b27e54fc4e7f672e1545955c19a14 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sat, 3 Oct 2020 03:15:54 +0200 Subject: [PATCH 617/826] UnsavedChangesDialog improvements: * Changed some labels on buttons. The Dialog name shows a purpose now * SaveDialog is called, when UnsavedChangesDialog is shown. * Added prototype for the "exit" icon + Fixed layout for the "Machine limits" page --- resources/icons/exit.svg | 20 ++++++++ src/slic3r/GUI/GUI_App.cpp | 37 +------------- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 25 ++++----- src/slic3r/GUI/UnsavedChangesDialog.cpp | 68 ++++++++++++++++++++++--- src/slic3r/GUI/UnsavedChangesDialog.hpp | 9 ++++ 6 files changed, 102 insertions(+), 59 deletions(-) create mode 100644 resources/icons/exit.svg diff --git a/resources/icons/exit.svg b/resources/icons/exit.svg new file mode 100644 index 0000000000..eca78da625 --- /dev/null +++ b/resources/icons/exit.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 7b3309398f..cfbe4104b1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1526,41 +1526,8 @@ bool GUI_App::check_unsaved_changes(const wxString &header) if (dlg.save_preset()) // save selected changes { - struct NameType - { - std::string name; - Preset::Type type {Preset::TYPE_INVALID}; - }; - - std::vector names_and_types; - - // for system/default/external presets we should take an edited name - std::vector types; - for (Tab* tab : tabs_list) - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - { - const Preset& preset = tab->get_presets()->get_edited_preset(); - if (preset.is_system || preset.is_default || preset.is_external) - types.emplace_back(preset.type); - - names_and_types.emplace_back(NameType{ preset.name, preset.type }); - } - - - if (!types.empty()) { - SavePresetDialog save_dlg(types); - if (save_dlg.ShowModal() != wxID_OK) - return false; - - for (NameType& nt : names_and_types) { - const std::string name = save_dlg.get_name(nt.type); - if (!name.empty()) - nt.name = name; - } - } - - for (const NameType& nt : names_and_types) - preset_bundle->save_changes_for_preset(nt.name, nt.type, dlg.get_unselected_options(nt.type)); + for (const std::pair& nt : dlg.get_names_and_types()) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); // if we saved changes to the new presets, we should to // synchronize config.ini with the current selections. diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f5570c5147..3ef5012ee3 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -361,7 +361,7 @@ void MainFrame::update_layout() fromDlg, toDlg }; - State update_scaling_state = m_layout == ESettingsLayout::Unknown ? State::noUpdate : // don't scale settings dialog from the application start + State update_scaling_state = //m_layout == ESettingsLayout::Unknown ? State::noUpdate : // don't scale settings dialog from the application start m_layout == ESettingsLayout::Dlg ? State::fromDlg : layout == ESettingsLayout::Dlg ? State::toDlg : State::noUpdate; #endif //__WXMSW__ diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d46413320f..c5ac0b9d8a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3336,17 +3336,8 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr if (dlg.save_preset()) // save selected changes { - std::vector unselected_options = dlg.get_unselected_options(presets->type()); - const Preset& preset = presets->get_edited_preset(); - std::string name = preset.name; - - // for system/default/external presets we should take an edited name - if (preset.is_system || preset.is_default || preset.is_external) { - SavePresetDialog save_dlg(preset.type); - if (save_dlg.ShowModal() != wxID_OK) - return false; - name = save_dlg.get_name(); - } + const std::vector& unselected_options = dlg.get_unselected_options(presets->type()); + const std::string& name = dlg.get_preset_name(); if (m_type == presets->type()) // save changes for the current preset from this tab { @@ -3358,9 +3349,9 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr { m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options); - /* If filament preset is saved for multi-material printer preset, - * there are cases when filament comboboxs are updated for old (non-modified) colors, - * but in full_config a filament_colors option aren't.*/ + // If filament preset is saved for multi-material printer preset, + // there are cases when filament comboboxs are updated for old (non-modified) colors, + // but in full_config a filament_colors option aren't. if (presets->type() == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) wxGetApp().plater()->force_filament_colors_update(); } @@ -3878,8 +3869,8 @@ void TabPrinter::update_machine_limits_description(const MachineLimitsUsage usag text = _L("Machine limits will be emitted to G-code and used to estimate print time."); break; case MachineLimitsUsage::TimeEstimateOnly: - text = _L("Machine limits will NOT be emitted to G-code, however they will be used to estimate print time, \ - which may herefore not be accurate as the printer may apply a different set of machine limits."); + text = _L("Machine limits will NOT be emitted to G-code, however they will be used to estimate print time, " + "which may herefore not be accurate as the printer may apply a different set of machine limits."); break; case MachineLimitsUsage::Ignore: text = _L("Machine limits are not set, therefore the print time estimate may not be accurate."); @@ -3887,6 +3878,8 @@ void TabPrinter::update_machine_limits_description(const MachineLimitsUsage usag default: assert(false); } m_machine_limits_description_line->SetText(text); + + Layout(); } void Tab::compatible_widget_reload(PresetDependencies &deps) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index e43f738c4f..68ecd49d1d 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -13,6 +13,7 @@ #include "Tab.hpp" #include "ExtraRenderers.hpp" #include "wxExtensions.hpp" +#include "PresetComboBoxes.hpp" //#define FTS_FUZZY_MATCH_IMPLEMENTATION //#include "fts_fuzzy_match.h" @@ -515,13 +516,13 @@ void UnsavedChangesModel::Rescale() //------------------------------------------ UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header) - : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : DPIDialog(nullptr, wxID_ANY, _L("Close Aplication: Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { build(Preset::TYPE_INVALID, nullptr, "", header); } UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset) - : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : DPIDialog(nullptr, wxID_ANY, _L("Select New Preset: Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { build(type, dependent_presets, new_selected_preset); } @@ -577,12 +578,12 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ // Add Buttons wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); - auto add_btn = [this, buttons](ScalableButton** btn, int& btn_id, const std::string& icon_name, Action close_act, int idx, bool process_enable = true) + auto add_btn = [this, buttons, dependent_presets](ScalableButton** btn, int& btn_id, const std::string& icon_name, Action close_act, int idx, bool process_enable = true) { *btn = new ScalableButton(this, btn_id = NewControlId(), icon_name, "", wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); buttons->Insert(idx, *btn, 0, wxLEFT, 5); - (*btn)->Bind(wxEVT_BUTTON, [this, close_act](wxEvent&) { close(close_act); }); + (*btn)->Bind(wxEVT_BUTTON, [this, close_act, dependent_presets](wxEvent&) { close_act == Action::Save ? save_and_close(dependent_presets) : close(close_act); }); if (process_enable) (*btn)->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); (*btn)->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); @@ -596,7 +597,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() : printers.get_edited_preset().printer_technology() == printers.find_preset(new_selected_preset)->printer_technology() ) ) add_btn(&m_move_btn, m_move_btn_id, "paste_menu", Action::Move, btn_idx++); - add_btn(&m_continue_btn, m_continue_btn_id, "cross", Action::Continue, btn_idx, false); + add_btn(&m_continue_btn, m_continue_btn_id, dependent_presets ? "cross" : "exit", Action::Continue, btn_idx, false); m_info_line = new wxStaticText(this, wxID_ANY, ""); m_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); @@ -667,7 +668,7 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name if (action == Action::Undef) text = _L("Some fields are too long to fit. Right click on it to show full text."); else if (action == Action::Continue) - text = _L("All changed options will be reverted."); + text = _L("All modified options will be reverted."); else { std::string act_string = action == Action::Save ? _u8L("save") : _u8L("move"); if (preset_name.empty()) @@ -692,6 +693,58 @@ void UnsavedChangesDialog::close(Action action) this->EndModal(wxID_CLOSE); } +void UnsavedChangesDialog::save_and_close(PresetCollection* dependent_presets) +{ + names_and_types.clear(); + + // save one preset + if (dependent_presets) { + const Preset& preset = dependent_presets->get_edited_preset(); + std::string name = preset.name; + + // for system/default/external presets we should take an edited name + if (preset.is_system || preset.is_default || preset.is_external) { + SavePresetDialog save_dlg(preset.type); + if (save_dlg.ShowModal() != wxID_OK) + return; + name = save_dlg.get_name(); + } + + names_and_types.emplace_back(make_pair(name, preset.type)); + } + // save all presets + else + { + std::vector types_for_save; + + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + + for (Tab* tab : wxGetApp().tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) { + const Preset& preset = tab->get_presets()->get_edited_preset(); + if (preset.is_system || preset.is_default || preset.is_external) + types_for_save.emplace_back(preset.type); + + names_and_types.emplace_back(make_pair(preset.name, preset.type)); + } + + + if (!types_for_save.empty()) { + SavePresetDialog save_dlg(types_for_save); + if (save_dlg.ShowModal() != wxID_OK) + return; + + for (std::pair& nt : names_and_types) { + const std::string& name = save_dlg.get_name(nt.second); + if (!name.empty()) + nt.first = name; + } + } + } + + close(Action::Save); +} + template wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false) { @@ -866,11 +919,11 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent } m_continue_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Continue); e.Skip(); }); - m_continue_btn->SetLabel(_L("Continue without changes")); if (type == Preset::TYPE_INVALID) { m_action_line ->SetLabel(header + "\n" + _L("Next presets have the following unsaved changes:")); m_save_btn ->SetLabel(_L("Save selected")); + m_continue_btn ->SetLabel(_L("Close aplication without changes")); } else { wxString action_msg; @@ -891,6 +944,7 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent } m_action_line->SetLabel(from_u8((boost::format(_utf8(L("Preset \"%1%\" %2%"))) % _utf8(presets->get_edited_preset().name) % action_msg).str())); m_save_btn->SetLabel(from_u8((boost::format(_u8L("Save selected to preset: %1%")) % ("\"" + presets->get_selected_preset().name + "\"")).str())); + m_continue_btn->SetLabel(_L("Select new preset without changes")); } update_tree(type, presets); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index f78a1fec0e..a50a5f4804 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -227,6 +227,9 @@ class UnsavedChangesDialog : public DPIDialog // tree items related to the options std::map m_items_map; + // preset names which are modified in SavePresetDialog and related types + std::vector> names_and_types; + public: UnsavedChangesDialog(const wxString& header); UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); @@ -241,11 +244,17 @@ public: void context_menu(wxDataViewEvent &event); void show_info_line(Action action, std::string preset_name = ""); void close(Action action); + void save_and_close(PresetCollection* dependent_presets); bool save_preset() const { return m_exit_action == Action::Save; } bool move_preset() const { return m_exit_action == Action::Move; } bool just_continue() const { return m_exit_action == Action::Continue; } + // get full bundle of preset names and types for saving + const std::vector>& get_names_and_types() { return names_and_types; } + // short version of the previous function, for the case, when just one preset is modified + std::string get_preset_name() { return names_and_types[0].first; } + std::vector get_unselected_options(Preset::Type type); std::vector get_selected_options(); From 2b24a210982fe129b8f29cfd600ec7395aab28fa Mon Sep 17 00:00:00 2001 From: David Kocik Date: Sun, 4 Oct 2020 21:11:07 +0200 Subject: [PATCH 618/826] Correct strings in configWizard --- src/slic3r/GUI/ConfigWizard.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index cfc81f5a7d..ce2ea42e13 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -647,7 +647,6 @@ void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) const wxClientDC dc(list_profile); const wxPoint pos = evt.GetLogicalPosition(dc); int item = list_profile->HitTest(pos); - //BOOST_LOG_TRIVIAL(debug) << "hit test: " << item; on_material_hovered(item); } void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) @@ -661,7 +660,7 @@ void PageMaterials::reload_presets() clear(); list_printer->append(_(L("(All)")), &EMPTY); - list_printer->SetLabelMarkup("bald"); + //list_printer->SetLabelMarkup("bald"); for (const Preset* printer : materials->printers) { list_printer->append(printer->name, &printer->name); } @@ -680,7 +679,6 @@ void PageMaterials::reload_presets() void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) { - //Slic3r::GUI::wxGetApp().dark_mode() const auto bgr_clr = #if defined(__APPLE__) wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -690,9 +688,10 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector" "

RRB1G!>W$6Zg_Q8(ao6D_77rs3%|ao;`GWS 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≪ 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≻97~me>u^Z*Q7ho8p=eJXK&HA)2 znc*)teqhtMkQ42!xZ=__OwcfEBc=E`;s-p2*t)X>9Cy&NKJ<~lt1Zh$rf<()?my;LeaDWfCAqkeZ~aN_d;yy(Ic$&c7a@BqIam(h$h4L!#Ijx)(8 zQX8O7e_Q|iZL8k$EPWfJ11vK9#uVT7m7O$PNhKe2ScC5D2%rB>c>8=Xm^wv21r7s)cv%(Kuo zCL|pD1?-L|5Ae7QZYtOU5ltWQ|1Dm?vbgSc*JOaxDY1b6Q$rI_-&;nkGq96owT{30 zXdy!uyyx+hFOt*t__k*VtZyJ};CE-O>*4(K7mONIH=nktbs9ygq?;Y{Pu!Y4nKM0W zs#SUKr6{H?GIFutd&=;0C@-q(Z4Av>IiJlHb&`$fRoXh}Q!F$jOdMDd>^gKeI%)e| z*JRLxnmErGQNIH7EGgFj!f7AApGp}||2!P>?BI&o0iP6pv1Kt&vT-x`EOJ+d0>mt$ zExl{;@QZn1zRh#7gLoP3uOjztICl&Ud28A1rFUBU2v0Jn_)*#VYV>Yt>2eWw$*d__ zjt_ujKf(1-!9gQKyX44J;iqkOe`7BRQHmTs=W^eOdTIP;BLc-s0AusSx8F>(lV`cy zghzvXu0r=3*|f#tsTJtN8y4jzUFe2*%9nz?lU`!w;$K;mhP@?VmGV&A@7VL+MT*aj z6k$Tk{%XU8=hYTnVHIPy=b_b;zZoLGP!Oi4REnJQeCJTS%oy6P9$MS&@#-cC@X6`Q{+yz*E@$VbQqcN@ z%tjspnaqC>Lx2g=*(AVu8y?J0m(kp<#NsCwDZh(eE6Uz5~g`!BQT;k7dQ-({dTukF2| zkX8`@^0x-YVs9g@KDbTFy4w{!WEWl3SBmRqLOOwGhF{DA@;6<7let0+_3E=JIBj}aGwFr7Wl>h znf-6YXD!~iJb%d|=D5(A=4*yCRF1&&^`~6?kbZKX#&t&pXuM?z7@HFp<;omt1l{;^t!D zfxhUf41+f1K5@D)2_|PE2Wk?(C+io!wiQq2SIxTdaW1-Ze81`5q})-~lLHUvsAG_SjF4(#kMh{b{g?nOFk?3q^+hSZMIZ(i7iTIUIVZ4YJ8RF_)r4 z7lRuDGB)40oR>~BSPiyqxL^EP9>t-9S;O0%+F2dc$Y`<^@QcEGjqHSV$!Tna8|%(w zWT%RjPFuw{1_6KNm2Py`*JxwJHD5|dC2mcP>I*Ucq-Xx|IM(A%2b`&9W{KdeuOSG4 zn@&}q_;}!lt~~fqCt0x3D;xEhe_L7V^{q$vAl1R;jX{X%$#D;}=!?n|bY6i(E??^1 z?Ln$?jCG`M=>k9TyW28l zzkKX@)y`3ypIoM05N=T~e4=2pY}o+b-*a0BILm+7w>aqw$4Unl;5P>3A;8l=aWbRG zu1ZEaxfiTy?3}{~kO1VD_uBkFzb@B=$IcE_ggB+y#Y@y09}<3S@s#e@`oax%kRo6e zbb@xLADo29&>(s?2gq7Hb=0s9SEv<@NZN!W=jl0ZiUG{8>p;H+|q)%Nx-MF ziPoII(Z-Y*{tmhhSxmG{0mcvWRGzH5@7uw#k0)@9SVvTL>e?>s#6RMK@{xn9yAV{n zx{Zdawew4<7yakTf7S0_eq`?291B{17jA+{^~oE~-jlU^P!JFP6#B8_5LwP!g;G7h zjZ}p?qWc$alNn(*!=hId^85Ae87i1ySZ{se3s~pndkYwUe9N62ptmlbre-y$eBuV5 z%fmbq>DG%a(>C;Ep}~O3fBbeHX#3aon{fUkE@UP(#&=P$lP1`lFKM0Eot_@DY79J7 z5D%@jt(yoL3iyYerI2W*$fQ^{9YFbaE(Z4=)I;6*TVr$cgNGsrp1$Dt81`XQxTE)V zsXJYpDbI2Ho+C9EaIEil3Q@yv;ge*{{r*S0)O2&`_Hv>!>wS1TE@Zb^xu^OL(XI|) zX}Q)2F!pFXW@-h49$;zzv*dh~dBXIFvs43-lkP~#k`osM!9KqHYlVu!gBMu)mn_h` zX?H^=aI7N|GbL;z}j-;x>yk9b!>tE5v^W8-xy zH_MiP2F4a7c-P!j)$4=|OW%JNQPTUkcm0pbL*0y){NLp@>WBG&ou{Q)Y!`}Ou3oT! zC&?4Ws3MY7n|!WT6XvqqN21J{I6TR{2&N?z?pWa_aOk(0%6ur*Yr-~>;vBGinBoln z_`lHv5Mqq#dMK0+QllSzaIwB4yNu!7?%nWcN0ke#oetyFP?i*3~Su-J8u%elbeslK*K;bzUYu^Hx>-_KtS6h=G)4CnKeic^# zziW0vq_hDzWGCT$dB#<{&EJeZI5DjKi?lhzV4AFV5!Mq z_fXjFF1A(T`@-46ieczwhc?YF?c16r%ILhsGqF777ClDR1_g)CA?tO8wB2wEoxYV# z#=oUjx9zl7OjU!zCw|GqaN~i{OI2|I5^==e4TuDah`Jg@?+6$$uf z+UbCm_$8_&)7#3hg^8-3)kO;QW_KmibB~H5rmfIcACAT30xeA%gVZuHeG z<+HG#PPmeGM|UP=6cTNQ$ysBJPMl$8{Toz(P+$4+%{B9vFhQmpFMyb7Pl0{|82H9ShHxO%)2Q?KXT?rQ7ZQhE-JY%^EBS0M9ZE>FFfna^Wo~-<@MErgQ^~ zgWRz9eRRy)lOO3fH@ws3TCS(QN@V&otH>}o0_#g3AL%($qJx8GD?%7BhbzmGINFo1 z<+ZE#X9Zhc6y7HO_1<(vZ1s2Nd1WszcEZ_U1XX`TJX1xU>FAo5ejsIO4!$T}NnGWh zvq`jzj$eE7y;PNc9Vs8WQ|<6|I1GQi=khp{2wiKusyvQ0a^!;6h@n_gVUE}4>2Ad? zK)tY2Q@F)d`+Prwm<>s9wdnHp4uR}oF^3n~50BI4VSmwT@5~&$g{i@~(>CF(Bb2ErWRn1GY&ZAsw`Xqr=K1t>r|es?|*K@!>-_w~ySAL-DI; zmy+jtvDrau^*FY$p==q9jk~XJaX{FSrUXL4^)~0)pvKylZ#OFk#b-oh7b=kzOy1KH zM>ccHSK0I*h~POPYY}9o{bd*dZZe$`iX$$sT+33I4c2gA>C&lhOmz|!Vh0T9h$3|Z zI=R4jQk3)a&eckF{Xg~eZaLanV-Uuu5*5Vf7C&crKV}H+G!7Ss$6K=2*CHFgh(iE}Z=c3MK@fHN>YKYhl`N zlrZ6V$*_CTrf0sc%u%8iE;114^_@dg*np&+dsXo*h@gVm;qQBD1_5UG)7sXTwURP_ ze$Njbx9ds%T?WCiR_uEW7gK%`H%~Zg1X$kDtxz;5LLLwFQ}50IEVKwY=DgD9oY^_{ z6i<@ai(R+wJRfPKrq#fR1jJG! zw8`=ZuZ5K&2%CU5Usou;GQ)^i{*KLQp@(OV)`j<97UoIhE!Z$|&yML^0PXR5`6cUQ zbBTL&lVuu{1X|+E3^^a$N7YU3j|Z|}N|y&EXFpx4DQ#v{NzX4+mkb?o|C&R9&->li zN}caA-80I|Cv=Ngz?1!QTDqoUgZ)_&*7yydpJx=6%>i}|cE-tXKWeQ#@mz+x@pk({ zz6l06E`p$S2-<6HGqx9inHUeQ;Uo#QN}md|k3#h<(b^%-R+G@0DW`ASzq- zB>PTjHQ#*vW5Lqh5>qMwPiZ=3DlZh2=+Y}X9?>P;*nm#qyQ)pi!>BNxc{^1C3SIA> z+~>zx0+)|I2lf$ovW1^f$r!!%wN2ziK7A|~rr!Z4+(@K#mo=l8-ffOWZ@T+_|LK^P zNN1Do`PB0iCNNB?=m9IWVnGM`gQkNWIyx^vv@Pb`(-m9GY6VE?3l;6nEdjh(|pS%+SK`5C?EIfS_KqzKq5t+nYd_vsx2;gka1&;fuciZmbL+n z_W0L=3KpswjW5UdbTUKMS@$;2eLMB`h^epRC`k*V2G^tWOk^h2PUk18t;i39ks7Un z1u5HW|BmTXC}mPesbBO_(hhC{LA$%WF6$fRhh@h{ITZwYZ->a#5uy(V`iCkBc{V&2 z2s-5TgFi7Z6??ij%`BOK>&afe9yS2BUP>7rPLVwn5#n7F{FZ|qs z&jkWyVK-Fga||jkt1_#OTzuBu)Z|n@<45Td2({RVXJL~&`94Dkn0;PTYW_Ph(^W#^ z%69lH=Ziy20oE?_(^^4?-J-TpHOSxnW9QuC_boQ7>D0F|CC|&NV$EXC+Ts9g(*D#f zP5n$cf4ErE7{hSs&A^s86B!pT9p6Z-?e`~{>(t`C^+k^8eTF5q%@LP9y;0dbU6mKo z+iYZYFF$)c=n3#YIKJ@=phSN%8}e_)n@9aks_O#XYW8>6ucAhG-~Cw8V3{TYQ-Ih$ zWg^-*amzU*SdvL?&oyTYZ+*bXE~QVU#<|c4>OcrtP~J{xCgKu5^ObTJj`RN-5E!i7 zq~m(MlMT(AyHseCv-mMYQn_prS(P&Br55CLX`wgmtJ=IAkbX+@`a_B%_$x_OGFxgN z)i3-c{y3AnUyAKBAL~Q$qMsu3i*JmEgw3zsFr{)df|9-q-A0&Us(69FZR9v4zIP1% z&L)&$DZtm=P%;{xPfmDBEOJmT#q^mKW79t{5=>GmvpAOKUVm`*JnKV;=kxxTtEqHf zGBY_gDpe8Kvu|GKTMF4b5|Cr%UZ=^BPOp zBGz~M=?1Qd8l4`nKN85aYqJ@U>LBzC&&h9pkHHQB4<+95=}tjXIzN685oWs6LDH~d zG%}NIdrT#f8u%Gus`ynp|}Jic2i_cpGkw+0oT3+8o8cfiMa6WvWfO`(4FG^!(2n+Kri;F zSjTpVJOq#|a` zb?t%(x!7rQ*gnG7a0?J8n{?Sw#T!%TWoyaa@@#TUipIK--yM1^_FVY%%In`ekvmK} z-}dXf_ogaI^d!Grcl#izPFcAT3z|2ZKSgi{Dlq;rL*Tq)n@HZV(!BJsYO_m%K(E)s z`yG3C*al@IJhNjeM9nDXEi?d`S^Z z04=9fHvwq-MV}r1xdS5T^8Sur#xo@P%rEI>K7Xt4w-_%IrhmuubvlimwGzK{?))$; zLa$%k&zOc^Yvn2i{3&YK+Pi~E-=AdGZ&y@pi!UGwoAKJ5a*QxxtN^61zn=YkToKXm zdNx?NgPL=?rDCKu&vmJbEk5m*r!R6yxokf9)E{t{qVi_(0LErMKG!5L8ysSx+|l|oQLeZDu59GaSGyYiGkJS8@uMCYh>})>1 zOr(@zT5nw6e}hc8b^bNAo}`61oS{jfXLO=NdOg%5Kb)u{X4;_Wtvt4IusH87f5 zFB$_He?9P9zgalvik+lrAHFsmtp&*#zzu$O(m$a6oEyk-y|H0a7s`$D%TB$db>^`2 z>Z8C4E_HxqgMILRFS?_^TUIm>yXsJ z;7f|OrpDE8+5<%e%`@J~{vi@lWcRnCzvn+(x|ElWblMi^}+p!+2>h zbi_6G#^0Lkq2iGcaF<7x(NiGDU9aWCR{U}ug{j5=Jep~d zbv~g~)d&`(F;*>hn6Jz6L}x*C;?@nhIp~ml!5;$_?lY_Cm#{vX^vwvCJx@Fuw)~r) z5-S}St$^P-G5Pn(&hAC_QevJ1mw%UISy5^LZHV9rSEO&5hNY0jKyCrHVlX|l4;_ne zykz$!Wgl~QXGea|)ph6;p#M3$v9k_`Ngo^VxIHooPNIJ{cP~<=@BYSt{tJ^Q`Atw7mO&?a2Ry*Zw9y%Ry@hxDkbZW83xlJBevQc&AwP$Yp zClvHJ{R*KxRal2wTu|`&e1~V)hQiN%UgfY=(guv5!FGflG#0K5Z_ zYx?H!ABfa<{lg?ZR}Emy|3h%t)W|sAJCln3l!)Fk*npL8zQoj0q%?lyI}II9o9{CS z9Nr9OlLS$kj${5MeVw6|mXQtO{QGg9$gEJWg>y>LV6E z+|y2m^tYh2-{)l$ON|E4p7~0f3j6JIT?tO)%qAB%N<+BeBLJ=lGm9=p4cE^vG(`@{ znx!NfaRPyW1dMU=yxVYMGqavxUypHU1D;B>o%QR)$_4u#g|G59)?`q7CzhDl zK+D>aPD>Y3#KV!lUkfqXmB7f~ikcQ(O-BQX~tUFBsOR}tzKkP53qeKhLaU%=JYJH~MU z&H=y+=dnXWj#J%RPf%~wFF5IX+76MHb+fsrR$b7jFg8r^8g&zDCNC7WtnRVzdKTS* zqX)BsiCy zQSQC6eUJg0A7l8pD^aK8ZyW2Tyv-L1d_3NdZ|n&*S+OEC#Y&F593HIJ#uX;8C^6>9 zsE`#2-5DE-yy>8tgDg^GPkNTvRfRgo1iHEwlFOIMOkhXn+Q;L#vK1?mC7t*P`{=Tu z1Wit6nqt`SWn#Sszt-Pbwd4{7nT28~xeY2!K4IK_{WDN-Ei2(I3RB~Jlfa`b< z;9nTLb^!_>DgFO#WJ>C^v`13*bHZv*&G}heyc2h6F*VSZb%uE95Nq62JIB~(U}Xnc z8;&EsSm~$a_dH=4=)0|+1TKVgx`wGSBHAp;H#&GimJVh$b=9h~o%AXrucdvucJ3+b z=|pbK5~0m9ROZFi?0Q`R3cat6a*e3<x-*( zxLD-olbMxq5#_s2B<5fG>(w!NB69QG%Rnm2L7xk=_2?lRI@Ms=cb!Vw8#(;J z7{PJhZXH~|uk8rr*77}l9Qc!4jcq$AAzNN%WeO$V+`~~X)Q|l3#5VMoB$Ll}cy``L z?z=5@5U`kTXo1aJ+o#(7T4620H&ZGzFWk4@wN&_h4%_AOT)|P>^gD-2+nHD4r>E>M z6kmXj)=n&sNJEWI&1TfR+D!-ko($+5D?Z}mvstY92ulQr+8{-+9@`PdwoQw?FuK*B zp0Qy-fxJcfUuB9Lqb)XtC)h#JcTpX;l&e#~lzODy#|9IGM4yDJW`{lfN0?#K{qw|7^ z$(*S^F;#^(sZo1u6UNrPAj;O@KH{mPDD?YvT)IC(p$HwZjf%L^&%VNw1qimz_w{R) zww(0t1^kNu2T>-Ez0~(3gwB*Xbg?}DdVi!$V&YRT<)GsEIr`6a4K5TrXQ21@dkfj8 z(K`iz+b`#UFFY8(k>s?N{eX!du2<@x6}kSzJ|6cz^L~qY_Tig;!)cdTj{W6m1y<@fm1nkDzF5o?(c20%|Wnjfk&d-XXr%;$@ zeD*K>q&;W>CawIJy@&uYhpT|Lr7X+83+hH+m{O>XdnnnGIhDp%?(^2Xq(pr$1>F~1 z_CSRgndPzgJ!J!a_d3_oc3yaBS5qvo z%^b8|E7>g}Tl3G2Mm&z=N4#-ORN!EaWl!KBQp@EQU1!x1(k z5+XA*tBS`)hWqqPCk(chW_Xwf9re=L+Jm!s&vG7B7{_Xnrc#v8lm$M^sLuu>4{-Rs z7A7P(AlekIcGWzOGd&6&@pr$6&XMyf^*3Cy+@?DEt`cC4e0`HA_17k03NN@}>2voU ziz+0;uHJrn{M#(zy`sEu_Rrn}>y!D$op$GTvMw7Y;ekMwsedm%+9awW2f)XVvIEZZ z+ipuko{G0E*DlhycBUnfsWJi{-q{aEU&}kANCKG5Eq`pZK86(Uy{|a6VhTI=%I3#- z=FXRm{N0%k0qnnPa(p6{1AVNA&AnrWB)pXf+1g1TJuD|QsAFoQWaypZN( zN)!qE6>GG^8iQ|2xVI2Zb~&m2Y{ntBG84Sc#^gI>|BH!5jo$Ri8x4GecD6s_<7kLE zU1QvTJND!^Xc%;_rOHc^e8GoqK<*qqjUlCv?lP)l7uig#E8hKwRqY`wkC%Or&7!j% z-ZULD8jOC8fe>{EBfptN=SRc&pFMr_xdxvrCuT??baScD4IpGVaZJ&mb+EM(a4^r~ zIqfQ9yZJ0@glMnGi2}+lmJc_LalJFYC+VtgenwE;V?;f;iPe5F?&EZtMT&Mffy7{S z{MeisgZ&$lTzk4#z1r;G-uKLI&k8&Iso!OddQvIAkvF_q5=vcz9ov#C5$xTfYx*Jd zV_{xrf_MHBSNl9fa`xDV+)p+TJU`K2FsYq@C}d0;x1|D_&jI)Z>%Y7e-`Q_1!mLr^ zQH2toDHalA34C2{+jFON4_Q=m2M9?{mgjPC#3QSrT;21QwQI)=j@MIum&6`E4Zly{ zXE@$_l_;8M5V7KMUzPdHD$vpN=u1io+CqJhZZ#JC%1!mC!t?a}D#(?!`wkOXIu;Ti z@+!}a>~x_@vlp;pDxJw0EKj+$va|D7OnUXx)=vLDh+xabhJXjxXD_`AFRWIzZO-;{7!-Tc-8gnBqOB7f zzs{BEY2e~Z>R!WK?-yJ7=hq{-ceYOrKDq@8NZLhjtV;tWeu?~Rfo8e-vGt=IdM#(M z*%=-bLc&)uSk*KE*tr~d!xdxM<4f9L8)x}?Zd&pCx>M!6m&|EL_`-^xIC?Y(_8-XB z@w?gW@-t5D!Y7oIZK9~!!}5$ZQrX7*Z$r_!S>QO)#sHfLE{@F1MBalT)+ff8<1UZA z^pG^wzMnw;X+Bz^mG?+WaIPuY*L=p**V(MSA7d$NAnjnHpy_sPF+X$$c>-D4fiNuY zv)018Ae2a+*KMpQ??s_=Nuxy6QqOyJY;Oa?my+|hv3)$vsEvt8_$)#Fs#Op^O6V67 zh_7|t#y;8a9slz=n;zjaa__bL8^6Ey3d>^KP{8Smc-lhBfNA!8OE0{RBjgC2&8gj71+zsO191L!Y){I zmmmKrJoN5Fk9iZlM3l2l<{I94Jx1QS=TM%m5imD!R(IpFtL%Q^<1G`{oZYJY#OPi-SsDcmsJ3QcL${_G-fF>`n!XeBoCNA&Sor3K$m0{xx7+pPs^Q*2} zkQm|z)2++Y!SvtI;q+t~O}`Cua*%D)x{c~-*KarO6nve%^KN;g&f zD~=xYk13fqz9(X}3}SUc<2~tCkL8H$%N)G|j#wW#rHfBZDy=y)i*+(j-+ouD06Lxd zVsPeP{$y=>WCV<^Yso^jtVDlcR&Sn%DBfm@LCN;;id%W{FUK&kn&3r}(>39ZJx?%C z|3n3cokrm%he+g0{rkEh=(hCRKX?}Uk^tu9h7rq);V*Ndxh{-DEjhvu3lgV7%js>& z3PMgzHdG9k)aE6bylZT>UIxGbsr++xe0y-?FNEUy^*D@GJ18;Ip4rJ+qpRXLU3#y-zqOY$F4e2g^b@T~Cy%czZ! z*M7eazw%wnqABI2Lr5s`ZoR2OB%gle1&UkF#mp zUQ3AV@ISMyfBICfpEdq=1nxL7{`@}y;R+u0&Z3M;#<8~GcJ2UXxC&iF(ER3O!fS-aa!7+i~j($G;2Fui|0ZKd09t)1g^pc zI^=SFO<>VQz_p>wwz{pnuP2nTuwwWms2zH9My;rSY3kP(Uw6-a*zccH_3On|qMg9p z)wQcxd?{yYnU>}dM*ygZ6OyNaxOW|JLH4S?7rBPlSys5**6gJlYi(>WAmiTzo;%iU z0lDvrFRj3_t6lxGP?}8cRyf^%jARZ0$F2a+y=-_hQiWFL&hkMGysBMfKYAy~IRmNa z2?LTB1ClE@L(p!#Lp!ay?lC6gmfN59LG?MWeEpk0;F-S?KWrEAou|VcH~tdsU%}c- zYcFA9eA)FVQb?ncI6~+5%jWJ8u?526a_oxEH{4RwbHjDLRy*r8k1W!ELfPC;%Gm%g z9;4-46VIQ@$5mh;)(TFTJR z1o4>Vf<+}+8C#WLz=97Tj2=O%{$U()K8LLr*5e&0Q6p{2{M|PL!RNOW-2T03q~z0x z`?Vk~8359rX$azgC5`rfTW9;SDgJfnKMVDL?B5bwr0xETAvh=a2^@YIt^(R0Itd59 ze@g4T5Q9=!_jOh0*w>ev!8$LSW{<1kT9S=>!{SXf@;%MsE7hPsEyADp2(FGM#7i(#sM2eentSzfcW>^p`516c9c47?&b(i= zsU+R3?2@uyec#OW+dXpP)fa5U3=#LeD(d*A`FA&-$ACxAUN)XJ)zE*fLyuJzG=C8F zSev9R#yMP9#mgd-*F*GLd{m;Xc`xhK_Nz}FYBubx_L-M|YhdBX>s=<1@f%f*lr8np z`LV>Le_HwG-{QUeINfa#T!MY;e@ytzFe7v^sLoF{=Hj?^ZF~#xJ$GI+gA8>w)*XwZ9eIiGQ{rMKx}>_n)`G14)}*3h_(Go!}7Z->bB_CYU6_}XQ%!0 zO@3+q#rpJrUMTo+eRiro(H-$g0Acd3#oB*UUgf0z*P0LPg{$fQE!TBOEM~EHxVN_t z{*iphq!fxV%bq|u7#oIh#e5zHHL#h~RVX(lD_JYHo{c2Dn?CzC#8AcL^&NVOF>$(+ zX(ZOMZM~J!KQ%vZkBCR&@9oX;hr}rA@#-EezDUS_IZ}~7*})#zh*y;U@DG(&Z}*gr zl|G|$HnpzbUC!U>8kO9)u5t?onnrR7?m=TwM{37zPs@yMAPWA(!_)S3qfPWleaHFz zkHjkYnw6K@lYe`;10G0`mpwDkR+Xi^wzJ5u365yn4ZL*Yimi=ELG?0W7#{~pz&sl~S%KI3g4cLR!Kk(0x4bDD1Lyz})vDq-^#_orZ6$gZGdH-2aW zn-U5C06C_%1J$7z+G#cbXabXrH`k>#hssVqzJJww?CL@M=~^Yn;r(f#Gd7ct^O`)Y z{{RhX3uXDK!re;ur*JY%c>e$j3_0yuk;EADMA53K`A1xIpjR@)8SH-wPb8kqYh9sO z(Q*7;XolTn2P^kYP|TKx?QT>=P>DIQE*><=4AfM3FO|pQeo;yPos5Y{W>T6L~ap%^MLapmdlsSyQB31O}kVhaNclE6$ zg??%bN>tIxJLz*X=l=knW{j}@3e)>KpMPJ@n;HJ}mv(B-5elFd+Tr;-(l!CT8Ga5+HO+y^Cc1%12w2>$@VP38THz9wlJr-k6sFQM>{hIKtM32h8^ z7uQx(-otqfz2vd7{jZjrV00e*Q{{WxA`7iturQ!uY`UU?0$^QWRL)4_v?d?_l6fk6wbfD;96E`GJ{PoP&eGrE}$|;5V4yHhb}1 z6!>Nfr`y z&W+Ed6E@K|_e8x9)baG>*H&A@m<$DJ;%Gznlip8P=({fVwSW4aHhIPwrfotPY*kO) zLuf@>KCf4I+U?t=jYDv)BJFLxLrzc0=P;nuzGc6_fW{{WeOWsyRG!N>Beu)?Wp2@B>Ba7Xu#Tz~cIq^AR) zn;GPude)7liZ8oD8KjePf=e9qrx|J%zN8u5{{SsEcG_SkAC4%hPpQchZ3`a&by_O< IiRC~4*?PBEF#rGn delta 116562 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