mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-23 08:41:11 -06:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_splitted_vbuffer
This commit is contained in:
commit
676540279d
111 changed files with 12989 additions and 9870 deletions
|
@ -150,15 +150,11 @@ int CLI::run(int argc, char **argv)
|
|||
}
|
||||
|
||||
// Read input file(s) if any.
|
||||
for (const std::string& file : m_input_files) {
|
||||
std::string ext = boost::filesystem::path(file).extension().string();
|
||||
if (ext == ".gcode" || ext == ".g") {
|
||||
if (boost::filesystem::exists(file)) {
|
||||
start_as_gcodeviewer = true;
|
||||
break;
|
||||
}
|
||||
for (const std::string& file : m_input_files)
|
||||
if (is_gcode_file(file) && boost::filesystem::exists(file)) {
|
||||
start_as_gcodeviewer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!start_as_gcodeviewer) {
|
||||
for (const std::string& file : m_input_files) {
|
||||
if (!boost::filesystem::exists(file)) {
|
||||
|
|
|
@ -716,45 +716,33 @@ static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *ou
|
|||
});
|
||||
}
|
||||
|
||||
static void traverse_pt_old(ClipperLib::PolyNodes &nodes, Polygons* retval)
|
||||
static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval)
|
||||
{
|
||||
/* use a nearest neighbor search to order these children
|
||||
TODO: supply start_near to chained_path() too? */
|
||||
|
||||
// collect ordering points
|
||||
Points ordering_points;
|
||||
ordering_points.reserve(nodes.size());
|
||||
for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
|
||||
Point p((*it)->Contour.front().X, (*it)->Contour.front().Y);
|
||||
ordering_points.push_back(p);
|
||||
}
|
||||
|
||||
// perform the ordering
|
||||
ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes);
|
||||
|
||||
// push results recursively
|
||||
for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) {
|
||||
for (const ClipperLib::PolyNode *node : nodes)
|
||||
ordering_points.emplace_back(node->Contour.front().X, node->Contour.front().Y);
|
||||
|
||||
// Perform the ordering, push results recursively.
|
||||
//FIXME pass the last point to chain_clipper_polynodes?
|
||||
for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
|
||||
retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour));
|
||||
if (node->IsHole())
|
||||
// Orient a hole, which is clockwise oriented, to CCW.
|
||||
retval->back().reverse();
|
||||
// traverse the next depth
|
||||
traverse_pt_old((*it)->Childs, retval);
|
||||
retval->push_back(ClipperPath_to_Slic3rPolygon((*it)->Contour));
|
||||
if ((*it)->IsHole()) retval->back().reverse(); // ccw
|
||||
traverse_pt_outside_in(node->Childs, retval);
|
||||
}
|
||||
}
|
||||
|
||||
Polygons union_pt_chained(const Polygons &subject, bool safety_offset_)
|
||||
Polygons union_pt_chained_outside_in(const Polygons &subject, bool safety_offset_)
|
||||
{
|
||||
ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_);
|
||||
|
||||
Polygons retval;
|
||||
traverse_pt_old(polytree.Childs, &retval);
|
||||
traverse_pt_outside_in(polytree.Childs, &retval);
|
||||
return retval;
|
||||
|
||||
// TODO: This needs to be tested:
|
||||
// ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_);
|
||||
|
||||
// Polygons retval;
|
||||
// traverse_pt_noholes(polytree.Childs, &retval);
|
||||
// return retval;
|
||||
}
|
||||
|
||||
Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
|
||||
|
|
|
@ -219,7 +219,7 @@ ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject, bool safety_off
|
|||
ClipperLib::PolyTree union_pt(Slic3r::Polygons &&subject, bool safety_offset_ = false);
|
||||
ClipperLib::PolyTree union_pt(Slic3r::ExPolygons &&subject, bool safety_offset_ = false);
|
||||
|
||||
Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false);
|
||||
Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject, bool safety_offset_ = false);
|
||||
|
||||
ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes);
|
||||
|
||||
|
|
|
@ -233,6 +233,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
|
|||
case coFloats: return new ConfigOptionFloatsNullable();
|
||||
case coInts: return new ConfigOptionIntsNullable();
|
||||
case coPercents: return new ConfigOptionPercentsNullable();
|
||||
case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
|
||||
case coBools: return new ConfigOptionBoolsNullable();
|
||||
default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
|
||||
}
|
||||
|
@ -247,6 +248,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
|
|||
case coPercent: return new ConfigOptionPercent();
|
||||
case coPercents: return new ConfigOptionPercents();
|
||||
case coFloatOrPercent: return new ConfigOptionFloatOrPercent();
|
||||
case coFloatsOrPercents: return new ConfigOptionFloatsOrPercents();
|
||||
case coPoint: return new ConfigOptionPoint();
|
||||
case coPoints: return new ConfigOptionPoints();
|
||||
case coPoint3: return new ConfigOptionPoint3();
|
||||
|
@ -587,7 +589,7 @@ void ConfigBase::setenv_() const
|
|||
|
||||
void ConfigBase::load(const std::string &file)
|
||||
{
|
||||
if (boost::iends_with(file, ".gcode") || boost::iends_with(file, ".g"))
|
||||
if (is_gcode_file(file))
|
||||
this->load_from_gcode_file(file);
|
||||
else
|
||||
this->load_from_ini(file);
|
||||
|
@ -950,6 +952,8 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent)
|
|||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercentsNullable)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsOrPercents)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsOrPercentsNullable)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints)
|
||||
CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3)
|
||||
|
@ -984,6 +988,8 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOp
|
|||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercentsNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::FloatOrPercent>, Slic3r::ConfigOptionFloatsOrPercents)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::FloatOrPercent>, Slic3r::ConfigOptionFloatsOrPercentsNullable)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>, Slic3r::ConfigOptionPoint)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::Vec2d>, Slic3r::ConfigOptionPoints)
|
||||
CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>, Slic3r::ConfigOptionPoint3)
|
||||
|
|
|
@ -86,6 +86,8 @@ enum ConfigOptionType {
|
|||
coPercents = coPercent + coVectorType,
|
||||
// a fraction or an absolute value
|
||||
coFloatOrPercent = 5,
|
||||
// vector of the above
|
||||
coFloatsOrPercents = coFloatOrPercent + coVectorType,
|
||||
// single 2d point (Point2f). Currently not used.
|
||||
coPoint = 6,
|
||||
// vector of 2d points (Point2f). Currently used for the definition of the print bed and for the extruder offsets.
|
||||
|
@ -889,6 +891,143 @@ private:
|
|||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionPercent>(this), percent); }
|
||||
};
|
||||
|
||||
|
||||
struct FloatOrPercent
|
||||
{
|
||||
double value;
|
||||
bool percent;
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive & ar) { ar(this->value); ar(this->percent); }
|
||||
};
|
||||
|
||||
inline bool operator==(const FloatOrPercent &l, const FloatOrPercent &r)
|
||||
{
|
||||
return l.value == r.value && l.percent == r.percent;
|
||||
}
|
||||
|
||||
inline bool operator!=(const FloatOrPercent& l, const FloatOrPercent& r)
|
||||
{
|
||||
return !(l == r);
|
||||
}
|
||||
|
||||
template<bool NULLABLE>
|
||||
class ConfigOptionFloatsOrPercentsTempl : public ConfigOptionVector<FloatOrPercent>
|
||||
{
|
||||
public:
|
||||
ConfigOptionFloatsOrPercentsTempl() : ConfigOptionVector<FloatOrPercent>() {}
|
||||
explicit ConfigOptionFloatsOrPercentsTempl(size_t n, FloatOrPercent value) : ConfigOptionVector<FloatOrPercent>(n, value) {}
|
||||
explicit ConfigOptionFloatsOrPercentsTempl(std::initializer_list<FloatOrPercent> il) : ConfigOptionVector<FloatOrPercent>(std::move(il)) {}
|
||||
explicit ConfigOptionFloatsOrPercentsTempl(const std::vector<FloatOrPercent> &vec) : ConfigOptionVector<FloatOrPercent>(vec) {}
|
||||
explicit ConfigOptionFloatsOrPercentsTempl(std::vector<FloatOrPercent> &&vec) : ConfigOptionVector<FloatOrPercent>(std::move(vec)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coFloatsOrPercents; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionFloatsOrPercentsTempl(*this); }
|
||||
bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
|
||||
bool operator==(const ConfigOption &rhs) const override {
|
||||
if (rhs.type() != this->type())
|
||||
throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
|
||||
assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
|
||||
return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
|
||||
}
|
||||
// Could a special "nil" value be stored inside the vector, indicating undefined value?
|
||||
bool nullable() const override { return NULLABLE; }
|
||||
// Special "nil" value to be stored into the vector if this->supports_nil().
|
||||
static FloatOrPercent nil_value() { return { std::numeric_limits<double>::quiet_NaN(), false }; }
|
||||
// A scalar is nil, or all values of a vector are nil.
|
||||
bool is_nil() const override { for (auto v : this->values) if (! std::isnan(v.value)) return false; return true; }
|
||||
bool is_nil(size_t idx) const override { return std::isnan(this->values[idx].value); }
|
||||
|
||||
std::string serialize() const override
|
||||
{
|
||||
std::ostringstream ss;
|
||||
for (const FloatOrPercent &v : this->values) {
|
||||
if (&v != &this->values.front())
|
||||
ss << ",";
|
||||
serialize_single_value(ss, v);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> vserialize() const override
|
||||
{
|
||||
std::vector<std::string> vv;
|
||||
vv.reserve(this->values.size());
|
||||
for (const FloatOrPercent &v : this->values) {
|
||||
std::ostringstream ss;
|
||||
serialize_single_value(ss, v);
|
||||
vv.push_back(ss.str());
|
||||
}
|
||||
return vv;
|
||||
}
|
||||
|
||||
bool deserialize(const std::string &str, bool append = false) override
|
||||
{
|
||||
if (! append)
|
||||
this->values.clear();
|
||||
std::istringstream is(str);
|
||||
std::string item_str;
|
||||
while (std::getline(is, item_str, ',')) {
|
||||
boost::trim(item_str);
|
||||
if (item_str == "nil") {
|
||||
if (NULLABLE)
|
||||
this->values.push_back(nil_value());
|
||||
else
|
||||
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
|
||||
} else {
|
||||
bool percent = item_str.find_first_of("%") != std::string::npos;
|
||||
std::istringstream iss(item_str);
|
||||
double value;
|
||||
iss >> value;
|
||||
this->values.push_back({ value, percent });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfigOptionFloatsOrPercentsTempl& operator=(const ConfigOption *opt)
|
||||
{
|
||||
this->set(opt);
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
void serialize_single_value(std::ostringstream &ss, const FloatOrPercent &v) const {
|
||||
if (std::isfinite(v.value)) {
|
||||
ss << v.value;
|
||||
if (v.percent)
|
||||
ss << "%";
|
||||
} else if (std::isnan(v.value)) {
|
||||
if (NULLABLE)
|
||||
ss << "nil";
|
||||
else
|
||||
throw Slic3r::RuntimeError("Serializing NaN");
|
||||
} else
|
||||
throw Slic3r::RuntimeError("Serializing invalid number");
|
||||
}
|
||||
static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
|
||||
if (NULLABLE) {
|
||||
if (v1.size() != v2.size())
|
||||
return false;
|
||||
for (auto it1 = v1.begin(), it2 = v2.begin(); it1 != v1.end(); ++ it1, ++ it2)
|
||||
if (! ((std::isnan(it1->value) && std::isnan(it2->value)) || *it1 == *it2))
|
||||
return false;
|
||||
return true;
|
||||
} else
|
||||
// Not supporting nullable values, the default vector compare is cheaper.
|
||||
return v1 == v2;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<FloatOrPercent>>(this)); }
|
||||
};
|
||||
|
||||
using ConfigOptionFloatsOrPercents = ConfigOptionFloatsOrPercentsTempl<false>;
|
||||
using ConfigOptionFloatsOrPercentsNullable = ConfigOptionFloatsOrPercentsTempl<true>;
|
||||
|
||||
class ConfigOptionPoint : public ConfigOptionSingle<Vec2d>
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -297,15 +297,16 @@ std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_ob
|
|||
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||
double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
|
||||
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 && config.fill_pattern == ipSupportCubic;
|
||||
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 && config.fill_pattern == ipSupportCubic;
|
||||
double infill_extrusion_width = config.infill_extrusion_width.percent ? default_infill_extrusion_width * 0.01 * config.infill_extrusion_width : config.infill_extrusion_width;
|
||||
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 != 0. ? config.infill_extrusion_width : default_infill_extrusion_width
|
||||
infill_extrusion_width != 0. ? infill_extrusion_width : default_infill_extrusion_width
|
||||
}));
|
||||
build_octree |= has_adaptive_infill || has_support_infill;
|
||||
}
|
||||
|
|
|
@ -66,7 +66,11 @@ protected:
|
|||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
bool no_sort() const override { return true; }
|
||||
// Let the G-code export reoder the infill lines.
|
||||
//FIXME letting the G-code exporter to reorder infill lines of Adaptive Cubic Infill
|
||||
// may not be optimal as the internal infill lines may get extruded before the long infill
|
||||
// lines to which the short infill lines are supposed to anchor.
|
||||
bool no_sort() const override { return false; }
|
||||
};
|
||||
|
||||
}; // namespace FillAdaptive
|
||||
|
|
|
@ -96,10 +96,10 @@ coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance)
|
|||
assert(width >= 0);
|
||||
assert(distance > 0);
|
||||
// floor(width / distance)
|
||||
coord_t number_of_intervals = (width - EPSILON) / distance;
|
||||
coord_t distance_new = (number_of_intervals == 0) ?
|
||||
const auto number_of_intervals = coord_t((width - EPSILON) / distance);
|
||||
coord_t distance_new = (number_of_intervals == 0) ?
|
||||
distance :
|
||||
((width - EPSILON) / number_of_intervals);
|
||||
coord_t((width - EPSILON) / number_of_intervals);
|
||||
const coordf_t factor = coordf_t(distance_new) / coordf_t(distance);
|
||||
assert(factor > 1. - 1e-5);
|
||||
// How much could the extrusion width be increased? By 20%.
|
||||
|
@ -143,7 +143,7 @@ std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const
|
|||
#ifdef SLIC3R_DEBUG
|
||||
printf("Filling bridge with angle %f\n", surface->bridge_angle);
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
out_angle = surface->bridge_angle;
|
||||
out_angle = float(surface->bridge_angle);
|
||||
} else if (this->layer_id != size_t(-1)) {
|
||||
// alternate fill direction
|
||||
out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
|
||||
|
@ -161,15 +161,15 @@ struct ContourIntersectionPoint {
|
|||
size_t contour_idx;
|
||||
size_t point_idx;
|
||||
// Eucleidean parameter of point_idx along its contour.
|
||||
float param;
|
||||
double param;
|
||||
// Other intersection points along the same contour. If there is only a single T-joint on a contour
|
||||
// with an intersection line, then the prev_on_contour and next_on_contour remain nulls.
|
||||
ContourIntersectionPoint* prev_on_contour { nullptr };
|
||||
ContourIntersectionPoint* next_on_contour { nullptr };
|
||||
// Length of the contour not yet allocated to some extrusion path going back (clockwise), or masked out by some overlapping infill line.
|
||||
float contour_not_taken_length_prev { std::numeric_limits<float>::max() };
|
||||
double contour_not_taken_length_prev { std::numeric_limits<double>::max() };
|
||||
// Length of the contour not yet allocated to some extrusion path going forward (counter-clockwise), or masked out by some overlapping infill line.
|
||||
float contour_not_taken_length_next { std::numeric_limits<float>::max() };
|
||||
double contour_not_taken_length_next { std::numeric_limits<double>::max() };
|
||||
// End point is consumed if an infill line connected to this T-joint was already connected left or right along the contour,
|
||||
// or if the infill line was processed, but it was not possible to connect it left or right along the contour.
|
||||
bool consumed { false };
|
||||
|
@ -180,13 +180,13 @@ struct ContourIntersectionPoint {
|
|||
void consume_prev() { this->contour_not_taken_length_prev = 0.; this->prev_trimmed = true; this->consumed = true; }
|
||||
void consume_next() { this->contour_not_taken_length_next = 0.; this->next_trimmed = true; this->consumed = true; }
|
||||
|
||||
void trim_prev(const float new_len) {
|
||||
void trim_prev(const double new_len) {
|
||||
if (new_len < this->contour_not_taken_length_prev) {
|
||||
this->contour_not_taken_length_prev = new_len;
|
||||
this->prev_trimmed = true;
|
||||
}
|
||||
}
|
||||
void trim_next(const float new_len) {
|
||||
void trim_next(const double new_len) {
|
||||
if (new_len < this->contour_not_taken_length_next) {
|
||||
this->contour_not_taken_length_next = new_len;
|
||||
this->next_trimmed = true;
|
||||
|
@ -207,24 +207,24 @@ struct ContourIntersectionPoint {
|
|||
};
|
||||
|
||||
// Distance from param1 to param2 when going counter-clockwise.
|
||||
static inline float closed_contour_distance_ccw(float param1, float param2, float contour_length)
|
||||
static inline double closed_contour_distance_ccw(double param1, double param2, double contour_length)
|
||||
{
|
||||
assert(param1 >= 0.f && param1 <= contour_length);
|
||||
assert(param2 >= 0.f && param2 <= contour_length);
|
||||
float d = param2 - param1;
|
||||
if (d < 0.f)
|
||||
assert(param1 >= 0. && param1 <= contour_length);
|
||||
assert(param2 >= 0. && param2 <= contour_length);
|
||||
double d = param2 - param1;
|
||||
if (d < 0.)
|
||||
d += contour_length;
|
||||
return d;
|
||||
}
|
||||
|
||||
// Distance from param1 to param2 when going clockwise.
|
||||
static inline float closed_contour_distance_cw(float param1, float param2, float contour_length)
|
||||
static inline double closed_contour_distance_cw(double param1, double param2, double contour_length)
|
||||
{
|
||||
return closed_contour_distance_ccw(param2, param1, contour_length);
|
||||
}
|
||||
|
||||
// Length along the contour from cp1 to cp2 going counter-clockwise.
|
||||
float path_length_along_contour_ccw(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, float contour_length)
|
||||
double path_length_along_contour_ccw(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, double contour_length)
|
||||
{
|
||||
assert(cp1 != nullptr);
|
||||
assert(cp2 != nullptr);
|
||||
|
@ -234,13 +234,13 @@ float path_length_along_contour_ccw(const ContourIntersectionPoint *cp1, const C
|
|||
}
|
||||
|
||||
// Lengths along the contour from cp1 to cp2 going CCW and going CW.
|
||||
std::pair<float, float> path_lengths_along_contour(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, float contour_length)
|
||||
std::pair<double, double> path_lengths_along_contour(const ContourIntersectionPoint *cp1, const ContourIntersectionPoint *cp2, double contour_length)
|
||||
{
|
||||
// Zero'th param is the length of the contour.
|
||||
float param_lo = cp1->param;
|
||||
float param_hi = cp2->param;
|
||||
assert(param_lo >= 0.f && param_lo <= contour_length);
|
||||
assert(param_hi >= 0.f && param_hi <= contour_length);
|
||||
double param_lo = cp1->param;
|
||||
double param_hi = cp2->param;
|
||||
assert(param_lo >= 0. && param_lo <= contour_length);
|
||||
assert(param_hi >= 0. && param_hi <= contour_length);
|
||||
bool reversed = false;
|
||||
if (param_lo > param_hi) {
|
||||
std::swap(param_lo, param_hi);
|
||||
|
@ -267,25 +267,25 @@ static inline void take_cw_full(Polyline &pl, const Points& contour, size_t idx_
|
|||
}
|
||||
|
||||
// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken.
|
||||
static inline float take_cw_limited(Polyline &pl, const Points &contour, const std::vector<float> ¶ms, size_t idx_start, size_t idx_end, float length_to_take)
|
||||
static inline double take_cw_limited(Polyline &pl, const Points &contour, const std::vector<double> ¶ms, size_t idx_start, size_t idx_end, double length_to_take)
|
||||
{
|
||||
// If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line.
|
||||
assert(pl.empty() || pl.points.back() == contour[idx_start]);
|
||||
assert(contour.size() + 1 == params.size());
|
||||
assert(length_to_take > SCALED_EPSILON);
|
||||
// Length of the contour.
|
||||
float length = params.back();
|
||||
double length = params.back();
|
||||
// Parameter (length from contour.front()) for the first point.
|
||||
float p0 = params[idx_start];
|
||||
double p0 = params[idx_start];
|
||||
// Current (2nd) point of the contour.
|
||||
size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1;
|
||||
// Previous point of the contour.
|
||||
size_t iprev = idx_start;
|
||||
// Length of the contour curve taken for iprev.
|
||||
float lprev = 0.f;
|
||||
double lprev = 0.;
|
||||
|
||||
for (;;) {
|
||||
float l = closed_contour_distance_cw(p0, params[i], length);
|
||||
double l = closed_contour_distance_cw(p0, params[i], length);
|
||||
if (l >= length_to_take) {
|
||||
// Trim the last segment.
|
||||
double t = double(length_to_take - lprev) / (l - lprev);
|
||||
|
@ -323,16 +323,16 @@ static inline void take_ccw_full(Polyline &pl, const Points &contour, size_t idx
|
|||
|
||||
// Add contour points from interval (idx_start, idx_end> to polyline, limited by the Eucleidean length taken.
|
||||
// Returns length of the contour taken.
|
||||
static inline float take_ccw_limited(Polyline &pl, const Points &contour, const std::vector<float> ¶ms, size_t idx_start, size_t idx_end, float length_to_take)
|
||||
static inline double take_ccw_limited(Polyline &pl, const Points &contour, const std::vector<double> ¶ms, size_t idx_start, size_t idx_end, double length_to_take)
|
||||
{
|
||||
// If appending to an infill line, then the start point of a perimeter line shall match the end point of an infill line.
|
||||
assert(pl.empty() || pl.points.back() == contour[idx_start]);
|
||||
assert(contour.size() + 1 == params.size());
|
||||
assert(length_to_take > SCALED_EPSILON);
|
||||
// Length of the contour.
|
||||
float length = params.back();
|
||||
double length = params.back();
|
||||
// Parameter (length from contour.front()) for the first point.
|
||||
float p0 = params[idx_start];
|
||||
double p0 = params[idx_start];
|
||||
// Current (2nd) point of the contour.
|
||||
size_t i = idx_start;
|
||||
if (++ i == contour.size())
|
||||
|
@ -340,9 +340,9 @@ static inline float take_ccw_limited(Polyline &pl, const Points &contour, const
|
|||
// Previous point of the contour.
|
||||
size_t iprev = idx_start;
|
||||
// Length of the contour curve taken at iprev.
|
||||
float lprev = 0.f;
|
||||
double lprev = 0;
|
||||
for (;;) {
|
||||
float l = closed_contour_distance_ccw(p0, params[i], length);
|
||||
double l = closed_contour_distance_ccw(p0, params[i], length);
|
||||
if (l >= length_to_take) {
|
||||
// Trim the last segment.
|
||||
double t = double(length_to_take - lprev) / (l - lprev);
|
||||
|
@ -411,8 +411,8 @@ static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, Cont
|
|||
}
|
||||
|
||||
static void take_limited(
|
||||
Polyline &pl1, const Points &contour, const std::vector<float> ¶ms,
|
||||
ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, float take_max_length, float line_half_width)
|
||||
Polyline &pl1, const Points &contour, const std::vector<double> ¶ms,
|
||||
ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, double take_max_length, double line_half_width)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
// This is a valid case, where a single infill line connect to two different contours (outer contour + hole or two holes).
|
||||
|
@ -445,11 +445,11 @@ static void take_limited(
|
|||
pl1.points.reserve(pl1.points.size() + pl_tmp.size() + size_t(new_points));
|
||||
}
|
||||
|
||||
float length = params.back();
|
||||
float length_to_go = take_max_length;
|
||||
double length = params.back();
|
||||
double length_to_go = take_max_length;
|
||||
cp_start->consumed = true;
|
||||
if (cp_start == cp_end) {
|
||||
length_to_go = std::max(0.f, std::min(length_to_go, length - line_half_width));
|
||||
length_to_go = std::max(0., std::min(length_to_go, length - line_half_width));
|
||||
length_to_go = std::min(length_to_go, clockwise ? cp_start->contour_not_taken_length_prev : cp_start->contour_not_taken_length_next);
|
||||
cp_start->consume_prev();
|
||||
cp_start->consume_next();
|
||||
|
@ -462,11 +462,11 @@ static void take_limited(
|
|||
assert(cp_start != cp_end);
|
||||
for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->prev_on_contour) {
|
||||
// Length of the segment from cp to cp->prev_on_contour.
|
||||
float l = closed_contour_distance_cw(cp->param, cp->prev_on_contour->param, length);
|
||||
double l = closed_contour_distance_cw(cp->param, cp->prev_on_contour->param, length);
|
||||
length_to_go = std::min(length_to_go, cp->contour_not_taken_length_prev);
|
||||
//if (cp->prev_on_contour->consumed)
|
||||
// Don't overlap with an already extruded infill line.
|
||||
length_to_go = std::max(0.f, std::min(length_to_go, l - line_half_width));
|
||||
length_to_go = std::max(0., std::min(length_to_go, l - line_half_width));
|
||||
cp->consume_prev();
|
||||
if (l >= length_to_go) {
|
||||
if (length_to_go > SCALED_EPSILON) {
|
||||
|
@ -475,7 +475,7 @@ static void take_limited(
|
|||
}
|
||||
break;
|
||||
} else {
|
||||
cp->prev_on_contour->trim_next(0.f);
|
||||
cp->prev_on_contour->trim_next(0.);
|
||||
take_cw_full(pl1, contour, cp->point_idx, cp->prev_on_contour->point_idx);
|
||||
length_to_go -= l;
|
||||
}
|
||||
|
@ -483,11 +483,11 @@ static void take_limited(
|
|||
} else {
|
||||
assert(cp_start != cp_end);
|
||||
for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->next_on_contour) {
|
||||
float l = closed_contour_distance_ccw(cp->param, cp->next_on_contour->param, length);
|
||||
double l = closed_contour_distance_ccw(cp->param, cp->next_on_contour->param, length);
|
||||
length_to_go = std::min(length_to_go, cp->contour_not_taken_length_next);
|
||||
//if (cp->next_on_contour->consumed)
|
||||
// Don't overlap with an already extruded infill line.
|
||||
length_to_go = std::max(0.f, std::min(length_to_go, l - line_half_width));
|
||||
length_to_go = std::max(0., std::min(length_to_go, l - line_half_width));
|
||||
cp->consume_next();
|
||||
if (l >= length_to_go) {
|
||||
if (length_to_go > SCALED_EPSILON) {
|
||||
|
@ -496,7 +496,7 @@ static void take_limited(
|
|||
}
|
||||
break;
|
||||
} else {
|
||||
cp->next_on_contour->trim_prev(0.f);
|
||||
cp->next_on_contour->trim_prev(0.);
|
||||
take_ccw_full(pl1, contour, cp->point_idx, cp->next_on_contour->point_idx);
|
||||
length_to_go -= l;
|
||||
}
|
||||
|
@ -678,19 +678,19 @@ static inline bool line_rounded_thick_segment_collision(
|
|||
return intersects;
|
||||
}
|
||||
|
||||
static inline bool inside_interval(float low, float high, float p)
|
||||
static inline bool inside_interval(double low, double high, double p)
|
||||
{
|
||||
return p >= low && p <= high;
|
||||
}
|
||||
|
||||
static inline bool interval_inside_interval(float outer_low, float outer_high, float inner_low, float inner_high, float epsilon)
|
||||
static inline bool interval_inside_interval(double outer_low, double outer_high, double inner_low, double inner_high, double epsilon)
|
||||
{
|
||||
outer_low -= epsilon;
|
||||
outer_high += epsilon;
|
||||
return inside_interval(outer_low, outer_high, inner_low) && inside_interval(outer_low, outer_high, inner_high);
|
||||
}
|
||||
|
||||
static inline bool cyclic_interval_inside_interval(float outer_low, float outer_high, float inner_low, float inner_high, float length)
|
||||
static inline bool cyclic_interval_inside_interval(double outer_low, double outer_high, double inner_low, double inner_high, double length)
|
||||
{
|
||||
if (outer_low > outer_high)
|
||||
outer_high += length;
|
||||
|
@ -700,7 +700,7 @@ static inline bool cyclic_interval_inside_interval(float outer_low, float outer_
|
|||
inner_low += length;
|
||||
inner_high += length;
|
||||
}
|
||||
return interval_inside_interval(outer_low, outer_high, inner_low, inner_high, float(SCALED_EPSILON));
|
||||
return interval_inside_interval(outer_low, outer_high, inner_low, inner_high, double(SCALED_EPSILON));
|
||||
}
|
||||
|
||||
// #define INFILL_DEBUG_OUTPUT
|
||||
|
@ -710,7 +710,7 @@ static void export_infill_to_svg(
|
|||
// Boundary contour, along which the perimeter extrusions will be drawn.
|
||||
const std::vector<Points> &boundary,
|
||||
// Parametrization of boundary with Euclidian length.
|
||||
const std::vector<std::vector<float>> &boundary_parameters,
|
||||
const std::vector<std::vector<double>> &boundary_parameters,
|
||||
// Intersections (T-joints) of the infill lines with the boundary.
|
||||
std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections,
|
||||
// Infill lines, either completely inside the boundary, or touching the boundary.
|
||||
|
@ -739,7 +739,7 @@ static void export_infill_to_svg(
|
|||
for (const std::vector<ContourIntersectionPoint*> &intersections : boundary_intersections) {
|
||||
const size_t boundary_idx = &intersections - boundary_intersections.data();
|
||||
const Points &contour = boundary[boundary_idx];
|
||||
const std::vector<float> &contour_param = boundary_parameters[boundary_idx];
|
||||
const std::vector<double> &contour_param = boundary_parameters[boundary_idx];
|
||||
for (const ContourIntersectionPoint *ip : intersections) {
|
||||
assert(ip->next_trimmed == ip->next_on_contour->prev_trimmed);
|
||||
assert(ip->prev_trimmed == ip->prev_on_contour->next_trimmed);
|
||||
|
@ -834,7 +834,7 @@ void mark_boundary_segments_touching_infill(
|
|||
// Boundary contour, along which the perimeter extrusions will be drawn.
|
||||
const std::vector<Points> &boundary,
|
||||
// Parametrization of boundary with Euclidian length.
|
||||
const std::vector<std::vector<float>> &boundary_parameters,
|
||||
const std::vector<std::vector<double>> &boundary_parameters,
|
||||
// Intersections (T-joints) of the infill lines with the boundary.
|
||||
std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections,
|
||||
// Bounding box around the boundary.
|
||||
|
@ -865,12 +865,12 @@ void mark_boundary_segments_touching_infill(
|
|||
// Make sure that the the grid is big enough for queries against the thick segment.
|
||||
grid.set_bbox(boundary_bbox.inflated(distance_colliding * 1.43));
|
||||
// Inflate the bounding box by a thick line width.
|
||||
grid.create(boundary, std::max(clip_distance, distance_colliding) + scale_(10.));
|
||||
grid.create(boundary, coord_t(std::max(clip_distance, distance_colliding) + scale_(10.)));
|
||||
|
||||
// Visitor for the EdgeGrid to trim boundary_intersections with existing infill lines.
|
||||
struct Visitor {
|
||||
Visitor(const EdgeGrid::Grid &grid,
|
||||
const std::vector<Points> &boundary, const std::vector<std::vector<float>> &boundary_parameters, std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections,
|
||||
const std::vector<Points> &boundary, const std::vector<std::vector<double>> &boundary_parameters, std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections,
|
||||
const double radius) :
|
||||
grid(grid), boundary(boundary), boundary_parameters(boundary_parameters), boundary_intersections(boundary_intersections), radius(radius), trim_l_threshold(0.5 * radius) {}
|
||||
|
||||
|
@ -907,10 +907,10 @@ void mark_boundary_segments_touching_infill(
|
|||
// The boundary segment intersects with the infill segment thickened by radius.
|
||||
// Interval is specified in Euclidian length from seg_pt1 to seg_pt2.
|
||||
// 1) Find the Euclidian parameters of seg_pt1 and seg_pt2 on its boundary contour.
|
||||
const std::vector<float> &contour_parameters = boundary_parameters[it_contour_and_segment->first];
|
||||
const float contour_length = contour_parameters.back();
|
||||
const float param_seg_pt1 = contour_parameters[it_contour_and_segment->second];
|
||||
const float param_seg_pt2 = contour_parameters[it_contour_and_segment->second + 1];
|
||||
const std::vector<double> &contour_parameters = boundary_parameters[it_contour_and_segment->first];
|
||||
const double contour_length = contour_parameters.back();
|
||||
const double param_seg_pt1 = contour_parameters[it_contour_and_segment->second];
|
||||
const double param_seg_pt2 = contour_parameters[it_contour_and_segment->second + 1];
|
||||
#ifdef INFILL_DEBUG_OUTPUT
|
||||
this->perimeter_overlaps.push_back({ Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.first).cast<coord_t>()),
|
||||
Point((seg_pt1 + (seg_pt2 - seg_pt1).normalized() * interval.second).cast<coord_t>()) });
|
||||
|
@ -918,8 +918,8 @@ void mark_boundary_segments_touching_infill(
|
|||
assert(interval.first >= 0.);
|
||||
assert(interval.second >= 0.);
|
||||
assert(interval.first <= interval.second);
|
||||
const auto param_overlap1 = std::min(param_seg_pt2, float(param_seg_pt1 + interval.first));
|
||||
const auto param_overlap2 = std::min(param_seg_pt2, float(param_seg_pt1 + interval.second));
|
||||
const auto param_overlap1 = std::min(param_seg_pt2, param_seg_pt1 + interval.first);
|
||||
const auto param_overlap2 = std::min(param_seg_pt2, param_seg_pt1 + interval.second);
|
||||
// 2) Find the ContourIntersectionPoints before param_overlap1 and after param_overlap2.
|
||||
// Find the span of ContourIntersectionPoints, that is trimmed by the interval (param_overlap1, param_overlap2).
|
||||
ContourIntersectionPoint *ip_low, *ip_high;
|
||||
|
@ -946,7 +946,7 @@ void mark_boundary_segments_touching_infill(
|
|||
ip->consume_next();
|
||||
}
|
||||
// Subtract the interval from the first and last segments.
|
||||
float trim_l = closed_contour_distance_ccw(ip_low->param, param_overlap1, contour_length);
|
||||
double trim_l = closed_contour_distance_ccw(ip_low->param, param_overlap1, contour_length);
|
||||
//if (trim_l > trim_l_threshold)
|
||||
ip_low->trim_next(trim_l);
|
||||
trim_l = closed_contour_distance_ccw(param_overlap2, ip_high->param, contour_length);
|
||||
|
@ -978,12 +978,12 @@ void mark_boundary_segments_touching_infill(
|
|||
|
||||
const EdgeGrid::Grid &grid;
|
||||
const std::vector<Points> &boundary;
|
||||
const std::vector<std::vector<float>> &boundary_parameters;
|
||||
const std::vector<std::vector<double>> &boundary_parameters;
|
||||
std::vector<std::vector<ContourIntersectionPoint*>> &boundary_intersections;
|
||||
// Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line.
|
||||
const double radius;
|
||||
// Region around the contour / infill line intersection point, where the intersections are ignored.
|
||||
const float trim_l_threshold;
|
||||
const double trim_l_threshold;
|
||||
|
||||
const Vec2d *infill_pt1;
|
||||
const Vec2d *infill_pt2;
|
||||
|
@ -1100,11 +1100,11 @@ void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_s
|
|||
void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms)
|
||||
{
|
||||
assert(! infill_ordered.empty());
|
||||
assert(params.anchor_length >= 0.f);
|
||||
assert(params.anchor_length >= 0.);
|
||||
assert(params.anchor_length_max >= 0.01f);
|
||||
assert(params.anchor_length_max >= params.anchor_length);
|
||||
const auto anchor_length = float(scale_(params.anchor_length));
|
||||
const auto anchor_length_max = float(scale_(params.anchor_length_max));
|
||||
const double anchor_length = scale_(params.anchor_length);
|
||||
const double anchor_length_max = scale_(params.anchor_length_max);
|
||||
|
||||
#if 0
|
||||
append(polylines_out, infill_ordered);
|
||||
|
@ -1113,9 +1113,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
|
||||
// 1) Add the end points of infill_ordered to boundary_src.
|
||||
std::vector<Points> boundary;
|
||||
std::vector<std::vector<float>> boundary_params;
|
||||
std::vector<std::vector<double>> boundary_params;
|
||||
boundary.assign(boundary_src.size(), Points());
|
||||
boundary_params.assign(boundary_src.size(), std::vector<float>());
|
||||
boundary_params.assign(boundary_src.size(), std::vector<double>());
|
||||
// Mapping the infill_ordered end point to a (contour, point) of boundary.
|
||||
static constexpr auto boundary_idx_unconnected = std::numeric_limits<size_t>::max();
|
||||
std::vector<ContourIntersectionPoint> map_infill_end_point_to_boundary(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected });
|
||||
|
@ -1125,11 +1125,11 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
{
|
||||
EdgeGrid::Grid grid;
|
||||
grid.set_bbox(bbox.inflated(SCALED_EPSILON));
|
||||
grid.create(boundary_src, scale_(10.));
|
||||
grid.create(boundary_src, coord_t(scale_(10.)));
|
||||
intersection_points.reserve(infill_ordered.size() * 2);
|
||||
for (const Polyline &pl : infill_ordered)
|
||||
for (const Point *pt : { &pl.points.front(), &pl.points.back() }) {
|
||||
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point(*pt, SCALED_EPSILON);
|
||||
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point(*pt, coord_t(SCALED_EPSILON));
|
||||
if (cp.valid()) {
|
||||
// The infill end point shall lie on the contour.
|
||||
assert(cp.distance <= 3.);
|
||||
|
@ -1163,21 +1163,29 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
contour_intersection_points.reserve(n_intersection_points);
|
||||
}
|
||||
for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) {
|
||||
contour_dst.emplace_back(contour_src.points[idx_point]);
|
||||
const Point &ipt = contour_src.points[idx_point];
|
||||
if (contour_dst.empty() || contour_dst.back() != ipt)
|
||||
contour_dst.emplace_back(ipt);
|
||||
for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) {
|
||||
// Add these points to the destination contour.
|
||||
const Polyline &infill_line = infill_ordered[it->second / 2];
|
||||
const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front();
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
const Vec2d pt1 = contour_src[idx_point].cast<double>();
|
||||
const Vec2d pt1 = ipt.cast<double>();
|
||||
const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast<double>();
|
||||
const Vec2d ptx = lerp(pt1, pt2, it->first.t);
|
||||
assert(std::abs(pt.x() - pt.x()) < SCALED_EPSILON);
|
||||
assert(std::abs(pt.y() - pt.y()) < SCALED_EPSILON);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ idx_contour, contour_dst.size() };
|
||||
size_t idx_tjoint_pt = 0;
|
||||
if (idx_point + 1 < contour_src.size() || pt != contour_dst.front()) {
|
||||
if (pt != contour_dst.back())
|
||||
contour_dst.emplace_back(pt);
|
||||
idx_tjoint_pt = contour_dst.size() - 1;
|
||||
}
|
||||
map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ idx_contour, idx_tjoint_pt };
|
||||
ContourIntersectionPoint *pthis = &map_infill_end_point_to_boundary[it->second];
|
||||
if (pprev) {
|
||||
pprev->next_on_contour = pthis;
|
||||
|
@ -1186,8 +1194,6 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
pfirst = pthis;
|
||||
contour_intersection_points.emplace_back(pthis);
|
||||
pprev = pthis;
|
||||
//add new point here
|
||||
contour_dst.emplace_back(pt);
|
||||
}
|
||||
if (pfirst) {
|
||||
pprev->next_on_contour = pfirst;
|
||||
|
@ -1195,16 +1201,19 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
}
|
||||
}
|
||||
// Parametrize the new boundary with the intersection points inserted.
|
||||
std::vector<float> &contour_params = boundary_params[idx_contour];
|
||||
contour_params.assign(contour_dst.size() + 1, 0.f);
|
||||
for (size_t i = 1; i < contour_dst.size(); ++ i)
|
||||
contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast<float>() - contour_dst[i - 1].cast<float>()).norm();
|
||||
contour_params.back() = contour_params[contour_params.size() - 2] + (contour_dst.back().cast<float>() - contour_dst.front().cast<float>()).norm();
|
||||
std::vector<double> &contour_params = boundary_params[idx_contour];
|
||||
contour_params.assign(contour_dst.size() + 1, 0.);
|
||||
for (size_t i = 1; i < contour_dst.size(); ++i) {
|
||||
contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast<double>() - contour_dst[i - 1].cast<double>()).norm();
|
||||
assert(contour_params[i] > contour_params[i - 1]);
|
||||
}
|
||||
contour_params.back() = contour_params[contour_params.size() - 2] + (contour_dst.back().cast<double>() - contour_dst.front().cast<double>()).norm();
|
||||
assert(contour_params.back() > contour_params[contour_params.size() - 2]);
|
||||
// Map parameters from contour_params to boundary_intersection_points.
|
||||
for (ContourIntersectionPoint *ip : contour_intersection_points)
|
||||
ip->param = contour_params[ip->point_idx];
|
||||
// and measure distance to the previous and next intersection point.
|
||||
const float contour_length = contour_params.back();
|
||||
const double contour_length = contour_params.back();
|
||||
for (ContourIntersectionPoint *ip : contour_intersection_points)
|
||||
if (ip->next_on_contour == ip) {
|
||||
assert(ip->prev_on_contour == ip);
|
||||
|
@ -1238,9 +1247,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
}
|
||||
|
||||
// Connection from end of one infill line to the start of another infill line.
|
||||
//const float length_max = scale_(spacing);
|
||||
// const auto length_max = float(scale_((2. / params.density) * spacing));
|
||||
const auto length_max = float(scale_((1000. / params.density) * spacing));
|
||||
//const double length_max = scale_(spacing);
|
||||
// const auto length_max = double(scale_((2. / params.density) * spacing));
|
||||
const auto length_max = double(scale_((1000. / params.density) * spacing));
|
||||
std::vector<size_t> merged_with(infill_ordered.size());
|
||||
std::iota(merged_with.begin(), merged_with.end(), 0);
|
||||
struct ConnectionCost {
|
||||
|
@ -1258,7 +1267,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
const ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2];
|
||||
if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) {
|
||||
// End points on the same contour. Try to connect them.
|
||||
std::pair<float, float> len = path_lengths_along_contour(cp1, cp2, boundary_params[cp1->contour_idx].back());
|
||||
std::pair<double, double> len = path_lengths_along_contour(cp1, cp2, boundary_params[cp1->contour_idx].back());
|
||||
if (len.first < length_max)
|
||||
connections_sorted.emplace_back(idx_chain - 1, len.first, false);
|
||||
if (len.second < length_max)
|
||||
|
@ -1281,7 +1290,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
return std::numeric_limits<size_t>::max();
|
||||
};
|
||||
|
||||
const float line_half_width = 0.5f * scale_(spacing);
|
||||
const double line_half_width = 0.5 * scale_(spacing);
|
||||
|
||||
#if 0
|
||||
for (ConnectionCost &connection_cost : connections_sorted) {
|
||||
|
@ -1291,7 +1300,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
assert(cp1->contour_idx == cp2->contour_idx && cp1->contour_idx != boundary_idx_unconnected);
|
||||
if (cp1->consumed || cp2->consumed)
|
||||
continue;
|
||||
const float length = connection_cost.cost;
|
||||
const double length = connection_cost.cost;
|
||||
bool could_connect;
|
||||
{
|
||||
// cp1, cp2 sorted CCW.
|
||||
|
@ -1334,7 +1343,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
|
||||
struct Arc {
|
||||
ContourIntersectionPoint *intersection;
|
||||
float arc_length;
|
||||
double arc_length;
|
||||
};
|
||||
std::vector<Arc> arches;
|
||||
arches.reserve(map_infill_end_point_to_boundary.size());
|
||||
|
@ -1352,7 +1361,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
size_t polyline_idx1 = get_and_update_merged_with(((cp1 - map_infill_end_point_to_boundary.data()) / 2));
|
||||
size_t polyline_idx2 = get_and_update_merged_with(((cp2 - map_infill_end_point_to_boundary.data()) / 2));
|
||||
const Points &contour = boundary[cp1->contour_idx];
|
||||
const std::vector<float> &contour_params = boundary_params[cp1->contour_idx];
|
||||
const std::vector<double> &contour_params = boundary_params[cp1->contour_idx];
|
||||
if (polyline_idx1 != polyline_idx2) {
|
||||
Polyline &polyline1 = infill_ordered[polyline_idx1];
|
||||
Polyline &polyline2 = infill_ordered[polyline_idx2];
|
||||
|
@ -1385,23 +1394,23 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
// Connect the remaining open infill lines to the perimeter lines if possible.
|
||||
for (ContourIntersectionPoint &contour_point : map_infill_end_point_to_boundary)
|
||||
if (! contour_point.consumed && contour_point.contour_idx != boundary_idx_unconnected) {
|
||||
const Points &contour = boundary[contour_point.contour_idx];
|
||||
const std::vector<float> &contour_params = boundary_params[contour_point.contour_idx];
|
||||
const size_t contour_pt_idx = contour_point.point_idx;
|
||||
const Points &contour = boundary[contour_point.contour_idx];
|
||||
const std::vector<double> &contour_params = boundary_params[contour_point.contour_idx];
|
||||
const size_t contour_pt_idx = contour_point.point_idx;
|
||||
|
||||
float lprev = contour_point.could_connect_prev() ?
|
||||
double lprev = contour_point.could_connect_prev() ?
|
||||
path_length_along_contour_ccw(contour_point.prev_on_contour, &contour_point, contour_params.back()) :
|
||||
std::numeric_limits<float>::max();
|
||||
float lnext = contour_point.could_connect_next() ?
|
||||
std::numeric_limits<double>::max();
|
||||
double lnext = contour_point.could_connect_next() ?
|
||||
path_length_along_contour_ccw(&contour_point, contour_point.next_on_contour, contour_params.back()) :
|
||||
std::numeric_limits<float>::max();
|
||||
std::numeric_limits<double>::max();
|
||||
size_t polyline_idx = get_and_update_merged_with(((&contour_point - map_infill_end_point_to_boundary.data()) / 2));
|
||||
Polyline &polyline = infill_ordered[polyline_idx];
|
||||
assert(! polyline.empty());
|
||||
assert(contour[contour_point.point_idx] == polyline.points.front() || contour[contour_point.point_idx] == polyline.points.back());
|
||||
bool connected = false;
|
||||
for (float l : { std::min(lprev, lnext), std::max(lprev, lnext) }) {
|
||||
if (l == std::numeric_limits<float>::max() || l > anchor_length_max)
|
||||
for (double l : { std::min(lprev, lnext), std::max(lprev, lnext) }) {
|
||||
if (l == std::numeric_limits<double>::max() || l > anchor_length_max)
|
||||
break;
|
||||
// Take the complete contour.
|
||||
bool reversed = l == lprev;
|
||||
|
@ -1439,7 +1448,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
|
|||
// 2) Hook length
|
||||
// ...
|
||||
// Let's take the longer now, as this improves the chance of another hook to be placed on the other side of this contour point.
|
||||
float l = std::max(contour_point.contour_not_taken_length_prev, contour_point.contour_not_taken_length_next);
|
||||
double l = std::max(contour_point.contour_not_taken_length_prev, contour_point.contour_not_taken_length_next);
|
||||
if (l > SCALED_EPSILON) {
|
||||
if (contour_point.contour_not_taken_length_prev > contour_point.contour_not_taken_length_next)
|
||||
take_limited(polyline, contour, contour_params, &contour_point, contour_point.prev_on_contour, true, anchor_length, line_half_width);
|
||||
|
|
|
@ -24,22 +24,22 @@ void FillConcentric::_fill_surface_single(
|
|||
this->spacing = unscale<double>(distance);
|
||||
}
|
||||
|
||||
Polygons loops = (Polygons)expolygon;
|
||||
Polygons loops = to_polygons(std::move(expolygon));
|
||||
Polygons last = loops;
|
||||
while (! last.empty()) {
|
||||
last = offset2(last, -(distance + min_spacing/2), +min_spacing/2);
|
||||
loops.insert(loops.end(), last.begin(), last.end());
|
||||
append(loops, last);
|
||||
}
|
||||
|
||||
// generate paths from the outermost to the innermost, to avoid
|
||||
// adhesion problems of the first central tiny loops
|
||||
loops = union_pt_chained(loops, false);
|
||||
loops = union_pt_chained_outside_in(loops, false);
|
||||
|
||||
// split paths using a nearest neighbor search
|
||||
size_t iPathFirst = polylines_out.size();
|
||||
Point last_pos(0, 0);
|
||||
for (const Polygon &loop : loops) {
|
||||
polylines_out.push_back(loop.split_at_index(last_pos.nearest_point_index(loop.points)));
|
||||
polylines_out.emplace_back(loop.split_at_index(last_pos.nearest_point_index(loop.points)));
|
||||
last_pos = polylines_out.back().last_point();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace Slic3r {
|
|||
class FillConcentric : public Fill
|
||||
{
|
||||
public:
|
||||
~FillConcentric() override {}
|
||||
~FillConcentric() override = default;
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new FillConcentric(*this); };
|
||||
|
|
|
@ -37,7 +37,7 @@ protected:
|
|||
|
||||
bool _can_connect(coord_t dist_X, coord_t dist_Y)
|
||||
{
|
||||
coord_t TOLERANCE = 10 * SCALED_EPSILON;
|
||||
const auto TOLERANCE = coord_t(10 * SCALED_EPSILON);
|
||||
return (dist_X >= (this->_line_spacing - this->_line_oscillation) - TOLERANCE)
|
||||
&& (dist_X <= (this->_line_spacing + this->_line_oscillation) + TOLERANCE)
|
||||
&& (dist_Y <= this->_diagonal_distance);
|
||||
|
|
|
@ -56,58 +56,59 @@ const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_poin
|
|||
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
|
||||
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
|
||||
|
||||
const char* MODEL_TAG = "model";
|
||||
const char* RESOURCES_TAG = "resources";
|
||||
const char* OBJECT_TAG = "object";
|
||||
const char* MESH_TAG = "mesh";
|
||||
const char* VERTICES_TAG = "vertices";
|
||||
const char* VERTEX_TAG = "vertex";
|
||||
const char* TRIANGLES_TAG = "triangles";
|
||||
const char* TRIANGLE_TAG = "triangle";
|
||||
const char* COMPONENTS_TAG = "components";
|
||||
const char* COMPONENT_TAG = "component";
|
||||
const char* BUILD_TAG = "build";
|
||||
const char* ITEM_TAG = "item";
|
||||
const char* METADATA_TAG = "metadata";
|
||||
static constexpr const char* MODEL_TAG = "model";
|
||||
static constexpr const char* RESOURCES_TAG = "resources";
|
||||
static constexpr const char* OBJECT_TAG = "object";
|
||||
static constexpr const char* MESH_TAG = "mesh";
|
||||
static constexpr const char* VERTICES_TAG = "vertices";
|
||||
static constexpr const char* VERTEX_TAG = "vertex";
|
||||
static constexpr const char* TRIANGLES_TAG = "triangles";
|
||||
static constexpr const char* TRIANGLE_TAG = "triangle";
|
||||
static constexpr const char* COMPONENTS_TAG = "components";
|
||||
static constexpr const char* COMPONENT_TAG = "component";
|
||||
static constexpr const char* BUILD_TAG = "build";
|
||||
static constexpr const char* ITEM_TAG = "item";
|
||||
static constexpr const char* METADATA_TAG = "metadata";
|
||||
|
||||
const char* CONFIG_TAG = "config";
|
||||
const char* VOLUME_TAG = "volume";
|
||||
static constexpr const char* CONFIG_TAG = "config";
|
||||
static constexpr const char* VOLUME_TAG = "volume";
|
||||
|
||||
const char* UNIT_ATTR = "unit";
|
||||
const char* NAME_ATTR = "name";
|
||||
const char* TYPE_ATTR = "type";
|
||||
const char* ID_ATTR = "id";
|
||||
const char* X_ATTR = "x";
|
||||
const char* Y_ATTR = "y";
|
||||
const char* Z_ATTR = "z";
|
||||
const char* V1_ATTR = "v1";
|
||||
const char* V2_ATTR = "v2";
|
||||
const char* V3_ATTR = "v3";
|
||||
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* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
|
||||
static constexpr const char* UNIT_ATTR = "unit";
|
||||
static constexpr const char* NAME_ATTR = "name";
|
||||
static constexpr const char* TYPE_ATTR = "type";
|
||||
static constexpr const char* ID_ATTR = "id";
|
||||
static constexpr const char* X_ATTR = "x";
|
||||
static constexpr const char* Y_ATTR = "y";
|
||||
static constexpr const char* Z_ATTR = "z";
|
||||
static constexpr const char* V1_ATTR = "v1";
|
||||
static constexpr const char* V2_ATTR = "v2";
|
||||
static constexpr const char* V3_ATTR = "v3";
|
||||
static constexpr const char* OBJECTID_ATTR = "objectid";
|
||||
static constexpr const char* TRANSFORM_ATTR = "transform";
|
||||
static constexpr const char* PRINTABLE_ATTR = "printable";
|
||||
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
|
||||
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
|
||||
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
|
||||
|
||||
const char* KEY_ATTR = "key";
|
||||
const char* VALUE_ATTR = "value";
|
||||
const char* FIRST_TRIANGLE_ID_ATTR = "firstid";
|
||||
const char* LAST_TRIANGLE_ID_ATTR = "lastid";
|
||||
static constexpr const char* KEY_ATTR = "key";
|
||||
static constexpr const char* VALUE_ATTR = "value";
|
||||
static constexpr const char* FIRST_TRIANGLE_ID_ATTR = "firstid";
|
||||
static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid";
|
||||
|
||||
const char* OBJECT_TYPE = "object";
|
||||
const char* VOLUME_TYPE = "volume";
|
||||
static constexpr const char* OBJECT_TYPE = "object";
|
||||
static constexpr const char* VOLUME_TYPE = "volume";
|
||||
|
||||
const char* NAME_KEY = "name";
|
||||
const char* MODIFIER_KEY = "modifier";
|
||||
const char* VOLUME_TYPE_KEY = "volume_type";
|
||||
const char* MATRIX_KEY = "matrix";
|
||||
const char* SOURCE_FILE_KEY = "source_file";
|
||||
const char* SOURCE_OBJECT_ID_KEY = "source_object_id";
|
||||
const char* SOURCE_VOLUME_ID_KEY = "source_volume_id";
|
||||
const char* SOURCE_OFFSET_X_KEY = "source_offset_x";
|
||||
const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
|
||||
const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
|
||||
static constexpr const char* NAME_KEY = "name";
|
||||
static constexpr const char* MODIFIER_KEY = "modifier";
|
||||
static constexpr const char* VOLUME_TYPE_KEY = "volume_type";
|
||||
static constexpr const char* MATRIX_KEY = "matrix";
|
||||
static constexpr const char* SOURCE_FILE_KEY = "source_file";
|
||||
static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id";
|
||||
static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id";
|
||||
static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x";
|
||||
static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
|
||||
static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
|
||||
static constexpr const char* SOURCE_IN_INCHES = "source_in_inches";
|
||||
|
||||
const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
|
||||
const char* VALID_OBJECT_TYPES[] =
|
||||
|
@ -1926,6 +1927,8 @@ namespace Slic3r {
|
|||
volume->source.mesh_offset(1) = ::atof(metadata.value.c_str());
|
||||
else if (metadata.key == SOURCE_OFFSET_Z_KEY)
|
||||
volume->source.mesh_offset(2) = ::atof(metadata.value.c_str());
|
||||
else if (metadata.key == SOURCE_IN_INCHES)
|
||||
volume->source.is_converted_from_inches = metadata.value == "1";
|
||||
else
|
||||
volume->config.set_deserialize(metadata.key, metadata.value);
|
||||
}
|
||||
|
@ -2248,7 +2251,7 @@ namespace Slic3r {
|
|||
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n";
|
||||
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n";
|
||||
std::string name = boost::filesystem::path(filename).stem().string();
|
||||
std::string name = xml_escape(boost::filesystem::path(filename).stem().string());
|
||||
stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "</" << METADATA_TAG << ">\n";
|
||||
stream << " <" << METADATA_TAG << " name=\"Designer\">" << "</" << METADATA_TAG << ">\n";
|
||||
stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "</" << METADATA_TAG << ">\n";
|
||||
|
@ -2749,15 +2752,20 @@ namespace Slic3r {
|
|||
stream << "\"/>\n";
|
||||
|
||||
// stores volume's source data
|
||||
if (!volume->source.input_file.empty())
|
||||
{
|
||||
std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
|
||||
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
|
||||
std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\"";
|
||||
if (! volume->source.input_file.empty()) {
|
||||
stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
|
||||
stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
|
||||
stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
|
||||
stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
|
||||
stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
|
||||
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
|
||||
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
|
||||
}
|
||||
if (volume->source.is_converted_from_inches)
|
||||
stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
||||
}
|
||||
|
||||
// stores volume's config data
|
||||
|
|
|
@ -778,6 +778,9 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
else if (strcmp(opt_key, "source_offset_z") == 0) {
|
||||
m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str());
|
||||
}
|
||||
else if (strcmp(opt_key, "source_in_inches") == 0) {
|
||||
m_volume->source.is_converted_from_inches = m_value[1] == "1";
|
||||
}
|
||||
}
|
||||
} else if (m_path.size() == 3) {
|
||||
if (m_path[1] == NODE_TYPE_MATERIAL) {
|
||||
|
@ -1017,6 +1020,12 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
|
|||
#endif // forward compatibility
|
||||
|
||||
close_zip_reader(&archive);
|
||||
|
||||
for (ModelObject *o : model->objects)
|
||||
for (ModelVolume *v : o->volumes)
|
||||
if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART))
|
||||
v->source.input_file = path;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1205,6 +1214,8 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config,
|
|||
stream << " <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
|
||||
stream << " <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
|
||||
}
|
||||
if (volume->source.is_converted_from_inches)
|
||||
stream << " <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
|
||||
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
for (size_t i = 0; i < its.indices.size(); ++i) {
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_SEPARATOR '\\'
|
||||
#else
|
||||
|
@ -22,7 +24,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
// Parse the OBJ file.
|
||||
ObjParser::ObjData data;
|
||||
if (! ObjParser::objparse(path, data)) {
|
||||
// die "Failed to parse $file\n" if !-e $path;
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -70,7 +72,8 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
++ num_normals;
|
||||
}
|
||||
}
|
||||
if (data.vertices[i].coordIdx != -1) {
|
||||
// Result of obj_parseline() call is not checked, thus not all vertices are necessarily finalized with coord_Idx == -1.
|
||||
if (i < data.vertices.size() && data.vertices[i].coordIdx != -1) {
|
||||
// This is a quad. Produce the other triangle.
|
||||
stl_facet &facet2 = stl.facet_start[i_face++];
|
||||
facet2.vertex[0] = facet.vertex[0];
|
||||
|
@ -102,7 +105,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
stl_get_size(&stl);
|
||||
mesh.repair();
|
||||
if (mesh.facets_count() == 0) {
|
||||
// die "This OBJ file couldn't be read because it's empty.\n"
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: This OBJ file couldn't be read because it's empty. " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
#include "objparser.hpp"
|
||||
|
@ -312,7 +313,7 @@ static bool obj_parseline(const char *line, ObjData &data)
|
|||
break;
|
||||
}
|
||||
default:
|
||||
printf("ObjParser: Unknown command: %c\r\n", c1);
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Unknown command: " << c1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -338,15 +339,22 @@ bool objparse(const char *path, ObjData &data)
|
|||
char *c = buf + lastLine;
|
||||
while (*c == ' ' || *c == '\t')
|
||||
++ c;
|
||||
//FIXME check the return value and exit on error?
|
||||
// Will it break parsing of some obj files?
|
||||
obj_parseline(c, data);
|
||||
lastLine = i + 1;
|
||||
}
|
||||
lenPrev = len - lastLine;
|
||||
if (lenPrev > 65536) {
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Excessive line length";
|
||||
::fclose(pFile);
|
||||
return false;
|
||||
}
|
||||
memmove(buf, buf + lastLine, lenPrev);
|
||||
}
|
||||
}
|
||||
catch (std::bad_alloc&) {
|
||||
printf("Out of memory\r\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Out of memory";
|
||||
}
|
||||
::fclose(pFile);
|
||||
|
||||
|
@ -378,7 +386,8 @@ bool objparse(std::istream &stream, ObjData &data)
|
|||
}
|
||||
}
|
||||
catch (std::bad_alloc&) {
|
||||
printf("Out of memory\r\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "ObjParser: Out of memory";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -413,14 +413,11 @@ namespace Slic3r {
|
|||
|
||||
std::string WipeTowerIntegration::prime(GCode& gcodegen)
|
||||
{
|
||||
assert(m_layer_idx == 0);
|
||||
std::string gcode;
|
||||
|
||||
for (const WipeTower::ToolChangeResult& tcr : m_priming) {
|
||||
if (! tcr.extrusions.empty())
|
||||
gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
|
||||
}
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
|
@ -717,8 +714,21 @@ namespace DoExport {
|
|||
if (region->config().get_abs_value("infill_speed") == 0 ||
|
||||
region->config().get_abs_value("solid_infill_speed") == 0 ||
|
||||
region->config().get_abs_value("top_solid_infill_speed") == 0 ||
|
||||
region->config().get_abs_value("bridge_speed") == 0)
|
||||
mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
|
||||
region->config().get_abs_value("bridge_speed") == 0)
|
||||
{
|
||||
// Minimal volumetric flow should not be calculated over ironing extrusions.
|
||||
// Use following lambda instead of the built-it method.
|
||||
// https://github.com/prusa3d/PrusaSlicer/issues/5082
|
||||
auto min_mm3_per_mm_no_ironing = [](const ExtrusionEntityCollection& eec) -> double {
|
||||
double min = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionEntity* ee : eec.entities)
|
||||
if (ee->role() != erIroning)
|
||||
min = std::min(min, ee->min_mm3_per_mm());
|
||||
return min;
|
||||
};
|
||||
|
||||
mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (object->config().get_abs_value("support_material_speed") == 0 ||
|
||||
|
@ -1243,18 +1253,30 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
|
|||
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
|
||||
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
|
||||
bbox_prime.offset(0.5f);
|
||||
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
|
||||
_write(file, this->retract());
|
||||
_write(file, "M300 S800 P500\n");
|
||||
if (bbox_prime.overlap(bbox_print)) {
|
||||
// Wait for the user to remove the priming extrusions, otherwise they would
|
||||
// get covered by the print.
|
||||
_write(file, "M1 Remove priming towers and click button.\n");
|
||||
}
|
||||
else {
|
||||
// Just wait for a bit to let the user check, that the priming succeeded.
|
||||
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
|
||||
_write(file, "M1 S10\n");
|
||||
bool overlap = bbox_prime.overlap(bbox_print);
|
||||
|
||||
if (print.config().gcode_flavor == gcfMarlin) {
|
||||
_write(file, this->retract());
|
||||
_write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz.
|
||||
if (overlap) {
|
||||
// Wait for the user to remove the priming extrusions.
|
||||
_write(file, "M1 Remove priming towers and click button.\n");
|
||||
} else {
|
||||
// Just wait for a bit to let the user check, that the priming succeeded.
|
||||
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
|
||||
_write(file, "M1 S10\n");
|
||||
}
|
||||
} else {
|
||||
// This is not Marlin, M1 command is probably not supported.
|
||||
// (See https://github.com/prusa3d/PrusaSlicer/issues/5441.)
|
||||
if (overlap) {
|
||||
print.active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
|
||||
_(L("Your print is very close to the priming regions. "
|
||||
"Make sure there is no collision.")));
|
||||
} else {
|
||||
// Just continue printing, no action necessary.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
|
@ -1700,7 +1722,9 @@ namespace Skirt {
|
|||
//FIXME infinite or high skirt does not make sense for sequential print!
|
||||
(skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) &&
|
||||
// This print_z has not been extruded yet (sequential print)
|
||||
skirt_done.back() < layer_tools.print_z - EPSILON &&
|
||||
// FIXME: The skirt_done should not be empty at this point. The check is a workaround
|
||||
// of https://github.com/prusa3d/PrusaSlicer/issues/5652, but it deserves a real fix.
|
||||
(! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON) &&
|
||||
// and this layer is an object layer, or it is a raft layer.
|
||||
(layer_tools.has_object || support_layer->id() < (size_t)support_layer->object()->config().raft_layers.value)) {
|
||||
#if 0
|
||||
|
@ -2087,6 +2111,8 @@ void GCode::process_layer(
|
|||
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
|
||||
m_layer = layers[instance_to_print.layer_id].layer();
|
||||
}
|
||||
//FIXME order islands?
|
||||
// Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
|
||||
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<unsigned int>(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.
|
||||
|
|
|
@ -269,10 +269,10 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
|
|||
}
|
||||
|
||||
// Called by avoid_perimeters() and by simplify_travel_heuristics().
|
||||
static size_t avoid_perimeters_inner(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
std::vector<TravelPoint> &result_out)
|
||||
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
std::vector<TravelPoint> &result_out)
|
||||
{
|
||||
const Polygons &boundaries = boundary.boundaries;
|
||||
const EdgeGrid::Grid &edge_grid = boundary.grid;
|
||||
|
@ -372,14 +372,14 @@ static size_t avoid_perimeters_inner(const GCode &gcodegen, const AvoidCrossingP
|
|||
}
|
||||
|
||||
// Called by AvoidCrossingPerimeters::travel_to()
|
||||
static size_t avoid_perimeters(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
Polyline &result_out)
|
||||
static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary,
|
||||
const Point &start,
|
||||
const Point &end,
|
||||
Polyline &result_out)
|
||||
{
|
||||
// Travel line is completely or partially inside the bounding box.
|
||||
std::vector<TravelPoint> path;
|
||||
size_t num_intersections = avoid_perimeters_inner(gcodegen, boundary, start, end, path);
|
||||
size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path);
|
||||
result_out = to_polyline(path);
|
||||
|
||||
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
||||
|
@ -726,6 +726,7 @@ static std::vector<float> contour_distance(const EdgeGrid::Grid &grid,
|
|||
}
|
||||
|
||||
// Polygon offset which ensures that if a polygon breaks up into several separate parts, the original polygon will be used in these places.
|
||||
// ExPolygons are handled one by one so returned ExPolygons could intersect.
|
||||
static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset, double min_contour_width = scale_(0.001))
|
||||
{
|
||||
double search_radius = 2. * (offset + min_contour_width);
|
||||
|
@ -781,12 +782,25 @@ static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset, dou
|
|||
return ex_poly_result;
|
||||
}
|
||||
|
||||
//#define INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
|
||||
// called by AvoidCrossingPerimeters::travel_to()
|
||||
static ExPolygons get_boundary(const Layer &layer)
|
||||
{
|
||||
const float perimeter_spacing = get_perimeter_spacing(layer);
|
||||
const float perimeter_offset = perimeter_spacing / 2.f;
|
||||
auto const *support_layer = dynamic_cast<const SupportLayer *>(&layer);
|
||||
ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset));
|
||||
if(support_layer) {
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset));
|
||||
#endif
|
||||
auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON);
|
||||
if (layer_below)
|
||||
append(boundary, inner_offset(layer_below->lslices, perimeter_offset));
|
||||
// After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons
|
||||
boundary = union_ex(boundary);
|
||||
}
|
||||
// Collect all top layers that will not be crossed.
|
||||
size_t polygons_count = 0;
|
||||
for (const LayerRegion *layer_region : layer.regions())
|
||||
|
@ -810,20 +824,41 @@ static ExPolygons get_boundary(const Layer &layer)
|
|||
// called by AvoidCrossingPerimeters::travel_to()
|
||||
static Polygons get_boundary_external(const Layer &layer)
|
||||
{
|
||||
const float perimeter_spacing = get_perimeter_spacing(layer);
|
||||
const float perimeter_spacing = get_perimeter_spacing_external(layer);
|
||||
const float perimeter_offset = perimeter_spacing / 2.f;
|
||||
auto const *support_layer = dynamic_cast<const SupportLayer *>(&layer);
|
||||
Polygons boundary;
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
ExPolygons supports_boundary;
|
||||
#endif
|
||||
// Collect all holes for all printed objects and their instances, which will be printed at the same time as passed "layer".
|
||||
for (const PrintObject *object : layer.object()->print()->objects()) {
|
||||
Polygons polygons_per_obj;
|
||||
Polygons polygons_per_obj;
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
ExPolygons supports_per_obj;
|
||||
#endif
|
||||
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
|
||||
for (const ExPolygon &island : l->lslices) append(polygons_per_obj, island.holes);
|
||||
if (support_layer) {
|
||||
auto *layer_below = object->get_first_layer_bellow_printz(layer.print_z, EPSILON);
|
||||
if (layer_below)
|
||||
for (const ExPolygon &island : layer_below->lslices) append(polygons_per_obj, island.holes);
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
append(supports_per_obj, support_layer->support_islands.expolygons);
|
||||
#endif
|
||||
}
|
||||
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
size_t boundary_idx = boundary.size();
|
||||
append(boundary, polygons_per_obj);
|
||||
for (; boundary_idx < boundary.size(); ++boundary_idx)
|
||||
boundary[boundary_idx].translate(instance.shift);
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
size_t support_idx = supports_boundary.size();
|
||||
append(supports_boundary, supports_per_obj);
|
||||
for (; support_idx < supports_boundary.size(); ++support_idx)
|
||||
supports_boundary[support_idx].translate(instance.shift);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -832,7 +867,9 @@ static Polygons get_boundary_external(const Layer &layer)
|
|||
// Reverse all polygons for making normals point from the polygon out.
|
||||
for (Polygon &poly : boundary)
|
||||
poly.reverse();
|
||||
|
||||
#ifdef INCLUDE_SUPPORTS_IN_BOUNDARY
|
||||
append(boundary, to_polygons(inner_offset(supports_boundary, perimeter_offset)));
|
||||
#endif
|
||||
return boundary;
|
||||
}
|
||||
|
||||
|
@ -873,14 +910,17 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
|||
Vec2d startf = start.cast<double>();
|
||||
Vec2d endf = end .cast<double>();
|
||||
|
||||
if (!use_external && !gcodegen.layer()->lslices.empty() && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) {
|
||||
const ExPolygons &lslices = gcodegen.layer()->lslices;
|
||||
const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes;
|
||||
bool is_support_layer = dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr;
|
||||
if (!use_external && (is_support_layer || (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)))) {
|
||||
// Initialize m_internal only when it is necessary.
|
||||
if (m_internal.boundaries.empty())
|
||||
init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer())));
|
||||
|
||||
// Trim the travel line by the bounding box.
|
||||
if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) {
|
||||
travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl);
|
||||
if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) {
|
||||
travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl);
|
||||
result_pl.points.front() = start;
|
||||
result_pl.points.back() = end;
|
||||
}
|
||||
|
@ -891,7 +931,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
|||
|
||||
// Trim the travel line by the bounding box.
|
||||
if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) {
|
||||
travel_intersection_count = avoid_perimeters(gcodegen, m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl);
|
||||
travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl);
|
||||
result_pl.points.front() = start;
|
||||
result_pl.points.back() = end;
|
||||
}
|
||||
|
@ -903,13 +943,25 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
|||
travel_intersection_count = 0;
|
||||
}
|
||||
|
||||
double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour);
|
||||
if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length)
|
||||
result_pl = {start, end};
|
||||
const ConfigOptionFloatOrPercent &opt_max_detour = gcodegen.config().avoid_crossing_perimeters_max_detour;
|
||||
bool max_detour_length_exceeded = false;
|
||||
if (opt_max_detour.value > 0) {
|
||||
double direct_length = travel.length();
|
||||
double detour = result_pl.length() - direct_length;
|
||||
double max_detour_length = opt_max_detour.percent ?
|
||||
direct_length * 0.01 * opt_max_detour.value :
|
||||
scale_(opt_max_detour.value);
|
||||
if (detour > max_detour_length) {
|
||||
result_pl = {start, end};
|
||||
max_detour_length_exceeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_external) {
|
||||
result_pl.translate(-scaled_origin);
|
||||
*could_be_wipe_disabled = false;
|
||||
} else if (max_detour_length_exceeded) {
|
||||
*could_be_wipe_disabled = false;
|
||||
} else
|
||||
*could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count);
|
||||
|
||||
|
|
|
@ -540,7 +540,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||
m_filament_diameters[i] = static_cast<float>(config.filament_diameter.values[i]);
|
||||
}
|
||||
|
||||
if (config.machine_limits_usage.value != MachineLimitsUsage::Ignore)
|
||||
if (m_flavor == gcfMarlin && config.machine_limits_usage.value != MachineLimitsUsage::Ignore)
|
||||
m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config);
|
||||
|
||||
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
|
||||
|
@ -562,6 +562,10 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
|||
}
|
||||
|
||||
m_time_processor.export_remaining_time_enabled = config.remaining_times.value;
|
||||
|
||||
#if ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
m_use_volumetric_e = config.use_volumetric_e;
|
||||
#endif // ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
}
|
||||
|
||||
void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
|
||||
|
@ -620,7 +624,6 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// replace missing values with default
|
||||
std::string default_color = "#FF8000";
|
||||
for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) {
|
||||
|
@ -649,75 +652,86 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
|
|||
}
|
||||
}
|
||||
|
||||
const ConfigOptionFloats* machine_max_acceleration_x = config.option<ConfigOptionFloats>("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;
|
||||
if (m_flavor == gcfMarlin) {
|
||||
const ConfigOptionFloats* machine_max_acceleration_x = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_y = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_z = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_acceleration_e = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_x = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_y = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_z = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_feedrate_e = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_x = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_y = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_z = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_jerk_e = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_extruding = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_max_acceleration_retracting = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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_extruding_rate = config.option<ConfigOptionFloats>("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<ConfigOptionFloats>("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;
|
||||
const ConfigOptionFloats* machine_min_travel_rate = config.option<ConfigOptionFloats>("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<size_t>(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;
|
||||
}
|
||||
|
||||
if (m_time_processor.machine_limits.machine_max_acceleration_x.values.size() > 1)
|
||||
enable_stealth_time_estimator(true);
|
||||
|
||||
#if ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
const ConfigOptionBool* use_volumetric_e = config.option<ConfigOptionBool>("use_volumetric_e");
|
||||
if (use_volumetric_e != nullptr)
|
||||
m_use_volumetric_e = use_volumetric_e->value;
|
||||
#endif // ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
}
|
||||
|
||||
void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
|
||||
|
@ -772,6 +786,10 @@ void GCodeProcessor::reset()
|
|||
m_result.reset();
|
||||
m_result.id = ++s_result_id;
|
||||
|
||||
#if ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
m_use_volumetric_e = false;
|
||||
#endif // ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_mm3_per_mm_compare.reset();
|
||||
m_height_compare.reset();
|
||||
|
@ -1652,8 +1670,14 @@ void GCodeProcessor::process_G0(const GCodeReader::GCodeLine& line)
|
|||
|
||||
void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
auto absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1)
|
||||
{
|
||||
#if ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
float filament_diameter = (static_cast<size_t>(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<float>(M_PI) * sqr(filament_radius);
|
||||
auto absolute_position = [this, area_filament_cross_section](Axis axis, const GCodeReader::GCodeLine& lineG1) {
|
||||
#else
|
||||
auto absolute_position = [this](Axis axis, const GCodeReader::GCodeLine& lineG1) {
|
||||
#endif // ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
bool is_relative = (m_global_positioning_type == EPositioningType::Relative);
|
||||
if (axis == E)
|
||||
is_relative |= (m_e_local_positioning_type == EPositioningType::Relative);
|
||||
|
@ -1661,6 +1685,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
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;
|
||||
#if ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
if (axis == E && m_use_volumetric_e)
|
||||
ret /= area_filament_cross_section;
|
||||
#endif // ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
return is_relative ? m_start_position[axis] + ret : m_origin[axis] + ret;
|
||||
}
|
||||
else
|
||||
|
@ -1718,9 +1746,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
|
||||
if (type == EMoveType::Extrude) {
|
||||
float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
|
||||
#if !ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
float filament_diameter = (static_cast<size_t>(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<float>(M_PI) * sqr(filament_radius);
|
||||
#endif // !ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
float volume_extruded_filament = area_filament_cross_section * delta_pos[E];
|
||||
float area_toolpath_cross_section = volume_extruded_filament / delta_xyz;
|
||||
|
||||
|
@ -1772,7 +1802,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
|||
m_width = delta_pos[E] * static_cast<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(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, 1.0f);
|
||||
m_width = std::min(m_width, std::max(1.0f, 4.0f * m_height));
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
m_width_compare.update(m_width, m_extrusion_role);
|
||||
|
@ -2113,32 +2143,29 @@ 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 != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f;
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
if (line.has_x())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor);
|
||||
if (static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal ||
|
||||
m_time_processor.machine_envelope_processing_enabled) {
|
||||
if (line.has_x())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor);
|
||||
|
||||
if (line.has_y())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor);
|
||||
if (line.has_y())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_y, i, line.y() * factor);
|
||||
|
||||
if (line.has_z())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor);
|
||||
if (line.has_z())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_z, i, line.z() * factor);
|
||||
|
||||
if (line.has_e())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor);
|
||||
if (line.has_e())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_acceleration_e, i, line.e() * factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -2148,45 +2175,48 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line)
|
|||
float factor = (m_flavor == gcfMarlin || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC;
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
|
||||
if (line.has_x())
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor);
|
||||
if (static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal ||
|
||||
m_time_processor.machine_envelope_processing_enabled) {
|
||||
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_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_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);
|
||||
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)
|
||||
{
|
||||
if (!m_time_processor.machine_envelope_processing_enabled)
|
||||
return;
|
||||
|
||||
float value;
|
||||
for (size_t i = 0; i < static_cast<size_t>(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<PrintEstimatedTimeStatistics::ETimeMode>(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))
|
||||
if (static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal ||
|
||||
m_time_processor.machine_envelope_processing_enabled) {
|
||||
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<PrintEstimatedTimeStatistics::ETimeMode>(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);
|
||||
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<PrintEstimatedTimeStatistics::ETimeMode>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2194,31 +2224,31 @@ 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<size_t>(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);
|
||||
set_option_value(m_time_processor.machine_limits.machine_max_jerk_y, i, max_jerk);
|
||||
if (static_cast<PrintEstimatedTimeStatistics::ETimeMode>(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal ||
|
||||
m_time_processor.machine_envelope_processing_enabled) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2315,7 +2345,9 @@ void GCodeProcessor::process_T(const std::string_view command)
|
|||
if (command.length() > 1) {
|
||||
int eid;
|
||||
if (! parse_number(command.substr(1), eid) || eid < 0 || eid > 255) {
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
|
||||
// T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677
|
||||
if ((m_flavor != gcfRepRapFirmware && m_flavor != gcfRepRapSprinter) || eid != -1)
|
||||
BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange (" << command << ").";
|
||||
} else {
|
||||
unsigned char id = static_cast<unsigned char>(eid);
|
||||
if (m_extruder_id != id) {
|
||||
|
|
|
@ -242,7 +242,7 @@ namespace Slic3r {
|
|||
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()
|
||||
// allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() for non-Normal time estimate mode
|
||||
bool machine_envelope_processing_enabled;
|
||||
MachineEnvelopeConfig machine_limits;
|
||||
// Additional load / unload times for a filament exchange sequence.
|
||||
|
@ -421,6 +421,9 @@ namespace Slic3r {
|
|||
unsigned int m_g1_line_id;
|
||||
unsigned int m_layer_id;
|
||||
CpColor m_cp_color;
|
||||
#if ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
bool m_use_volumetric_e;
|
||||
#endif // ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING
|
||||
|
||||
enum class EProducer
|
||||
{
|
||||
|
|
|
@ -504,7 +504,7 @@ Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, size
|
|||
bool* saw_custom) const
|
||||
{
|
||||
// Parametrize the polygon by its length.
|
||||
std::vector<float> lengths = polygon.parameter_by_length();
|
||||
const std::vector<float> lengths = polygon.parameter_by_length();
|
||||
|
||||
// Which of the points are inside enforcers/blockers?
|
||||
std::vector<size_t> enforcers_idxs;
|
||||
|
@ -516,23 +516,73 @@ Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, size
|
|||
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);
|
||||
assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end()));
|
||||
assert(std::is_sorted(blockers_idxs.begin(), blockers_idxs.end()));
|
||||
std::vector<float> edges;
|
||||
|
||||
// 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)));
|
||||
// Lambda to calculate lengths of all edges of interest. Last parameter
|
||||
// decides whether to measure edges inside or outside idxs.
|
||||
// Negative number = not an edge of interest.
|
||||
auto get_valid_length = [&lengths](const std::vector<size_t>& idxs,
|
||||
std::vector<float>& edges,
|
||||
bool measure_inside_edges) -> float
|
||||
{
|
||||
// First mark edges we are interested in by assigning a positive number.
|
||||
edges.assign(lengths.size()-1, measure_inside_edges ? -1.f : 1.f);
|
||||
for (size_t i=0; i<idxs.size(); ++i) {
|
||||
size_t this_pt_idx = idxs[i];
|
||||
// Two concurrent indices in the list -> the edge between them is the enforcer/blocker.
|
||||
bool inside_edge = ((i != idxs.size()-1 && idxs[i+1] == this_pt_idx + 1)
|
||||
|| (i == idxs.size()-1 && idxs.back() == lengths.size()-2 && idxs[0] == 0));
|
||||
if (inside_edge)
|
||||
edges[this_pt_idx] = measure_inside_edges ? 1.f : -1.f;
|
||||
}
|
||||
// Now measure them.
|
||||
float running_total = 0.f;
|
||||
for (size_t i=0; i<edges.size(); ++i) {
|
||||
if (edges[i] > 0.f) {
|
||||
edges[i] = lengths[i+1] - lengths[i];
|
||||
running_total += edges[i];
|
||||
}
|
||||
}
|
||||
return running_total;
|
||||
};
|
||||
|
||||
// Find all seam candidate edges and their lengths.
|
||||
float valid_length = 0.f;
|
||||
if (has_enforcers)
|
||||
valid_length = get_valid_length(enforcers_idxs, edges, true);
|
||||
|
||||
if (! has_enforcers || valid_length == 0.f) {
|
||||
// Second condition covers case with isolated enf points. Given how the painted
|
||||
// triangles are projected, this should not happen. Stay on the safe side though.
|
||||
if (has_blockers)
|
||||
valid_length = get_valid_length(blockers_idxs, edges, false);
|
||||
if (valid_length == 0.f) // No blockers or everything blocked - use the whole polygon.
|
||||
valid_length = lengths.back();
|
||||
}
|
||||
assert(valid_length != 0.f);
|
||||
// Now generate a random length and find the respective edge.
|
||||
float rand_len = valid_length * (rand()/float(RAND_MAX));
|
||||
size_t pt_idx = 0; // Index of the edge where to put the seam.
|
||||
if (valid_length == lengths.back()) {
|
||||
// Whole polygon is used for placing the seam.
|
||||
auto it = std::lower_bound(lengths.begin(), lengths.end(), rand_len);
|
||||
pt_idx = it == lengths.begin() ? 0 : (it-lengths.begin()-1); // this takes care of a corner case where rand() returns 0
|
||||
} else {
|
||||
float running = 0.f;
|
||||
for (size_t i=0; i<edges.size(); ++i) {
|
||||
running += edges[i] > 0.f ? edges[i] : 0.f;
|
||||
if (running >= rand_len) {
|
||||
pt_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! has_enforcers && ! has_blockers) {
|
||||
// The polygon may be too coarse, calculate the point exactly.
|
||||
assert(valid_length == lengths.back());
|
||||
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];
|
||||
|
|
|
@ -461,11 +461,11 @@ bool Model::looks_like_imperial_units() const
|
|||
return false;
|
||||
}
|
||||
|
||||
void Model::convert_from_imperial_units()
|
||||
void Model::convert_from_imperial_units(bool only_small_volumes)
|
||||
{
|
||||
double in_to_mm = 25.4;
|
||||
for (ModelObject* obj : this->objects)
|
||||
if (obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3;
|
||||
if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3;
|
||||
obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
|
||||
for (ModelVolume* v : obj->volumes)
|
||||
v->source.is_converted_from_inches = true;
|
||||
|
@ -1042,8 +1042,6 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
|
|||
int vol_idx = 0;
|
||||
for (ModelVolume* volume : volumes)
|
||||
{
|
||||
volume->supported_facets.clear();
|
||||
volume->seam_facets.clear();
|
||||
if (!volume->mesh().empty()) {
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.require_shared_vertices();
|
||||
|
@ -1060,9 +1058,14 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial
|
|||
vol->source.object_idx = (int)new_objects.size();
|
||||
vol->source.volume_idx = vol_idx;
|
||||
|
||||
// Perform conversion
|
||||
if (volume_idxs.empty() ||
|
||||
std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end()) {
|
||||
vol->supported_facets.assign(volume->supported_facets);
|
||||
vol->seam_facets.assign(volume->seam_facets);
|
||||
|
||||
// Perform conversion only if the target "imperial" state is different from the current one.
|
||||
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
|
||||
if (//vol->source.is_converted_from_inches != from_imperial &&
|
||||
(volume_idxs.empty() ||
|
||||
std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end())) {
|
||||
vol->scale_geometry_after_creation(versor);
|
||||
vol->set_offset(versor.cwiseProduct(volume->get_offset()));
|
||||
vol->source.is_converted_from_inches = from_imperial;
|
||||
|
|
|
@ -1018,7 +1018,7 @@ public:
|
|||
bool looks_like_multipart_object() const;
|
||||
void convert_multipart_object(unsigned int max_extruders);
|
||||
bool looks_like_imperial_units() const;
|
||||
void convert_from_imperial_units();
|
||||
void convert_from_imperial_units(bool only_small_volumes);
|
||||
|
||||
// Ensures that the min z of the model is not negative
|
||||
void adjust_min_z();
|
||||
|
|
|
@ -1152,18 +1152,22 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
|
|||
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
|
||||
{
|
||||
if (opt_key == "bed_shape" || opt_key == "thumbnails" || opt_key == "compatible_prints" || opt_key == "compatible_printers") {
|
||||
// Scalar variable, or a vector variable, which is independent from number of extruders,
|
||||
// thus the vector is presented to the user as a single input.
|
||||
diff.emplace_back(opt_key);
|
||||
continue;
|
||||
}
|
||||
switch (other_opt->type())
|
||||
{
|
||||
case coInts: add_correct_opts_to_diff<ConfigOptionInts >(opt_key, diff, config_other, config_this); break;
|
||||
case coBools: add_correct_opts_to_diff<ConfigOptionBools >(opt_key, diff, config_other, config_this); break;
|
||||
case coFloats: add_correct_opts_to_diff<ConfigOptionFloats >(opt_key, diff, config_other, config_this); break;
|
||||
case coStrings: add_correct_opts_to_diff<ConfigOptionStrings >(opt_key, diff, config_other, config_this); break;
|
||||
case coPercents:add_correct_opts_to_diff<ConfigOptionPercents >(opt_key, diff, config_other, config_this); break;
|
||||
case coPoints: add_correct_opts_to_diff<ConfigOptionPoints >(opt_key, diff, config_other, config_this); break;
|
||||
default: diff.emplace_back(opt_key); break;
|
||||
} else if (opt_key == "default_filament_profile") {
|
||||
// Ignore this field, it is not presented to the user, therefore showing a "modified" flag for this parameter does not help.
|
||||
// Also the length of this field may differ, which may lead to a crash if the block below is used.
|
||||
} else {
|
||||
switch (other_opt->type()) {
|
||||
case coInts: add_correct_opts_to_diff<ConfigOptionInts >(opt_key, diff, config_other, config_this); break;
|
||||
case coBools: add_correct_opts_to_diff<ConfigOptionBools >(opt_key, diff, config_other, config_this); break;
|
||||
case coFloats: add_correct_opts_to_diff<ConfigOptionFloats >(opt_key, diff, config_other, config_this); break;
|
||||
case coStrings: add_correct_opts_to_diff<ConfigOptionStrings >(opt_key, diff, config_other, config_this); break;
|
||||
case coPercents:add_correct_opts_to_diff<ConfigOptionPercents >(opt_key, diff, config_other, config_this); break;
|
||||
case coPoints: add_correct_opts_to_diff<ConfigOptionPoints >(opt_key, diff, config_other, config_this); break;
|
||||
default: diff.emplace_back(opt_key); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1849,6 +1853,13 @@ void PhysicalPrinterCollection::select_printer(const std::string& full_name)
|
|||
m_selected_preset = it->get_preset_name(full_name);
|
||||
}
|
||||
|
||||
void PhysicalPrinterCollection::select_printer(const std::string& printer_name, const std::string& preset_name)
|
||||
{
|
||||
if (preset_name.empty())
|
||||
return select_printer(printer_name);
|
||||
return select_printer(printer_name + PhysicalPrinter::separator() + preset_name);
|
||||
}
|
||||
|
||||
void PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer)
|
||||
{
|
||||
return select_printer(printer.name);
|
||||
|
|
|
@ -701,6 +701,7 @@ public:
|
|||
// 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);
|
||||
void select_printer(const std::string& printer_name, const std::string& preset_name);
|
||||
bool has_selection() const;
|
||||
void unselect_printer() ;
|
||||
bool is_selected(ConstIterator it, const std::string &preset_name) const;
|
||||
|
|
|
@ -86,9 +86,8 @@ PresetBundle::PresetBundle() :
|
|||
preset.config.optptr(key, true);
|
||||
if (i == 0) {
|
||||
preset.config.optptr("default_print_profile", true);
|
||||
preset.config.option<ConfigOptionStrings>("default_filament_profile", true)->values = { "" };
|
||||
}
|
||||
else {
|
||||
preset.config.option<ConfigOptionStrings>("default_filament_profile", true);
|
||||
} else {
|
||||
preset.config.optptr("default_sla_print_profile", true);
|
||||
preset.config.optptr("default_sla_material_profile", true);
|
||||
}
|
||||
|
@ -668,7 +667,7 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
|
|||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||
void PresetBundle::load_config_file(const std::string &path)
|
||||
{
|
||||
if (boost::iends_with(path, ".gcode") || boost::iends_with(path, ".g")) {
|
||||
if (is_gcode_file(path)) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode_file(path);
|
||||
|
@ -752,7 +751,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||
switch (printer_technology) {
|
||||
case ptFFF:
|
||||
config.option<ConfigOptionString>("default_print_profile", true);
|
||||
config.option<ConfigOptionStrings>("default_filament_profile", true)->values.resize(num_extruders, std::string());
|
||||
config.option<ConfigOptionStrings>("default_filament_profile", true);
|
||||
break;
|
||||
case ptSLA:
|
||||
config.option<ConfigOptionString>("default_sla_print_profile", true);
|
||||
|
@ -876,7 +875,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||
// Activate the physical printer profile if possible.
|
||||
PhysicalPrinter *pp = this->physical_printers.find_printer(physical_printer, true);
|
||||
if (pp != nullptr && std::find(pp->preset_names.begin(), pp->preset_names.end(), this->printers.get_edited_preset().name) != pp->preset_names.end())
|
||||
this->physical_printers.select_printer(*pp);
|
||||
this->physical_printers.select_printer(pp->name, this->printers.get_edited_preset().name);
|
||||
else
|
||||
this->physical_printers.unselect_printer();
|
||||
}
|
||||
|
@ -1396,7 +1395,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
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);
|
||||
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);
|
||||
|
|
|
@ -1851,10 +1851,7 @@ void Print::_make_brim()
|
|||
}
|
||||
polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
|
||||
}
|
||||
loops = union_pt_chained(loops, false);
|
||||
// The function above produces ordering well suited for concentric infill (from outside to inside).
|
||||
// For Brim, the ordering should be reversed (from inside to outside).
|
||||
std::reverse(loops.begin(), loops.end());
|
||||
loops = union_pt_chained_outside_in(loops, false);
|
||||
|
||||
// If there is a possibility that brim intersects skirt, go through loops and split those extrusions
|
||||
// The result is either the original Polygon or a list of Polylines
|
||||
|
|
|
@ -161,6 +161,8 @@ public:
|
|||
// Get a layer approximately at print_z.
|
||||
const Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const;
|
||||
Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon);
|
||||
// Get the first layer approximately bellow print_z.
|
||||
const Layer* get_first_layer_bellow_printz(coordf_t print_z, coordf_t epsilon) const;
|
||||
|
||||
// print_z: top of the layer; slice_z: center of the layer.
|
||||
Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
|
||||
|
|
|
@ -64,7 +64,7 @@ void PrintConfigDef::init_common_params()
|
|||
|
||||
def = this->add("thumbnails", coPoints);
|
||||
def->label = L("G-code thumbnails");
|
||||
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files");
|
||||
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
|
||||
def->mode = comExpert;
|
||||
def->gui_type = "one_string";
|
||||
def->set_default_value(new ConfigOptionPoints());
|
||||
|
@ -184,15 +184,16 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("avoid_crossing_perimeters_max_detour", coFloat);
|
||||
def->label = L("Avoid crossing perimeters - The max detour lenght");
|
||||
def = this->add("avoid_crossing_perimeters_max_detour", coFloatOrPercent);
|
||||
def->label = L("Avoid crossing perimeters - Max detour length");
|
||||
def->category = L("Layers and Perimeters");
|
||||
def->tooltip = L("The maximum detour length for avoid crossing perimeters. "
|
||||
"If the detour is longer than this value, avoid crossing perimeters is not applied for this path.");
|
||||
def->sidetext = L("mm (zero to disable)");
|
||||
"If the detour is longer than this value, avoid crossing perimeters is not applied for this travel path. "
|
||||
"Detour length could be specified either as an absolute value or as percentage (for example 50%) of a direct travel path.");
|
||||
def->sidetext = L("mm or % (zero to disable)");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.));
|
||||
def->set_default_value(new ConfigOptionFloatOrPercent(0., false));
|
||||
|
||||
def = this->add("bed_temperature", coInts);
|
||||
def->label = L("Other layers");
|
||||
|
@ -606,6 +607,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"this setting to get nice surface finish and correct single wall widths. "
|
||||
"Usual values are between 0.9 and 1.1. If you think you need to change this more, "
|
||||
"check filament diameter and your firmware E steps.");
|
||||
def->max = 2;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats { 1. });
|
||||
|
||||
|
@ -618,6 +620,7 @@ void PrintConfigDef::init_fff_params()
|
|||
"If expressed as percentage (for example: 230%), it will be computed over layer height.");
|
||||
def->sidetext = L("mm or %");
|
||||
def->min = 0;
|
||||
def->max = 1000;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloatOrPercent(0, false));
|
||||
|
||||
|
@ -3375,6 +3378,8 @@ void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders)
|
|||
const auto &defaults = FullPrintConfig::defaults();
|
||||
for (const std::string &key : print_config_def.extruder_option_keys()) {
|
||||
if (key == "default_filament_profile")
|
||||
// Don't resize this field, as it is presented to the user at the "Dependencies" page of the Printer profile and we don't want to present
|
||||
// empty fields there, if not defined by the system profile.
|
||||
continue;
|
||||
auto *opt = this->option(key, false);
|
||||
assert(opt != nullptr);
|
||||
|
|
|
@ -828,7 +828,7 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig
|
|||
public:
|
||||
|
||||
ConfigOptionBool avoid_crossing_perimeters;
|
||||
ConfigOptionFloat avoid_crossing_perimeters_max_detour;
|
||||
ConfigOptionFloatOrPercent avoid_crossing_perimeters_max_detour;
|
||||
ConfigOptionPoints bed_shape;
|
||||
ConfigOptionInts bed_temperature;
|
||||
ConfigOptionFloat bridge_acceleration;
|
||||
|
|
|
@ -567,12 +567,18 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|
|||
|| opt_key == "dont_support_bridges"
|
||||
|| opt_key == "first_layer_extrusion_width") {
|
||||
steps.emplace_back(posSupportMaterial);
|
||||
} else if (opt_key == "bottom_solid_layers") {
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
if(m_print->config().spiral_vase) {
|
||||
// Changing the number of bottom layers when a spiral vase is enabled requires re-slicing the object again.
|
||||
// Otherwise, holes in the bottom layers could be filled, as is reported in GH #5528.
|
||||
steps.emplace_back(posSlice);
|
||||
}
|
||||
} else if (
|
||||
opt_key == "interface_shells"
|
||||
|| opt_key == "infill_only_where_needed"
|
||||
|| opt_key == "infill_every_layers"
|
||||
|| opt_key == "solid_infill_every_layers"
|
||||
|| opt_key == "bottom_solid_layers"
|
||||
|| opt_key == "bottom_solid_min_thickness"
|
||||
|| opt_key == "top_solid_layers"
|
||||
|| opt_key == "top_solid_min_thickness"
|
||||
|
@ -655,6 +661,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
|||
} else if (step == posPrepareInfill) {
|
||||
invalidated |= this->invalidate_steps({ posInfill, posIroning });
|
||||
} else if (step == posInfill) {
|
||||
invalidated |= this->invalidate_steps({ posIroning });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
} else if (step == posSlice) {
|
||||
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial });
|
||||
|
@ -2943,4 +2950,11 @@ const Layer* PrintObject::get_layer_at_printz(coordf_t print_z, coordf_t epsilon
|
|||
|
||||
Layer* PrintObject::get_layer_at_printz(coordf_t print_z, coordf_t epsilon) { return const_cast<Layer*>(std::as_const(*this).get_layer_at_printz(print_z, epsilon)); }
|
||||
|
||||
const Layer *PrintObject::get_first_layer_bellow_printz(coordf_t print_z, coordf_t epsilon) const
|
||||
{
|
||||
coordf_t limit = print_z + epsilon;
|
||||
auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [limit](const Layer *layer) { return layer->print_z < limit; });
|
||||
return (it == m_layers.begin()) ? nullptr : *(--it);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -906,13 +906,15 @@ namespace SupportMaterialInternal {
|
|||
polyline.extend_start(fw);
|
||||
polyline.extend_end(fw);
|
||||
// Is the straight perimeter segment supported at both sides?
|
||||
for (size_t i = 0; i < lower_layer.lslices.size(); ++ i)
|
||||
if (lower_layer.lslices_bboxes[i].contains(polyline.first_point()) && lower_layer.lslices_bboxes[i].contains(polyline.last_point()) &&
|
||||
lower_layer.lslices[i].contains(polyline.first_point()) && lower_layer.lslices[i].contains(polyline.last_point())) {
|
||||
// Offset a polyline into a thick line.
|
||||
polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
|
||||
break;
|
||||
}
|
||||
Point pts[2] = { polyline.first_point(), polyline.last_point() };
|
||||
bool supported[2] = { false, false };
|
||||
for (size_t i = 0; i < lower_layer.lslices.size() && ! (supported[0] && supported[1]); ++ i)
|
||||
for (int j = 0; j < 2; ++ j)
|
||||
if (! supported[j] && lower_layer.lslices_bboxes[i].contains(pts[j]) && lower_layer.lslices[i].contains(pts[j]))
|
||||
supported[j] = true;
|
||||
if (supported[0] && supported[1])
|
||||
// Offset a polyline into a thick line.
|
||||
polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
|
||||
}
|
||||
bridges = union_(bridges);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active
|
||||
#define ENABLE_RENDER_SELECTION_CENTER 0
|
||||
// Shows an imgui dialog with render related data
|
||||
#define ENABLE_RENDER_STATISTICS 1
|
||||
#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)
|
||||
|
@ -99,4 +99,12 @@
|
|||
#define ENABLE_TOOLPATHS_WIDTH_HEIGHT_FROM_GCODE (1 && ENABLE_2_3_0_BETA3)
|
||||
#define ENABLE_RENDER_PATH_REFRESH_AFTER_OPTIONS_CHANGE (1 && ENABLE_2_3_0_BETA3)
|
||||
|
||||
|
||||
//=================
|
||||
// 2.3.0.rc1 techs
|
||||
//=================
|
||||
#define ENABLE_2_3_0_RC1 1
|
||||
|
||||
#define ENABLE_VOLUMETRIC_EXTRUSION_PROCESSING (1 && ENABLE_2_3_0_RC1)
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/tbb_thread.h>
|
||||
#include <tbb/task_arena.h>
|
||||
#include <tbb/task_scheduler_init.h>
|
||||
|
||||
|
||||
#include "Thread.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -194,7 +195,10 @@ void name_tbb_thread_pool_threads()
|
|||
return;
|
||||
initialized = true;
|
||||
|
||||
const size_t nthreads_hw = std::thread::hardware_concurrency();
|
||||
// see GH issue #5661 PrusaSlicer hangs on Linux when run with non standard task affinity
|
||||
// TBB will respect the task affinity mask on Linux and spawn less threads than std::thread::hardware_concurrency().
|
||||
// const size_t nthreads_hw = std::thread::hardware_concurrency();
|
||||
const size_t nthreads_hw = tbb::this_task_arena::max_concurrency();
|
||||
size_t nthreads = nthreads_hw;
|
||||
|
||||
#ifdef SLIC3R_PROFILE
|
||||
|
|
|
@ -45,9 +45,11 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
|||
m_cursor = Cursor(hit, source, radius, cursor_type, trafo);
|
||||
|
||||
// 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;
|
||||
// It is necessary to compare the internal radius in m_cursor! radius is in
|
||||
// world coords and does not change after scaling.
|
||||
if (m_old_cursor_radius_sqr != m_cursor.radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor.radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor.radius_sqr;
|
||||
}
|
||||
|
||||
// Now start with the facet the pointer points to and check all adjacent facets.
|
||||
|
|
|
@ -146,7 +146,7 @@ protected:
|
|||
};
|
||||
|
||||
Cursor m_cursor;
|
||||
float m_old_cursor_radius;
|
||||
float m_old_cursor_radius_sqr;
|
||||
|
||||
// Private functions:
|
||||
bool select_triangle(int facet_idx, EnforcerBlockerType type,
|
||||
|
|
|
@ -90,6 +90,7 @@ extern CopyFileResult check_copy(const std::string& origin, const std::string& c
|
|||
extern bool is_plain_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_ini_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_idx_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_gcode_file(const std::string &path);
|
||||
|
||||
// File path / name / extension splitting utilities, working with UTF-8,
|
||||
// to be published to Perl.
|
||||
|
@ -353,8 +354,12 @@ inline std::string get_time_dhm(float time_in_secs)
|
|||
|
||||
#if WIN32
|
||||
#define SLIC3R_STDVEC_MEMSIZE(NAME, TYPE) NAME.capacity() * ((sizeof(TYPE) + __alignof(TYPE) - 1) / __alignof(TYPE)) * __alignof(TYPE)
|
||||
//FIXME this is an inprecise hack. Add the hash table size and possibly some estimate of the linked list at each of the used bin.
|
||||
#define SLIC3R_STDUNORDEREDSET_MEMSIZE(NAME, TYPE) NAME.size() * ((sizeof(TYPE) + __alignof(TYPE) - 1) / __alignof(TYPE)) * __alignof(TYPE)
|
||||
#else
|
||||
#define SLIC3R_STDVEC_MEMSIZE(NAME, TYPE) NAME.capacity() * ((sizeof(TYPE) + alignof(TYPE) - 1) / alignof(TYPE)) * alignof(TYPE)
|
||||
//FIXME this is an inprecise hack. Add the hash table size and possibly some estimate of the linked list at each of the used bin.
|
||||
#define SLIC3R_STDUNORDEREDSET_MEMSIZE(NAME, TYPE) NAME.size() * ((sizeof(TYPE) + alignof(TYPE) - 1) / alignof(TYPE)) * alignof(TYPE)
|
||||
#endif
|
||||
|
||||
#endif // slic3r_Utils_hpp_
|
||||
|
|
|
@ -522,6 +522,12 @@ bool is_idx_file(const boost::filesystem::directory_entry &dir_entry)
|
|||
return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".idx") == 0;
|
||||
}
|
||||
|
||||
bool is_gcode_file(const std::string &path)
|
||||
{
|
||||
return boost::iends_with(path, ".gcode") || boost::iends_with(path, ".gco") ||
|
||||
boost::iends_with(path, ".g") || boost::iends_with(path, ".ngc");
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#ifdef WIN32
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Name=Prusa GCode viewer
|
||||
Exec=prusa-slicer --gcodeviewer %F
|
||||
Icon=PrusaSlicer # TODO: change when the new icon is ready
|
||||
Icon=PrusaSlicer-gcodeviewer
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=text/x.gcode;
|
||||
Categories=Graphics;3DGraphics;
|
||||
Keywords=3D;Printing;Slicer;
|
||||
Keywords=3D;Printing;Slicer;
|
||||
|
|
|
@ -9,4 +9,4 @@ MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;
|
|||
Categories=Graphics;3DGraphics;Engineering;
|
||||
Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA
|
||||
StartupNotify=false
|
||||
StartupWMClass=prusa-slicer
|
||||
StartupWMClass=prusa-slicer
|
||||
|
|
|
@ -35,6 +35,11 @@
|
|||
#include "slic3r/Config/Snapshot.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
|
||||
#if defined(__linux__) && defined(__WXGTK3__)
|
||||
#define wxLinux_gtk3 true
|
||||
#else
|
||||
#define wxLinux_gtk3 false
|
||||
#endif //defined(__linux__) && defined(__WXGTK3__)
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
@ -209,7 +214,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
|
|||
load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width);
|
||||
}
|
||||
}
|
||||
auto *title = new wxStaticText(this, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
title->SetFont(font_name);
|
||||
const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width);
|
||||
title->Wrap(wrap_width);
|
||||
|
@ -409,7 +414,11 @@ ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxStrin
|
|||
|
||||
SetSizer(sizer);
|
||||
|
||||
this->Hide();
|
||||
// There is strange layout on Linux with GTK3,
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861
|
||||
// So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages
|
||||
if (!wxLinux_gtk3)
|
||||
this->Hide();
|
||||
|
||||
Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
|
||||
this->Layout();
|
||||
|
@ -2271,7 +2280,6 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
|
|||
wxString out;
|
||||
for (const VendorProfile::PrinterModel *printer_model : printer_models) {
|
||||
wxString name = from_u8(printer_model->name);
|
||||
name.Replace("&", "&&", true);
|
||||
out += "\t\t";
|
||||
out += name;
|
||||
out += "\n";
|
||||
|
@ -2643,6 +2651,20 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
|||
|
||||
Layout();
|
||||
});
|
||||
|
||||
if (wxLinux_gtk3)
|
||||
this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) {
|
||||
ConfigWizardPage* active_page = p->index->active_page();
|
||||
if (!active_page)
|
||||
return;
|
||||
for (auto page : p->all_pages)
|
||||
if (page != active_page)
|
||||
page->Hide();
|
||||
// update best size for the dialog after hiding of the non-active pages
|
||||
vsizer->SetSizeHints(this);
|
||||
// set initial dialog size
|
||||
p->init_dialog_size();
|
||||
});
|
||||
}
|
||||
|
||||
ConfigWizard::~ConfigWizard() {}
|
||||
|
|
|
@ -971,11 +971,15 @@ void Control::draw_ruler(wxDC& dc)
|
|||
double value = 0.0;
|
||||
int sequence = 0;
|
||||
|
||||
int prev_y_pos = -1;
|
||||
wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2;
|
||||
int values_size = (int)m_values.size();
|
||||
|
||||
while (tick <= m_max_value) {
|
||||
value += m_ruler.long_step;
|
||||
if (value > m_values.back() && sequence < m_ruler.count) {
|
||||
value = m_ruler.long_step;
|
||||
for (tick; tick < m_values.size(); tick++)
|
||||
for (; tick < values_size; tick++)
|
||||
if (m_values[tick] < value)
|
||||
break;
|
||||
// short ticks from the last tick to the end of current sequence
|
||||
|
@ -984,7 +988,7 @@ void Control::draw_ruler(wxDC& dc)
|
|||
}
|
||||
short_tick = tick;
|
||||
|
||||
for (tick; tick < m_values.size(); tick++) {
|
||||
for (; tick < values_size; tick++) {
|
||||
if (m_values[tick] == value)
|
||||
break;
|
||||
if (m_values[tick] > value) {
|
||||
|
@ -998,7 +1002,10 @@ void Control::draw_ruler(wxDC& dc)
|
|||
|
||||
wxCoord pos = get_position_from_value(tick);
|
||||
draw_ticks_pair(dc, pos, mid, 5);
|
||||
draw_tick_text(dc, wxPoint(mid, pos), tick);
|
||||
if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) {
|
||||
draw_tick_text(dc, wxPoint(mid, pos), tick);
|
||||
prev_y_pos = pos;
|
||||
}
|
||||
|
||||
draw_short_ticks(dc, short_tick, tick);
|
||||
|
||||
|
|
|
@ -57,9 +57,10 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/)
|
|||
wxString get_thumbnails_string(const std::vector<Vec2d>& values)
|
||||
{
|
||||
wxString ret_str;
|
||||
if (!values.empty())
|
||||
for (auto el : values)
|
||||
ret_str += wxString::Format("%ix%i, ", int(el[0]), int(el[1]));
|
||||
for (size_t i = 0; i < values.size(); ++ i) {
|
||||
const Vec2d& el = values[i];
|
||||
ret_str += wxString::Format((i == 0) ? "%ix%i" : ", %ix%i", int(el[0]), int(el[1]));
|
||||
}
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
|
@ -258,10 +259,28 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
|
|||
m_value.clear();
|
||||
break;
|
||||
}
|
||||
show_error(m_parent, _(L("Input value is out of range")));
|
||||
if (m_opt.min > val) val = m_opt.min;
|
||||
if (val > m_opt.max) val = m_opt.max;
|
||||
set_value(double_to_string(val), true);
|
||||
if (m_opt_id == "extrusion_multiplier") {
|
||||
if (m_value.empty() || boost::any_cast<double>(m_value) != val) {
|
||||
wxString msg_text = format_wxstr(_L("Input value is out of range\n"
|
||||
"Are you sure that %s is a correct value and that you want to continue?"), str);
|
||||
wxMessageDialog dialog(m_parent, msg_text, _L("Parameter validation") + ": " + m_opt_id, wxICON_WARNING | wxYES | wxNO);
|
||||
if (dialog.ShowModal() == wxID_NO) {
|
||||
if (m_value.empty()) {
|
||||
if (m_opt.min > val) val = m_opt.min;
|
||||
if (val > m_opt.max) val = m_opt.max;
|
||||
}
|
||||
else
|
||||
val = boost::any_cast<double>(m_value);
|
||||
set_value(double_to_string(val), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
show_error(m_parent, _L("Input value is out of range"));
|
||||
if (m_opt.min > val) val = m_opt.min;
|
||||
if (val > m_opt.max) val = m_opt.max;
|
||||
set_value(double_to_string(val), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_value = val;
|
||||
|
@ -359,7 +378,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
|
|||
if (!m_value.empty())
|
||||
text_value = get_thumbnails_string(boost::any_cast<std::vector<Vec2d>>(m_value));
|
||||
set_value(text_value, true);
|
||||
show_error(m_parent, format_wxstr(_L("Invalid input format. It must be represented like \"%1%\""),"XxY, XxY, ..." ));
|
||||
show_error(m_parent, format_wxstr(_L("Invalid input format. Expected vector of dimensions in the following format: \"%1%\""),"XxY, XxY, ..." ));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -898,6 +917,12 @@ void Choice::BUILD() {
|
|||
#endif //__WXOSX__
|
||||
}
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
wxSize best_sz = temp->GetBestSize();
|
||||
if (best_sz.x > size.x)
|
||||
temp->SetSize(best_sz);
|
||||
#endif //__WXGTK3__
|
||||
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
|
|
|
@ -147,9 +147,9 @@ void GCodeViewer::TBuffer::reset()
|
|||
}
|
||||
|
||||
// release cpu memory
|
||||
indices = std::vector<IBuffer>();
|
||||
paths = std::vector<Path>();
|
||||
render_paths = std::vector<RenderPath>();
|
||||
indices.clear();
|
||||
paths.clear();
|
||||
render_paths.clear();
|
||||
}
|
||||
|
||||
void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id)
|
||||
|
@ -370,6 +370,13 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print&
|
|||
}
|
||||
|
||||
m_time_statistics = gcode_result.time_statistics;
|
||||
|
||||
if (m_time_estimate_mode != PrintEstimatedTimeStatistics::ETimeMode::Normal) {
|
||||
float time = m_time_statistics.modes[static_cast<size_t>(m_time_estimate_mode)].time;
|
||||
if (time == 0.0f ||
|
||||
short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_time_statistics.modes[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time)))
|
||||
m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal;
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors)
|
||||
|
@ -445,6 +452,12 @@ void GCodeViewer::refresh_render_paths()
|
|||
}
|
||||
#endif // ENABLE_RENDER_PATH_REFRESH_AFTER_OPTIONS_CHANGE
|
||||
|
||||
void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config)
|
||||
{
|
||||
if (config != nullptr)
|
||||
m_shells.volumes.update_colors_by_extruder(config);
|
||||
}
|
||||
|
||||
void GCodeViewer::reset()
|
||||
{
|
||||
m_initialized = false;
|
||||
|
@ -467,7 +480,6 @@ void GCodeViewer::reset()
|
|||
m_layers_z_range = { 0, 0 };
|
||||
m_roles = std::vector<ExtrusionRole>();
|
||||
m_time_statistics.reset();
|
||||
m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
m_statistics.reset_all();
|
||||
|
@ -769,9 +781,9 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
|
|||
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) {
|
||||
size_t i = 0;
|
||||
for (const RenderPath& render_path : buffer.render_paths) {
|
||||
// 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;
|
||||
|
@ -936,6 +948,8 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
|
|||
for (const Triangle& t : out_triangles) {
|
||||
fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", t[0], t[0], t[1], t[1], t[2], t[2]);
|
||||
}
|
||||
|
||||
++ i;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
@ -1888,6 +1902,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
|||
}
|
||||
|
||||
// second pass: filter paths by sequential data and collect them by color
|
||||
RenderPath *render_path = nullptr;
|
||||
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)
|
||||
|
@ -1918,16 +1933,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
|||
default: { color = { 0.0f, 0.0f, 0.0f }; break; }
|
||||
}
|
||||
|
||||
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 = path_id;
|
||||
it->index_buffer_id = index_buffer_id;
|
||||
}
|
||||
|
||||
RenderPath key{ color, static_cast<unsigned int>(index_buffer_id), path_id };
|
||||
if (render_path == nullptr || ! RenderPathPropertyEqual()(*render_path, key))
|
||||
render_path = const_cast<RenderPath*>(&(*buffer->render_paths.emplace(key).first));
|
||||
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)
|
||||
|
@ -1936,7 +1944,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
|||
case TBuffer::ERenderPrimitiveType::Line:
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; }
|
||||
}
|
||||
it->sizes.push_back(size_in_indices);
|
||||
render_path->sizes.push_back(size_in_indices);
|
||||
|
||||
unsigned int delta_1st = 0;
|
||||
if (path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= path.last.s_id)
|
||||
|
@ -1945,7 +1953,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
|||
if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle)
|
||||
delta_1st *= buffer->indices_per_segment();
|
||||
|
||||
it->offsets.push_back(static_cast<size_t>((path.first.i_id + delta_1st) * sizeof(unsigned int)));
|
||||
render_path->offsets.push_back(static_cast<size_t>((path.first.i_id + delta_1st) * sizeof(unsigned int)));
|
||||
}
|
||||
|
||||
// set sequential data to their final value
|
||||
|
@ -2931,7 +2939,7 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional)
|
|||
int64_t 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);
|
||||
render_paths_size += SLIC3R_STDUNORDEREDSET_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);
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <float.h>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -146,11 +148,35 @@ class GCodeViewer
|
|||
// Used to batch the indices needed to render paths
|
||||
struct RenderPath
|
||||
{
|
||||
Color color;
|
||||
unsigned int path_id;
|
||||
unsigned int index_buffer_id;
|
||||
std::vector<unsigned int> sizes;
|
||||
std::vector<size_t> offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements())
|
||||
// Render path property
|
||||
Color color;
|
||||
unsigned int index_buffer_id;
|
||||
// Render path content
|
||||
unsigned int path_id;
|
||||
std::vector<unsigned int> sizes;
|
||||
std::vector<size_t> offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements())
|
||||
};
|
||||
struct RenderPathPropertyHash {
|
||||
size_t operator() (const RenderPath &p) const {
|
||||
// Conver the RGB value to an integer hash.
|
||||
// return (size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) * 7919) ^ size_t(p.index_buffer_id);
|
||||
return size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) ^ size_t(p.index_buffer_id);
|
||||
}
|
||||
};
|
||||
struct RenderPathPropertyLower {
|
||||
bool operator() (const RenderPath &l, const RenderPath &r) const {
|
||||
for (int i = 0; i < 3; ++ i)
|
||||
if (l.color[i] < r.color[i])
|
||||
return true;
|
||||
else if (l.color[i] > r.color[i])
|
||||
return false;
|
||||
return l.index_buffer_id < r.index_buffer_id;
|
||||
}
|
||||
};
|
||||
struct RenderPathPropertyEqual {
|
||||
bool operator() (const RenderPath &l, const RenderPath &r) const {
|
||||
return l.color == r.color && l.index_buffer_id == r.index_buffer_id;
|
||||
}
|
||||
};
|
||||
|
||||
// buffer containing data for rendering a specific toolpath type
|
||||
|
@ -169,7 +195,9 @@ class GCodeViewer
|
|||
|
||||
std::string shader;
|
||||
std::vector<Path> paths;
|
||||
std::vector<RenderPath> render_paths;
|
||||
// std::set seems to perform singificantly better, at least on Windows.
|
||||
// std::unordered_set<RenderPath, RenderPathPropertyHash, RenderPathPropertyEqual> render_paths;
|
||||
std::set<RenderPath, RenderPathPropertyLower> render_paths;
|
||||
bool visible{ false };
|
||||
|
||||
void reset();
|
||||
|
@ -472,6 +500,7 @@ public:
|
|||
#if ENABLE_RENDER_PATH_REFRESH_AFTER_OPTIONS_CHANGE
|
||||
void refresh_render_paths();
|
||||
#endif // ENABLE_RENDER_PATH_REFRESH_AFTER_OPTIONS_CHANGE
|
||||
void update_shells_color_by_extruder(const DynamicPrintConfig* config);
|
||||
|
||||
void reset();
|
||||
void render() const;
|
||||
|
|
|
@ -86,6 +86,13 @@ static const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4;
|
|||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support.
|
||||
RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {}
|
||||
RetinaHelper::~RetinaHelper() {}
|
||||
float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); }
|
||||
#endif // __WXGTK3__
|
||||
|
||||
Size::Size()
|
||||
: m_width(0)
|
||||
, m_height(0)
|
||||
|
@ -1093,6 +1100,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
|
|||
wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/);
|
||||
|
||||
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
|
||||
|
||||
|
@ -1177,6 +1185,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas)
|
|||
{
|
||||
if (m_canvas != nullptr) {
|
||||
m_timer.SetOwner(m_canvas);
|
||||
m_render_timer.SetOwner(m_canvas);
|
||||
#if ENABLE_RETINA_GL
|
||||
m_retina_helper.reset(new RetinaHelper(canvas));
|
||||
#endif // ENABLE_RETINA_GL
|
||||
|
@ -1322,7 +1331,7 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
|
|||
{
|
||||
for (GLVolume* vol : m_volumes.volumes) {
|
||||
if (vol->composite_id.object_id == 1000) { // wipe tower
|
||||
vol->is_active = (visible && mo == nullptr);
|
||||
vol->is_active = (visible && mo == nullptr);
|
||||
}
|
||||
else {
|
||||
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
||||
|
@ -1602,6 +1611,9 @@ void GLCanvas3D::render()
|
|||
wxGetApp().plater()->init_environment_texture();
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
|
||||
m_render_timer.Stop();
|
||||
m_extra_frame_requested_delayed = std::numeric_limits<int>::max();
|
||||
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
// Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
|
||||
// to preview, this was called before canvas had its final size. It reported zero width
|
||||
|
@ -2276,8 +2288,11 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume&
|
|||
void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result)
|
||||
{
|
||||
m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized);
|
||||
if (wxGetApp().is_editor())
|
||||
|
||||
if (wxGetApp().is_editor()) {
|
||||
m_gcode_viewer.update_shells_color_by_extruder(m_config);
|
||||
_show_warning_texture_if_needed(WarningTexture::ToolpathOutside);
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors)
|
||||
|
@ -2339,6 +2354,7 @@ void GLCanvas3D::bind_event_handlers()
|
|||
m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this);
|
||||
m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
|
||||
m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
|
||||
m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
|
||||
m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
|
||||
|
@ -2368,6 +2384,7 @@ void GLCanvas3D::unbind_event_handlers()
|
|||
m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this);
|
||||
m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
|
||||
m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
|
||||
m_canvas->Unbind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
|
||||
m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
|
||||
m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
|
||||
|
@ -2396,13 +2413,12 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
|||
{
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
NotificationManager* notification_mgr = wxGetApp().plater()->get_notification_manager();
|
||||
/*NotificationManager* notification_mgr = wxGetApp().plater()->get_notification_manager();
|
||||
if (notification_mgr->requires_update())
|
||||
notification_mgr->update_notifications();
|
||||
|
||||
m_dirty |= notification_mgr->requires_render();
|
||||
m_dirty |= notification_mgr->requires_render();*/
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
// FIXME
|
||||
m_dirty |= m_main_toolbar.update_items_state();
|
||||
|
@ -2413,9 +2429,10 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
|||
m_dirty |= mouse3d_controller_applied;
|
||||
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
/*
|
||||
if (notification_mgr->requires_update()) {
|
||||
evt.RequestMore();
|
||||
}
|
||||
}*/
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
if (!m_dirty)
|
||||
|
@ -2454,21 +2471,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
if (m_gizmos.on_char(evt))
|
||||
return;
|
||||
|
||||
auto action_plus = [this](wxKeyEvent& evt) {
|
||||
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
||||
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
|
||||
else
|
||||
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1));
|
||||
};
|
||||
|
||||
auto action_a = [this]() {
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE));
|
||||
};
|
||||
|
||||
auto action_question_mark = [this]() {
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK));
|
||||
};
|
||||
|
||||
if ((evt.GetModifiers() & ctrlMask) != 0) {
|
||||
// CTRL is pressed
|
||||
switch (keyCode) {
|
||||
|
@ -2478,7 +2480,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
#else /* __APPLE__ */
|
||||
case WXK_CONTROL_A:
|
||||
#endif /* __APPLE__ */
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
|
||||
break;
|
||||
#ifdef __APPLE__
|
||||
case 'c':
|
||||
|
@ -2562,35 +2564,13 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break;
|
||||
default: evt.Skip();
|
||||
}
|
||||
}
|
||||
else if ((evt.GetModifiers() & shiftMask) != 0) {
|
||||
// SHIFT is pressed
|
||||
switch (keyCode) {
|
||||
case '+': { action_plus(evt); break; }
|
||||
case 'A':
|
||||
case 'a': { action_a(); break; }
|
||||
case 'G':
|
||||
case 'g': {
|
||||
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
||||
post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt));
|
||||
break;
|
||||
}
|
||||
case '?': { action_question_mark(); break; }
|
||||
default:
|
||||
evt.Skip();
|
||||
}
|
||||
} else if (evt.HasModifiers()) {
|
||||
evt.Skip();
|
||||
} else {
|
||||
switch (keyCode)
|
||||
{
|
||||
case WXK_BACK:
|
||||
case WXK_DELETE:
|
||||
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
|
||||
break;
|
||||
case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
|
||||
case WXK_ESCAPE: { deselect_all(); break; }
|
||||
case WXK_F5:
|
||||
{
|
||||
case WXK_F5: {
|
||||
if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) ||
|
||||
(wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty()))
|
||||
post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK));
|
||||
|
@ -2603,33 +2583,48 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
case '4': { select_view("rear"); break; }
|
||||
case '5': { select_view("left"); break; }
|
||||
case '6': { select_view("right"); break; }
|
||||
case '+': { action_plus(evt); break; }
|
||||
case '+': {
|
||||
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
||||
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
|
||||
else
|
||||
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1));
|
||||
break;
|
||||
}
|
||||
case '-': {
|
||||
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
||||
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
|
||||
else
|
||||
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1));
|
||||
break; }
|
||||
case '?': { action_question_mark(); break; }
|
||||
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
||||
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
|
||||
else
|
||||
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1));
|
||||
break;
|
||||
}
|
||||
case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
|
||||
case 'A':
|
||||
case 'a': { action_a(); break; }
|
||||
case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; }
|
||||
case 'B':
|
||||
case 'b': { zoom_to_bed(); break; }
|
||||
case 'E':
|
||||
case 'e': { m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
|
||||
case 'G':
|
||||
case 'g': {
|
||||
if ((evt.GetModifiers() & shiftMask) != 0) {
|
||||
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
||||
post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'I':
|
||||
case 'i': { _update_camera_zoom(1.0); break; }
|
||||
case 'K':
|
||||
case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; }
|
||||
case 'L':
|
||||
case 'l': {
|
||||
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();
|
||||
}
|
||||
break;
|
||||
}
|
||||
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();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'O':
|
||||
case 'o': { _update_camera_zoom(-1.0); break; }
|
||||
#if ENABLE_RENDER_PICKING_PASS
|
||||
|
@ -2641,8 +2636,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
}
|
||||
#endif // ENABLE_RENDER_PICKING_PASS
|
||||
case 'Z':
|
||||
case 'z':
|
||||
{
|
||||
case 'z': {
|
||||
if (!m_selection.is_empty())
|
||||
zoom_to_selection();
|
||||
else {
|
||||
|
@ -2651,7 +2645,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
else
|
||||
_zoom_to_box(m_gcode_viewer.get_paths_bounding_box());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: { evt.Skip(); break; }
|
||||
|
@ -2990,6 +2983,39 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt)
|
|||
_perform_layer_editing_action();
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
|
||||
{
|
||||
// If slicer is not top window -> restart timer with one second to try again
|
||||
wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater());
|
||||
while (p->GetParent() != nullptr)
|
||||
p = p->GetParent();
|
||||
wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
|
||||
if (!top_level_wnd->IsActive()) {
|
||||
request_extra_frame_delayed(1000);
|
||||
return;
|
||||
}
|
||||
//render();
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::request_extra_frame_delayed(int miliseconds)
|
||||
{
|
||||
int64_t now = timestamp_now();
|
||||
if (! m_render_timer.IsRunning()) {
|
||||
m_extra_frame_requested_delayed = miliseconds;
|
||||
m_render_timer.StartOnce(miliseconds);
|
||||
m_render_timer_start = now;
|
||||
} else {
|
||||
const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now;
|
||||
if (miliseconds < remaining_time) {
|
||||
m_render_timer.Stop();
|
||||
m_extra_frame_requested_delayed = miliseconds;
|
||||
m_render_timer.StartOnce(miliseconds);
|
||||
m_render_timer_start = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define SLIC3R_DEBUG_MOUSE_EVENTS
|
||||
#endif
|
||||
|
@ -3116,6 +3142,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
}
|
||||
|
||||
if (m_gizmos.on_mouse(evt)) {
|
||||
if (wxWindow::FindFocus() != this->m_canvas)
|
||||
// Grab keyboard focus for input in gizmo dialogs.
|
||||
m_canvas->SetFocus();
|
||||
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
|
@ -3752,7 +3782,8 @@ void GLCanvas3D::update_ui_from_settings()
|
|||
{
|
||||
m_dirty = true;
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
#if __APPLE__
|
||||
// Update OpenGL scaling on OSX after the user toggled the "use_retina_opengl" settings in Preferences dialog.
|
||||
const float orig_scaling = m_retina_helper->get_scale_factor();
|
||||
|
||||
const bool use_retina = wxGetApp().app_config->get("use_retina_opengl") == "1";
|
||||
|
@ -3999,7 +4030,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x)
|
|||
dist_key += postfix;
|
||||
rot_key += postfix;
|
||||
|
||||
imgui->text(GUI::format_wxstr(_L("Use %1%left mouse key to enter text edit mode:"), shortkey_ctrl_prefix()));
|
||||
imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix()));
|
||||
|
||||
if (imgui->slider_float(_L("Spacing"), &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance) {
|
||||
settings.distance = std::max(dist_min, settings.distance);
|
||||
|
@ -4120,9 +4151,13 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
|
|||
shader->start_using();
|
||||
shader->set_uniform("print_box.volume_detection", 0);
|
||||
|
||||
for (const GLVolume* vol : visible_volumes) {
|
||||
for (GLVolume* vol : visible_volumes) {
|
||||
shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray);
|
||||
// the volume may have been deactivated by an active gizmo
|
||||
bool is_active = vol->is_active;
|
||||
vol->is_active = true;
|
||||
vol->render();
|
||||
vol->is_active = is_active;
|
||||
}
|
||||
|
||||
shader->stop_using();
|
||||
|
@ -5301,14 +5336,16 @@ void GLCanvas3D::_render_view_toolbar() const
|
|||
GLToolbar& view_toolbar = wxGetApp().plater()->get_view_toolbar();
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
// m_view_toolbar.set_scale(m_retina_helper->get_scale_factor());
|
||||
const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale();
|
||||
view_toolbar.set_scale(scale); //! #ys_FIXME_experiment
|
||||
#if __APPLE__
|
||||
view_toolbar.set_scale(scale);
|
||||
#else // if GTK3
|
||||
const float size = int(GLGizmosManager::Default_Icons_Size * scale);
|
||||
view_toolbar.set_icons_size(size);
|
||||
#endif // __APPLE__
|
||||
#else
|
||||
// m_view_toolbar.set_scale(m_canvas->GetContentScaleFactor());
|
||||
// m_view_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
|
||||
const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale());
|
||||
view_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
|
||||
view_toolbar.set_icons_size(size);
|
||||
#endif // ENABLE_RETINA_GL
|
||||
|
||||
Size cnv_size = get_canvas_size();
|
||||
|
@ -6451,5 +6488,10 @@ void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const
|
|||
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
|
||||
}
|
||||
|
||||
|
||||
void GLCanvas3D::RenderTimer::Notify()
|
||||
{
|
||||
wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this));
|
||||
}
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -31,8 +31,9 @@ class wxPaintEvent;
|
|||
class wxGLCanvas;
|
||||
class wxGLContext;
|
||||
|
||||
// Support for Retina OpenGL on Mac OS
|
||||
#define ENABLE_RETINA_GL __APPLE__
|
||||
// Support for Retina OpenGL on Mac OS.
|
||||
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support, enable it as well.
|
||||
#define ENABLE_RETINA_GL (__APPLE__ || __WXGTK3__)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -73,6 +74,25 @@ public:
|
|||
};
|
||||
|
||||
|
||||
class RenderTimerEvent : public wxEvent
|
||||
{
|
||||
public:
|
||||
RenderTimerEvent(wxEventType type, wxTimer& timer)
|
||||
: wxEvent(timer.GetId(), type),
|
||||
m_timer(&timer)
|
||||
{
|
||||
SetEventObject(timer.GetOwner());
|
||||
}
|
||||
int GetInterval() const { return m_timer->GetInterval(); }
|
||||
wxTimer& GetTimer() const { return *m_timer; }
|
||||
|
||||
virtual wxEvent* Clone() const { return new RenderTimerEvent(*this); }
|
||||
virtual wxEventCategory GetEventCategory() const { return wxEVT_CATEGORY_TIMER; }
|
||||
private:
|
||||
wxTimer* m_timer;
|
||||
};
|
||||
|
||||
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
|
||||
|
||||
using Vec2dEvent = Event<Vec2d>;
|
||||
|
@ -118,6 +138,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
|
|||
wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/);
|
||||
|
||||
class GLCanvas3D
|
||||
{
|
||||
|
@ -328,7 +349,7 @@ class GLCanvas3D
|
|||
|
||||
public:
|
||||
void add_frame(int64_t frame) {
|
||||
int64_t now = wxGetLocalTimeMillis().GetValue();
|
||||
int64_t now = GLCanvas3D::timestamp_now();
|
||||
if (!m_frames.empty() && now - m_frames.front().first > 1000) {
|
||||
m_curr_total -= m_frames.front().second;
|
||||
m_frames.pop();
|
||||
|
@ -390,6 +411,11 @@ class GLCanvas3D
|
|||
static float get_window_width() { return s_window_width; };
|
||||
};
|
||||
|
||||
class RenderTimer : public wxTimer {
|
||||
private:
|
||||
virtual void Notify() override;
|
||||
};
|
||||
|
||||
public:
|
||||
enum ECursorType : unsigned char
|
||||
{
|
||||
|
@ -427,12 +453,16 @@ private:
|
|||
std::string m_sidebar_field;
|
||||
// when true renders an extra frame by not resetting m_dirty to false
|
||||
// see request_extra_frame()
|
||||
bool m_extra_frame_requested;
|
||||
bool m_extra_frame_requested;
|
||||
int m_extra_frame_requested_delayed { std::numeric_limits<int>::max() };
|
||||
bool m_event_handlers_bound{ false };
|
||||
|
||||
mutable GLVolumeCollection m_volumes;
|
||||
GCodeViewer m_gcode_viewer;
|
||||
|
||||
RenderTimer m_render_timer;
|
||||
int64_t m_render_timer_start;
|
||||
|
||||
Selection m_selection;
|
||||
const DynamicPrintConfig* m_config;
|
||||
Model* m_model;
|
||||
|
@ -649,6 +679,7 @@ public:
|
|||
void on_key(wxKeyEvent& evt);
|
||||
void on_mouse_wheel(wxMouseEvent& evt);
|
||||
void on_timer(wxTimerEvent& evt);
|
||||
void on_render_timer(wxTimerEvent& evt);
|
||||
void on_mouse(wxMouseEvent& evt);
|
||||
void on_paint(wxPaintEvent& evt);
|
||||
void on_set_focus(wxFocusEvent& evt);
|
||||
|
@ -711,6 +742,7 @@ public:
|
|||
void msw_rescale();
|
||||
|
||||
void request_extra_frame() { m_extra_frame_requested = true; }
|
||||
void request_extra_frame_delayed(int miliseconds);
|
||||
|
||||
int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); }
|
||||
void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); }
|
||||
|
@ -741,6 +773,17 @@ public:
|
|||
return ret;
|
||||
}
|
||||
|
||||
// Timestamp for FPS calculation and notification fade-outs.
|
||||
static int64_t timestamp_now() {
|
||||
#ifdef _WIN32
|
||||
// Cheaper on Windows, calls GetSystemTimeAsFileTime()
|
||||
return wxGetUTCTimeMillis().GetValue();
|
||||
#else
|
||||
// calls clock()
|
||||
return wxGetLocalTimeMillis().GetValue();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
bool _is_shown_on_screen() const;
|
||||
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
#include "GUI_Init.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GUI_ObjectManipulation.hpp"
|
||||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <exception>
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
@ -129,7 +131,7 @@ public:
|
|||
|
||||
memDC.SetFont(m_action_font);
|
||||
memDC.SetTextForeground(wxColour(237, 107, 33));
|
||||
memDC.DrawText(text, int(m_scale * 60), int(m_scale * 275));
|
||||
memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position);
|
||||
|
||||
memDC.SelectObject(wxNullBitmap);
|
||||
set_bitmap(bitmap);
|
||||
|
@ -204,14 +206,22 @@ public:
|
|||
|
||||
memDc.SetFont(m_constant_text.version_font);
|
||||
memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT);
|
||||
int version_height = memDc.GetTextExtent(m_constant_text.version).GetY();
|
||||
|
||||
memDc.SetFont(m_constant_text.credits_font);
|
||||
memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT);
|
||||
int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY();
|
||||
int text_height = memDc.GetTextExtent("text").GetY();
|
||||
|
||||
// calculate position for the dynamic text
|
||||
int logo_and_header_height = margin + logo_size + title_height + version_height;
|
||||
m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height);
|
||||
}
|
||||
|
||||
private:
|
||||
wxBitmap m_main_bitmap;
|
||||
wxFont m_action_font;
|
||||
int m_action_line_y_position;
|
||||
float m_scale {1.0};
|
||||
|
||||
struct ConstantText
|
||||
|
@ -256,7 +266,8 @@ private:
|
|||
float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX();
|
||||
scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale);
|
||||
|
||||
scale_font(m_constant_text.version_font, 2.f);
|
||||
float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX();
|
||||
scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale);
|
||||
|
||||
// The width of the credits information string doesn't respect to the banner width some times.
|
||||
// So, scale credits_font in the respect to the longest string width
|
||||
|
@ -667,14 +678,20 @@ wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas)
|
|||
|
||||
bool GUI_App::init_opengl()
|
||||
{
|
||||
#ifdef __linux__
|
||||
bool status = m_opengl_mgr.init_gl();
|
||||
m_opengl_initialized = true;
|
||||
return status;
|
||||
#else
|
||||
return m_opengl_mgr.init_gl();
|
||||
#endif
|
||||
}
|
||||
|
||||
void GUI_App::init_app_config()
|
||||
{
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
// SetAppName(SLIC3R_APP_KEY);
|
||||
SetAppName(SLIC3R_APP_KEY "-beta");
|
||||
SetAppName(SLIC3R_APP_KEY);
|
||||
// SetAppName(SLIC3R_APP_KEY "-beta");
|
||||
// SetAppDisplayName(SLIC3R_APP_NAME);
|
||||
|
||||
// Set the Slic3r data directory at the Slic3r XS module.
|
||||
|
@ -745,7 +762,7 @@ bool GUI_App::on_init_inner()
|
|||
|
||||
#ifdef __linux__
|
||||
if (! check_old_linux_datadir(GetAppName())) {
|
||||
std::cerr << "Quitting, user chose to move his data to new location." << std::endl;
|
||||
std::cerr << "Quitting, user chose to move their data to new location." << std::endl;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
@ -884,55 +901,6 @@ bool GUI_App::on_init_inner()
|
|||
|
||||
m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg()));
|
||||
|
||||
|
||||
Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
|
||||
{
|
||||
if (! plater_)
|
||||
return;
|
||||
|
||||
|
||||
if (app_config->dirty() && app_config->get("autosave") == "1")
|
||||
app_config->save();
|
||||
|
||||
this->obj_manipul()->update_if_dirty();
|
||||
|
||||
static bool update_gui_after_init = true;
|
||||
if (update_gui_after_init) {
|
||||
update_gui_after_init = false;
|
||||
#ifdef WIN32
|
||||
this->mainframe->register_win32_callbacks();
|
||||
#endif
|
||||
this->post_init();
|
||||
}
|
||||
|
||||
// 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
|
||||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
// This is ugly but I honestly found no better way to do it.
|
||||
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
|
||||
|
||||
static bool once = true;
|
||||
if (once) {
|
||||
once = false;
|
||||
|
||||
if (preset_updater != nullptr) {
|
||||
check_updates(false);
|
||||
|
||||
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);
|
||||
#endif //WIN32
|
||||
}
|
||||
});
|
||||
|
||||
if (is_gcode_viewer()) {
|
||||
mainframe->update_layout();
|
||||
if (plater_ != nullptr)
|
||||
|
@ -951,6 +919,61 @@ bool GUI_App::on_init_inner()
|
|||
other_instance_message_handler()->bring_instance_forward();
|
||||
#endif //__APPLE__
|
||||
|
||||
Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
|
||||
{
|
||||
if (! plater_)
|
||||
return;
|
||||
|
||||
|
||||
if (app_config->dirty() && app_config->get("autosave") == "1")
|
||||
app_config->save();
|
||||
|
||||
this->obj_manipul()->update_if_dirty();
|
||||
|
||||
static bool update_gui_after_init = true;
|
||||
|
||||
// An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT
|
||||
// and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized.
|
||||
#ifdef __linux__
|
||||
if (update_gui_after_init && m_opengl_initialized) {
|
||||
#else
|
||||
if (update_gui_after_init) {
|
||||
#endif
|
||||
update_gui_after_init = false;
|
||||
#ifdef WIN32
|
||||
this->mainframe->register_win32_callbacks();
|
||||
#endif
|
||||
this->post_init();
|
||||
}
|
||||
|
||||
// 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
|
||||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
// This is ugly but I honestly found no better way to do it.
|
||||
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
|
||||
|
||||
static bool once = true;
|
||||
if (once) {
|
||||
once = false;
|
||||
|
||||
if (preset_updater != nullptr) {
|
||||
check_updates(false);
|
||||
|
||||
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);
|
||||
#endif //WIN32
|
||||
}
|
||||
});
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -1271,22 +1294,85 @@ bool GUI_App::switch_language()
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language,
|
||||
const wxLanguageInfo* system_language)
|
||||
{
|
||||
constexpr size_t max_len = 50;
|
||||
char path[max_len] = "";
|
||||
std::vector<std::string> locales;
|
||||
const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_'));
|
||||
|
||||
// Call locale -a so we can parse the output to get the list of available locales
|
||||
// We expect lines such as "en_US.utf8". Pick ones starting with the language code
|
||||
// we are switching to. Lines with different formatting will be removed later.
|
||||
FILE* fp = popen("locale -a", "r");
|
||||
if (fp != NULL) {
|
||||
while (fgets(path, max_len, fp) != NULL) {
|
||||
std::string line(path);
|
||||
line = line.substr(0, line.find('\n'));
|
||||
if (boost::starts_with(line, lang_prefix))
|
||||
locales.push_back(line);
|
||||
}
|
||||
pclose(fp);
|
||||
}
|
||||
|
||||
// locales now contain all candidates for this language.
|
||||
// Sort them so ones containing anything about UTF-8 are at the end.
|
||||
std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b)
|
||||
{
|
||||
auto has_utf8 = [](const std::string & s) {
|
||||
auto S = boost::to_upper_copy(s);
|
||||
return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos;
|
||||
};
|
||||
return ! has_utf8(a) && has_utf8(b);
|
||||
});
|
||||
|
||||
// Remove the suffix behind a dot, if there is one.
|
||||
for (std::string& s : locales)
|
||||
s = s.substr(0, s.find("."));
|
||||
|
||||
// We just hope that dear Linux "locale -a" returns country codes
|
||||
// in ISO 3166-1 alpha-2 code (two letter) format.
|
||||
// https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes
|
||||
// To be sure, remove anything not looking as expected
|
||||
// (any number of lowercase letters, underscore, two uppercase letters).
|
||||
locales.erase(std::remove_if(locales.begin(),
|
||||
locales.end(),
|
||||
[](const std::string& s) {
|
||||
return ! std::regex_match(s,
|
||||
std::regex("^[a-z]+_[A-Z]{2}$"));
|
||||
}),
|
||||
locales.end());
|
||||
|
||||
// Is there a candidate matching a country code of a system language? Move it to the end,
|
||||
// while maintaining the order of matches, so that the best match ends up at the very end.
|
||||
std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2);
|
||||
int cnt = locales.size();
|
||||
for (int i=0; i<cnt; ++i)
|
||||
if (locales[i].find(system_country) != std::string::npos) {
|
||||
locales.emplace_back(std::move(locales[i]));
|
||||
locales[i].clear();
|
||||
}
|
||||
|
||||
// Now try them one by one.
|
||||
for (auto it = locales.rbegin(); it != locales.rend(); ++ it)
|
||||
if (! it->empty()) {
|
||||
const std::string &locale = *it;
|
||||
const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale));
|
||||
if (wxLocale::IsAvailable(lang->Language))
|
||||
return lang;
|
||||
}
|
||||
return language;
|
||||
}
|
||||
#endif
|
||||
|
||||
// select language from the list of installed languages
|
||||
bool GUI_App::select_language()
|
||||
{
|
||||
wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY);
|
||||
std::vector<const wxLanguageInfo*> language_infos;
|
||||
language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH));
|
||||
#ifdef __linux__
|
||||
// wxWidgets consider the default English locale to be en_GB, which is often missing on Linux.
|
||||
// Thus we offer en_US on Linux as well.
|
||||
language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US));
|
||||
//FIXME https://github.com/prusa3d/PrusaSlicer/issues/2580#issuecomment-524546743
|
||||
// In a correctly set up system, "locale -a" will get all the installed locales on that system.
|
||||
// According to the installed locales, the locales for the dictionaries may be modified with the available
|
||||
// CanonicalName of the locale, possibly duplicating the entries for each CanonicalName of the dictionary.
|
||||
// Other languages with missing locales of the system can be greyed out or not shown at all.
|
||||
#endif // __linux__
|
||||
for (size_t i = 0; i < translations.GetCount(); ++ i) {
|
||||
const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]);
|
||||
if (langinfo != nullptr)
|
||||
|
@ -1315,13 +1401,6 @@ bool GUI_App::select_language()
|
|||
if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en")
|
||||
// This will be the default selection if the active language does not match any dictionary.
|
||||
init_selection_default = i;
|
||||
#ifdef __linux__
|
||||
// wxWidgets consider the default English locale to be en_GB, which is often missing on Linux.
|
||||
// Thus we make the distintion between "en_US" and "en_GB" clear.
|
||||
if (language_infos[i]->CanonicalName == "en_GB" && language_infos[i]->Description == "English")
|
||||
names.Add("English (U.K.)");
|
||||
else
|
||||
#endif // __linux__
|
||||
names.Add(language_infos[i]->Description);
|
||||
}
|
||||
if (init_selection == -1)
|
||||
|
@ -1388,6 +1467,14 @@ bool GUI_App::load_language(wxString language, bool initial)
|
|||
m_language_info_best = wxLocale::FindLanguageInfo(best_language);
|
||||
BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data();
|
||||
}
|
||||
#ifdef __linux__
|
||||
wxString lc_all;
|
||||
if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) {
|
||||
// Best language returned by wxWidgets on Linux apparently does not respect LC_ALL.
|
||||
// Disregard the "best" suggestion in case LC_ALL is provided.
|
||||
m_language_info_best = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1433,6 +1520,17 @@ bool GUI_App::load_language(wxString language, bool initial)
|
|||
} else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_'))
|
||||
language_info = m_language_info_system;
|
||||
|
||||
#ifdef __linux__
|
||||
// If we can't find this locale , try to use different one for the language
|
||||
// instead of just reporting that it is impossible to switch.
|
||||
if (! wxLocale::IsAvailable(language_info->Language)) {
|
||||
std::string original_lang = into_u8(language_info->CanonicalName);
|
||||
language_info = linux_get_existing_locale_language(language_info, m_language_info_system);
|
||||
BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.")
|
||||
% original_lang % language_info->CanonicalName.ToUTF8().data();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (! wxLocale::IsAvailable(language_info->Language)) {
|
||||
// Loading the language dictionary failed.
|
||||
wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed.";
|
||||
|
@ -1719,7 +1817,7 @@ bool GUI_App::check_print_host_queue()
|
|||
// Show dialog
|
||||
wxString job_string = wxString();
|
||||
for (const auto& job : jobs) {
|
||||
job_string += (boost::format(" %1% : %2% \n") % job.first % job.second).str();
|
||||
job_string += format_wxstr(" %1% : %2% \n", job.first, job.second);
|
||||
}
|
||||
wxString message;
|
||||
message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?"));
|
||||
|
@ -1777,11 +1875,9 @@ bool GUI_App::OnExceptionInMainLoop()
|
|||
void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames)
|
||||
{
|
||||
size_t num_gcodes = 0;
|
||||
for (const wxString &filename : fileNames) {
|
||||
wxString fn = filename.Upper();
|
||||
if (fn.EndsWith(".G") || fn.EndsWith(".GCODE"))
|
||||
for (const wxString &filename : fileNames)
|
||||
if (is_gcode_file(into_u8(filename)))
|
||||
++ num_gcodes;
|
||||
}
|
||||
if (fileNames.size() == num_gcodes) {
|
||||
// Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder,
|
||||
// just G-codes were passed. Switch to G-code viewer mode.
|
||||
|
@ -1801,8 +1897,7 @@ void GUI_App::MacOpenFiles(const wxArrayString &fileNames)
|
|||
std::vector<wxString> gcode_files;
|
||||
std::vector<wxString> non_gcode_files;
|
||||
for (const auto& filename : fileNames) {
|
||||
wxString fn = filename.Upper();
|
||||
if (fn.EndsWith(".G") || fn.EndsWith(".GCODE"))
|
||||
if (is_gcode_file(into_u8(filename)))
|
||||
gcode_files.emplace_back(filename);
|
||||
else {
|
||||
files.emplace_back(into_u8(filename));
|
||||
|
|
|
@ -109,6 +109,9 @@ private:
|
|||
bool m_app_conf_exists{ false };
|
||||
EAppMode m_app_mode{ EAppMode::Editor };
|
||||
bool m_is_recreating_gui{ false };
|
||||
#ifdef __linux__
|
||||
bool m_opengl_initialized{ false };
|
||||
#endif
|
||||
|
||||
wxColour m_color_label_modified;
|
||||
wxColour m_color_label_sys;
|
||||
|
@ -251,7 +254,7 @@ public:
|
|||
RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); }
|
||||
OtherInstanceMessageHandler* other_instance_message_handler() { return m_other_instance_message_handler.get(); }
|
||||
wxSingleInstanceChecker* single_instance_checker() {return m_single_instance_checker.get();}
|
||||
|
||||
|
||||
void init_single_instance_checker(const std::string &name, const std::string &path);
|
||||
void set_instance_hash (const size_t hash) { m_instance_hash_int = hash; m_instance_hash_string = std::to_string(hash); }
|
||||
std::string get_instance_hash_string () { return m_instance_hash_string; }
|
||||
|
|
|
@ -261,10 +261,10 @@ ObjectList::~ObjectList()
|
|||
void ObjectList::set_min_height()
|
||||
{
|
||||
/* Temporary workaround for the correct behavior of the Scrolled sidebar panel:
|
||||
* change min hight of object list to the normal min value (35 * wxGetApp().em_unit())
|
||||
* change min hight of object list to the normal min value (20 * wxGetApp().em_unit())
|
||||
* after first whole Mainframe updating/layouting
|
||||
*/
|
||||
const int list_min_height = 35 * wxGetApp().em_unit();
|
||||
const int list_min_height = 20 * wxGetApp().em_unit();
|
||||
if (this->GetMinSize().GetY() > list_min_height)
|
||||
this->SetMinSize(wxSize(-1, list_min_height));
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ 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 (15 * wxGetApp().em_unit()) after first whole Mainframe updating/layouting
|
||||
* 2. change it to the normal min value (20 * wxGetApp().em_unit()) after first whole Mainframe updating/layouting
|
||||
*/
|
||||
SetMinSize(wxSize(-1, 3000));
|
||||
|
||||
|
@ -1070,6 +1070,8 @@ bool ObjectList::copy_to_clipboard()
|
|||
{
|
||||
wxDataViewItemArray sels;
|
||||
GetSelections(sels);
|
||||
if (sels.IsEmpty())
|
||||
return false;
|
||||
ItemType type = m_objects_model->GetItemType(sels.front());
|
||||
if (!(type & (itSettings | itLayer | itLayerRoot))) {
|
||||
m_clipboard.reset();
|
||||
|
@ -2391,6 +2393,8 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item)
|
|||
m_config->set_key_value("extruder", new ConfigOptionInt(extruder));
|
||||
if (is_layer_settings)
|
||||
m_config->set_key_value("layer_height", new ConfigOptionFloat(layer_height));
|
||||
|
||||
changed_object();
|
||||
}
|
||||
|
||||
void ObjectList::del_instances_from_object(const int obj_idx)
|
||||
|
|
|
@ -420,7 +420,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
|
||||
m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND);
|
||||
|
||||
m_check_inch = new wxCheckBox(parent, wxID_ANY, "Inches");
|
||||
m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches"));
|
||||
m_check_inch->SetFont(wxGetApp().normal_font());
|
||||
|
||||
m_check_inch->SetValue(m_imperial_units);
|
||||
|
|
|
@ -84,9 +84,8 @@ void GLGizmoCut::on_start_dragging()
|
|||
|
||||
void GLGizmoCut::on_update(const UpdateData& data)
|
||||
{
|
||||
if (m_hover_id != -1) {
|
||||
if (m_hover_id != -1)
|
||||
set_cut_z(m_start_z + calc_projection(data.mouse_ray));
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoCut::on_render() const
|
||||
|
@ -149,7 +148,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
|||
static float last_y = 0.0f;
|
||||
static float last_h = 0.0f;
|
||||
|
||||
m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
|
||||
|
@ -157,8 +156,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
|||
float win_h = ImGui::GetWindowHeight();
|
||||
y = std::min(y, bottom_limit - win_h);
|
||||
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
|
||||
if ((last_h != win_h) || (last_y != y))
|
||||
{
|
||||
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)
|
||||
|
@ -180,26 +178,24 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
|||
ImGui::SameLine();
|
||||
m_imgui->text(imperial_units ? _L("in") : _L("mm"));
|
||||
|
||||
if (imperial_units)
|
||||
m_cut_z = cut_z * ObjectManipulation::in_to_mm;
|
||||
m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper);
|
||||
m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower);
|
||||
m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower);
|
||||
m_imgui->checkbox(_L("Keep upper part"), m_keep_upper);
|
||||
m_imgui->checkbox(_L("Keep lower part"), m_keep_lower);
|
||||
m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
m_imgui->disabled_begin(!m_keep_upper && !m_keep_lower);
|
||||
const bool cut_clicked = m_imgui->button(_(L("Perform cut")));
|
||||
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z < m_cut_z);
|
||||
const bool cut_clicked = m_imgui->button(_L("Perform cut"));
|
||||
m_imgui->disabled_end();
|
||||
|
||||
m_imgui->end();
|
||||
|
||||
if (cut_clicked && (m_keep_upper || m_keep_lower)) {
|
||||
if (cut_clicked && (m_keep_upper || m_keep_lower))
|
||||
perform_cut(m_parent.get_selection());
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoCut::update_max_z(const Selection& selection) const
|
||||
|
|
|
@ -963,6 +963,8 @@ void ImGuiWrapper::init_font(bool compress)
|
|||
// Fill rectangles from the SVG-icons
|
||||
for (auto icon : font_icons) {
|
||||
if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) {
|
||||
assert(rect->Width == icon_sz);
|
||||
assert(rect->Height == icon_sz);
|
||||
std::vector<unsigned char> raw_data = load_svg(icon.second, icon_sz, icon_sz);
|
||||
const ImU32* pIn = (ImU32*)raw_data.data();
|
||||
for (int y = 0; y < icon_sz; y++) {
|
||||
|
@ -973,10 +975,12 @@ void ImGuiWrapper::init_font(bool compress)
|
|||
}
|
||||
rect_id++;
|
||||
}
|
||||
icon_sz = lround(32 * font_scale); // default size of large icon is 32 px
|
||||
|
||||
|
||||
icon_sz *= 2; // default size of large icon is 32 px
|
||||
for (auto icon : font_icons_large) {
|
||||
if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) {
|
||||
assert(rect->Width == icon_sz);
|
||||
assert(rect->Height == icon_sz);
|
||||
std::vector<unsigned char> raw_data = load_svg(icon.second, icon_sz, icon_sz);
|
||||
const ImU32* pIn = (ImU32*)raw_data.data();
|
||||
for (int y = 0; y < icon_sz; y++) {
|
||||
|
|
|
@ -73,9 +73,14 @@ void FillBedJob::prepare()
|
|||
// This is the maximum number of items, the real number will always be close but less.
|
||||
int needed_items = (bed_area - fixed_area) / poly_area;
|
||||
|
||||
ModelInstance *mi = model_object->instances[0];
|
||||
int sel_id = m_plater->get_selection().get_instance_idx();
|
||||
// if the selection is not a single instance, choose the first as template
|
||||
sel_id = std::max(sel_id, 0);
|
||||
ModelInstance *mi = model_object->instances[sel_id];
|
||||
ArrangePolygon template_ap = get_arrange_poly(PtrWrapper{mi}, m_plater);
|
||||
|
||||
for (int i = 0; i < needed_items; ++i) {
|
||||
ArrangePolygon ap;
|
||||
ArrangePolygon ap = template_ap;
|
||||
ap.poly = m_selected.front().poly;
|
||||
ap.bed_idx = arrangement::UNARRANGED;
|
||||
ap.setter = [this, mi](const ArrangePolygon &p) {
|
||||
|
|
|
@ -234,7 +234,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
|||
{ ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
|
||||
};
|
||||
|
||||
m_full_shortcuts.push_back({ { _L("Vertical Slider"), _L("The following shortcuts are applicable when the vertical slider, in preview, is active") }, layers_slider_shortcuts });
|
||||
m_full_shortcuts.push_back({ { _L("Vertical Slider"), _L("The following shortcuts are applicable in G-code preview when the vertical slider is active") }, layers_slider_shortcuts });
|
||||
|
||||
Shortcuts sequential_slider_shortcuts = {
|
||||
#if ENABLE_ARROW_KEYS_WITH_SLIDERS
|
||||
|
@ -250,7 +250,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
|||
{ ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
|
||||
};
|
||||
|
||||
m_full_shortcuts.push_back({ { _L("Horizontal Slider"), _L("The following shortcuts are applicable when the horizontal slider, in preview, is active") }, sequential_slider_shortcuts });
|
||||
m_full_shortcuts.push_back({ { _L("Horizontal Slider"), _L("The following shortcuts are applicable in G-code preview when the horizontal slider is active") }, sequential_slider_shortcuts });
|
||||
}
|
||||
|
||||
wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_font)
|
||||
|
|
|
@ -59,10 +59,9 @@ public:
|
|||
PrusaSlicerTaskBarIcon(wxTaskBarIconType iconType = wxTBI_DEFAULT_TYPE) : wxTaskBarIcon(iconType) {}
|
||||
wxMenu *CreatePopupMenu() override {
|
||||
wxMenu *menu = new wxMenu;
|
||||
if(wxGetApp().app_config->get("single_instance") == "1") {
|
||||
if(wxGetApp().app_config->get("single_instance") == "0") {
|
||||
// Only allow opening a new PrusaSlicer instance on OSX if "single_instance" is disabled,
|
||||
// as starting new instances would interfere with the locking mechanism of "single_instance" support.
|
||||
//FIXME Vojtech thinks the condition is wrong.
|
||||
append_menu_item(menu, wxID_ANY, _L("Open new instance"), _L("Open a new PrusaSlicer instance"),
|
||||
[this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr);
|
||||
}
|
||||
|
@ -229,7 +228,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
|||
// OSX specific issue:
|
||||
// When we move application between Retina and non-Retina displays, The legend on a canvas doesn't redraw
|
||||
// So, redraw explicitly canvas, when application is moved
|
||||
#if ENABLE_RETINA_GL
|
||||
//FIXME maybe this is useful for __WXGTK3__ as well?
|
||||
#if __APPLE__
|
||||
Bind(wxEVT_MOVE, [this](wxMoveEvent& event) {
|
||||
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
|
||||
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
|
||||
|
|
|
@ -346,7 +346,6 @@ bool Mouse3DController::State::apply(const Mouse3DController::Params ¶ms, Ca
|
|||
if (params.swap_yz)
|
||||
rot = Vec3d(rot.x(), -rot.z(), rot.y());
|
||||
camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y()));
|
||||
break;
|
||||
} else {
|
||||
assert(input_queue_item.is_buttons());
|
||||
switch (input_queue_item.type_or_buttons) {
|
||||
|
@ -895,7 +894,10 @@ bool Mouse3DController::connect_device()
|
|||
if (device.second.size() == 1) {
|
||||
#if defined(__linux__)
|
||||
hid_device* test_device = hid_open(device.first.first, device.first.second, nullptr);
|
||||
if (test_device != nullptr) {
|
||||
if (test_device == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << "3DConnexion device cannot be opened: " << device.second.front().path <<
|
||||
" You may need to update /etc/udev/rules.d";
|
||||
} else {
|
||||
hid_close(test_device);
|
||||
#else
|
||||
if (device.second.front().has_valid_usage()) {
|
||||
|
@ -940,10 +942,13 @@ bool Mouse3DController::connect_device()
|
|||
break;
|
||||
}
|
||||
#endif // __linux__
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << "3DConnexion device cannot be opened: " << data.path <<
|
||||
" You may need to update /etc/udev/rules.d";
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
else
|
||||
std::cout << "-> NOT PASSED" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "hidapi.h"
|
||||
|
||||
#include <queue>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
|
|
@ -77,7 +77,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
|
|||
{
|
||||
html->SetMinSize(wxSize(40 * wxGetApp().em_unit(), monospaced_font ? 30 * wxGetApp().em_unit() : -1));
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
wxFont monospace = wxSystemSettings::GetFont(wxSYS_ANSI_FIXED_FONT);
|
||||
wxFont monospace = wxGetApp().code_font();
|
||||
wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
|
||||
|
|
|
@ -20,8 +20,11 @@ static constexpr float GAP_WIDTH = 10.0f;
|
|||
static constexpr float SPACE_RIGHT_PANEL = 10.0f;
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
static constexpr float FADING_OUT_DURATION = 2.0f;
|
||||
// Time in Miliseconds after next render is requested
|
||||
// Time in Miliseconds after next render when fading out is requested
|
||||
static constexpr int FADING_OUT_TIMEOUT = 100;
|
||||
// If timeout is changed to higher than 1 second, substract_time call should be revorked
|
||||
//static constexpr int MAX_TIMEOUT_MILISECONDS = 1000;
|
||||
//static constexpr int MAX_TIMEOUT_SECONDS = 1;
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -136,17 +139,25 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n,
|
|||
, m_hypertext (n.hypertext)
|
||||
, m_text2 (n.text2)
|
||||
, m_evt_handler (evt_handler)
|
||||
, m_notification_start (GLCanvas3D::timestamp_now())
|
||||
{
|
||||
//init();
|
||||
}
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
init();
|
||||
}
|
||||
|
||||
if (m_hidden) {
|
||||
m_top_y = initial_y - GAP_WIDTH;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_fading_out)
|
||||
m_last_render_fading = GLCanvas3D::timestamp_now();
|
||||
|
||||
Size cnv_size = canvas.get_canvas_size();
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
ImVec2 mouse_pos = ImGui::GetMousePos();
|
||||
|
@ -747,48 +758,65 @@ void NotificationManager::PopNotification::update_state()
|
|||
if (!m_initialized)
|
||||
init();
|
||||
|
||||
m_next_render = std::numeric_limits<int64_t>::max();
|
||||
|
||||
if (m_hidden) {
|
||||
m_state = EState::Hidden;
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t now = GLCanvas3D::timestamp_now();
|
||||
|
||||
if (m_hovered) {
|
||||
// reset fading
|
||||
m_fading_out = false;
|
||||
m_current_fade_opacity = 1.0f;
|
||||
m_remaining_time = m_data.duration;
|
||||
m_notification_start = now;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (m_counting_down) {
|
||||
int64_t up_time = now - m_notification_start;
|
||||
|
||||
if (m_fading_out && m_current_fade_opacity <= 0.0f)
|
||||
m_finished = true;
|
||||
else if (!m_fading_out && m_remaining_time == 0) {
|
||||
else if (!m_fading_out && /*m_remaining_time <=0*/up_time >= m_data.duration * 1000) {
|
||||
m_fading_out = true;
|
||||
m_fading_start = wxGetLocalTimeMillis();
|
||||
m_fading_start = now;
|
||||
m_last_render_fading = now;
|
||||
} else if (!m_fading_out) {
|
||||
m_next_render = m_data.duration * 1000 - up_time;//std::min<int64_t>(/*m_data.duration * 1000 - up_time*/m_remaining_time * 1000, MAX_TIMEOUT_MILISECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (m_finished) {
|
||||
m_state = EState::Finished;
|
||||
m_next_render = 0;
|
||||
return;
|
||||
}
|
||||
if (m_close_pending) {
|
||||
m_finished = true;
|
||||
m_state = EState::ClosePending;
|
||||
m_next_render = 0;
|
||||
return;
|
||||
}
|
||||
if (m_fading_out) {
|
||||
if (!m_paused) {
|
||||
m_state = EState::FadingOutStatic;
|
||||
wxMilliClock_t curr_time = wxGetLocalTimeMillis() - m_fading_start;
|
||||
wxMilliClock_t no_render_time = wxGetLocalTimeMillis() - m_last_render_fading;
|
||||
m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast<float>(curr_time.GetValue()) / FADING_OUT_DURATION, 0.0f, 1.0f);
|
||||
if (no_render_time > FADING_OUT_TIMEOUT) {
|
||||
m_last_render_fading = wxGetLocalTimeMillis();
|
||||
int64_t curr_time = now - m_fading_start;
|
||||
int64_t no_render_time = now - m_last_render_fading;
|
||||
m_current_fade_opacity = std::clamp(1.0f - 0.001f * static_cast<float>(curr_time) / FADING_OUT_DURATION, 0.0f, 1.0f);
|
||||
auto next_render = FADING_OUT_TIMEOUT - no_render_time;
|
||||
if (next_render <= 0) {
|
||||
//m_last_render_fading = GLCanvas3D::timestamp_now();
|
||||
m_state = EState::FadingOutRender;
|
||||
}
|
||||
m_next_render = 0;
|
||||
} else
|
||||
m_next_render = next_render;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
@ -919,7 +947,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW
|
|||
if (m_hover_time > 0 && m_hover_time < time_now) {
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
imgui.text(_u8L("Eject drive"));
|
||||
imgui.text(_u8L("Eject drive") + " " + GUI::shortkey_ctrl_prefix() + "T");
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
@ -1011,7 +1039,13 @@ void NotificationManager::push_notification(const std::string& text, int timesta
|
|||
{
|
||||
push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, timestamp);
|
||||
}
|
||||
void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, int timestamp)
|
||||
|
||||
void NotificationManager::push_notification(NotificationType type,
|
||||
NotificationLevel level,
|
||||
const std::string& text,
|
||||
const std::string& hypertext,
|
||||
std::function<bool(wxEvtHandler*)> callback,
|
||||
int timestamp)
|
||||
{
|
||||
int duration = 0;
|
||||
switch (level) {
|
||||
|
@ -1022,7 +1056,7 @@ void NotificationManager::push_notification(const std::string& text, Notificatio
|
|||
assert(false);
|
||||
return;
|
||||
}
|
||||
push_notification_data({ NotificationType::CustomNotification, level, duration, text }, timestamp);
|
||||
push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp);
|
||||
}
|
||||
void NotificationManager::push_slicing_error_notification(const std::string& text)
|
||||
{
|
||||
|
@ -1181,10 +1215,11 @@ bool NotificationManager::push_notification_data(const NotificationData& notific
|
|||
}
|
||||
bool NotificationManager::push_notification_data(std::unique_ptr<NotificationManager::PopNotification> notification, int timestamp)
|
||||
{
|
||||
/*
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
m_requires_update = true;
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
*/
|
||||
// if timestamped notif, push only new one
|
||||
if (timestamp != 0) {
|
||||
if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) {
|
||||
|
@ -1209,6 +1244,7 @@ bool NotificationManager::push_notification_data(std::unique_ptr<NotificationMan
|
|||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
void NotificationManager::render_notifications(float overlay_width)
|
||||
{
|
||||
|
||||
sort_notifications();
|
||||
|
||||
GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D();
|
||||
|
@ -1222,6 +1258,7 @@ void NotificationManager::render_notifications(float overlay_width)
|
|||
}
|
||||
|
||||
}
|
||||
update_notifications();
|
||||
}
|
||||
#else
|
||||
void NotificationManager::render_notifications(float overlay_width)
|
||||
|
@ -1342,6 +1379,14 @@ void NotificationManager::set_in_preview(bool preview)
|
|||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
void NotificationManager::update_notifications()
|
||||
{
|
||||
// no update if not top window
|
||||
wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater());
|
||||
while (p->GetParent() != nullptr)
|
||||
p = p->GetParent();
|
||||
wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
|
||||
if (!top_level_wnd->IsActive())
|
||||
return;
|
||||
|
||||
static size_t last_size = m_pop_notifications.size();
|
||||
|
||||
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
|
||||
|
@ -1354,7 +1399,7 @@ void NotificationManager::update_notifications()
|
|||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
m_requires_update = false;
|
||||
for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->requires_update()) {
|
||||
|
@ -1362,7 +1407,7 @@ void NotificationManager::update_notifications()
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
// update hovering state
|
||||
m_hovered = false;
|
||||
for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
|
@ -1372,6 +1417,7 @@ void NotificationManager::update_notifications()
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Reuire render if some notification was just deleted.
|
||||
size_t curr_size = m_pop_notifications.size();
|
||||
m_requires_render = m_hovered || (last_size != curr_size);
|
||||
|
@ -1389,7 +1435,20 @@ void NotificationManager::update_notifications()
|
|||
// Make sure there will be update after last notification erased
|
||||
if (m_requires_render)
|
||||
m_requires_update = true;
|
||||
*/
|
||||
//request frames
|
||||
int64_t next_render = std::numeric_limits<int64_t>::max();
|
||||
const int64_t max = std::numeric_limits<int64_t>::max();
|
||||
for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
next_render = std::min<int64_t>(next_render, notification->next_render());
|
||||
}
|
||||
|
||||
if (next_render == 0)
|
||||
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
|
||||
else if (next_render < max)
|
||||
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame_delayed(int(next_render));
|
||||
|
||||
/*
|
||||
// actualizate timers
|
||||
wxWindow* p = dynamic_cast<wxWindow*>(wxGetApp().plater());
|
||||
while (p->GetParent() != nullptr)
|
||||
|
@ -1404,15 +1463,16 @@ void NotificationManager::update_notifications()
|
|||
long now = wxGetLocalTime();
|
||||
// Pausing fade-out when the mouse is over some notification.
|
||||
if (!m_hovered && m_last_time < now) {
|
||||
if (now - m_last_time >= 1) {
|
||||
if (now - m_last_time >= MAX_TIMEOUT_SECONDS) {
|
||||
for (auto& notification : m_pop_notifications) {
|
||||
//if (notification->get_state() != PopNotification::EState::Static)
|
||||
notification->substract_remaining_time();
|
||||
notification->substract_remaining_time(MAX_TIMEOUT_SECONDS);
|
||||
}
|
||||
m_last_time = now;
|
||||
}
|
||||
m_last_time = now;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
|
|
|
@ -67,7 +67,9 @@ enum class NotificationType
|
|||
// Progress bar instead of text.
|
||||
ProgressBar,
|
||||
// Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider.
|
||||
EmptyColorChangeCode
|
||||
EmptyColorChangeCode,
|
||||
// Notification that custom supports/seams were deleted after mesh repair.
|
||||
CustomSupportsAndSeamRemovedAfterRepair
|
||||
};
|
||||
|
||||
class NotificationManager
|
||||
|
@ -97,7 +99,8 @@ public:
|
|||
void push_notification(const std::string& text, int timestamp = 0);
|
||||
// Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotification.
|
||||
// ErrorNotification and ImportantNotification are never faded out.
|
||||
void push_notification(const std::string& text, NotificationLevel level, int timestamp = 0);
|
||||
void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "",
|
||||
std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>(), int timestamp = 0);
|
||||
// Creates Slicing Error notification with a custom text and no fade out.
|
||||
void push_slicing_error_notification(const std::string& text);
|
||||
// Creates Slicing Warning notification with a custom text and no fade out.
|
||||
|
@ -145,13 +148,13 @@ 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; }
|
||||
|
||||
/*
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
void update_notifications();
|
||||
|
||||
bool requires_update() const { return m_requires_update; }
|
||||
bool requires_render() const { return m_requires_render; }
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
*/
|
||||
private:
|
||||
// duration 0 means not disapearing
|
||||
struct NotificationData {
|
||||
|
@ -186,6 +189,7 @@ private:
|
|||
class PopNotification
|
||||
{
|
||||
public:
|
||||
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
enum class EState
|
||||
{
|
||||
|
@ -206,6 +210,7 @@ private:
|
|||
Hovered
|
||||
};
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
PopNotification(const NotificationData &n, NotificationIDProvider &id_provider, wxEvtHandler* evt_handler);
|
||||
virtual ~PopNotification() { if (m_id) m_id_provider.release_id(m_id); }
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
@ -229,18 +234,21 @@ private:
|
|||
const NotificationData get_data() const { return m_data; }
|
||||
const bool is_gray() const { return m_is_gray; }
|
||||
// Call equals one second down
|
||||
void substract_remaining_time() { m_remaining_time--; }
|
||||
void substract_remaining_time(int seconds) { m_remaining_time -= seconds; }
|
||||
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; }
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
// sets m_next_render with time of next mandatory rendering
|
||||
void update_state();
|
||||
int64_t next_render() const { return m_next_render; }
|
||||
/*
|
||||
bool requires_render() const { return m_state == EState::FadingOutRender || m_state == EState::ClosePending || m_state == EState::Finished; }
|
||||
bool requires_update() const { return m_state != EState::Hidden; }
|
||||
*/
|
||||
EState get_state() const { return m_state; }
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
protected:
|
||||
// Call after every size change
|
||||
void init();
|
||||
|
@ -275,9 +283,11 @@ private:
|
|||
|
||||
// For reusing ImGUI windows.
|
||||
NotificationIDProvider &m_id_provider;
|
||||
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
EState m_state { EState::Unknown };
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
int m_id { 0 };
|
||||
bool m_initialized { false };
|
||||
// Main text
|
||||
|
@ -294,8 +304,13 @@ private:
|
|||
int m_countdown_frame { 0 };
|
||||
bool m_fading_out { false };
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
wxMilliClock_t m_fading_start { 0LL };
|
||||
wxMilliClock_t m_last_render_fading { 0LL };
|
||||
int64_t m_fading_start { 0LL };
|
||||
// time of last done render when fading
|
||||
int64_t m_last_render_fading { 0LL };
|
||||
// first appereance of notification or last hover;
|
||||
int64_t m_notification_start;
|
||||
// time to next must-do render
|
||||
int64_t m_next_render { std::numeric_limits<int64_t>::max() };
|
||||
#else
|
||||
// total time left when fading beggins
|
||||
float m_fading_time{ 0.0f };
|
||||
|
@ -423,8 +438,12 @@ private:
|
|||
void sort_notifications();
|
||||
// If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed.
|
||||
bool has_slicing_error_notification();
|
||||
|
||||
// Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
// perform update_state on each notification and ask for more frames if needed
|
||||
void update_notifications();
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
// Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
|
||||
wxEvtHandler* m_evt_handler;
|
||||
// Cache of IDs to identify and reuse ImGUI windows.
|
||||
NotificationIDProvider m_id_provider;
|
||||
|
@ -439,11 +458,12 @@ private:
|
|||
bool m_in_preview { false };
|
||||
// True if the layer editing is enabled in Plater, so that the notifications are shifted left of it.
|
||||
bool m_move_from_overlay { false };
|
||||
/*
|
||||
#if ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
bool m_requires_update{ false };
|
||||
bool m_requires_render{ false };
|
||||
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
|
||||
|
||||
*/
|
||||
//prepared (basic) notifications
|
||||
const std::vector<NotificationData> basic_notifications = {
|
||||
// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
|
||||
|
|
|
@ -544,7 +544,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event)
|
|||
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."));
|
||||
warning_catcher(this, _L("You should change the name of your printer device."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -630,7 +630,7 @@ void Sidebar::priv::show_preset_comboboxes()
|
|||
// Sidebar / public
|
||||
|
||||
Sidebar::Sidebar(Plater *parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)), p(new priv(parent))
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(42 * wxGetApp().em_unit(), -1)), p(new priv(parent))
|
||||
{
|
||||
p->scrolled = new wxScrolledWindow(this);
|
||||
p->scrolled->SetScrollbars(0, 100, 1, 2);
|
||||
|
@ -1336,6 +1336,9 @@ void Sidebar::update_ui_from_settings()
|
|||
p->object_manipulation->update_ui_from_settings();
|
||||
show_info_sizer();
|
||||
update_sliced_info_sizer();
|
||||
// update Cut gizmo, if it's open
|
||||
p->plater->canvas3D()->update_gizmos_on_off_state();
|
||||
p->plater->canvas3D()->request_extra_frame();
|
||||
}
|
||||
|
||||
std::vector<PlaterPresetComboBox*>& Sidebar::combos_filament()
|
||||
|
@ -2121,11 +2124,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
if (evt.data.second) {
|
||||
this->show_action_buttons(this->ready_to_slice);
|
||||
notification_manager->close_notification_of_type(NotificationType::ExportFinished);
|
||||
notification_manager->push_notification(format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path),
|
||||
NotificationManager::NotificationLevel::RegularNotification);
|
||||
notification_manager->push_notification(NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path)
|
||||
);
|
||||
} else {
|
||||
notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path),
|
||||
NotificationManager::NotificationLevel::ErrorNotification);
|
||||
notification_manager->push_notification(NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::ErrorNotification,
|
||||
format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path)
|
||||
);
|
||||
}
|
||||
});
|
||||
this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) {
|
||||
|
@ -2393,22 +2400,24 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
{
|
||||
// The model should now be initialized
|
||||
|
||||
auto convert_from_imperial_units = [](Model& model) {
|
||||
model.convert_from_imperial_units();
|
||||
wxGetApp().app_config->set("use_inches", "1");
|
||||
auto convert_from_imperial_units = [](Model& model, bool only_small_volumes) {
|
||||
model.convert_from_imperial_units(only_small_volumes);
|
||||
// wxGetApp().app_config->set("use_inches", "1");
|
||||
wxGetApp().sidebar().update_ui_from_settings();
|
||||
};
|
||||
|
||||
if (!is_project_file) {
|
||||
if (imperial_units)
|
||||
convert_from_imperial_units(model);
|
||||
// Convert even if the object is big.
|
||||
convert_from_imperial_units(model, false);
|
||||
else if (model.looks_like_imperial_units()) {
|
||||
wxMessageDialog msg_dlg(q, format_wxstr(_L(
|
||||
"Some object(s) in file %s looks like saved in inches.\n"
|
||||
"Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n",
|
||||
_L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES)
|
||||
convert_from_imperial_units(model);
|
||||
//FIXME up-scale only the small parts?
|
||||
convert_from_imperial_units(model, true);
|
||||
}
|
||||
|
||||
if (model.looks_like_multipart_object()) {
|
||||
|
@ -3356,10 +3365,54 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
|
|||
if (obj_idx < 0)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(q, _L("Fix Throught NetFabb"));
|
||||
// Do not fix anything when a gizmo is open. There might be issues with updates
|
||||
// and what is worse, the snapshot time would refer to the internal stack.
|
||||
if (q->canvas3D()->get_gizmos_manager().get_current_type() != GLGizmosManager::Undefined) {
|
||||
notification_manager->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("ERROR: Please close all manipulators available from "
|
||||
"the left toolbar before fixing the mesh."));
|
||||
return;
|
||||
}
|
||||
|
||||
fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
|
||||
sla::reproject_points_and_holes(model.objects[obj_idx]);
|
||||
// size_t snapshot_time = undo_redo_stack().active_snapshot_time();
|
||||
Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb"));
|
||||
|
||||
ModelObject* mo = model.objects[obj_idx];
|
||||
|
||||
// If there are custom supports/seams, remove them. Fixed mesh
|
||||
// may be different and they would make no sense.
|
||||
bool paint_removed = false;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty();
|
||||
mv->supported_facets.clear();
|
||||
mv->seam_facets.clear();
|
||||
}
|
||||
if (paint_removed) {
|
||||
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
|
||||
notification_manager->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("Custom supports and seams were removed after repairing the mesh."));
|
||||
// _u8L("Undo the repair"),
|
||||
// [this, snapshot_time](wxEvtHandler*){
|
||||
// // Make sure the snapshot is still available and that
|
||||
// // we are in the main stack and not in a gizmo-stack.
|
||||
// if (undo_redo_stack().has_undo_snapshot(snapshot_time)
|
||||
// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
|
||||
// undo_redo_to(snapshot_time);
|
||||
// else
|
||||
// notification_manager->push_notification(
|
||||
// NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
// NotificationManager::NotificationLevel::RegularNotification,
|
||||
// _u8L("Cannot undo to before the mesh repair!"));
|
||||
// return true;
|
||||
// });
|
||||
}
|
||||
|
||||
fix_model_by_win10_sdk_gui(*mo, vol_idx);
|
||||
sla::reproject_points_and_holes(mo);
|
||||
this->update();
|
||||
this->object_list_changed();
|
||||
this->schedule_background_process();
|
||||
|
@ -3505,6 +3558,14 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
|||
*/
|
||||
wxGetApp().obj_list()->update_object_list_by_printer_technology();
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
// From the Win 2004 preset combobox lose a focus after change the preset selection
|
||||
// and that is why the up/down arrow doesn't work properly
|
||||
// (see https://github.com/prusa3d/PrusaSlicer/issues/5531 ).
|
||||
// So, set the focus to the combobox explicitly
|
||||
combo->SetFocus();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||
|
@ -4786,9 +4847,7 @@ void Plater::load_gcode()
|
|||
|
||||
void Plater::load_gcode(const wxString& filename)
|
||||
{
|
||||
if (filename.empty() ||
|
||||
(!filename.Lower().EndsWith(".gcode") && !filename.Lower().EndsWith(".g")) ||
|
||||
m_last_loaded_gcode == filename)
|
||||
if (! is_gcode_file(into_u8(filename)) || m_last_loaded_gcode == filename)
|
||||
return;
|
||||
|
||||
m_last_loaded_gcode = filename;
|
||||
|
@ -4804,7 +4863,6 @@ void Plater::load_gcode(const wxString& filename)
|
|||
// process gcode
|
||||
GCodeProcessor processor;
|
||||
processor.enable_producers(true);
|
||||
processor.enable_machine_envelope_processing(true);
|
||||
processor.process_file(filename.ToUTF8().data(), false);
|
||||
p->gcode_result = std::move(processor.extract_result());
|
||||
|
||||
|
@ -5860,15 +5918,14 @@ void Plater::force_print_bed_update()
|
|||
|
||||
void Plater::on_activate()
|
||||
{
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) || defined(_WIN32)
|
||||
wxWindow *focus_window = wxWindow::FindFocus();
|
||||
// Activating the main frame, and no window has keyboard focus.
|
||||
// Set the keyboard focus to the visible Canvas3D.
|
||||
if (this->p->view3D->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas()))
|
||||
this->p->view3D->get_wxglcanvas()->SetFocus();
|
||||
|
||||
else if (this->p->preview->IsShown() && (!focus_window || focus_window == this->p->view3D->get_wxglcanvas()))
|
||||
this->p->preview->get_wxglcanvas()->SetFocus();
|
||||
if (this->p->view3D->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas())
|
||||
CallAfter([this]() { this->p->view3D->get_wxglcanvas()->SetFocus(); });
|
||||
else if (this->p->preview->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas())
|
||||
CallAfter([this]() { this->p->preview->get_wxglcanvas()->SetFocus(); });
|
||||
#endif
|
||||
|
||||
this->p->show_delayed_error_message();
|
||||
|
|
|
@ -156,11 +156,14 @@ void PreferencesDialog::build()
|
|||
option = Option(def, "show_drop_project_dialog");
|
||||
m_optgroup_general->append_single_option_line(option);
|
||||
|
||||
def.label = L("Single instance mode");
|
||||
def.type = coBool;
|
||||
|
||||
#if __APPLE__
|
||||
def.label = L("Allow just a single PrusaSlicer instance");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances of same app from the command line. In such case this settings will allow only one instance.");
|
||||
#else
|
||||
def.label = L("Allow just a single PrusaSlicer instance");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead.");
|
||||
#endif
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false });
|
||||
|
|
|
@ -905,6 +905,13 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type)
|
|||
}
|
||||
|
||||
evt.StopPropagation();
|
||||
#ifdef __WXMSW__
|
||||
// From the Win 2004 preset combobox lose a focus after change the preset selection
|
||||
// and that is why the up/down arrow doesn't work properly
|
||||
// (see https://github.com/prusa3d/PrusaSlicer/issues/5531 ).
|
||||
// So, set the focus to the combobox explicitly
|
||||
this->SetFocus();
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -80,6 +80,17 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
|
|||
Fit();
|
||||
CenterOnParent();
|
||||
|
||||
#ifdef __linux__
|
||||
// On Linux with GTK2 when text control lose the focus then selection (colored background) disappears but text color stay white
|
||||
// and as a result the text is invisible with light mode
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/4532
|
||||
// Workaround: Unselect text selection explicitly on kill focus
|
||||
txt_filename->Bind(wxEVT_KILL_FOCUS, [this](wxEvent& e) {
|
||||
e.Skip();
|
||||
txt_filename->SetInsertionPoint(txt_filename->GetLastPosition());
|
||||
}, txt_filename->GetId());
|
||||
#endif /* __linux__ */
|
||||
|
||||
Bind(wxEVT_SHOW, [=](const wxShowEvent &) {
|
||||
// Another similar case where the function only works with EVT_SHOW + CallAfter,
|
||||
// this time on Mac.
|
||||
|
@ -230,6 +241,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job)
|
|||
fields.push_back(wxVariant(job.upload_data.upload_path.string()));
|
||||
fields.push_back(wxVariant(""));
|
||||
job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW));
|
||||
// Both strings are UTF-8 encoded.
|
||||
upload_names.emplace_back(job.printhost->get_host(), job.upload_data.upload_path.string());
|
||||
}
|
||||
|
||||
|
|
|
@ -277,14 +277,15 @@ void RemovableDriveManager::eject_drive()
|
|||
//std::cout<<"Ejecting "<<(*it).name<<" from "<< correct_path<<"\n";
|
||||
// there is no usable command in c++ so terminal command is used instead
|
||||
// but neither triggers "succesful safe removal messege"
|
||||
BOOST_LOG_TRIVIAL(info) << "Ejecting started";
|
||||
boost::process::ipstream istd_err;
|
||||
boost::process::child child(
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Ejecting started";
|
||||
boost::process::ipstream istd_err;
|
||||
boost::process::child child(
|
||||
#if __APPLE__
|
||||
boost::process::search_path("diskutil"), "eject", correct_path.c_str(), (boost::process::std_out & boost::process::std_err) > istd_err);
|
||||
//Another option how to eject at mac. Currently not working.
|
||||
//used insted of system() command;
|
||||
//this->eject_device(correct_path);
|
||||
//Another option how to eject at mac. Currently not working.
|
||||
//used insted of system() command;
|
||||
//this->eject_device(correct_path);
|
||||
#else
|
||||
boost::process::search_path("umount"), correct_path.c_str(), (boost::process::std_out & boost::process::std_err) > istd_err);
|
||||
#endif
|
||||
|
@ -293,8 +294,19 @@ void RemovableDriveManager::eject_drive()
|
|||
BOOST_LOG_TRIVIAL(trace) << line;
|
||||
}
|
||||
// wait for command to finnish (blocks ui thread)
|
||||
child.wait();
|
||||
int err = child.exit_code();
|
||||
std::error_code ec;
|
||||
child.wait(ec);
|
||||
if (ec) {
|
||||
// The wait call can fail, as it did in https://github.com/prusa3d/PrusaSlicer/issues/5507
|
||||
// It can happen even in cases where the eject is sucessful, but better report it as failed.
|
||||
// We did not find a way to reliably retrieve the exit code of the process.
|
||||
BOOST_LOG_TRIVIAL(error) << "boost::process::child::wait() failed during Ejection. State of Ejection is unknown. Error code: " << ec.value();
|
||||
assert(m_callback_evt_handler);
|
||||
if (m_callback_evt_handler)
|
||||
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair<DriveData, bool>(*it_drive_data, false)));
|
||||
return;
|
||||
}
|
||||
int err = child.exit_code();
|
||||
if (err) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Ejecting failed. Exit code: " << err;
|
||||
assert(m_callback_evt_handler);
|
||||
|
|
|
@ -2803,7 +2803,7 @@ void TabPrinter::toggle_options()
|
|||
toggle_option("retract_restart_extra_toolchange", have_multiple_extruders && toolchange_retraction, i);
|
||||
}
|
||||
|
||||
if (m_active_page->title() == "Machine limits") {
|
||||
if (m_active_page->title() == "Machine limits" && m_machine_limits_description_line) {
|
||||
assert(m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == gcfMarlin);
|
||||
const auto *machine_limits_usage = m_config->option<ConfigOptionEnum<MachineLimitsUsage>>("machine_limits_usage");
|
||||
bool enabled = machine_limits_usage->value != MachineLimitsUsage::Ignore;
|
||||
|
@ -2963,7 +2963,7 @@ void Tab::rebuild_page_tree()
|
|||
continue;
|
||||
auto itemId = m_treectrl->AppendItem(rootItem, translate_category(p->title(), m_type), p->iconID());
|
||||
m_treectrl->SetItemTextColour(itemId, p->get_item_colour());
|
||||
if (p->title() == selected)
|
||||
if (translate_category(p->title(), m_type) == selected)
|
||||
item = itemId;
|
||||
}
|
||||
if (!item) {
|
||||
|
@ -3269,7 +3269,7 @@ void Tab::clear_pages()
|
|||
|
||||
void Tab::update_description_lines()
|
||||
{
|
||||
if (m_active_page && m_active_page->title() == "Dependencies")
|
||||
if (m_active_page && m_active_page->title() == "Dependencies" && m_parent_preset_description_line)
|
||||
update_preset_description_line();
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,12 @@ static std::string get_icon_name(Preset::Type type, PrinterTechnology pt) {
|
|||
return pt == ptSLA && type == Preset::TYPE_PRINTER ? "sla_printer" : type_icon_names.at(type);
|
||||
}
|
||||
|
||||
static std::string black = "#000000";
|
||||
static std::string def_text_color()
|
||||
{
|
||||
wxColour def_colour = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), def_colour.Red(), def_colour.Green(), def_colour.Blue());
|
||||
return clr_str.ToStdString();
|
||||
}
|
||||
static std::string grey = "#808080";
|
||||
static std::string orange = "#ed6b21";
|
||||
|
||||
|
@ -158,7 +163,7 @@ ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& ol
|
|||
}
|
||||
|
||||
// "color" strings
|
||||
color_string(m_old_value, black);
|
||||
color_string(m_old_value, def_text_color());
|
||||
color_string(m_new_value, orange);
|
||||
|
||||
UpdateIcons();
|
||||
|
@ -176,13 +181,13 @@ void ModelNode::UpdateEnabling()
|
|||
};
|
||||
|
||||
if (!m_toggle) {
|
||||
change_text_color(m_text, black, grey);
|
||||
change_text_color(m_old_value, black, grey);
|
||||
change_text_color(m_text, def_text_color(), grey);
|
||||
change_text_color(m_old_value, def_text_color(), 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_text, grey, def_text_color());
|
||||
change_text_color(m_old_value, grey, def_text_color());
|
||||
change_text_color(m_new_value, grey, orange);
|
||||
}
|
||||
// update icons for the colors
|
||||
|
@ -227,7 +232,7 @@ UnsavedChangesModel::~UnsavedChangesModel()
|
|||
wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset_name, PrinterTechnology pt)
|
||||
{
|
||||
// "color" strings
|
||||
color_string(preset_name, black);
|
||||
color_string(preset_name, def_text_color());
|
||||
make_string_bold(preset_name);
|
||||
|
||||
auto preset = new ModelNode(type, m_parent_win, preset_name, get_icon_name(type, pt));
|
||||
|
@ -274,9 +279,9 @@ wxDataViewItem UnsavedChangesModel::AddOption(Preset::Type type, wxString catego
|
|||
wxString old_value, wxString new_value, const std::string category_icon_name)
|
||||
{
|
||||
// "color" strings
|
||||
color_string(category_name, black);
|
||||
color_string(group_name, black);
|
||||
color_string(option_name, black);
|
||||
color_string(category_name, def_text_color());
|
||||
color_string(group_name, def_text_color());
|
||||
color_string(option_name, def_text_color());
|
||||
|
||||
// "make" strings bold
|
||||
make_string_bold(category_name);
|
||||
|
|
|
@ -45,6 +45,14 @@ namespace internal {
|
|||
inline const utf8_buffer cook(const wxString &arg) {
|
||||
return utf8_buffer { arg.ToUTF8() };
|
||||
}
|
||||
// Vojtech seemingly does not understand perfect forwarding:
|
||||
// Why Slic3r::internal::format::cook(T&& arg) is taken for non-const wxString reference?
|
||||
inline const utf8_buffer cook(wxString &arg) {
|
||||
return utf8_buffer { arg.ToUTF8() };
|
||||
}
|
||||
inline const utf8_buffer cook(wxString &&arg) {
|
||||
return utf8_buffer{ arg.ToUTF8() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,10 +116,10 @@ namespace fts {
|
|||
int num_matched = std::tolower(*pattern) == std::tolower(*str) ? 1 : 0;
|
||||
bool folded_match = false;
|
||||
if (! num_matched) {
|
||||
char tmp[4];
|
||||
char *end = Slic3r::fold_to_ascii(*str, tmp);
|
||||
char *c = tmp;
|
||||
for (const wchar_t* d = pattern; c != end && *d != 0 && wchar_t(std::tolower(*c)) == std::tolower(*d); ++c, ++d);
|
||||
wchar_t tmp[4];
|
||||
wchar_t *end = Slic3r::fold_to_ascii(*str, tmp);
|
||||
wchar_t *c = tmp;
|
||||
for (const wchar_t* d = pattern; c != end && *d != 0 && std::towlower(*c) == std::towlower(*d); ++c, ++d);
|
||||
if (c == end) {
|
||||
folded_match = true;
|
||||
num_matched = end - tmp;
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
#include "Plater.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
|
||||
#ifdef __linux__
|
||||
#define wxLinux true
|
||||
#else
|
||||
#define wxLinux false
|
||||
#ifndef __linux__
|
||||
// msw_menuitem_bitmaps is used for MSW and OSX
|
||||
static std::map<int, std::string> msw_menuitem_bitmaps;
|
||||
#ifdef __WXMSW__
|
||||
|
@ -660,7 +657,15 @@ void ModeButton::focus_button(const bool focus)
|
|||
Slic3r::GUI::wxGetApp().normal_font();
|
||||
|
||||
SetFont(new_font);
|
||||
SetForegroundColour(wxSystemSettings::GetColour(focus ? wxSYS_COLOUR_BTNTEXT : wxLinux ? wxSYS_COLOUR_GRAYTEXT : wxSYS_COLOUR_BTNSHADOW));
|
||||
SetForegroundColour(wxSystemSettings::GetColour(focus ? wxSYS_COLOUR_BTNTEXT :
|
||||
#if defined (__linux__) && defined (__WXGTK3__)
|
||||
wxSYS_COLOUR_GRAYTEXT
|
||||
#elif defined (__linux__) && defined (__WXGTK2__)
|
||||
wxSYS_COLOUR_BTNTEXT
|
||||
#else
|
||||
wxSYS_COLOUR_BTNSHADOW
|
||||
#endif
|
||||
));
|
||||
|
||||
Refresh();
|
||||
Update();
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Slic3r {
|
|||
// Convert the input UNICODE character to a string of maximum 4 output ASCII characters.
|
||||
// Return the end of the string written to the output.
|
||||
// The output buffer must be at least 4 characters long.
|
||||
char* fold_to_ascii(wchar_t c, char *out)
|
||||
wchar_t* fold_to_ascii(wchar_t c, wchar_t *out)
|
||||
{
|
||||
if (c < 0x080) {
|
||||
*out ++ = c;
|
||||
|
@ -1925,7 +1925,7 @@ char* fold_to_ascii(wchar_t c, char *out)
|
|||
*out ++ = '~';
|
||||
break;
|
||||
default:
|
||||
*out ++ = c;
|
||||
*out ++ = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1934,6 +1934,14 @@ char* fold_to_ascii(wchar_t c, char *out)
|
|||
return out;
|
||||
}
|
||||
|
||||
static void fold_to_ascii(wchar_t c, std::back_insert_iterator<std::wstring>& out)
|
||||
{
|
||||
wchar_t tmp[4];
|
||||
wchar_t *end = fold_to_ascii(c, tmp);
|
||||
for (wchar_t *it = tmp; it != end; ++ it)
|
||||
*out = *it;
|
||||
}
|
||||
|
||||
std::string fold_utf8_to_ascii(const std::string &src)
|
||||
{
|
||||
std::wstring wstr = boost::locale::conv::utf_to_utf<wchar_t>(src.c_str(), src.c_str() + src.size());
|
||||
|
@ -1945,15 +1953,5 @@ std::string fold_utf8_to_ascii(const std::string &src)
|
|||
return boost::locale::conv::utf_to_utf<char>(dst.c_str(), dst.c_str() + dst.size());
|
||||
}
|
||||
|
||||
std::string fold_utf8_to_ascii(const char *src)
|
||||
{
|
||||
std::wstring wstr = boost::locale::conv::utf_to_utf<wchar_t>(src, src + strlen(src));
|
||||
std::wstring dst;
|
||||
dst.reserve(wstr.size());
|
||||
auto out = std::back_insert_iterator<std::wstring>(dst);
|
||||
for (wchar_t c : wstr)
|
||||
fold_to_ascii(c, out);
|
||||
return boost::locale::conv::utf_to_utf<char>(dst.c_str(), dst.c_str() + dst.size());
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -7,23 +7,13 @@ namespace Slic3r {
|
|||
|
||||
// If possible, remove accents from accented latin characters.
|
||||
// This function is useful for generating file names to be processed by legacy firmwares.
|
||||
extern std::string fold_utf8_to_ascii(const char *src);
|
||||
extern std::string fold_utf8_to_ascii(const std::string &src);
|
||||
|
||||
// Convert the input UNICODE character to a string of maximum 4 output ASCII characters.
|
||||
// Return the end of the string written to the output.
|
||||
// The output buffer must be at least 4 characters long.
|
||||
extern char* fold_to_ascii(wchar_t c, char *out);
|
||||
extern wchar_t* fold_to_ascii(wchar_t c, wchar_t *out);
|
||||
|
||||
template<typename OUTPUT_ITERATOR>
|
||||
void fold_to_ascii(wchar_t c, OUTPUT_ITERATOR out)
|
||||
{
|
||||
char tmp[4];
|
||||
char *end = fold_to_ascii(c, tmp);
|
||||
for (char *it = tmp; it != end; ++ it)
|
||||
*out = *it;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_ASCIIFolding_hpp_ */
|
||||
|
|
|
@ -54,28 +54,18 @@ static const char *INDEX_FILENAME = "index.idx";
|
|||
static const char *TMP_EXTENSION = ".download";
|
||||
|
||||
|
||||
//void copy_file_fix(const fs::path &source, const fs::path &target)
|
||||
void copy_file_fix(const fs::path& source, const fs::path& target,const std::string& caller_function_name)
|
||||
void copy_file_fix(const fs::path &source, const fs::path &target)
|
||||
{
|
||||
static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target);
|
||||
|
||||
// Make sure the file has correct permission both before and after we copy over it
|
||||
boost::system::error_code ec;
|
||||
if (fs::exists(target)) {
|
||||
fs::permissions(target, perms, ec);
|
||||
if(ec)
|
||||
throw Slic3r::CriticalException((boost::format(_utf8(L("Copying of file %1% to %2% failed. Permissions fail at target file before copying.\nError message : %3%\n This error happend during %4% phase."))) % source % target % ec.message() % caller_function_name).str());
|
||||
fs::permissions(target, perms);
|
||||
}
|
||||
ec.clear();
|
||||
fs::copy_file(source, target, fs::copy_option::overwrite_if_exists, ec);
|
||||
if (ec)
|
||||
throw Slic3r::CriticalException((boost::format(_utf8(L("Copying of file %1% to %2% failed.\nError message : %3%\n Copying was triggered by function: %4%"))) % source % target % ec.message() % caller_function_name).str());
|
||||
ec.clear();
|
||||
fs::permissions(target, perms, ec);
|
||||
if (ec)
|
||||
throw Slic3r::CriticalException((boost::format(_utf8(L("Copying of file %1% to %2% failed. Permissions fail at target file after copying.\nError message : %3%\n Copying was triggered by function: %4%"))) % source % target % ec.message() % caller_function_name).str());
|
||||
fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
|
||||
fs::permissions(target, perms);
|
||||
}
|
||||
|
||||
struct Update
|
||||
|
@ -101,9 +91,7 @@ struct Update
|
|||
|
||||
void install() const
|
||||
{
|
||||
std::string error_message;
|
||||
copy_file_fix(source, target, _utf8(L("install")));
|
||||
|
||||
copy_file_fix(source, target);
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Update &self)
|
||||
|
@ -394,7 +382,7 @@ void PresetUpdater::priv::check_install_indices() const
|
|||
|
||||
if (! fs::exists(path_in_cache)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename();
|
||||
copy_file_fix(path, path_in_cache, _utf8(L("checking install indices")));
|
||||
copy_file_fix(path, path_in_cache);
|
||||
} else {
|
||||
Index idx_rsrc, idx_cache;
|
||||
idx_rsrc.load(path);
|
||||
|
@ -402,7 +390,7 @@ void PresetUpdater::priv::check_install_indices() const
|
|||
|
||||
if (idx_cache.version() < idx_rsrc.version()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename();
|
||||
copy_file_fix(path, path_in_cache, _utf8(L("checking install indices")));
|
||||
copy_file_fix(path, path_in_cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -582,7 +570,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
|||
updates.updates.emplace_back(std::move(new_update));
|
||||
// 'Install' the index in the vendor directory. This is used to memoize
|
||||
// offered updates and to not offer the same update again if it was cancelled by the user.
|
||||
copy_file_fix(bundle_path_idx_to_install, bundle_path_idx, _utf8(L("getting config updates")));
|
||||
copy_file_fix(bundle_path_idx_to_install, bundle_path_idx);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources",
|
||||
idx.vendor(),
|
||||
|
|
|
@ -36,6 +36,15 @@
|
|||
namespace Slic3r {
|
||||
namespace UndoRedo {
|
||||
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
static inline std::string ptr_to_string(const void* ptr)
|
||||
{
|
||||
char buf[64];
|
||||
sprintf(buf, "%p", ptr);
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range_idx(-1)
|
||||
{
|
||||
}
|
||||
|
@ -368,15 +377,6 @@ private:
|
|||
MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs);
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
static inline std::string ptr_to_string(const void* ptr)
|
||||
{
|
||||
char buf[64];
|
||||
sprintf(buf, "%p", ptr);
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig)
|
||||
// are mutable and there is not tracking of the changes, therefore a snapshot needs to be
|
||||
// taken every time and compared to the previous data at the Undo / Redo stack.
|
||||
|
@ -548,6 +548,7 @@ public:
|
|||
void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos);
|
||||
|
||||
bool has_undo_snapshot() const;
|
||||
bool has_undo_snapshot(size_t time_to_load) const;
|
||||
bool has_redo_snapshot() const;
|
||||
bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, const SnapshotData &snapshot_data, size_t jump_to_time);
|
||||
bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time);
|
||||
|
@ -907,6 +908,11 @@ bool StackImpl::has_undo_snapshot() const
|
|||
return -- it != m_snapshots.begin();
|
||||
}
|
||||
|
||||
bool StackImpl::has_undo_snapshot(size_t time_to_load) const
|
||||
{
|
||||
return time_to_load < m_active_snapshot_time && std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load));
|
||||
}
|
||||
|
||||
bool StackImpl::has_redo_snapshot() const
|
||||
{
|
||||
assert(this->valid());
|
||||
|
@ -1083,6 +1089,7 @@ void Stack::release_least_recently_used() { pimpl->release_least_recently_used()
|
|||
void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data)
|
||||
{ pimpl->take_snapshot(snapshot_name, model, selection, gizmos, snapshot_data); }
|
||||
bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
|
||||
bool Stack::has_undo_snapshot(size_t time_to_load) const { return pimpl->has_undo_snapshot(time_to_load); }
|
||||
bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); }
|
||||
bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data, size_t time_to_load)
|
||||
{ return pimpl->undo(model, selection, gizmos, snapshot_data, time_to_load); }
|
||||
|
|
|
@ -107,6 +107,8 @@ public:
|
|||
// To be queried to enable / disable the Undo / Redo buttons at the UI.
|
||||
bool has_undo_snapshot() const;
|
||||
bool has_redo_snapshot() const;
|
||||
// To query whether one can undo to a snapshot. Useful for notifications, that want to Undo a specific operation.
|
||||
bool has_undo_snapshot(size_t time_to_load) const;
|
||||
|
||||
// Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated.
|
||||
// Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue