This commit is contained in:
bubnikv 2019-03-13 15:45:01 +01:00
commit 2ba661cb76
33 changed files with 1890 additions and 1427 deletions

View file

@ -14,6 +14,8 @@ public:
localmethod_ = m;
return *this;
}
inline void seed(unsigned long val) { nlopt::srand(val); }
};
template<>

View file

@ -487,7 +487,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
// starts analyzer calculations
if (m_enable_analyzer) {
BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data";
m_analyzer.calc_gcode_preview_data(*preview_data);
m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); });
m_analyzer.reset();
}

View file

@ -137,22 +137,22 @@ const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode)
return m_process_output;
}
void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data)
void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
{
// resets preview data
preview_data.reset();
// calculates extrusion layers
_calc_gcode_preview_extrusion_layers(preview_data);
_calc_gcode_preview_extrusion_layers(preview_data, cancel_callback);
// calculates travel
_calc_gcode_preview_travel(preview_data);
_calc_gcode_preview_travel(preview_data, cancel_callback);
// calculates retractions
_calc_gcode_preview_retractions(preview_data);
_calc_gcode_preview_retractions(preview_data, cancel_callback);
// calculates unretractions
_calc_gcode_preview_unretractions(preview_data);
_calc_gcode_preview_unretractions(preview_data, cancel_callback);
}
bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role)
@ -676,7 +676,7 @@ bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const
return ((int)erNone <= value) && (value <= (int)erMixed);
}
void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data)
void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
{
struct Helper
{
@ -725,9 +725,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
GCodePreviewData::Range feedrate_range;
GCodePreviewData::Range volumetric_rate_range;
// to avoid to call the callback too often
unsigned int cancel_callback_threshold = (unsigned int)extrude_moves->second.size() / 25;
unsigned int cancel_callback_curr = 0;
// constructs the polylines while traversing the moves
for (const GCodeMove& move : extrude_moves->second)
{
// to avoid to call the callback too often
cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
if (cancel_callback_curr == 0)
cancel_callback();
if ((data != move.data) || (z != move.start_position.z()) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm))
{
// store current polyline
@ -769,7 +778,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range);
}
void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
{
struct Helper
{
@ -797,9 +806,17 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
GCodePreviewData::Range width_range;
GCodePreviewData::Range feedrate_range;
// to avoid to call the callback too often
unsigned int cancel_callback_threshold = (unsigned int)travel_moves->second.size() / 25;
unsigned int cancel_callback_curr = 0;
// constructs the polylines while traversing the moves
for (const GCodeMove& move : travel_moves->second)
{
cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
if (cancel_callback_curr == 0)
cancel_callback();
GCodePreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? GCodePreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? GCodePreviewData::Travel::Extrude : GCodePreviewData::Travel::Move);
GCodePreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x() != move.end_position.x()) || (move.start_position.y() != move.end_position.y())) ? GCodePreviewData::Travel::Polyline::Generic : GCodePreviewData::Travel::Polyline::Vertical;
@ -840,28 +857,44 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
preview_data.ranges.feedrate.update_from(feedrate_range);
}
void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data)
void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
{
TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract);
if (retraction_moves == m_moves_map.end())
return;
// to avoid to call the callback too often
unsigned int cancel_callback_threshold = (unsigned int)retraction_moves->second.size() / 25;
unsigned int cancel_callback_curr = 0;
for (const GCodeMove& move : retraction_moves->second)
{
cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
if (cancel_callback_curr == 0)
cancel_callback();
// store position
Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height);
}
}
void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data)
void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback)
{
TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract);
if (unretraction_moves == m_moves_map.end())
return;
// to avoid to call the callback too often
unsigned int cancel_callback_threshold = (unsigned int)unretraction_moves->second.size() / 25;
unsigned int cancel_callback_curr = 0;
for (const GCodeMove& move : unretraction_moves->second)
{
cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold;
if (cancel_callback_curr == 0)
cancel_callback();
// store position
Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height);

View file

@ -122,7 +122,8 @@ public:
const std::string& process_gcode(const std::string& gcode);
// Calculates all data needed for gcode visualization
void calc_gcode_preview_data(GCodePreviewData& preview_data);
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
void calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback = std::function<void()>());
// Return an estimate of the memory consumed by the time estimator.
size_t memory_used() const;
@ -237,10 +238,11 @@ private:
// Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
bool _is_valid_extrusion_role(int value) const;
void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data);
void _calc_gcode_preview_travel(GCodePreviewData& preview_data);
void _calc_gcode_preview_retractions(GCodePreviewData& preview_data);
void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data);
// All the following methods throw CanceledException through print->throw_if_canceled() (sent by the caller as callback).
void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
void _calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
void _calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback);
};
} // namespace Slic3r

View file

@ -2,6 +2,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#ifdef WIN32
@ -88,7 +89,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line)
// Run the script. If it is a perl script, run it through the bundled perl interpreter.
// If it is a batch file, run it through the cmd.exe.
// Otherwise run it directly.
static int run_script_win32(const std::string &script, const std::string &gcode)
static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/)
{
// Unpack the argument list provided by the user.
int nArgs;
@ -132,9 +133,46 @@ static int run_script_win32(const std::string &script, const std::string &gcode)
}
#else
#include <sys/stat.h> //for getting filesystem UID/GID
#include <unistd.h> //for getting current UID/GID
#include <boost/process.hpp>
// POSIX
#include <cstdlib> // getenv()
#include <sstream>
#include <boost/process.hpp>
namespace process = boost::process;
static int run_script(const std::string &script, const std::string &gcode, std::string &std_err)
{
// Try to obtain user's default shell
const char *shell = ::getenv("SHELL");
if (shell == nullptr) { shell = "sh"; }
// Quote and escape the gcode path argument
std::string command { script };
command.append(" '");
for (char c : gcode) {
if (c == '\'') { command.append("'\\''"); }
else { command.push_back(c); }
}
command.push_back('\'');
BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command;
process::ipstream istd_err;
process::child child(shell, "-c", command, process::std_err > istd_err);
std_err.clear();
std::string line;
while (child.running() && std::getline(istd_err, line)) {
std_err.append(line);
std_err.push_back('\n');
}
child.wait();
return child.exit_code();
}
#endif
namespace Slic3r {
@ -158,27 +196,15 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
#ifdef WIN32
int result = run_script_win32(script, gcode_file.string());
#else
//FIXME testing existence of a script is risky, as the script line may contain the script and some additional command line parameters.
// We would have to process the script line into parameters before testing for the existence of the command, the command may be looked up
// in the PATH etc.
if (! boost::filesystem::exists(boost::filesystem::path(script)))
throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + script);
struct stat info;
if (stat(script.c_str(), &info))
throw std::runtime_error(std::string("Cannot read information for post-processing script: ") + script);
boost::filesystem::perms script_perms = boost::filesystem::status(script).permissions();
//if UID matches, check UID perm. else if GID matches, check GID perm. Otherwise check other perm.
if (!(script_perms & ((info.st_uid == geteuid()) ? boost::filesystem::perms::owner_exe
: ((info.st_gid == getegid()) ? boost::filesystem::perms::group_exe
: boost::filesystem::perms::others_exe))))
throw std::runtime_error(std::string("The configured post-processing script is not executable: check permissions. ") + script);
int result = boost::process::system(script, gcode_file);
if (result < 0)
BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned.";
#endif
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
throw std::runtime_error(msg);
}
}
}
}

View file

@ -709,12 +709,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
// The tool is supposed to be active and primed at the time when the wipe tower brim is extruded.
// Extrude 4 rounds of a brim around the future wipe tower.
box_coordinates box(wipeTower_box);
box.expand(m_perimeter_width);
for (size_t i = 0; i < 4; ++ i) {
box.expand(m_perimeter_width - m_layer_height*(1.f-M_PI_4)); // the brim shall have 'normal' spacing with no extra void space
writer.travel (box.ld, 7000)
.extrude(box.lu, 2100).extrude(box.ru)
.extrude(box.rd ).extrude(box.ld);
box.expand(m_perimeter_width);
}
writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner.

View file

@ -1461,6 +1461,15 @@ int ModelVolume::extruder_id() const
return extruder_id;
}
bool ModelVolume::is_splittable() const
{
// the call mesh.has_multiple_patches() is expensive, so cache the value to calculate it only once
if (m_is_splittable == -1)
m_is_splittable = (int)mesh.has_multiple_patches();
return m_is_splittable == 1;
}
void ModelVolume::center_geometry()
{
#if ENABLE_VOLUMES_CENTERING_FIXES

View file

@ -340,8 +340,7 @@ public:
// Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes).
int extruder_id() const;
void set_splittable(const int val) { m_is_splittable = val; }
int is_splittable() const { return m_is_splittable; }
bool is_splittable() const;
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
@ -421,7 +420,7 @@ private:
// -1 -> is unknown value (before first cheking)
// 0 -> is not splittable
// 1 -> is splittable
int m_is_splittable {-1};
mutable int m_is_splittable{ -1 };
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object)
{

View file

@ -2450,6 +2450,7 @@ void PrintConfigDef::init_sla_params()
def->enum_values.push_back("portrait");
def->enum_labels.push_back(L("Landscape"));
def->enum_labels.push_back(L("Portrait"));
def->mode = comExpert;
def->default_value = new ConfigOptionEnum<SLADisplayOrientation>(sladoPortrait);
def = this->add("fast_tilt_time", coFloat);
@ -2563,6 +2564,7 @@ void PrintConfigDef::init_sla_params()
def->category = L("Supports");
def->tooltip = L("Generate supports for the models");
def->cli = "";
def->mode = comSimple;
def->default_value = new ConfigOptionBool(true);
def = this->add("support_head_front_diameter", coFloat);
@ -2572,6 +2574,7 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(0.4);
def = this->add("support_head_penetration", coFloat);
@ -2580,6 +2583,7 @@ void PrintConfigDef::init_sla_params()
def->tooltip = L("How much the pinhead has to penetrate the model surface");
def->sidetext = L("mm");
def->cli = "";
def->mode = comAdvanced;
def->min = 0;
def->default_value = new ConfigOptionFloat(0.2);
@ -2590,6 +2594,8 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->max = 20;
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(1.0);
def = this->add("support_pillar_diameter", coFloat);
@ -2599,6 +2605,8 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->max = 15;
def->mode = comSimple;
def->default_value = new ConfigOptionFloat(1.0);
def = this->add("support_pillar_connection_mode", coEnum);
@ -2615,6 +2623,7 @@ void PrintConfigDef::init_sla_params()
def->enum_labels.push_back(L("Zig-Zag"));
def->enum_labels.push_back(L("Cross"));
def->enum_labels.push_back(L("Dynamic"));
def->mode = comAdvanced;
def->default_value = new ConfigOptionEnum<SLAPillarConnectionMode>(slapcmDynamic);
def = this->add("support_buildplate_only", coBool);
@ -2634,6 +2643,7 @@ void PrintConfigDef::init_sla_params()
def->cli = "";
def->min = 0;
def->max = 1;
def->mode = comExpert;
def->default_value = new ConfigOptionFloat(0.0);
def = this->add("support_base_diameter", coFloat);
@ -2643,6 +2653,8 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->max = 30;
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(4.0);
def = this->add("support_base_height", coFloat);
@ -2652,6 +2664,7 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(1.0);
def = this->add("support_critical_angle", coFloat);
@ -2661,6 +2674,8 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("°");
def->cli = "";
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->default_value = new ConfigOptionFloat(45);
def = this->add("support_max_bridge_length", coFloat);
@ -2670,8 +2685,20 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(15.0);
def = this->add("support_max_pillar_link_distance", coFloat);
def->label = L("Max pillar linking distance");
def->category = L("Supports");
def->tooltip = L("The max distance of two pillars to get linked with each other."
" A zero value will prohibit pillar cascading.");
def->sidetext = L("mm");
def->cli = "";
def->min = 0; // 0 means no linking
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(10.0);
def = this->add("support_object_elevation", coFloat);
def->label = L("Object elevation");
def->category = L("Supports");
@ -2679,6 +2706,8 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->max = 150; // This is the max height of print on SL1
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(5.0);
def = this->add("support_points_density_relative", coInt);
@ -2704,35 +2733,46 @@ void PrintConfigDef::init_sla_params()
def->category = L("Pad");
def->tooltip = L("Add a pad underneath the supported model");
def->cli = "";
def->mode = comSimple;
def->default_value = new ConfigOptionBool(true);
def = this->add("pad_wall_thickness", coFloat);
def->label = L("Pad wall thickness");
def->category = L("Pad");
// def->tooltip = L("");
def->tooltip = L("The thickness of the pad and its optional cavity walls.");
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->max = 30;
def->mode = comSimple;
def->default_value = new ConfigOptionFloat(2.0);
def = this->add("pad_wall_height", coFloat);
def->label = L("Pad wall height");
def->tooltip = L("Defines the cavity depth. Set to zero to disable the cavity.");
def->category = L("Pad");
// def->tooltip = L("");
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->max = 30;
def->mode = comSimple;
def->default_value = new ConfigOptionFloat(5.0);
def = this->add("pad_max_merge_distance", coFloat);
def->label = L("Max merge distance");
def->category = L("Pad");
// def->tooltip = L("");
def->tooltip = L("Some objects can get along with a few smaller pads "
"instead of a single big one. This parameter defines "
"how far the center of two smaller pads should be. If they"
"are closer, they will get merged into one pad.");
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->mode = comExpert;
def->default_value = new ConfigOptionFloat(50.0);
// This is disabled on the UI. I hope it will never be enabled.
def = this->add("pad_edge_radius", coFloat);
def->label = L("Pad edge radius");
def->category = L("Pad");
@ -2740,6 +2780,7 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(1.0);
def = this->add("pad_wall_slope", coFloat);
@ -2751,6 +2792,7 @@ void PrintConfigDef::init_sla_params()
def->cli = "";
def->min = 45;
def->max = 90;
def->mode = comAdvanced;
def->default_value = new ConfigOptionFloat(45.0);
}

View file

@ -1008,6 +1008,9 @@ public:
// The max length of a bridge in mm
ConfigOptionFloat support_max_bridge_length /*= 15.0*/;
// The max distance of two pillars to get cross linked.
ConfigOptionFloat support_max_pillar_link_distance;
// The elevation in Z direction upwards. This is the space between the pad
// and the model object's bounding box bottom. Units in mm.
ConfigOptionFloat support_object_elevation /*= 5.0*/;
@ -1055,6 +1058,7 @@ protected:
OPT_PTR(support_base_height);
OPT_PTR(support_critical_angle);
OPT_PTR(support_max_bridge_length);
OPT_PTR(support_max_pillar_link_distance);
OPT_PTR(support_points_density_relative);
OPT_PTR(support_points_minimal_distance);
OPT_PTR(support_object_elevation);

View file

@ -496,7 +496,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
}
for (const Vec2f &pt : poisson_samples) {
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island);
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, m_config.head_diameter/2.f, is_new_island);
structure.supports_force_this_layer += m_config.support_force();
grid3d.insert(pt, &structure);
}

View file

@ -17,6 +17,7 @@ public:
struct Config {
float density_relative;
float minimal_distance;
float head_diameter;
///////////////
inline float support_force() const { return 10.f / density_relative; } // a force one point can support (arbitrary force unit)
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)

View file

@ -2,6 +2,7 @@
#define SLACOMMON_HPP
#include <Eigen/Geometry>
#include <memory>
// #define SLIC3R_SLA_NEEDS_WINDTREE
@ -44,7 +45,6 @@ struct SupportPoint {
bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); }
};
/// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree
/*struct EigenMesh3D {
@ -78,24 +78,38 @@ public:
// Result of a raycast
class hit_result {
double m_t = std::numeric_limits<double>::infinity();
double m_t = std::nan("");
int m_face_id = -1;
const EigenMesh3D& m_mesh;
const EigenMesh3D *m_mesh = nullptr;
Vec3d m_dir;
inline hit_result(const EigenMesh3D& em): m_mesh(em) {}
Vec3d m_source;
friend class EigenMesh3D;
// A valid object of this class can only be obtained from
// EigenMesh3D::query_ray_hit method.
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
public:
// This can create a placeholder object which is invalid (not created
// by a query_ray_hit call) but the distance can be preset to
// a specific value for distinguishing the placeholder.
inline hit_result(double val = std::nan("")): m_t(val) {}
inline double distance() const { return m_t; }
inline const Vec3d& direction() const { return m_dir; }
inline Vec3d position() const { return m_source + m_dir * m_t; }
inline int face() const { return m_face_id; }
inline bool is_valid() const { return m_mesh != nullptr; }
// Hit_result can decay into a double as the hit distance.
inline operator double() const { return distance(); }
inline Vec3d normal() const {
if(m_face_id < 0) return {};
auto trindex = m_mesh.m_F.row(m_face_id);
const Vec3d& p1 = m_mesh.V().row(trindex(0));
const Vec3d& p2 = m_mesh.V().row(trindex(1));
const Vec3d& p3 = m_mesh.V().row(trindex(2));
if(m_face_id < 0 || !is_valid()) return {};
auto trindex = m_mesh->m_F.row(m_face_id);
const Vec3d& p1 = m_mesh->V().row(trindex(0));
const Vec3d& p2 = m_mesh->V().row(trindex(1));
const Vec3d& p3 = m_mesh->V().row(trindex(2));
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
return U.cross(V).normalized();
@ -133,6 +147,8 @@ public:
bool inside(const Vec3d& p) const;
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
};

View file

@ -43,6 +43,8 @@ public:
// For testing
size_t size() const;
bool empty() const { return size() == 0; }
void foreach(std::function<void(const SpatElement& el)> fn);
};
}

File diff suppressed because it is too large Load diff

View file

@ -50,11 +50,6 @@ struct SupportConfig {
// Width in mm from the back sphere center to the front sphere center.
double head_width_mm = 1.0;
// Radius in mm of the support pillars. The actual radius of the pillars
// beginning with a head will not be higher than head_back_radius but the
// headless pillars will have half of this value.
double headless_pillar_radius_mm = 0.4;
// How to connect pillars
PillarConnectionMode pillar_connection_mode = PillarConnectionMode::dynamic;
@ -74,18 +69,34 @@ struct SupportConfig {
double base_height_mm = 1.0;
// The default angle for connecting support sticks and junctions.
double tilt = M_PI/4;
double bridge_slope = M_PI/4;
// The max length of a bridge in mm
double max_bridge_length_mm = 15.0;
double max_bridge_length_mm = 10.0;
// The max distance of a pillar to pillar link.
double max_pillar_link_distance_mm = 10.0;
// The elevation in Z direction upwards. This is the space between the pad
// and the model object's bounding box bottom.
double object_elevation_mm = 10;
// The max Z angle for a normal at which it will get completely ignored.
double normal_cutoff_angle = 150.0 * M_PI / 180.0;
// /////////////////////////////////////////////////////////////////////////
// Compile time configuration values (candidates for runtime)
// /////////////////////////////////////////////////////////////////////////
// The max Z angle for a normal at which it will get completely ignored.
static const double normal_cutoff_angle;
// The shortest distance of any support structure from the model surface
static const double safety_distance_mm;
static const double max_solo_pillar_height_mm;
static const double max_dual_pillar_height_mm;
static const double optimizer_rel_score_diff;
static const unsigned optimizer_max_iterations;
static const unsigned pillar_cascade_neighbors;
static const unsigned max_bridges_on_pillar;
};
struct PoolConfig;
@ -116,21 +127,14 @@ using PointSet = Eigen::MatrixXd;
//EigenMesh3D to_eigenmesh(const ModelObject& model);
// Simple conversion of 'vector of points' to an Eigen matrix
PointSet to_point_set(const std::vector<sla::SupportPoint>&);
//PointSet to_point_set(const std::vector<sla::SupportPoint>&);
/* ************************************************************************** */
/// Just a wrapper to the runtime error to be recognizable in try blocks
class SLASupportsStoppedException: public std::runtime_error {
public:
using std::runtime_error::runtime_error;
SLASupportsStoppedException();
};
/// The class containing mesh data for the generated supports.
class SLASupportTree {
class Impl;
class Impl; // persistent support data
std::unique_ptr<Impl> m_impl;
Impl& get() { return *m_impl; }
@ -140,16 +144,25 @@ class SLASupportTree {
const SupportConfig&,
const Controller&);
/// Generate the 3D supports for a model intended for SLA print.
bool generate(const PointSet& pts,
// The generation algorithm is quite long and will be captured in a separate
// class with private data, helper methods, etc... This data is only needed
// during the calculation whereas the Impl class contains the persistent
// data, mostly the meshes.
class Algorithm;
// Generate the 3D supports for a model intended for SLA print. This
// will instantiate the Algorithm class and call its appropriate methods
// with status indication.
bool generate(const std::vector<SupportPoint>& pts,
const EigenMesh3D& mesh,
const SupportConfig& cfg = {},
const Controller& ctl = {});
public:
SLASupportTree();
SLASupportTree(const PointSet& pts,
SLASupportTree(const std::vector<SupportPoint>& pts,
const EigenMesh3D& em,
const SupportConfig& cfg = {},
const Controller& ctl = {});

View file

@ -17,6 +17,8 @@
#include <igl/remove_duplicate_vertices.h>
#include <igl/signed_distance.h>
#include <tbb/parallel_for.h>
#include "SLASpatIndex.hpp"
#include "ClipperUtils.hpp"
@ -89,6 +91,11 @@ size_t SpatIndex::size() const
return m_impl->m_store.size();
}
void SpatIndex::foreach(std::function<void (const SpatElement &)> fn)
{
for(auto& el : m_impl->m_store) fn(el);
}
/* ****************************************************************************
* EigenMesh3D implementation
* ****************************************************************************/
@ -167,6 +174,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
hit_result ret(*this);
ret.m_t = double(hit.t);
ret.m_dir = dir;
ret.m_source = s;
if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id;
return ret;
@ -186,6 +194,15 @@ bool EigenMesh3D::inside(const Vec3d &p) const {
}
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
double sqdst = 0;
Eigen::Matrix<double, 1, 3> pp = p;
Eigen::Matrix<double, 1, 3> cc;
sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc);
c = cc;
return sqdst;
}
/* ****************************************************************************
* Misc functions
* ****************************************************************************/
@ -208,22 +225,31 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
PointSet normals(const PointSet& points,
const EigenMesh3D& mesh,
double eps,
std::function<void()> throw_on_cancel)
std::function<void()> thr, // throw on cancel
const std::vector<unsigned>& pt_indices = {})
{
if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
return {};
Eigen::VectorXd dists;
Eigen::VectorXi I;
PointSet C;
std::vector<unsigned> range = pt_indices;
if(range.empty()) {
range.resize(size_t(points.rows()), 0);
std::iota(range.begin(), range.end(), 0);
}
igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C);
PointSet ret(range.size(), 3);
PointSet ret(I.rows(), 3);
for(int i = 0; i < I.rows(); i++) {
throw_on_cancel();
auto idx = I(i);
auto trindex = mesh.F().row(idx);
tbb::parallel_for(size_t(0), range.size(),
[&ret, &range, &mesh, &points, thr, eps](size_t ridx)
{
thr();
auto eidx = Eigen::Index(range[ridx]);
int faceid = 0;
Vec3d p;
mesh.squared_distance(points.row(eidx), faceid, p);
auto trindex = mesh.F().row(faceid);
const Vec3d& p1 = mesh.V().row(trindex(0));
const Vec3d& p2 = mesh.V().row(trindex(1));
@ -237,8 +263,6 @@ PointSet normals(const PointSet& points,
// of its triangle. The procedure is the same, get the neighbor
// triangles and calculate an average normal.
const Vec3d& p = C.row(i);
// mark the vertex indices of the edge. ia and ib marks and edge ic
// will mark a single vertex.
int ia = -1, ib = -1, ic = -1;
@ -266,7 +290,7 @@ PointSet normals(const PointSet& points,
std::vector<Vec3i> neigh;
if(ic >= 0) { // The point is right on a vertex of the triangle
for(int n = 0; n < mesh.F().rows(); ++n) {
throw_on_cancel();
thr();
Vec3i ni = mesh.F().row(n);
if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
neigh.emplace_back(ni);
@ -275,7 +299,7 @@ PointSet normals(const PointSet& points,
else if(ia >= 0 && ib >= 0) { // the point is on and edge
// now get all the neigboring triangles
for(int n = 0; n < mesh.F().rows(); ++n) {
throw_on_cancel();
thr();
Vec3i ni = mesh.F().row(n);
if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
@ -316,52 +340,32 @@ PointSet normals(const PointSet& points,
Vec3d sumnorm(0, 0, 0);
sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm);
sumnorm.normalize();
ret.row(i) = sumnorm;
ret.row(long(ridx)) = sumnorm;
}
else { // point lies safely within its triangle
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
ret.row(i) = U.cross(V).normalized();
}
ret.row(long(ridx)) = U.cross(V).normalized();
}
});
return ret;
}
namespace bgi = boost::geometry::index;
using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >;
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const sla::PointSet& points,
std::function<bool(const SpatElement&, const SpatElement&)> pred,
unsigned max_points = 0)
ClusteredPoints cluster(Index3D& sindex, unsigned max_points,
std::function<std::vector<SpatElement>(const Index3D&, const SpatElement&)> qfn)
{
namespace bgi = boost::geometry::index;
using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >;
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(unsigned idx = 0; idx < points.rows(); idx++)
sindex.insert( std::make_pair(points.row(idx), idx));
using Elems = std::vector<SpatElement>;
// Recursive function for visiting all the points in a given distance to
// each other
std::function<void(Elems&, Elems&)> group =
[&sindex, &group, pred, max_points](Elems& pts, Elems& cluster)
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
{
for(auto& p : pts) {
std::vector<SpatElement> tmp;
sindex.query(
bgi::satisfies([p, pred](const SpatElement& se) {
return pred(p, se);
}),
std::back_inserter(tmp)
);
std::vector<SpatElement> tmp = qfn(sindex, p);
auto cmp = [](const SpatElement& e1, const SpatElement& e2){
return e1.second < e2.second;
};
@ -405,5 +409,84 @@ ClusteredPoints cluster(
return result;
}
namespace {
std::vector<SpatElement> distance_queryfn(const Index3D& sindex,
const SpatElement& p,
double dist,
unsigned max_points)
{
std::vector<SpatElement> tmp; tmp.reserve(max_points);
sindex.query(
bgi::nearest(p.first, max_points),
std::back_inserter(tmp)
);
for(auto it = tmp.begin(); it < tmp.end(); ++it)
if(distance(p.first, it->first) > dist) it = tmp.erase(it);
return tmp;
}
}
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
double dist,
unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
return cluster(sindex, max_points,
[dist, max_points](const Index3D& sidx, const SpatElement& p)
{
return distance_queryfn(sidx, p, dist, max_points);
});
}
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const std::vector<unsigned>& indices,
std::function<Vec3d(unsigned)> pointfn,
std::function<bool(const SpatElement&, const SpatElement&)> predicate,
unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
return cluster(sindex, max_points,
[max_points, predicate](const Index3D& sidx, const SpatElement& p)
{
std::vector<SpatElement> tmp; tmp.reserve(max_points);
sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){
return predicate(p, e);
}), std::back_inserter(tmp));
return tmp;
});
}
ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
{
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(Eigen::Index i = 0; i < pts.rows(); i++)
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
return cluster(sindex, max_points,
[dist, max_points](const Index3D& sidx, const SpatElement& p)
{
return distance_queryfn(sidx, p, dist, max_points);
});
}
}
}

View file

@ -545,9 +545,9 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
scfg.head_width_mm = c.support_head_width.getFloat();
scfg.object_elevation_mm = c.support_object_elevation.getFloat();
scfg.tilt = c.support_critical_angle.getFloat() * PI / 180.0 ;
scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
scfg.headless_pillar_radius_mm = 0.375*c.support_pillar_diameter.getFloat();
scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat();
switch(c.support_pillar_connection_mode.getInt()) {
case slapcmZigZag:
scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break;
@ -674,6 +674,7 @@ void SLAPrint::process()
// the density config value is in percents:
config.density_relative = float(cfg.support_points_density_relative / 100.f);
config.minimal_distance = float(cfg.support_points_minimal_distance);
config.head_diameter = float(cfg.support_head_front_diameter);
// Construction of this object does the calculation.
this->throw_if_canceled();
@ -711,7 +712,6 @@ void SLAPrint::process()
return;
}
try {
sla::SupportConfig scfg = make_support_cfg(po.m_config);
sla::Controller ctl;
@ -737,9 +737,11 @@ void SLAPrint::process()
ctl.cancelfn = [this]() { throw_if_canceled(); };
po.m_supportdata->support_tree_ptr.reset(
new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points),
new SLASupportTree(po.m_supportdata->support_points,
po.m_supportdata->emesh, scfg, ctl));
throw_if_canceled();
// Create the unified mesh
auto rc = SlicingStatus::RELOAD_SCENE;
@ -755,10 +757,7 @@ void SLAPrint::process()
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
report_status(*this, -1, L("Visualizing supports"), rc);
} catch(sla::SLASupportsStoppedException&) {
// no need to rethrow
// throw_if_canceled();
}
};
// This step generates the sla base pad
@ -1390,6 +1389,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "support_base_height"
|| opt_key == "support_critical_angle"
|| opt_key == "support_max_bridge_length"
|| opt_key == "support_max_pillar_link_distance"
|| opt_key == "support_object_elevation") {
steps.emplace_back(slaposSupportTree);
} else if (

View file

@ -247,6 +247,11 @@ private:
// Invalidate steps based on a set of parameters changed.
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
std::vector<float> calculate_heights(const BoundingBoxf3& bb,
float elevation,
float initial_layer_height,
float layer_height) const;
void fill_statistics();
SLAPrintConfig m_print_config;
@ -270,8 +275,6 @@ private:
lref(std::cref(lyr)), copies(std::cref(cp)) {}
};
std::vector<float> calculate_heights(const BoundingBoxf3& bb, float elevation, float initial_layer_height, float layer_height) const;
// One level may contain multiple slices from multiple objects and their
// supports
using LayerRefs = std::vector<LayerRef>;

View file

@ -4564,11 +4564,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
const Print *print = m_process->fff_print();
float depth = print->get_wipe_tower_depth();
// Calculate wipe tower brim spacing.
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4);
if (!print->is_step_done(psWipeTower))
depth = (900.f/w) * (float)(extruders_count - 1) ;
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower),
print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
brim_spacing * 4.5f);
if (volume_idx_wipe_tower_old != -1)
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
}
@ -7852,10 +7859,17 @@ void GLCanvas3D::_load_shells_fff()
unsigned int extruders_count = config.nozzle_diameter.size();
if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
float depth = print->get_wipe_tower_depth();
// Calculate wipe tower brim spacing.
const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4);
if (!print->is_step_done(psWipeTower))
depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ;
m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), print->config().nozzle_diameter.values[0] * 1.25f * 4.5f);
m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f);
}
}
}

View file

@ -1873,8 +1873,8 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b
float render_color[3];
for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i)
{
const sla::SupportPoint& support_point = m_editing_mode_cache[i].first;
const bool& point_selected = m_editing_mode_cache[i].second;
const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point;
const bool& point_selected = m_editing_mode_cache[i].selected;
// First decide about the color of the point.
if (picking) {
@ -1889,7 +1889,7 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b
render_color[2] = 1.0f;
}
else { // neigher hover nor picking
bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island;
bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].support_point.is_new_island;
if (m_editing_mode) {
render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f);
render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f);
@ -1903,12 +1903,32 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b
float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f};
::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive);
// Now render the sphere. Inverse matrix of the instance scaling is applied so that the
// sphere does not scale with the object.
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
::glPushMatrix();
::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2));
::glMultMatrixd(instance_scaling_matrix_inverse.data());
::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36);
// Matrices set, we can render the point mark now.
// If in editing mode, we'll also render a cone pointing to the sphere.
if (m_editing_mode) {
if (m_editing_mode_cache[i].normal == Vec3f::Zero())
update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it
Eigen::Quaterniond q;
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_mode_cache[i].normal.cast<double>());
Eigen::AngleAxisd aa(q);
::glRotated(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
const float cone_radius = 0.25f; // mm
const float cone_height = 0.75f;
::glPushMatrix();
::glTranslatef(0.f, 0.f, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale);
::gluCylinder(m_quadric, 0.f, cone_radius, cone_height, 36, 1);
::glTranslatef(0.f, 0.f, cone_height);
::gluDisk(m_quadric, 0.0, cone_radius, 36, 1);
::glPopMatrix();
}
::gluSphere(m_quadric, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale, 64, 36);
::glPopMatrix();
}
@ -1957,7 +1977,7 @@ void GLGizmoSlaSupports::update_mesh()
m_AABB.init(m_V, m_F);
}
Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
{
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
if (m_V.size() == 0)
@ -1992,9 +2012,16 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hit))
throw std::invalid_argument("unproject_on_mesh(): No intersection found.");
int fid = hit.id;
Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
int fid = hit.id; // facet id
Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
// Calculate and return both the point and the facet normal.
return std::make_pair(
bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)),
a.cross(b)
);
}
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
@ -2046,10 +2073,9 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous
// If there is some selection, don't add new point and deselect everything instead.
if (m_selection_empty) {
Vec3f new_pos;
try {
new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case
m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), false));
std::pair<Vec3f, Vec3f> pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws
m_editing_mode_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second);
m_unsaved_changes = true;
}
catch (...) { // not clicked on object
@ -2090,7 +2116,7 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous
// Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh:
for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) {
const sla::SupportPoint &support_point = m_editing_mode_cache[i].first;
const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point;
Vec3f pos = instance_matrix.cast<float>() * support_point.pos;
pos(2) += z_offset;
GLdouble out_x, out_y, out_z;
@ -2166,7 +2192,7 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous
void GLGizmoSlaSupports::delete_selected_points(bool force)
{
for (unsigned int idx=0; idx<m_editing_mode_cache.size(); ++idx) {
if (m_editing_mode_cache[idx].second && (!m_editing_mode_cache[idx].first.is_new_island || !m_lock_unique_islands || force)) {
if (m_editing_mode_cache[idx].selected && (!m_editing_mode_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) {
m_editing_mode_cache.erase(m_editing_mode_cache.begin() + (idx--));
m_unsaved_changes = true;
}
@ -2181,14 +2207,15 @@ void GLGizmoSlaSupports::delete_selected_points(bool force)
void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection)
{
if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].first.is_new_island || !m_lock_unique_islands)) {
Vec3f new_pos;
if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) {
std::pair<Vec3f, Vec3f> pos_and_normal;
try {
new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1)));
pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1)));
}
catch (...) { return; }
m_editing_mode_cache[m_hover_id].first.pos = new_pos;
m_editing_mode_cache[m_hover_id].first.is_new_island = false;
m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first;
m_editing_mode_cache[m_hover_id].support_point.is_new_island = false;
m_editing_mode_cache[m_hover_id].normal = pos_and_normal.second;
m_unsaved_changes = true;
// Do not update immediately, wait until the mouse is released.
// m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
@ -2227,15 +2254,15 @@ void GLGizmoSlaSupports::render_tooltip_texture() const {
#endif // not ENABLE_IMGUI
std::vector<ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const
std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const
{
std::vector<ConfigOption*> out;
std::vector<const ConfigOption*> out;
if (!m_model_object)
return out;
DynamicPrintConfig& object_cfg = m_model_object->config;
DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
const DynamicPrintConfig& object_cfg = m_model_object->config;
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
for (const std::string& key : keys) {
@ -2255,6 +2282,18 @@ std::vector<ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vec
}
void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const
{
int idx = 0;
Eigen::Matrix<float, 1, 3> pp = m_editing_mode_cache[i].support_point.pos;
Eigen::Matrix<float, 1, 3> cc;
m_AABB.squared_distance(m_V, m_F, pp, idx, cc);
Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0)));
Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0)));
m_editing_mode_cache[i].normal = a.cross(b);
}
#if ENABLE_IMGUI
void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection)
@ -2268,7 +2307,7 @@ RENDER_AGAIN:
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
const float scaling = m_imgui->get_style_scaling();
const ImVec2 window_size(285.f * scaling, 260.f * scaling);
const ImVec2 window_size(285.f * scaling, 300.f * scaling);
ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
ImGui::SetNextWindowSize(ImVec2(window_size));
@ -2287,30 +2326,19 @@ RENDER_AGAIN:
m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)")));
m_imgui->text(" "); // vertical gap
static const std::vector<float> options = {0.2f, 0.4f, 0.6f, 0.8f, 1.0f};
static const std::vector<std::string> options_str = {"0.2", "0.4", "0.6", "0.8", "1.0"};
int selection = -1;
for (size_t i = 0; i < options.size(); i++) {
if (options[i] == m_new_point_head_diameter) { selection = (int)i; }
}
float diameter_upper_cap = static_cast<ConfigOptionFloat*>(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value;
if (m_new_point_head_diameter > diameter_upper_cap)
m_new_point_head_diameter = diameter_upper_cap;
bool old_combo_state = m_combo_box_open;
// The combo is commented out for now, until the feature is supported by backend.
// m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options_str, selection);
force_refresh |= (old_combo_state != m_combo_box_open);
// float current_number = atof(str);
const float current_number = selection < options.size() ? options[selection] : m_new_point_head_diameter;
if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change)
for (auto& point_and_selection : m_editing_mode_cache)
if (point_and_selection.second) {
point_and_selection.first.head_front_radius = current_number / 2.f;
m_imgui->text(_(L("Head diameter: ")));
ImGui::SameLine();
if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) {
// value was changed
for (auto& cache_entry : m_editing_mode_cache)
if (cache_entry.selected) {
cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f;
m_unsaved_changes = true;
}
if (std::abs(current_number - m_new_point_head_diameter) > 0.001) {
force_refresh = true;
m_new_point_head_diameter = current_number;
}
bool changed = m_lock_unique_islands;
@ -2343,9 +2371,9 @@ RENDER_AGAIN:
m_imgui->text(_(L("Minimal points distance: ")));
ImGui::SameLine();
std::vector<ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"});
float density = static_cast<ConfigOptionInt*>(opts[0])->value;
float minimal_point_distance = static_cast<ConfigOptionFloat*>(opts[1])->value;
std::vector<const ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"});
float density = static_cast<const ConfigOptionInt*>(opts[0])->value;
float minimal_point_distance = static_cast<const ConfigOptionFloat*>(opts[1])->value;
bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm");
if (value_changed)
@ -2451,6 +2479,10 @@ void GLGizmoSlaSupports::on_set_state()
m_parent.toggle_model_objects_visibility(false);
if (m_model_object)
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
// Set default head diameter from config.
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
if (m_model_object) {
@ -2487,12 +2519,16 @@ void GLGizmoSlaSupports::select_point(int i)
{
if (i == AllPoints || i == NoPoints) {
for (auto& point_and_selection : m_editing_mode_cache)
point_and_selection.second = ( i == AllPoints );
point_and_selection.selected = ( i == AllPoints );
m_selection_empty = (i == NoPoints);
if (i == AllPoints)
m_new_point_head_diameter = m_editing_mode_cache[0].support_point.head_front_radius * 2.f;
}
else {
m_editing_mode_cache[i].second = true;
m_editing_mode_cache[i].selected = true;
m_selection_empty = false;
m_new_point_head_diameter = m_editing_mode_cache[i].support_point.head_front_radius * 2.f;
}
}
@ -2502,7 +2538,7 @@ void GLGizmoSlaSupports::editing_mode_discard_changes()
{
m_editing_mode_cache.clear();
for (const sla::SupportPoint& point : m_model_object->sla_support_points)
m_editing_mode_cache.push_back(std::make_pair(point, false));
m_editing_mode_cache.emplace_back(point, false);
m_editing_mode = false;
m_unsaved_changes = false;
}
@ -2516,8 +2552,8 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
if (m_unsaved_changes) {
m_model_object->sla_points_status = sla::PointsStatus::UserModified;
m_model_object->sla_support_points.clear();
for (const std::pair<sla::SupportPoint, bool>& point_and_selection : m_editing_mode_cache)
m_model_object->sla_support_points.push_back(point_and_selection.first);
for (const CacheEntry& cache_entry : m_editing_mode_cache)
m_model_object->sla_support_points.push_back(cache_entry.support_point);
// Recalculate support structures once the editing mode is left.
// m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
@ -2534,7 +2570,7 @@ void GLGizmoSlaSupports::editing_mode_reload_cache()
{
m_editing_mode_cache.clear();
for (const sla::SupportPoint& point : m_model_object->sla_support_points)
m_editing_mode_cache.push_back(std::make_pair(point, false));
m_editing_mode_cache.emplace_back(point, false);
m_unsaved_changes = false;
}

View file

@ -471,7 +471,7 @@ private:
ModelObject* m_old_model_object = nullptr;
int m_active_instance = -1;
int m_old_instance_id = -1;
Vec3f unproject_on_mesh(const Vec2d& mouse_pos);
std::pair<Vec3f, Vec3f> unproject_on_mesh(const Vec2d& mouse_pos);
const float RenderPointScale = 1.f;
@ -484,6 +484,16 @@ private:
Geometry::Transformation transformation;
};
class CacheEntry {
public:
CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) :
support_point(point), selected(sel), normal(norm) {}
sla::SupportPoint support_point;
bool selected; // whether the point is selected
Vec3f normal;
};
// This holds information to decide whether recalculation is necessary:
SourceDataSummary m_source_data;
@ -510,6 +520,7 @@ private:
void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const;
bool is_mesh_update_necessary() const;
void update_mesh();
void update_cache_entry_normal(unsigned int i) const;
#if !ENABLE_IMGUI
void render_tooltip_texture() const;
@ -520,10 +531,10 @@ private:
bool m_lock_unique_islands = false;
bool m_editing_mode = false; // Is editing mode active?
bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes).
float m_new_point_head_diameter = 0.4f; // Size of a new point.
float m_new_point_head_diameter; // Size of a new point.
float m_minimal_point_distance = 20.f;
float m_density = 100.f;
std::vector<std::pair<sla::SupportPoint, bool>> m_editing_mode_cache; // a support point and whether it is currently selected
mutable std::vector<CacheEntry> m_editing_mode_cache; // a support point and whether it is currently selected
bool m_selection_rectangle_active = false;
Vec2d m_selection_rectangle_start_corner;
@ -536,7 +547,7 @@ private:
int m_canvas_width;
int m_canvas_height;
std::vector<ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
// Methods that do the model_object and editing cache synchronization,
// editing mode selection, etc:

View file

@ -167,17 +167,6 @@ bool GUI_App::OnInit()
if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save();
// ! Temporary workaround for the correct behavior of the Scrolled sidebar panel
// Do this "manipulations" only once ( after (re)create of the application )
if (sidebar().obj_list()->GetMinHeight() > 15 * wxGetApp().em_unit())
{
wxWindowUpdateLocker noUpdates_sidebar(&sidebar());
sidebar().obj_list()->SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit()));
// !!! to correct later layouts
update_mode(); // update view mode after fix of the object_list size
}
this->obj_manipul()->update_if_dirty();
// Preset updating & Configwizard are done after the above initializations,
@ -205,12 +194,13 @@ bool GUI_App::OnInit()
}
preset_updater->sync(preset_bundle);
});
load_current_presets();
}
});
load_current_presets();
mainframe->Show(true);
update_mode(); // update view mode after fix of the object_list size
m_initialized = true;
return true;
}

View file

@ -115,10 +115,7 @@ ObjectList::~ObjectList()
void ObjectList::create_objects_ctrl()
{
// temporary workaround for the correct behavior of the Scrolled sidebar panel:
// 1. set a height of the list to some big value
// 2. change it to the normal min value (200) after first whole App updating/layouting
SetMinSize(wxSize(-1, 3000)); // #ys_FIXME
SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit()));
m_sizer = new wxBoxSizer(wxVERTICAL);
m_sizer->Add(this, 1, wxGROW);
@ -430,10 +427,8 @@ void ObjectList::OnContextMenu(wxDataViewEvent&)
else if (title == _("Name") && pt.x >15 &&
m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData())
{
if (is_windows10()) {
const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item));
wxGetApp().plater()->fix_through_netfabb(obj_idx);
}
if (is_windows10())
fix_through_netfabb();
}
#ifndef __WXMSW__
GetMainWindow()->SetToolTip(""); // hide tooltip
@ -1522,12 +1517,7 @@ bool ObjectList::is_splittable()
if (!get_volume_by_item(item, volume) || !volume)
return false;
int splittable = volume->is_splittable();
if (splittable == -1) {
splittable = (int)volume->mesh.has_multiple_patches();
volume->set_splittable(splittable);
}
return splittable != 0;
return volume->is_splittable();
}
bool ObjectList::selected_instances_of_same_object()
@ -2280,13 +2270,37 @@ void ObjectList::fix_through_netfabb() const
if (!item)
return;
ItemType type = m_objects_model->GetItemType(item);
const ItemType type = m_objects_model->GetItemType(item);
if (type & itObject)
wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(item));
else if (type & itVolume)
wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)),
m_objects_model->GetVolumeIdByItem(item));
const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) :
type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1;
const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1;
wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx);
update_item_error_icon(obj_idx, vol_idx);
}
void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const
{
const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) :
m_objects_model->GetItemByVolumeId(obj_idx, vol_idx);
if (!item)
return;
auto model_object = (*m_objects)[obj_idx];
const stl_stats& stats = model_object->volumes[vol_idx<0 ? 0 : vol_idx]->mesh.stl.stats;
const int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
stats.facets_added + stats.facets_reversed + stats.backwards_edges;
if (errors == 0) {
// delete Error_icon if all errors are fixed
wxVariant variant;
variant << PrusaDataViewBitmapText(from_u8(model_object->name), wxNullBitmap);
m_objects_model->SetValue(variant, item, 0);
}
}
void ObjectList::ItemValueChanged(wxDataViewEvent &event)

View file

@ -270,6 +270,7 @@ public:
void split_instances();
void rename_item();
void fix_through_netfabb() const;
void update_item_error_icon(const int obj_idx, int vol_idx) const ;
private:
void OnChar(wxKeyEvent& event);
void OnContextMenu(wxDataViewEvent &event);

View file

@ -33,9 +33,6 @@ namespace GUI {
#if !ENABLE_IMGUI
, m_gizmo_widget(nullptr)
#endif // !ENABLE_IMGUI
, m_model(nullptr)
, m_config(nullptr)
, m_process(nullptr)
{
init(parent, bed, camera, view_toolbar, model, config, process);
}
@ -202,18 +199,15 @@ Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_t
, m_enabled(false)
, m_schedule_background_process(schedule_background_process_func)
{
if (init(parent, bed, camera, view_toolbar, config, process, gcode_preview_data))
if (init(parent, bed, camera, view_toolbar))
{
show_hide_ui_elements("none");
load_print();
}
}
bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data)
bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
{
if ((config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr))
return false;
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
return false;
@ -222,7 +216,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view
m_canvas = _3DScene::get_canvas(this->m_canvas_widget);
m_canvas->allow_multisample(GLCanvas3DManager::can_multisample());
m_canvas->set_config(m_config);
m_canvas->set_process(process);
m_canvas->set_process(m_process);
m_canvas->enable_legend_texture(true);
m_canvas->enable_dynamic_background(true);

View file

@ -39,10 +39,6 @@ class View3D : public wxPanel
wxPanel* m_gizmo_widget;
#endif // !ENABLE_IMGUI
Model* m_model;
DynamicPrintConfig* m_config;
BackgroundSlicingProcess* m_process;
public:
View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process);
virtual ~View3D();
@ -124,7 +120,7 @@ public:
void refresh_print();
private:
bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data);
bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
void bind_event_handlers();
void unbind_event_handlers();

View file

@ -107,11 +107,11 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt)
ImGuiIO& io = ImGui::GetIO();
io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY());
io.MouseDown[0] = evt.LeftDown();
io.MouseDown[1] = evt.RightDown();
io.MouseDown[2] = evt.MiddleDown();
io.MouseDown[0] = evt.LeftIsDown();
io.MouseDown[1] = evt.RightIsDown();
io.MouseDown[2] = evt.MiddleIsDown();
unsigned buttons = (evt.LeftDown() ? 1 : 0) | (evt.RightDown() ? 2 : 0) | (evt.MiddleDown() ? 4 : 0);
unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0);
m_mouse_buttons = buttons;
new_frame();
@ -441,6 +441,10 @@ void ImGuiWrapper::init_style()
set_color(ImGuiCol_Header, COL_ORANGE_DARK);
set_color(ImGuiCol_HeaderHovered, COL_ORANGE_LIGHT);
set_color(ImGuiCol_HeaderActive, COL_ORANGE_LIGHT);
// Slider
set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK);
set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT);
}
void ImGuiWrapper::render_draw_data(ImDrawData *draw_data)

View file

@ -524,6 +524,7 @@ struct Sidebar::priv
Plater *plater;
wxScrolledWindow *scrolled;
wxPanel* presets_panel; // Used for MSW better layouts
PrusaModeSizer *mode_sizer;
wxFlexGridSizer *sizer_presets;
@ -555,8 +556,6 @@ void Sidebar::priv::show_preset_comboboxes()
{
const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA;
wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent());
for (size_t i = 0; i < 4; ++i)
sizer_presets->Show(i, !showSLA);
@ -580,6 +579,7 @@ Sidebar::Sidebar(Plater *parent)
p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1));
p->scrolled->SetScrollbars(0, 20, 1, 2);
// Sizer in the scrolled area
auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL);
p->scrolled->SetSizer(scrolled_sizer);
@ -591,12 +591,25 @@ Sidebar::Sidebar(Plater *parent)
p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2);
p->sizer_presets->AddGrowableCol(0, 1);
p->sizer_presets->SetFlexibleDirection(wxBOTH);
bool is_msw = false;
#ifdef __WINDOWS__
p->scrolled->SetDoubleBuffered(true);
p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
p->presets_panel->SetSizer(p->sizer_presets);
is_msw = true;
#else
p->presets_panel = p->scrolled;
#endif //__WINDOWS__
p->sizer_filaments = new wxBoxSizer(wxVERTICAL);
auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) {
auto *text = new wxStaticText(p->scrolled, wxID_ANY, label+" :");
auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :");
text->SetFont(wxGetApp().small_font());
*combo = new PresetComboBox(p->scrolled, preset_type);
*combo = new PresetComboBox(p->presets_panel, preset_type);
auto *sizer_presets = this->p->sizer_presets;
auto *sizer_filaments = this->p->sizer_filaments;
@ -645,9 +658,7 @@ Sidebar::Sidebar(Plater *parent)
p->object_settings->Hide();
p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5);
wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG);
p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer")));
p->btn_send_gcode->SetBitmap(arrow_up);
p->btn_send_gcode->SetFont(wxGetApp().bold_font());
p->btn_send_gcode->Hide();
@ -657,6 +668,8 @@ Sidebar::Sidebar(Plater *parent)
// Sizer in the scrolled area
scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/);
is_msw ?
scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) :
scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5);
scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5);
scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5);
@ -695,7 +708,7 @@ Sidebar::Sidebar(Plater *parent)
Sidebar::~Sidebar() {}
void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) {
*combo = new PresetComboBox(p->scrolled, Slic3r::Preset::TYPE_FILAMENT);
*combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT);
// # copy icons from first choice
// $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
@ -757,6 +770,8 @@ void Sidebar::update_presets(Preset::Type preset_type)
case Preset::TYPE_PRINTER:
{
// wxWindowUpdateLocker noUpdates_scrolled(p->scrolled);
// Update the print choosers to only contain the compatible presets, update the dirty flags.
if (print_tech == ptFFF)
preset_bundle.prints.update_platter_ui(p->combo_print);
@ -817,6 +832,11 @@ wxScrolledWindow* Sidebar::scrolled_panel()
return p->scrolled;
}
wxPanel* Sidebar::presets_panel()
{
return p->presets_panel;
}
ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff)
{
return p->frequently_changed_parameters->get_og(is_fff);
@ -2390,7 +2410,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
//! instead of
//! combo->GetStringSelection().ToUTF8().data());
std::string selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data();
const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data();
if (preset_type == Preset::TYPE_FILAMENT) {
wxGetApp().preset_bundle->set_filament_preset(idx, selected_string);
@ -2402,12 +2422,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo);
}
else {
for (Tab* tab : wxGetApp().tabs_list) {
if (tab->type() == preset_type) {
tab->select_preset(selected_string);
break;
}
}
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
wxGetApp().get_tab(preset_type)->select_preset(selected_string);
}
// update plater with new config
@ -2463,14 +2479,17 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
this->statusbar()->reset_cancel_callback();
this->statusbar()->stop_busy();
bool canceled = evt.GetInt() < 0;
bool success = evt.GetInt() > 0;
const bool canceled = evt.GetInt() < 0;
const bool error = evt.GetInt() == 0;
const bool success = evt.GetInt() > 0;
// Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
this->background_process.reset_export();
if (! success) {
if (error) {
wxString message = evt.GetString();
if (message.IsEmpty())
message = _(L("Export failed"));
show_error(q, message);
this->statusbar()->set_status_text(message);
}
if (canceled)
@ -2890,6 +2909,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
sidebar->show_export(!is_ready_to_slice);
sidebar->show_send(send_gcode_shown && !is_ready_to_slice);
}
sidebar->Layout();
}
void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
@ -3307,8 +3327,10 @@ void Plater::on_extruders_change(int num_extruders)
{
auto& choices = sidebar().combos_filament();
if (num_extruders == choices.size())
return;
wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/);
// sidebar().scrolled_panel()->Freeze();
int i = choices.size();
while ( i < num_extruders )

View file

@ -51,6 +51,8 @@ public:
int get_extruder_idx() const { return extruder_idx; }
void check_selection();
std::string selected_preset_name;
private:
typedef std::size_t Marker;
enum { LABEL_ITEM_MARKER = 0x4d };
@ -81,6 +83,7 @@ public:
ObjectList* obj_list();
ObjectSettings* obj_settings();
wxScrolledWindow* scrolled_panel();
wxPanel* presets_panel();
ConfigOptionsGroup* og_freq_chng_params(const bool is_fff);
wxButton* get_wiping_dialog_button();

View file

@ -457,6 +457,7 @@ const std::vector<std::string>& Preset::sla_print_options()
"support_base_height",
"support_critical_angle",
"support_max_bridge_length",
"support_max_pillar_link_distance",
"support_object_elevation",
"support_points_density_relative",
"support_points_minimal_distance",
@ -878,8 +879,10 @@ size_t PresetCollection::update_compatible_internal(const Preset &active_printer
// Hide the
void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
{
if (ui == nullptr)
if (ui == nullptr ||
ui->selected_preset_name == this->get_selected_preset().name)
return;
// Otherwise fill in the list from scratch.
ui->Freeze();
ui->Clear();
@ -948,6 +951,8 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui)
ui->SetSelection(selected_preset_item);
ui->SetToolTip(ui->GetString(selected_preset_item));
ui->Thaw();
ui->selected_preset_name = this->get_selected_preset().name;
}
size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible)

View file

@ -1436,7 +1436,8 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out
void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui)
{
if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA ||
this->filament_presets.size() <= idx_extruder )
this->filament_presets.size() <= idx_extruder ||
ui->selected_preset_name == this->filaments.find_preset(this->filament_presets[idx_extruder])->name)
return;
unsigned char rgb[3];
@ -1525,6 +1526,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr
ui->SetSelection(selected_preset_item);
ui->SetToolTip(ui->GetString(selected_preset_item));
ui->Thaw();
ui->selected_preset_name = this->filaments.find_preset(this->filament_presets[idx_extruder])->name;
}
void PresetBundle::set_default_suppressed(bool default_suppressed)

View file

@ -73,6 +73,10 @@ void Tab::set_type()
// sub new
void Tab::create_preset_tab()
{
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif //__WINDOWS__
m_preset_bundle = wxGetApp().preset_bundle;
// Vertical sizer to hold the choice menu and the rest of the page.
@ -751,6 +755,10 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo
void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
{
if (wxGetApp().plater() == nullptr) {
return;
}
const bool is_fff = supports_printer_technology(ptFFF);
ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff);
if (opt_key == "fill_density" || opt_key == "pad_enable")
@ -783,25 +791,6 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
wxGetApp().plater()->on_extruders_change(boost::any_cast<size_t>(value));
update();
// #ys_FIXME_to_delete
// Post event to the Plater after updating of the all dirty options
// It helps to avoid needless schedule_background_processing
// if (update_completed())
// if (m_update_stack.empty())
// {
// // wxCommandEvent event(EVT_TAB_VALUE_CHANGED);
// // event.SetEventObject(this);
// // event.SetString(opt_key);
// // if (opt_key == "extruders_count")
// // {
// // const int val = boost::any_cast<size_t>(value);
// // event.SetInt(val);
// // }
// //
// // wxPostEvent(this, event);
// wxGetApp().mainframe->on_value_changed(m_config);
// }
}
// Show/hide the 'purging volumes' button
@ -824,9 +813,13 @@ void Tab::update_wiping_button_visibility() {
// To update the content of the selection boxes,
// to update the filament colors of the selection boxes,
// to update the "dirty" flags of the selection boxes,
// to uddate number of "filament" selection boxes when the number of extruders change.
// to update number of "filament" selection boxes when the number of extruders change.
void Tab::on_presets_changed()
{
// Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
wxGetApp().plater()->sidebar().update_presets(m_type);
update_preset_description_line();
// Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors.
for (auto t: m_dependent_tabs)
{
@ -837,16 +830,6 @@ void Tab::on_presets_changed()
// clear m_dependent_tabs after first update from select_preset()
// to avoid needless preset loading from update() function
m_dependent_tabs.clear();
// #ys_FIXME_to_delete
// wxCommandEvent event(EVT_TAB_PRESETS_CHANGED);
// event.SetEventObject(this);
// wxPostEvent(this, event);
// Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
wxGetApp().plater()->sidebar().update_presets(m_type);
update_preset_description_line();
}
void Tab::update_preset_description_line()
@ -2472,7 +2455,7 @@ void Tab::load_current_preset()
//Regerenerate content of the page tree.
void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/)
{
Freeze();
// Freeze();
// get label of the currently selected item
const auto sel_item = m_treectrl->GetSelection();
@ -2498,7 +2481,7 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/)
// this is triggered on first load, so we don't disable the sel change event
m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem));
}
Thaw();
// Thaw();
}
void Tab::update_page_tree_visibility()
@ -3273,7 +3256,8 @@ void TabSLAPrint::build()
optgroup->append_single_option_line("support_pillar_diameter");
optgroup->append_single_option_line("support_pillar_connection_mode");
optgroup->append_single_option_line("support_buildplate_only");
optgroup->append_single_option_line("support_pillar_widening_factor");
// TODO: This parameter is not used at the moment.
// optgroup->append_single_option_line("support_pillar_widening_factor");
optgroup->append_single_option_line("support_base_diameter");
optgroup->append_single_option_line("support_base_height");
optgroup->append_single_option_line("support_object_elevation");
@ -3281,6 +3265,7 @@ void TabSLAPrint::build()
optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions")));
optgroup->append_single_option_line("support_critical_angle");
optgroup->append_single_option_line("support_max_bridge_length");
optgroup->append_single_option_line("support_max_pillar_link_distance");
optgroup = page->new_optgroup(_(L("Automatic generation")));
optgroup->append_single_option_line("support_points_density_relative");
@ -3339,11 +3324,37 @@ void TabSLAPrint::update()
return; // #ys_FIXME
// #ys_FIXME
// m_update_cnt++;
// ! something to update
// m_update_cnt--;
//
// if (m_update_cnt == 0)
m_update_cnt++;
double head_penetration = m_config->opt_float("support_head_penetration");
double head_width = m_config->opt_float("support_head_width");
if(head_penetration > head_width) {
wxString msg_text = _(L("Head penetration should not be greater than the head width."));
auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *m_config;
if (dialog->ShowModal() == wxID_OK) {
new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width));
}
load_config(new_conf);
}
double pinhead_d = m_config->opt_float("support_head_front_diameter");
double pillar_d = m_config->opt_float("support_pillar_diameter");
if(pinhead_d > pillar_d) {
wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter."));
auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *m_config;
if (dialog->ShowModal() == wxID_OK) {
new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0));
}
load_config(new_conf);
}
m_update_cnt--;
if (m_update_cnt == 0)
wxGetApp().mainframe->on_config_changed(m_config);
}