mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 14:20:58 +00:00
Merge upstream updates and preserve named nozzle variants
Keep hardened-steel and high-flow nozzle variants selected when refreshing the nozzle dropdown. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
@@ -697,7 +697,8 @@ public:
|
||||
}
|
||||
} else {
|
||||
// Resize by duplicating the last value.
|
||||
this->values.resize(n, this->values./*back*/front());
|
||||
T v = this->values./*back*/front();
|
||||
this->values.resize(n, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,8 +773,10 @@ public:
|
||||
|
||||
if (this->values.empty())
|
||||
this->values.resize(rhs_vec->size());
|
||||
else
|
||||
this->values.resize(rhs_vec->size(), this->values.front());
|
||||
else {
|
||||
T v = this->values.front();
|
||||
this->values.resize(rhs_vec->size(), v);
|
||||
}
|
||||
|
||||
assert(default_index.size() == rhs_vec->size());
|
||||
|
||||
|
||||
@@ -990,6 +990,7 @@ EmbossStyles Emboss::get_font_list_by_enumeration() {
|
||||
std::vector<std::wstring> font_names;
|
||||
EnumFontFamilies(hDC, (LPCTSTR) NULL, EnumFamCallBack,
|
||||
(LPARAM) &font_names);
|
||||
ReleaseDC(NULL, hDC);
|
||||
|
||||
EmbossStyles font_list;
|
||||
for (const std::wstring &font_name : font_names) {
|
||||
|
||||
@@ -464,6 +464,16 @@ void group_region_by_fuzzify(PerimeterGenerator& g)
|
||||
}
|
||||
}
|
||||
|
||||
g.fuzzy_supported_area.reset();
|
||||
if ((g.has_fuzzy_skin || g.has_fuzzy_hole) && g.lower_slices != nullptr) {
|
||||
coord_t max_thickness = 0;
|
||||
for (const auto& region : regions)
|
||||
if (should_fuzzify(region.config, g.layer_id, 0, true) || should_fuzzify(region.config, g.layer_id, 0, false))
|
||||
max_thickness = std::max(max_thickness, region.config.thickness);
|
||||
// Walls farther than a line width plus the noise amplitude from the layer below are bridging; keep them smooth.
|
||||
g.fuzzy_supported_area = offset_ex(*g.lower_slices, float(g.ext_perimeter_flow.scaled_width() + max_thickness));
|
||||
}
|
||||
|
||||
if (regions.size() == 1) { // optimization
|
||||
g.regions_by_fuzzify.push_back({regions.front().config, {}});
|
||||
return;
|
||||
@@ -560,13 +570,23 @@ static std::vector<MergedFuzzyRegion> collect_merged_fuzzy_regions(const std::ve
|
||||
return merged_regions;
|
||||
}
|
||||
|
||||
// Afterwards an empty region means nothing to fuzzify, no longer full coverage.
|
||||
static void restrict_to_supported(std::vector<MergedFuzzyRegion>& merged_regions, const std::optional<ExPolygons>& supported)
|
||||
{
|
||||
if (!supported)
|
||||
return;
|
||||
for (auto& merged_region : merged_regions)
|
||||
merged_region.expolygons = merged_region.expolygons.empty() ? *supported : intersection_ex(merged_region.expolygons, *supported);
|
||||
}
|
||||
|
||||
Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perimeter_generator, const size_t loop_idx, const bool is_contour)
|
||||
{
|
||||
Polygon fuzzified;
|
||||
|
||||
const auto slice_z = perimeter_generator.slice_z;
|
||||
const auto& regions = perimeter_generator.regions_by_fuzzify;
|
||||
if (regions.size() == 1) { // optimization
|
||||
const auto& supported = perimeter_generator.fuzzy_supported_area;
|
||||
if (regions.size() == 1 && !supported) { // optimization
|
||||
const auto& config = regions.begin()->first;
|
||||
const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, loop_idx, is_contour);
|
||||
if (!fuzzify) {
|
||||
@@ -590,7 +610,7 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
|
||||
// Fast path: single merged region — apply directly without splitting
|
||||
if (merged_regions.size() == 1) {
|
||||
const auto& mr = merged_regions.front();
|
||||
if (mr.expolygons.empty()) {
|
||||
if (mr.expolygons.empty() && !supported) {
|
||||
fuzzified = polygon;
|
||||
fuzzy_polyline(fuzzified.points, true, slice_z, *mr.config);
|
||||
return fuzzified;
|
||||
@@ -626,6 +646,8 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
|
||||
if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty())
|
||||
merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons);
|
||||
|
||||
restrict_to_supported(merged_regions, supported);
|
||||
|
||||
// Split the loops into lines with different config, and fuzzy them separately
|
||||
fuzzified = polygon;
|
||||
for (const auto& r : merged_regions) {
|
||||
@@ -689,7 +711,8 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
|
||||
const auto slice_z = perimeter_generator.slice_z;
|
||||
const auto layer_height = perimeter_generator.layer_height;
|
||||
const auto& regions = perimeter_generator.regions_by_fuzzify;
|
||||
if (regions.size() == 1) { // optimization
|
||||
const auto& supported = perimeter_generator.fuzzy_supported_area;
|
||||
if (regions.size() == 1 && !supported) { // optimization
|
||||
const auto& config = regions.begin()->first;
|
||||
const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, extrusion->inset_idx, is_contour);
|
||||
if (fuzzify)
|
||||
@@ -703,7 +726,7 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
|
||||
if (!merged_regions.empty()) {
|
||||
|
||||
// Fast path: single merged region — apply directly without splitting
|
||||
if (merged_regions.size() == 1 && merged_regions.front().expolygons.empty()) {
|
||||
if (merged_regions.size() == 1 && merged_regions.front().expolygons.empty() && !supported) {
|
||||
fuzzy_extrusion_line(extrusion->junctions, slice_z, perimeter_generator.layer_height, *merged_regions.front().config, closed);
|
||||
return;
|
||||
}
|
||||
@@ -753,6 +776,8 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
|
||||
if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty())
|
||||
merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons);
|
||||
|
||||
restrict_to_supported(merged_regions, supported);
|
||||
|
||||
// Split the loops into lines with different config, and fuzzy them separately
|
||||
for (const auto& r : merged_regions) {
|
||||
const auto splitted = Algorithm::split_line(*extrusion, r.expolygons, false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "../AABBTreeLines.hpp"
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../Surface.hpp"
|
||||
@@ -14,7 +15,9 @@
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <tuple>
|
||||
|
||||
// Boost pool: Don't use mutexes to synchronize memory allocation.
|
||||
#define BOOST_POOL_NO_MT
|
||||
@@ -1318,6 +1321,564 @@ bool has_no_collinear_lines(const Polylines &polylines)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Non-crossing centerlines for multiline adaptive cubic, see docs/HLSD/multiline-infill.md.
|
||||
namespace noncrossing {
|
||||
|
||||
// y = x() * x + y() in the frame where the sweep family is horizontal.
|
||||
using Lin = Vec2d;
|
||||
|
||||
static const Vec2d family_dir[3] { Vec2d(1., 0.), Vec2d(0.5, 0.5 * sqrt(3.)), Vec2d(0.5, -0.5 * sqrt(3.)) };
|
||||
|
||||
struct SweepLine
|
||||
{
|
||||
Vec2d a, b; // a.x() < b.x()
|
||||
int family;
|
||||
Lin lin;
|
||||
std::vector<std::pair<double, int>> junctions; // (x, junction)
|
||||
};
|
||||
|
||||
struct Junction
|
||||
{
|
||||
Vec2d p;
|
||||
std::vector<int> lines;
|
||||
std::vector<std::pair<int, int>> pairs; // (left, right) line of each path through, bottom-up
|
||||
std::vector<std::pair<int, int>> bends; // (path, bend) of each pair, -1 where it runs straight
|
||||
};
|
||||
|
||||
struct LevelPath
|
||||
{
|
||||
std::vector<Vec2d> verts; // start, bends, end
|
||||
std::vector<int> lines; // line of each piece
|
||||
std::vector<int> junctions; // junction of each bend
|
||||
std::vector<int> turn; // 1 turning up, -1 turning down
|
||||
std::vector<std::vector<Lin>> cuts;
|
||||
std::vector<std::pair<Lin, int>> pushes; // (line, bend) keeping a wall away from a neighbour's cut
|
||||
int start_term { -1 }; // junction where the path stops on another line, or -1
|
||||
int end_term { -1 };
|
||||
};
|
||||
|
||||
// Moves f onto line c (side 1: from below) wherever c lies beyond it, over the stretches overlapping [w0, w1].
|
||||
static void clip_profile(std::vector<Vec2d> &f, const Lin &c, int side, double w0, double w1, double lim0, double lim1, bool cut_at_window, bool drop_past_limits)
|
||||
{
|
||||
const double r0 = std::max(lim0, f.front().x()), r1 = std::min(lim1, f.back().x());
|
||||
if (r1 <= r0)
|
||||
return;
|
||||
const size_t ia = std::upper_bound(f.begin(), f.end(), r0, [](double x, const Vec2d &p) { return x < p.x(); }) - f.begin();
|
||||
const size_t ib = std::lower_bound(f.begin() + ia, f.end(), r1, [](const Vec2d &p, double x) { return p.x() < x; }) - f.begin();
|
||||
auto interpolate = [](const Vec2d &a, const Vec2d &b, double x) { return b.x() > a.x() ? a.y() + (x - a.x()) / (b.x() - a.x()) * (b.y() - a.y()) : b.y(); };
|
||||
std::vector<Vec2d> local{ Vec2d(r0, interpolate(f[ia - 1], f[ia], r0)) };
|
||||
local.insert(local.end(), f.begin() + ia, f.begin() + ib);
|
||||
local.emplace_back(r1, interpolate(f[ib - 1], f[ib], r1));
|
||||
|
||||
const double tol = 1.;
|
||||
auto beyond = [&c, side, tol](const Vec2d &p) { return side * (c.x() * p.x() + c.y() - p.y()) - tol; };
|
||||
std::vector<std::pair<double, double>> stretches;
|
||||
auto add = [&stretches](double x0, double x1) {
|
||||
if (!stretches.empty() && stretches.back().second >= x0)
|
||||
stretches.back().second = x1;
|
||||
else
|
||||
stretches.emplace_back(x0, x1);
|
||||
};
|
||||
for (size_t i = 1; i < local.size(); ++i) {
|
||||
const Vec2d &p = local[i - 1], &q = local[i];
|
||||
if (q.x() <= p.x())
|
||||
continue;
|
||||
const double bp = beyond(p), bq = beyond(q);
|
||||
if (bp > 0. && bq > 0.)
|
||||
add(p.x(), q.x());
|
||||
else if (bp > 0. || bq > 0.) {
|
||||
const double x = p.x() + bp / (bp - bq) * (q.x() - p.x());
|
||||
if (bp > 0.)
|
||||
add(p.x(), x);
|
||||
else
|
||||
add(x, q.x());
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<double, double>> keep;
|
||||
for (auto [x0, x1] : stretches) {
|
||||
if (x1 < w0 || x0 > w1)
|
||||
continue;
|
||||
if (drop_past_limits && ((x0 <= r0 && r0 == lim0) || (x1 >= r1 && r1 == lim1)))
|
||||
continue;
|
||||
keep.emplace_back(cut_at_window ? std::max(x0, w0) : x0, cut_at_window ? std::min(x1, w1) : x1);
|
||||
}
|
||||
if (keep.empty())
|
||||
return;
|
||||
|
||||
auto y_local = [&](double x) {
|
||||
size_t i = 1;
|
||||
while (i + 1 < local.size() && local[i].x() < x)
|
||||
++i;
|
||||
return interpolate(local[i - 1], local[i], x);
|
||||
};
|
||||
std::vector<Vec2d> out(f.begin(), f.begin() + ia);
|
||||
auto push = [&out, tol](double x, double y) {
|
||||
if (out.empty() || x > out.back().x() || std::abs(y - out.back().y()) > 2. * tol)
|
||||
out.emplace_back(x, y);
|
||||
};
|
||||
size_t k = 0;
|
||||
for (const Vec2d &p : local) {
|
||||
for (; k < keep.size() && keep[k].second < p.x(); ++k) {
|
||||
const auto [x0, x1] = keep[k];
|
||||
push(x0, y_local(x0));
|
||||
push(x0, c.x() * x0 + c.y());
|
||||
push(x1, c.x() * x1 + c.y());
|
||||
push(x1, y_local(x1));
|
||||
}
|
||||
if (k < keep.size() && keep[k].first <= p.x() && p.x() <= keep[k].second)
|
||||
continue;
|
||||
push(p.x(), p.y());
|
||||
}
|
||||
for (; k < keep.size(); ++k) {
|
||||
const auto [x0, x1] = keep[k];
|
||||
push(x0, y_local(x0));
|
||||
push(x0, c.x() * x0 + c.y());
|
||||
push(x1, c.x() * x1 + c.y());
|
||||
push(x1, y_local(x1));
|
||||
}
|
||||
for (size_t i = ib; i < f.size(); ++i)
|
||||
push(f[i].x(), f[i].y());
|
||||
f = std::move(out);
|
||||
}
|
||||
|
||||
static std::vector<Vec2d> path_points(const LevelPath &path, const std::vector<SweepLine> &lines, double reach)
|
||||
{
|
||||
std::vector<Vec2d> f = path.verts;
|
||||
const int nb = int(path.junctions.size());
|
||||
auto x_of = [&path](int b) { return path.verts[b + 1].x(); };
|
||||
auto sharp = [&](int b) { return lines[path.lines[b]].family != 0 && lines[path.lines[b + 1]].family != 0; };
|
||||
// The run of bends turning the same way as bend b, up to the neighbouring bends turning the other way.
|
||||
auto window = [&](int b) {
|
||||
int l = b - 1, r = b + 1;
|
||||
while (l >= 0 && path.turn[l] == path.turn[b])
|
||||
--l;
|
||||
while (r < nb && path.turn[r] == path.turn[b])
|
||||
++r;
|
||||
return std::make_pair(l >= 0 ? x_of(l) : f.front().x(), r < nb ? x_of(r) : f.back().x());
|
||||
};
|
||||
// Sharp bends between the slanted lines go last, so they win at the tip of a small triangle.
|
||||
for (int b = 0; b < nb; ++b)
|
||||
if (!sharp(b)) {
|
||||
const auto [w0, w1] = window(b);
|
||||
for (const Lin &c : path.cuts[b])
|
||||
clip_profile(f, c, path.turn[b], w0, w1, x_of(b) - reach, x_of(b) + reach, true, false);
|
||||
}
|
||||
for (int b = 0; b < nb; ++b)
|
||||
if (sharp(b))
|
||||
for (const Lin &c : path.cuts[b])
|
||||
clip_profile(f, c, path.turn[b], x_of(b), x_of(b), x_of(b) - reach, x_of(b) + reach, false, true);
|
||||
for (const auto &[c, b] : path.pushes) {
|
||||
const auto [w0, w1] = window(b);
|
||||
clip_profile(f, c, path.turn[b], w0, w1, x_of(b) - reach, x_of(b) + reach, true, true);
|
||||
}
|
||||
std::vector<Vec2d> pts;
|
||||
for (const Vec2d &p : f) {
|
||||
while (pts.size() >= 2 && std::abs(cross2(Vec2d(pts.back() - pts[pts.size() - 2]), Vec2d(p - pts.back()))) <=
|
||||
1e-9 * (pts.back() - pts[pts.size() - 2]).norm() * (p - pts.back()).norm())
|
||||
pts.pop_back();
|
||||
pts.push_back(p);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
static double polyline_length(const std::vector<Vec2d> &pts)
|
||||
{
|
||||
double len = 0.;
|
||||
for (size_t i = 1; i < pts.size(); ++i)
|
||||
len += (pts[i] - pts[i - 1]).norm();
|
||||
return len;
|
||||
}
|
||||
|
||||
// Point at the given distance along pts, and the index of the segment it lies on.
|
||||
static std::pair<Vec2d, size_t> point_along(const std::vector<Vec2d> &pts, double t)
|
||||
{
|
||||
for (size_t i = 1; i < pts.size(); ++i) {
|
||||
const double len = (pts[i] - pts[i - 1]).norm();
|
||||
if (t <= len)
|
||||
return { pts[i - 1] + (len > 0. ? t / len : 0.) * (pts[i] - pts[i - 1]), i };
|
||||
t -= len;
|
||||
}
|
||||
return { pts.back(), pts.size() - 1 };
|
||||
}
|
||||
|
||||
} // namespace noncrossing
|
||||
|
||||
Polylines multiline_paths(const Lines &lines_in, double d1, double end_overlap, int sweep, const BoundingBox &cover)
|
||||
{
|
||||
using namespace noncrossing;
|
||||
const double eps = scale_(0.002);
|
||||
const Eigen::Rotation2Dd to_sweep(-sweep * M_PI / 3.);
|
||||
const BoundingBoxf box(cover.min.cast<double>(), cover.max.cast<double>());
|
||||
|
||||
// Lines in the sweep frame, collinear pieces merged.
|
||||
struct Piece { double c, s0, s1; };
|
||||
std::array<std::vector<Piece>, 3> pieces;
|
||||
for (const Line &line : lines_in) {
|
||||
Vec2d a = line.a.cast<double>(), b = line.b.cast<double>();
|
||||
if (!Geometry::liang_barsky_line_clipping(a, b, box) || (b - a).norm() < 10. * eps)
|
||||
continue;
|
||||
a = to_sweep * a;
|
||||
b = to_sweep * b;
|
||||
const double angle = std::atan2(b.y() - a.y(), b.x() - a.x()) / (M_PI / 3.);
|
||||
if (std::abs(angle - std::round(angle)) > 0.01)
|
||||
// Not one of the three families.
|
||||
return {};
|
||||
const int f = (int(std::round(angle)) % 3 + 3) % 3;
|
||||
const Vec2d &d = family_dir[f];
|
||||
const Vec2d n(-d.y(), d.x());
|
||||
pieces[f].push_back({ n.dot(a), std::min(d.dot(a), d.dot(b)), std::max(d.dot(a), d.dot(b)) });
|
||||
}
|
||||
std::vector<SweepLine> lines;
|
||||
for (int f = 0; f < 3; ++f) {
|
||||
std::vector<Piece> &ps = pieces[f];
|
||||
const Vec2d &d = family_dir[f];
|
||||
const Vec2d n(-d.y(), d.x());
|
||||
const double slope = d.y() / d.x();
|
||||
std::sort(ps.begin(), ps.end(), [](const Piece &l, const Piece &r) { return l.c < r.c; });
|
||||
for (size_t i = 0; i < ps.size();) {
|
||||
size_t j = i + 1;
|
||||
while (j < ps.size() && ps[j].c - ps[i].c < eps)
|
||||
++j;
|
||||
std::sort(ps.begin() + i, ps.begin() + j, [](const Piece &l, const Piece &r) { return l.s0 < r.s0; });
|
||||
double c = 0.;
|
||||
for (size_t k = i; k < j; ++k)
|
||||
c += ps[k].c / double(j - i);
|
||||
double s0 = ps[i].s0, s1 = ps[i].s1;
|
||||
for (size_t k = i + 1; k <= j; ++k) {
|
||||
if (k < j && ps[k].s0 <= s1 + eps) {
|
||||
s1 = std::max(s1, ps[k].s1);
|
||||
continue;
|
||||
}
|
||||
const Vec2d a = s0 * d + c * n;
|
||||
lines.push_back({ a, s1 * d + c * n, f, Lin(slope, a.y() - slope * a.x()), {} });
|
||||
if (k < j) {
|
||||
s0 = ps[k].s0;
|
||||
s1 = ps[k].s1;
|
||||
}
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
auto along = [](const SweepLine &l, const Vec2d &p) { return family_dir[l.family].dot(p - l.a); };
|
||||
auto length = [](const SweepLine &l) { return (l.b - l.a).norm(); };
|
||||
|
||||
// Crossings, including the ends of lines stopping on another line.
|
||||
std::vector<Junction> junctions;
|
||||
auto detect_junctions = [&]() {
|
||||
struct Hit { Vec2d p; int i, j; };
|
||||
std::vector<Hit> hits;
|
||||
std::vector<int> order(lines.size());
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
std::sort(order.begin(), order.end(), [&lines](int l, int r) { return lines[l].a.x() < lines[r].a.x(); });
|
||||
for (size_t oi = 0; oi < order.size(); ++oi) {
|
||||
const SweepLine &li = lines[order[oi]];
|
||||
for (size_t oj = oi + 1; oj < order.size() && lines[order[oj]].a.x() <= li.b.x() + eps; ++oj) {
|
||||
const SweepLine &lj = lines[order[oj]];
|
||||
if (li.family == lj.family)
|
||||
continue;
|
||||
const double x = (lj.lin.y() - li.lin.y()) / (li.lin.x() - lj.lin.x());
|
||||
const Vec2d p(x, li.lin.x() * x + li.lin.y());
|
||||
const double ti = along(li, p), tj = along(lj, p);
|
||||
if (ti > -eps && ti < length(li) + eps && tj > -eps && tj < length(lj) + eps)
|
||||
hits.push_back({ p, order[oi], order[oj] });
|
||||
}
|
||||
}
|
||||
std::sort(hits.begin(), hits.end(), [](const Hit &l, const Hit &r) { return l.p.x() < r.p.x(); });
|
||||
junctions.clear();
|
||||
for (const Hit &hit : hits) {
|
||||
int found = -1;
|
||||
for (int k = int(junctions.size()) - 1; k >= 0 && junctions[k].p.x() > hit.p.x() - eps; --k)
|
||||
if (std::abs(junctions[k].p.y() - hit.p.y()) < eps) {
|
||||
found = k;
|
||||
break;
|
||||
}
|
||||
if (found < 0) {
|
||||
found = int(junctions.size());
|
||||
junctions.push_back({ hit.p, {}, {}, {} });
|
||||
}
|
||||
std::vector<int> &jl = junctions[found].lines;
|
||||
for (int li : { hit.i, hit.j })
|
||||
if (std::find(jl.begin(), jl.end(), li) == jl.end())
|
||||
jl.push_back(li);
|
||||
}
|
||||
for (SweepLine &l : lines)
|
||||
l.junctions.clear();
|
||||
for (int ji = 0; ji < int(junctions.size()); ++ji)
|
||||
for (int li : junctions[ji].lines)
|
||||
lines[li].junctions.emplace_back(junctions[ji].p.x(), ji);
|
||||
for (SweepLine &l : lines)
|
||||
std::sort(l.junctions.begin(), l.junctions.end());
|
||||
};
|
||||
detect_junctions();
|
||||
auto has_arm = [&](int ji, int li, bool right) {
|
||||
const double t = along(lines[li], junctions[ji].p);
|
||||
return right ? t < length(lines[li]) - eps : t > eps;
|
||||
};
|
||||
|
||||
// A line ending on another just past a crossing stops at the crossing, where its stub would leave a hole.
|
||||
auto crosses = [&](int ji, int li) { return has_arm(ji, li, false) && has_arm(ji, li, true); };
|
||||
for (int pass = 0; pass < 3; ++pass) {
|
||||
std::vector<bool> touched(lines.size(), false);
|
||||
bool changed = false;
|
||||
for (int ji = 0; ji < int(junctions.size()); ++ji) {
|
||||
const Junction &J = junctions[ji];
|
||||
if (J.lines.size() != 2 || touched[J.lines[0]] || touched[J.lines[1]] || !crosses(ji, J.lines[0]) || !crosses(ji, J.lines[1]))
|
||||
continue;
|
||||
// Shortest arm of each line from J to the junction where it ends on another line.
|
||||
struct DeadArm { double length; bool at_b; int end; };
|
||||
std::array<DeadArm, 2> dead;
|
||||
dead.fill({ std::numeric_limits<double>::max(), false, -1 });
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
const SweepLine &l = lines[J.lines[k]];
|
||||
const size_t at = std::find_if(l.junctions.begin(), l.junctions.end(), [ji](const std::pair<double, int> &j) { return j.second == ji; }) - l.junctions.begin();
|
||||
const double t = along(l, J.p);
|
||||
if (at + 1 < l.junctions.size() && length(l) - along(l, junctions[l.junctions[at + 1].second].p) < eps)
|
||||
dead[k] = { length(l) - t, true, l.junctions[at + 1].second };
|
||||
if (at > 0 && along(l, junctions[l.junctions[at - 1].second].p) < eps && t < dead[k].length)
|
||||
dead[k] = { t, false, l.junctions[at - 1].second };
|
||||
}
|
||||
const int k = dead[0].length <= dead[1].length ? 0 : 1;
|
||||
if (dead[k].length >= 2. * d1)
|
||||
continue;
|
||||
SweepLine &l = lines[J.lines[k]];
|
||||
(dead[k].at_b ? l.b : l.a) = J.p;
|
||||
// Only the shortened line and those it ended on have stale junctions until the next pass.
|
||||
for (int li : junctions[dead[k].end].lines)
|
||||
touched[li] = true;
|
||||
changed = true;
|
||||
}
|
||||
if (!changed)
|
||||
break;
|
||||
detect_junctions();
|
||||
}
|
||||
|
||||
// At every crossing the lines bounce off each other, so that every path keeps running left to right.
|
||||
for (int ji = 0; ji < int(junctions.size()); ++ji) {
|
||||
Junction &J = junctions[ji];
|
||||
std::vector<int> left, right;
|
||||
for (int li : J.lines)
|
||||
if (has_arm(ji, li, false) && has_arm(ji, li, true))
|
||||
left.push_back(li);
|
||||
right = left;
|
||||
std::sort(left.begin(), left.end(), [&lines](int l, int r) { return lines[l].lin.x() > lines[r].lin.x(); });
|
||||
std::sort(right.begin(), right.end(), [&lines](int l, int r) { return lines[l].lin.x() < lines[r].lin.x(); });
|
||||
for (size_t k = 0; k < left.size(); ++k)
|
||||
J.pairs.emplace_back(left[k], right[k]);
|
||||
J.bends.assign(J.pairs.size(), { -1, -1 });
|
||||
}
|
||||
|
||||
std::vector<LevelPath> paths;
|
||||
for (int li = 0; li < int(lines.size()); ++li) {
|
||||
const SweepLine &l = lines[li];
|
||||
const int start = !l.junctions.empty() && !has_arm(l.junctions.front().second, li, false) ? l.junctions.front().second : -1;
|
||||
LevelPath path;
|
||||
path.start_term = start;
|
||||
path.verts.push_back(start >= 0 ? junctions[start].p : l.a);
|
||||
path.lines.push_back(li);
|
||||
int cur = li;
|
||||
double x = path.verts.front().x();
|
||||
for (;;) {
|
||||
const auto &js = lines[cur].junctions;
|
||||
const auto it = std::find_if(js.begin(), js.end(), [x, eps](const std::pair<double, int> &j) { return j.first > x + 0.25 * eps; });
|
||||
if (it == js.end()) {
|
||||
path.verts.push_back(lines[cur].b);
|
||||
break;
|
||||
}
|
||||
Junction &J = junctions[it->second];
|
||||
const size_t k = std::find_if(J.pairs.begin(), J.pairs.end(), [cur](const std::pair<int, int> &p) { return p.first == cur; }) - J.pairs.begin();
|
||||
if (k == J.pairs.size()) {
|
||||
path.verts.push_back(J.p);
|
||||
path.end_term = it->second;
|
||||
break;
|
||||
}
|
||||
if (const int next = J.pairs[k].second; next != cur) {
|
||||
J.bends[k] = { int(paths.size()), int(path.junctions.size()) };
|
||||
path.verts.push_back(J.p);
|
||||
path.lines.push_back(next);
|
||||
path.junctions.push_back(it->second);
|
||||
path.turn.push_back(lines[next].lin.x() > lines[cur].lin.x() ? 1 : -1);
|
||||
cur = next;
|
||||
}
|
||||
x = it->first;
|
||||
}
|
||||
path.cuts.resize(path.junctions.size());
|
||||
paths.push_back(std::move(path));
|
||||
}
|
||||
|
||||
// Cut the two bends of a crossing d1 apart, no further than the neighbouring bends turning the other way.
|
||||
for (const Junction &J : junctions) {
|
||||
if (J.pairs.size() < 2 || J.bends.front().first < 0 || J.bends.back().first < 0)
|
||||
continue;
|
||||
const Vec2d n = (family_dir[lines[J.pairs.back().second].family] - family_dir[lines[J.pairs.back().first].family]).normalized();
|
||||
auto cut = [&J, &n](double offset) {
|
||||
const Vec2d q = J.p + offset * n;
|
||||
const double s = -n.x() / n.y();
|
||||
return Lin(s, q.y() - s * q.x());
|
||||
};
|
||||
LevelPath &lo = paths[J.bends.front().first], &hi = paths[J.bends.back().first];
|
||||
const int lb = J.bends.front().second, hb = J.bends.back().second;
|
||||
if (J.pairs.size() == 3) {
|
||||
lo.cuts[lb].push_back(cut(-d1));
|
||||
hi.cuts[hb].push_back(cut(d1));
|
||||
continue;
|
||||
}
|
||||
// Only the tip of a small triangle, between the two slanted families, stops at its neighbouring bends.
|
||||
const bool sharp = lines[J.pairs.front().first].family != 0 && lines[J.pairs.front().second].family != 0;
|
||||
auto room = [&](const LevelPath &P, int b, int away, double sign) {
|
||||
double c = std::numeric_limits<double>::max();
|
||||
for (int nb : { b - 1, b + 1 })
|
||||
if (sharp && nb >= 0 && nb < int(P.junctions.size()) && P.turn[nb] == away)
|
||||
c = std::min(c, sign * n.dot(junctions[P.junctions[nb]].p - J.p));
|
||||
if (b == 0 && P.start_term >= 0)
|
||||
c = std::min(c, sign * n.dot(P.verts.front() - J.p));
|
||||
if (b + 1 == int(P.junctions.size()) && P.end_term >= 0)
|
||||
c = std::min(c, sign * n.dot(P.verts.back() - J.p));
|
||||
return std::max(c, 0.);
|
||||
};
|
||||
const double c_lo = room(lo, lb, 1, -1.), c_hi = room(hi, hb, -1, 1.);
|
||||
double d_lo = 0.5 * d1;
|
||||
if (d1 - c_hi <= c_lo)
|
||||
d_lo = std::clamp(d_lo, d1 - c_hi, c_lo);
|
||||
else if (c_lo + c_hi > 0.)
|
||||
d_lo = d1 * c_lo / (c_lo + c_hi);
|
||||
const double d_hi = d1 - d_lo;
|
||||
lo.cuts[lb].push_back(cut(-d_lo));
|
||||
hi.cuts[hb].push_back(cut(d_hi));
|
||||
auto propagate = [&](const LevelPath &P, int b, int away, int step, double offset) {
|
||||
for (int nb : { b - 1, b + 1 })
|
||||
if (nb >= 0 && nb < int(P.junctions.size()) && P.turn[nb] == away) {
|
||||
const Junction &W = junctions[P.junctions[nb]];
|
||||
const int k = int(std::find_if(W.pairs.begin(), W.pairs.end(), [&](const std::pair<int, int> &p) { return p.first == P.lines[nb]; }) - W.pairs.begin()) + step;
|
||||
if (k >= 0 && k < int(W.bends.size()) && W.bends[k].first >= 0)
|
||||
paths[W.bends[k].first].pushes.emplace_back(cut(offset), W.bends[k].second);
|
||||
}
|
||||
};
|
||||
if (sharp) {
|
||||
propagate(lo, lb, 1, -1, -d_lo - d1);
|
||||
propagate(hi, hb, -1, 1, d_hi + d1);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<Vec2d>> geometry;
|
||||
Linesf segments;
|
||||
std::vector<std::pair<int, double>> segment_start; // path, distance along it
|
||||
std::vector<std::pair<double, double>> kept; // stretch of each path left by the trimming
|
||||
for (const LevelPath &path : paths) {
|
||||
geometry.push_back(path.junctions.empty() ? std::vector<Vec2d>{ path.verts.front(), path.verts.back() } : path_points(path, lines, 4. * d1));
|
||||
double along_path = 0.;
|
||||
for (size_t i = 1; i < geometry.back().size(); ++i) {
|
||||
segments.emplace_back(geometry.back()[i - 1], geometry.back()[i]);
|
||||
segment_start.emplace_back(int(geometry.size()) - 1, along_path);
|
||||
along_path += (geometry.back()[i] - geometry.back()[i - 1]).norm();
|
||||
}
|
||||
kept.emplace_back(0., along_path);
|
||||
}
|
||||
|
||||
// A path ending on another line stops end_overlap inside the walls of the others, as trimmed so far.
|
||||
const double end_clearance = d1 - end_overlap;
|
||||
AABBTreeLines::LinesDistancer<Linef> tree(segments);
|
||||
auto clearance = [&](int pi, const Vec2d &q) {
|
||||
double dist = std::numeric_limits<double>::max();
|
||||
for (size_t s : tree.all_lines_in_radius(q, d1)) {
|
||||
const auto [pj, start] = segment_start[s];
|
||||
const Vec2d a = segments[s].a, d = segments[s].b - a;
|
||||
const double len = d.norm(), t0 = std::max(0., kept[pj].first - start), t1 = std::min(len, kept[pj].second - start);
|
||||
if (pj != pi && len > 0. && t0 <= t1)
|
||||
dist = std::min(dist, line_alg::distance_to(Linef(a + t0 / len * d, a + t1 / len * d), q));
|
||||
}
|
||||
return dist;
|
||||
};
|
||||
// Returns the length trimmed off.
|
||||
auto trim_front = [&](int pi, std::vector<Vec2d> &pts) {
|
||||
const double total = polyline_length(pts), step = d1 / 32.;
|
||||
double t = 0.;
|
||||
while (t <= total && clearance(pi, point_along(pts, t).first) < end_clearance)
|
||||
t += step;
|
||||
if (t > total) {
|
||||
pts.clear();
|
||||
return total;
|
||||
}
|
||||
if (t == 0.)
|
||||
return 0.;
|
||||
for (double lo = std::max(0., t - step); t - lo > step / 256.;)
|
||||
if (const double mid = 0.5 * (lo + t); clearance(pi, point_along(pts, mid).first) < end_clearance)
|
||||
lo = mid;
|
||||
else
|
||||
t = mid;
|
||||
const auto [q, seg] = point_along(pts, t);
|
||||
pts.erase(pts.begin(), pts.begin() + (seg - 1));
|
||||
pts.front() = q;
|
||||
return t;
|
||||
};
|
||||
|
||||
// A path stopping on the line of another path is trimmed first, so that it gives way to that path.
|
||||
std::vector<std::vector<std::tuple<double, double, int>>> carried(lines.size()); // x range and path of each piece
|
||||
for (int pi = 0; pi < int(paths.size()); ++pi)
|
||||
for (size_t i = 0; i < paths[pi].lines.size(); ++i)
|
||||
carried[paths[pi].lines[i]].emplace_back(paths[pi].verts[i].x(), paths[pi].verts[i + 1].x(), pi);
|
||||
std::vector<std::vector<int>> stopping_on(paths.size());
|
||||
for (int pi = 0; pi < int(paths.size()); ++pi)
|
||||
for (const auto &[ji, own] : { std::make_pair(paths[pi].start_term, paths[pi].lines.front()), std::make_pair(paths[pi].end_term, paths[pi].lines.back()) })
|
||||
if (ji >= 0)
|
||||
for (int li : junctions[ji].lines)
|
||||
if (li != own)
|
||||
for (const auto &[x0, x1, pj] : carried[li])
|
||||
if (pj != pi && x0 - eps <= junctions[ji].p.x() && junctions[ji].p.x() <= x1 + eps)
|
||||
stopping_on[pj].push_back(pi);
|
||||
std::vector<int> order;
|
||||
std::vector<bool> visited(paths.size(), false);
|
||||
std::function<void(int)> visit = [&](int pi) {
|
||||
if (visited[pi])
|
||||
return;
|
||||
visited[pi] = true;
|
||||
for (int child : stopping_on[pi])
|
||||
visit(child);
|
||||
order.push_back(pi);
|
||||
};
|
||||
for (int pi = 0; pi < int(paths.size()); ++pi)
|
||||
visit(pi);
|
||||
std::vector<std::vector<Vec2d>> trimmed(paths.size());
|
||||
auto trim = [&](int pi) {
|
||||
std::vector<Vec2d> &pts = trimmed[pi];
|
||||
if (paths[pi].start_term >= 0)
|
||||
kept[pi].first += trim_front(pi, pts);
|
||||
if (paths[pi].end_term >= 0 && !pts.empty()) {
|
||||
std::reverse(pts.begin(), pts.end());
|
||||
kept[pi].second -= trim_front(pi, pts);
|
||||
std::reverse(pts.begin(), pts.end());
|
||||
}
|
||||
if (pts.size() < 2 || polyline_length(pts) < d1) {
|
||||
pts.clear();
|
||||
kept[pi] = { 0., -1. };
|
||||
}
|
||||
};
|
||||
// Ends grow back where the ends they gave way to were trimmed later; the last pass only shortens them.
|
||||
for (int pass = 0; pass < 3; ++pass)
|
||||
for (int pi : order) {
|
||||
if (pass < 2) {
|
||||
trimmed[pi] = geometry[pi];
|
||||
kept[pi] = { 0., polyline_length(geometry[pi]) };
|
||||
} else if (trimmed[pi].empty())
|
||||
continue;
|
||||
trim(pi);
|
||||
}
|
||||
|
||||
Polylines out;
|
||||
const Eigen::Rotation2Dd to_world = to_sweep.inverse();
|
||||
for (const std::vector<Vec2d> &pts : trimmed) {
|
||||
if (pts.empty())
|
||||
continue;
|
||||
Polyline pl;
|
||||
for (const Vec2d &p : pts) {
|
||||
const Vec2d w = to_world * p;
|
||||
pl.points.emplace_back(coord_t(std::round(w.x())), coord_t(std::round(w.y())));
|
||||
}
|
||||
out.emplace_back(std::move(pl));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void Filler::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
@@ -1371,6 +1932,17 @@ void Filler::_fill_surface_single(
|
||||
all_polylines.reserve(lines.size());
|
||||
std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; });
|
||||
|
||||
if (params.multiline > 1) {
|
||||
const double d1 = scale_(this->spacing) * params.multiline;
|
||||
BoundingBox cover = get_extents(expolygon);
|
||||
cover.offset(coord_t(4. * d1));
|
||||
// Rotate the family the paths run along with the layer, like the other multiline patterns.
|
||||
const int sweep = int((this->layer_id / std::max(thickness_layers, 1u)) % 3);
|
||||
// Line ends overlap the walls they stop on by half a line, so that they bond.
|
||||
if (Polylines paths = multiline_paths(lines, d1, 0.5 * scale_(this->spacing), sweep, cover); !paths.empty())
|
||||
all_polylines = std::move(paths);
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(all_polylines, params, spacing);
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ FillAdaptive::OctreePtr build_octree(
|
||||
// If true, octree is densified below internal overhangs only.
|
||||
bool support_overhangs_only);
|
||||
|
||||
// Multiline infill: lines of the three families to non-crossing paths d1 apart, ends reaching end_overlap into walls.
|
||||
Polylines multiline_paths(const Lines &lines, double d1, double end_overlap, int sweep, const BoundingBox &cover);
|
||||
|
||||
//
|
||||
// Some of the algorithms used by class FillAdaptive were inspired by
|
||||
// Cura Engine's class SubDivCube
|
||||
|
||||
@@ -3047,12 +3047,56 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar
|
||||
return true;
|
||||
}
|
||||
|
||||
// Upper level of a cubic band [0, h] over one period, from the crossing at (0, tau) to the one at (period, tau).
|
||||
// See docs/HLSD/multiline-infill.md.
|
||||
static std::vector<Vec2d> cubic_upper_level(double tau, double h, double period, double d1)
|
||||
{
|
||||
const double s3 = std::sqrt(3.);
|
||||
const double y_cut = std::clamp(tau - 0.5 * d1, 0., h - d1) + d1;
|
||||
const double y_flat = std::min(h, h + y_cut - 2. * d1);
|
||||
const double x2 = (h - tau) / s3 + d1;
|
||||
const double x3 = (h + tau) / s3 - d1;
|
||||
// (slope, intercept) of the rising line, its chamfer, the horizontal line, the falling chamfer and line.
|
||||
const std::array<Vec2d, 5> lines{ Vec2d(s3, tau), Vec2d(1. / s3, h - x2 / s3), Vec2d(0., y_flat),
|
||||
Vec2d(-1. / s3, h + x3 / s3), Vec2d(-s3, tau + s3 * period) };
|
||||
auto y_at = [&lines, y_cut](double x) {
|
||||
double y = std::numeric_limits<double>::max();
|
||||
for (const Vec2d &l : lines)
|
||||
y = std::min(y, l.x() * x + l.y());
|
||||
return std::max(y, y_cut);
|
||||
};
|
||||
|
||||
std::vector<double> xs;
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
if (lines[i].x() != 0.)
|
||||
xs.emplace_back((y_cut - lines[i].y()) / lines[i].x());
|
||||
for (size_t j = i + 1; j < lines.size(); ++j)
|
||||
xs.emplace_back((lines[j].y() - lines[i].y()) / (lines[i].x() - lines[j].x()));
|
||||
}
|
||||
xs.erase(std::remove_if(xs.begin(), xs.end(), [period](double x) { return x <= 1. || x >= period - 1.; }), xs.end());
|
||||
xs.insert(xs.end(), { 0., period });
|
||||
std::sort(xs.begin(), xs.end());
|
||||
xs.erase(std::unique(xs.begin(), xs.end(), [](double a, double b) { return b - a < 1.; }), xs.end());
|
||||
|
||||
std::vector<Vec2d> pts;
|
||||
for (double x : xs) {
|
||||
const Vec2d p(x, y_at(x));
|
||||
if (pts.size() >= 2) {
|
||||
const Vec2d &a = pts[pts.size() - 2], &b = pts.back();
|
||||
if (std::abs((b.y() - a.y()) / (b.x() - a.x()) - (p.y() - b.y()) / (p.x() - b.x())) < EPSILON)
|
||||
pts.pop_back();
|
||||
}
|
||||
pts.emplace_back(p);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
bool FillRectilinear::fill_surface_trapezoidal(
|
||||
const Surface* surface,
|
||||
FillParams params,
|
||||
const std::initializer_list<SweepParams>& sweep_params,
|
||||
Polylines& polylines_out,
|
||||
int Pattern_type) // 0=grid, 1=triangular, 2=stars
|
||||
int Pattern_type) // 0=grid, 1=triangular, 2=stars, 3=cubic
|
||||
{
|
||||
assert(params.multiline > 1);
|
||||
|
||||
@@ -3350,6 +3394,55 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: // Cubic
|
||||
{
|
||||
// Same z shifted lines as the single-line cubic; the slanted ones cross tau above the horizontal ones.
|
||||
auto pos_mod = [](double a, double m) { const double r = std::fmod(a, m); return r < 0. ? r + m : r; };
|
||||
const double h = 0.5 * std::sqrt(3.0) * period;
|
||||
const double shift = scale_(std::sqrt(0.5) * this->z);
|
||||
const double tau = pos_mod(-3. * shift, h);
|
||||
const double y0 = pos_mod(-2. * shift, 2. * h);
|
||||
|
||||
std::array<std::vector<Vec2d>, 2> levels{ cubic_upper_level(h - tau, h, period, d1), cubic_upper_level(tau, h, period, d1) };
|
||||
for (Vec2d &p : levels.front())
|
||||
p.y() = h - p.y();
|
||||
|
||||
const size_t layer_mod = infill_layer_id % 3;
|
||||
const double angle = layer_mod * 2.0 * M_PI / 3.0;
|
||||
|
||||
// Only cover the surface, seen in the frame the pattern is built in.
|
||||
ExPolygon local = expolygon;
|
||||
local.translate(-rotate_vector.second.x(), -rotate_vector.second.y());
|
||||
if (layer_mod)
|
||||
local.rotate(-angle);
|
||||
BoundingBox cover = get_extents(local);
|
||||
cover.offset(period);
|
||||
|
||||
const int64_t n_min = int64_t(std::floor((cover.min.y() - y0) / h)) - 1;
|
||||
const int64_t n_max = int64_t(std::ceil((cover.max.y() - y0) / h)) + 1;
|
||||
for (int64_t n = n_min; n <= n_max; ++n) {
|
||||
const double x_off = (n & 1) ? 0.5 * period : 0.;
|
||||
const double base = y0 + double(n) * h - tau;
|
||||
const int64_t j_min = int64_t(std::floor((cover.min.x() - x_off) / period)) - 1;
|
||||
const int64_t j_max = int64_t(std::ceil((cover.max.x() - x_off) / period));
|
||||
for (const std::vector<Vec2d> &level : levels) {
|
||||
Polyline row;
|
||||
row.points.reserve(size_t(j_max - j_min + 1) * level.size());
|
||||
for (int64_t j = j_min; j <= j_max; ++j)
|
||||
for (size_t i = (j == j_min) ? 0 : 1; i < level.size(); ++i)
|
||||
row.points.emplace_back(coord_t(std::round(x_off + double(j * period) + level[i].x())),
|
||||
coord_t(std::round(base + level[i].y())));
|
||||
polylines.emplace_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
if (layer_mod)
|
||||
for (Polyline &pl : polylines)
|
||||
pl.rotate(angle, Point(0, 0));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Handle unknown pattern type
|
||||
break;
|
||||
@@ -3532,6 +3625,11 @@ Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶
|
||||
Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
Polylines polylines_out;
|
||||
if (params.multiline > 1) {
|
||||
if (!this->fill_surface_trapezoidal(surface, params, {}, polylines_out, 3))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillCubic::fill_surface_trapezoidal() failed.";
|
||||
return polylines_out;
|
||||
}
|
||||
coordf_t dx = sqrt(0.5) * z;
|
||||
if (! this->fill_surface_by_multilines(
|
||||
surface, params,
|
||||
|
||||
+179
-169
@@ -970,6 +970,27 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// A folded tower layer is thicker than the object layer it sits on, so the height process_layer
|
||||
// emitted is not the tower's. Both writers declare one, but each hardcodes a tag dialect - Type 1
|
||||
// forces s_IsBBLPrinter and writes "; LAYER_HEIGHT:", Type 2 writes ";HEIGHT:" - and the processor
|
||||
// reads only its printer's, so a Type 1 tower on a non-BBL printer loses it and the merged layer
|
||||
// is drawn and costed as a thin one. Declare it here, where the printer is known, unless the tower
|
||||
// already wrote the right tag. _extrude puts the object's height back on the next object path,
|
||||
// since process_layer forces the role to erWipeTower on any layer with a tower.
|
||||
std::string WipeTowerIntegration::tower_height_tag(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr,
|
||||
const std::string &tcr_gcode) const
|
||||
{
|
||||
const std::string tag = ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height);
|
||||
if (! m_sparse_layers_combined || std::abs(gcodegen.m_last_height - tcr.layer_height) <= EPSILON ||
|
||||
tcr_gcode.find(tag) != std::string::npos)
|
||||
return {};
|
||||
// Keep m_last_height what the G-code last declared, so a second visit does not repeat it.
|
||||
gcodegen.m_last_height = tcr.layer_height;
|
||||
char buf[64];
|
||||
sprintf(buf, "%s%g\n", tag.c_str(), tcr.layer_height);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
|
||||
{
|
||||
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
|
||||
@@ -1467,6 +1488,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str));
|
||||
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_filament_id, &config);
|
||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||
gcode += tower_height_tag(gcodegen, tcr, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
// Count the toolchange only when the emitted block really changed the tool —
|
||||
// tower visits without a filament change must not advance the ordinal.
|
||||
@@ -1799,6 +1821,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
std::string tcr_gcode,
|
||||
tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
|
||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||
gcode += tower_height_tag(gcodegen, tcr, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
check_add_eol(toolchange_gcode_str);
|
||||
|
||||
@@ -1946,7 +1969,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
// Folded into a later, thicker layer that prints at its own z: nothing to emit.
|
||||
bool ignore_sparse = wipe_tower_layer_is_combined_away(m_tool_changes[m_layer_idx]);
|
||||
if (m_sparse_layers_skipped) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]) && m_layer_idx != 0;
|
||||
@@ -1964,7 +1988,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
// Folded into a later, thicker layer that prints at its own z: nothing to emit.
|
||||
bool ignore_sparse = wipe_tower_layer_is_combined_away(m_tool_changes[m_layer_idx]);
|
||||
if (m_sparse_layers_skipped) {
|
||||
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]);
|
||||
wipe_tower_z = m_compacted_tower_z[m_layer_idx];
|
||||
@@ -1994,7 +2019,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
if (m_layer_idx >= (int) m_tool_changes.size())
|
||||
return true;
|
||||
|
||||
bool ignore_sparse = false;
|
||||
bool ignore_sparse = wipe_tower_layer_is_combined_away(m_tool_changes[m_layer_idx]);
|
||||
if (m_sparse_layers_skipped)
|
||||
ignore_sparse = wipe_tower_layer_is_sparse(m_tool_changes[m_layer_idx]);
|
||||
|
||||
@@ -3070,162 +3095,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (m_config.small_area_infill_flow_compensation.value && !m_config.small_area_infill_flow_compensation_model.empty())
|
||||
m_small_area_infill_flow_compensator = make_unique<SmallAreaInfillFlowCompensator>(print.config());
|
||||
|
||||
// Process file_start_gcode - written at the very top of the file, before any header
|
||||
{
|
||||
std::string top_gcode_template = print.config().file_start_gcode.value;
|
||||
if (!top_gcode_template.empty()) {
|
||||
DynamicConfig top_config;
|
||||
// file_start_gcode runs before the parser copy that normally restores these, so set them here.
|
||||
PlaceholderParser::update_timestamp(top_config);
|
||||
PlaceholderParser::update_user_name(top_config);
|
||||
top_config.set_key_value("print_time_total_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder)));
|
||||
top_config.set_key_value("print_time_day", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder)));
|
||||
top_config.set_key_value("print_time_hour", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder)));
|
||||
top_config.set_key_value("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
|
||||
top_config.set_key_value("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
||||
top_config.set_key_value("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||
std::string top_gcode = print.placeholder_parser().process(top_gcode_template, 0, &top_config);
|
||||
if (!top_gcode.empty())
|
||||
file.writeln(top_gcode);
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Don't output Header block if BTT thumbnail is identified in the list
|
||||
// Get the thumbnails value as a string
|
||||
std::string thumbnails_value = print.config().option<ConfigOptionString>("thumbnails")->value;
|
||||
// search string for the BTT_TFT label
|
||||
bool has_BTT_thumbnail = (thumbnails_value.find("BTT_TFT") != std::string::npos);
|
||||
|
||||
if(!has_BTT_thumbnail){
|
||||
file.write_format("; HEADER_BLOCK_START\n");
|
||||
// Write information on the generator.
|
||||
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
|
||||
if (is_bbl_printers)
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||
//BBS: total layer number
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str());
|
||||
//Orca: extra check for bbl printer
|
||||
if (is_bbl_printers) {
|
||||
if (print.calib_params().mode == CalibMode::Calib_None) { // Don't support skipping in cali mode
|
||||
// list all label_object_id with sorted order here
|
||||
m_enable_exclude_object = true;
|
||||
m_label_objects_ids.clear();
|
||||
m_label_objects_ids.reserve(print.num_object_instances());
|
||||
for (const PrintObject *print_object : print.objects())
|
||||
for (const PrintInstance &print_instance : print_object->instances())
|
||||
m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id());
|
||||
|
||||
std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end());
|
||||
|
||||
std::string objects_id_list = "; model label id: ";
|
||||
for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++)
|
||||
objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n"));
|
||||
file.writeln(objects_id_list);
|
||||
} else {
|
||||
m_enable_exclude_object = false;
|
||||
m_label_objects_ids.clear();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::string filament_density_list = "; filament_density: ";
|
||||
(filament_density_list+=m_config.filament_density.serialize()) +='\n';
|
||||
file.writeln(filament_density_list);
|
||||
|
||||
std::string filament_diameter_list = "; filament_diameter: ";
|
||||
(filament_diameter_list += m_config.filament_diameter.serialize()) += '\n';
|
||||
file.writeln(filament_diameter_list);
|
||||
|
||||
coordf_t max_height_z = -1;
|
||||
for (const auto& object : print.objects())
|
||||
max_height_z = std::max(object->layers().back()->print_z, max_height_z);
|
||||
|
||||
std::ostringstream max_height_z_tip;
|
||||
max_height_z_tip<<"; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n';
|
||||
file.writeln(max_height_z_tip.str());
|
||||
}
|
||||
|
||||
{
|
||||
auto used_filaments = print.get_slice_used_filaments(false);
|
||||
std::ostringstream out;
|
||||
out << "; filament: ";
|
||||
for (size_t idx = 0; idx < used_filaments.size(); ++idx) {
|
||||
if (idx != 0)
|
||||
out << ',';
|
||||
out << used_filaments[idx] + 1;
|
||||
}
|
||||
file.writeln(out.str());
|
||||
}
|
||||
|
||||
file.write_format("; HEADER_BLOCK_END\n\n");
|
||||
}
|
||||
|
||||
// BBS: write global config at the beginning of gcode file because printer
|
||||
// need these config information
|
||||
// Append full config, delimited by two 'phony' configuration keys
|
||||
// CONFIG_BLOCK_START and CONFIG_BLOCK_END. The delimiters are structured
|
||||
// as configuration key / value pairs to be parsable by older versions of
|
||||
// PrusaSlicer G-code viewer.
|
||||
{
|
||||
if (is_bbl_printers && !skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple image
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n",
|
||||
first_layer_bed_temperature);
|
||||
file.write_format(
|
||||
"; first_layer_temperature = %d\n",
|
||||
print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
} else if (thumbnail_cb != nullptr) {
|
||||
// generate the thumbnails
|
||||
auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
|
||||
|
||||
if (errors != enum_bitmask<ThumbnailError>()) {
|
||||
std::string error_str = format("Invalid thumbnails value:");
|
||||
error_str += GCodeThumbnails::get_error_string(errors);
|
||||
throw Slic3r::ExportError(error_str);
|
||||
}
|
||||
|
||||
if (!thumbnails.empty())
|
||||
GCodeThumbnails::export_thumbnails_to_file(
|
||||
thumbnail_cb, print.get_plate_index(), thumbnails, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write some terse information on the slicing parameters.
|
||||
const PrintObject *first_object = print.objects().front();
|
||||
const double layer_height = first_object->config().layer_height.value;
|
||||
const double initial_layer_print_height = print.config().initial_layer_print_height.value;
|
||||
for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = print.get_print_region(region_id);
|
||||
file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width());
|
||||
file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width());
|
||||
file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width());
|
||||
file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width());
|
||||
file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width());
|
||||
if (print.has_support_material())
|
||||
file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
|
||||
if (print.config().initial_layer_line_width.value > 0)
|
||||
file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, initial_layer_print_height, true).width());
|
||||
file.write_format("\n");
|
||||
}
|
||||
|
||||
file.write_format("; EXECUTABLE_BLOCK_START\n");
|
||||
|
||||
// SoftFever
|
||||
if( m_enable_exclude_object)
|
||||
file.write(set_object_info(&print));
|
||||
|
||||
// adds tags for time estimators
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str());
|
||||
|
||||
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
|
||||
m_placeholder_parser_integration.parser = print.placeholder_parser();
|
||||
m_placeholder_parser_integration.parser.update_timestamp();
|
||||
@@ -3360,16 +3229,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// Orca: Initialise AdaptivePA processor filter
|
||||
m_pa_processor = std::make_unique<AdaptivePAProcessor>(*this, tool_ordering.all_extruders());
|
||||
|
||||
// Emit machine envelope limits for the Marlin firmware.
|
||||
this->print_machine_envelope(file, print);
|
||||
|
||||
// Disable fan.
|
||||
if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) {
|
||||
file.write(m_writer.set_fan(0));
|
||||
//BBS: disable additional fan
|
||||
file.write(m_writer.set_additional_fan(0));
|
||||
}
|
||||
|
||||
// Update output variables after the extruders were initialized.
|
||||
m_placeholder_parser_integration.init(m_writer);
|
||||
|
||||
@@ -3721,6 +3580,157 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// Sync variant-mapped params into placeholder_parser before processing start gcode
|
||||
update_placeholder_parser_with_variant_params();
|
||||
|
||||
// Expand the file header only after the start-up placeholders and writer state are ready.
|
||||
// Use the regular custom G-code path so invalid placeholders report the template name.
|
||||
if (!print.config().file_start_gcode.value.empty())
|
||||
file.writeln(this->placeholder_parser_process("file_start_gcode", print.config().file_start_gcode.value, initial_extruder_id));
|
||||
|
||||
// Orca: Don't output Header block if BTT thumbnail is identified in the list
|
||||
// Get the thumbnails value as a string
|
||||
std::string thumbnails_value = print.config().option<ConfigOptionString>("thumbnails")->value;
|
||||
// search string for the BTT_TFT label
|
||||
bool has_BTT_thumbnail = (thumbnails_value.find("BTT_TFT") != std::string::npos);
|
||||
|
||||
if(!has_BTT_thumbnail){
|
||||
file.write_format("; HEADER_BLOCK_START\n");
|
||||
// Write information on the generator.
|
||||
file.write_format("; generated by %s on %s\n", Slic3r::header_slic3r_generated().c_str(), Slic3r::Utils::local_timestamp().c_str());
|
||||
if (is_bbl_printers)
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
|
||||
//BBS: total layer number
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str());
|
||||
//Orca: extra check for bbl printer
|
||||
if (is_bbl_printers) {
|
||||
if (print.calib_params().mode == CalibMode::Calib_None) { // Don't support skipping in cali mode
|
||||
// list all label_object_id with sorted order here
|
||||
m_enable_exclude_object = true;
|
||||
m_label_objects_ids.clear();
|
||||
m_label_objects_ids.reserve(print.num_object_instances());
|
||||
for (const PrintObject *print_object : print.objects())
|
||||
for (const PrintInstance &print_instance : print_object->instances())
|
||||
m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id());
|
||||
|
||||
std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end());
|
||||
|
||||
std::string objects_id_list = "; model label id: ";
|
||||
for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++)
|
||||
objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n"));
|
||||
file.writeln(objects_id_list);
|
||||
} else {
|
||||
m_enable_exclude_object = false;
|
||||
m_label_objects_ids.clear();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::string filament_density_list = "; filament_density: ";
|
||||
(filament_density_list+=m_config.filament_density.serialize()) +='\n';
|
||||
file.writeln(filament_density_list);
|
||||
|
||||
std::string filament_diameter_list = "; filament_diameter: ";
|
||||
(filament_diameter_list += m_config.filament_diameter.serialize()) += '\n';
|
||||
file.writeln(filament_diameter_list);
|
||||
|
||||
coordf_t max_height_z = -1;
|
||||
for (const auto& object : print.objects())
|
||||
max_height_z = std::max(object->layers().back()->print_z, max_height_z);
|
||||
|
||||
std::ostringstream max_height_z_tip;
|
||||
max_height_z_tip<<"; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n';
|
||||
file.writeln(max_height_z_tip.str());
|
||||
}
|
||||
|
||||
{
|
||||
auto used_filaments = print.get_slice_used_filaments(false);
|
||||
std::ostringstream out;
|
||||
out << "; filament: ";
|
||||
for (size_t idx = 0; idx < used_filaments.size(); ++idx) {
|
||||
if (idx != 0)
|
||||
out << ',';
|
||||
out << used_filaments[idx] + 1;
|
||||
}
|
||||
file.writeln(out.str());
|
||||
}
|
||||
|
||||
file.write_format("; HEADER_BLOCK_END\n\n");
|
||||
}
|
||||
|
||||
// BBS: write global config at the beginning of gcode file because printer
|
||||
// need these config information
|
||||
// Append full config, delimited by two 'phony' configuration keys
|
||||
// CONFIG_BLOCK_START and CONFIG_BLOCK_END. The delimiters are structured
|
||||
// as configuration key / value pairs to be parsable by older versions of
|
||||
// PrusaSlicer G-code viewer.
|
||||
{
|
||||
if (is_bbl_printers && !skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple image
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n",
|
||||
first_layer_bed_temperature);
|
||||
file.write_format(
|
||||
"; first_layer_temperature = %d\n",
|
||||
print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
} else if (thumbnail_cb != nullptr) {
|
||||
// generate the thumbnails
|
||||
auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
|
||||
|
||||
if (errors != enum_bitmask<ThumbnailError>()) {
|
||||
std::string error_str = format("Invalid thumbnails value:");
|
||||
error_str += GCodeThumbnails::get_error_string(errors);
|
||||
throw Slic3r::ExportError(error_str);
|
||||
}
|
||||
|
||||
if (!thumbnails.empty())
|
||||
GCodeThumbnails::export_thumbnails_to_file(
|
||||
thumbnail_cb, print.get_plate_index(), thumbnails, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write some terse information on the slicing parameters.
|
||||
const PrintObject *first_object = print.objects().front();
|
||||
const double layer_height = first_object->config().layer_height.value;
|
||||
const double initial_layer_print_height = print.config().initial_layer_print_height.value;
|
||||
for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) {
|
||||
const PrintRegion ®ion = print.get_print_region(region_id);
|
||||
file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width());
|
||||
file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width());
|
||||
file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width());
|
||||
file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width());
|
||||
file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width());
|
||||
if (print.has_support_material())
|
||||
file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
|
||||
if (print.config().initial_layer_line_width.value > 0)
|
||||
file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, initial_layer_print_height, true).width());
|
||||
file.write_format("\n");
|
||||
}
|
||||
|
||||
file.write_format("; EXECUTABLE_BLOCK_START\n");
|
||||
|
||||
// SoftFever
|
||||
if( m_enable_exclude_object)
|
||||
file.write(set_object_info(&print));
|
||||
|
||||
// adds tags for time estimators
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str());
|
||||
|
||||
// Emit machine envelope limits for the Marlin firmware.
|
||||
this->print_machine_envelope(file, print);
|
||||
|
||||
// Disable fan.
|
||||
if (m_config.auxiliary_fan.value && print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) {
|
||||
file.write(m_writer.set_fan(0));
|
||||
//BBS: disable additional fan
|
||||
file.write(m_writer.set_additional_fan(0));
|
||||
}
|
||||
|
||||
std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id);
|
||||
if (print.config().gcode_flavor != gcfKlipper) {
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
|
||||
@@ -107,7 +107,8 @@ public:
|
||||
m_is_first_print(true),
|
||||
m_print_config(&print_config),
|
||||
m_last_wipe_tower_print_z(print_config.z_offset.value),
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(print_config))
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(print_config)),
|
||||
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(print_config))
|
||||
{
|
||||
// Precomputed rather than accumulated while emitting, so that the clearance validator and
|
||||
// the emitter cannot disagree about where the compacted tower sits on any given layer.
|
||||
@@ -138,6 +139,7 @@ public:
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string tower_height_tag(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, const std::string &tcr_gcode) const;
|
||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const Polygons &bed_polygons) const;
|
||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
||||
@@ -175,6 +177,9 @@ private:
|
||||
// wipe_tower_no_sparse_layers, as answered by the shared compaction rule rather than by the raw
|
||||
// option: smooth timelapse and wrapping detection keep a tower on every layer regardless.
|
||||
const bool m_sparse_layers_skipped;
|
||||
// Combined tower layers are thicker than the object layer they sit on, the only case where the
|
||||
// tower's height is not the one process_layer already declared.
|
||||
const bool m_sparse_layers_combined;
|
||||
// Print z of the compacted tower per planned layer. Empty when the tower is not compacted.
|
||||
std::vector<float> m_compacted_tower_z;
|
||||
};
|
||||
|
||||
@@ -49,6 +49,48 @@ std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<
|
||||
return tower_z;
|
||||
}
|
||||
|
||||
bool wipe_tower_sparse_layers_combined(const PrintConfig &config)
|
||||
{
|
||||
return config.wipe_tower_sparse_layers_combination.value && ! wipe_tower_sparse_layers_skipped(config) &&
|
||||
config.timelapse_type.value != TimelapseType::tlSmooth && ! config.enable_wrapping_detection.value;
|
||||
}
|
||||
|
||||
bool wipe_tower_layer_is_combined_away(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes)
|
||||
{
|
||||
return ! layer_tool_changes.empty() && layer_tool_changes.front().combined_away;
|
||||
}
|
||||
|
||||
std::vector<char> combine_sparse_wipe_tower_layers(std::vector<float> &layer_height,
|
||||
const std::vector<char> &layer_is_sparse,
|
||||
const std::vector<float> &max_layer_height,
|
||||
size_t first_layer_idx)
|
||||
{
|
||||
assert(layer_is_sparse.size() == layer_height.size() && max_layer_height.size() == layer_height.size());
|
||||
std::vector<char> combined_away(layer_height.size(), 0);
|
||||
float pending_height = 0.f; // what the layers folded away so far add up to
|
||||
for (size_t i = 0; i < layer_height.size(); ++i) {
|
||||
// A toolchange has to purge at its own z, so it neither folds away nor takes over the run
|
||||
// below it - and a run always flushes on its own last layer, so nothing is ever pending here.
|
||||
if (! layer_is_sparse[i] || i <= first_layer_idx) {
|
||||
pending_height = 0.f;
|
||||
continue;
|
||||
}
|
||||
const float merged = pending_height + layer_height[i];
|
||||
// Hand the run on only if the next layer can swallow the whole thing; a layer already past
|
||||
// the cap is left alone rather than shrunk.
|
||||
const bool next_takes_it = i + 1 < layer_height.size() && layer_is_sparse[i + 1] &&
|
||||
merged + layer_height[i + 1] <= max_layer_height[i + 1] + float(EPSILON);
|
||||
if (next_takes_it) {
|
||||
combined_away[i] = 1;
|
||||
pending_height = merged;
|
||||
} else {
|
||||
layer_height[i] = merged;
|
||||
pending_height = 0.f;
|
||||
}
|
||||
}
|
||||
return combined_away;
|
||||
}
|
||||
|
||||
inline float align_round(float value, float base)
|
||||
{
|
||||
return std::round(value / base) * base;
|
||||
@@ -1904,6 +1946,7 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
|
||||
//m_bridging(float(config.wipe_tower_bridging)),
|
||||
m_bridging(10.f),
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(config)),
|
||||
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(config)),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_current_tool(initial_tool),
|
||||
@@ -2028,6 +2071,16 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
|
||||
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
|
||||
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
|
||||
|
||||
// Orca: max_layer_height is per nozzle, so read it through the filament->nozzle map rather than
|
||||
// by filament id. Zero means three quarters of the nozzle diameter, as in Slicing.cpp.
|
||||
{
|
||||
const std::vector<int> &filament_map = config.filament_map.values; // 1 based nozzle indices
|
||||
const size_t nozzle_idx = idx < filament_map.size() && filament_map[idx] > 0 ? size_t(filament_map[idx] - 1) : 0;
|
||||
const float max_layer_height = float(config.max_layer_height.get_at(nozzle_idx));
|
||||
m_filpar[idx].max_layer_height = max_layer_height > 0.f ? max_layer_height
|
||||
: 0.75f * float(config.nozzle_diameter.get_at(nozzle_idx));
|
||||
}
|
||||
|
||||
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
|
||||
if (max_vol_speed!= 0.f)
|
||||
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
|
||||
@@ -3001,7 +3054,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (! m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -3898,7 +3951,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter,
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -4008,7 +4061,7 @@ WipeTower::ToolChangeResult WipeTower::finish_block(const WipeTowerBlock &block,
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (filament_id < m_used_filament_length.size())
|
||||
m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -4125,7 +4178,7 @@ WipeTower::ToolChangeResult WipeTower::finish_block_solid(const WipeTowerBlock &
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (filament_id < m_used_filament_length.size())
|
||||
m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
@@ -4668,6 +4721,15 @@ void WipeTower::calc_block_infill_gap()
|
||||
m_extra_spacing = 1.f;
|
||||
}
|
||||
|
||||
// A folded layer is generated like any other but thrown away by the emitter, so its extrusions must
|
||||
// not be charged to the filament used.
|
||||
bool WipeTower::layer_is_printed(bool toolchanges_on_layer) const
|
||||
{
|
||||
if (m_layer_info != m_plan.end() && m_layer_info->combined_away)
|
||||
return false;
|
||||
return ! m_sparse_layers_skipped || toolchanges_on_layer;
|
||||
}
|
||||
|
||||
void WipeTower::plan_tower_new()
|
||||
{
|
||||
if (m_wipe_tower_brim_width < 0) m_wipe_tower_brim_width = get_auto_brim_by_height(m_wipe_tower_height);
|
||||
@@ -4820,6 +4882,9 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
if (m_plan.empty())
|
||||
return;
|
||||
//m_extra_spacing = 1.f;
|
||||
// Before planning: the layer heights this rewrites feed the extrusion flow of every later pass.
|
||||
if (m_sparse_layers_combined)
|
||||
combine_sparse_wipe_tower_plan(m_plan, m_filpar, m_first_layer_idx, m_current_tool);
|
||||
m_wipe_tower_height = m_plan.back().z;//real wipe_tower_height
|
||||
plan_tower_new();
|
||||
m_layer_info = m_plan.begin();
|
||||
@@ -5014,6 +5079,9 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
if (only_generate_wall && !timelapse_wall.gcode.empty()) {
|
||||
layer_result.insert(layer_result.begin(), std::move(timelapse_wall));
|
||||
}
|
||||
if (layer.combined_away)
|
||||
for (WipeTower::ToolChangeResult &tcr : layer_result)
|
||||
tcr.combined_away = true;
|
||||
result.emplace_back(std::move(layer_result));
|
||||
}
|
||||
assert(m_outer_wall.size() == m_plan.size());
|
||||
@@ -5179,7 +5247,7 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode)
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (!m_sparse_layers_skipped || toolchanges_on_layer)
|
||||
if (layer_is_printed(toolchanges_on_layer))
|
||||
if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
return construct_tcr(writer, false, old_tool, true, false, 0.f, false);
|
||||
|
||||
@@ -152,6 +152,10 @@ public:
|
||||
bool is_contact = false;
|
||||
NozzleChangeResult nozzle_change_result;
|
||||
|
||||
// Orca: folded into a later, thicker layer, so the emitter drops it. Set by the tower, so
|
||||
// the two cannot disagree about which layers print.
|
||||
bool combined_away = false;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
float total_extrusion_length_in_plane() {
|
||||
float e_length = 0.f;
|
||||
@@ -391,6 +395,8 @@ public:
|
||||
float filament_tower_interface_pre_extrusion_dist = 0;
|
||||
float filament_tower_interface_pre_extrusion_length = 0;
|
||||
float filament_petg_pre_extrusion_offset_dist = 0;
|
||||
// Tallest layer this filament's nozzle can lay down; caps the sparse layer combination.
|
||||
float max_layer_height = 0.f;
|
||||
};
|
||||
|
||||
|
||||
@@ -522,6 +528,7 @@ private:
|
||||
//float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_sparse_layers_skipped = false;
|
||||
bool m_sparse_layers_combined = false;
|
||||
// BBS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
@@ -595,6 +602,8 @@ private:
|
||||
}
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
// Whether the layer reaches the G-code, and so whether its extrusions count as filament used.
|
||||
bool layer_is_printed(bool toolchanges_on_layer) const;
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
@@ -634,6 +643,8 @@ private:
|
||||
float depth; // depth of the layer based on all layers above
|
||||
float extra_spacing;
|
||||
bool extruder_fill{true};
|
||||
// Folded into a later, thicker layer, so this one prints nothing at all.
|
||||
bool combined_away{false};
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
@@ -700,6 +711,62 @@ std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<
|
||||
float base_z = 0.f);
|
||||
|
||||
|
||||
// Combination rule for wipe_tower_sparse_layers_combination. Nothing is compacted - the tower keeps
|
||||
// following the object - but a run of consecutive toolchange-free layers prints as one thicker layer,
|
||||
// the way infill combination merges sparse infill. Shared so that neither tower generator nor the
|
||||
// G-code emitter can combine on its own.
|
||||
|
||||
// Whether sparse layers are really combined. Skipping them outright is the stronger answer to the
|
||||
// same problem and wins over this; smooth timelapse and wrapping detection need a tower on every
|
||||
// layer, so they rule it out too.
|
||||
bool wipe_tower_sparse_layers_combined(const PrintConfig &config);
|
||||
|
||||
// A planned layer folded into a later, thicker one prints nothing at all.
|
||||
bool wipe_tower_layer_is_combined_away(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes);
|
||||
|
||||
// Folds runs of sparse layers into one. layer_height is raised in place on the layer that prints a
|
||||
// run - always its last, so the merged extrusion lands on top of what it covers - and the returned
|
||||
// mask marks the layers that now print nothing. A run stops growing once one more layer would pass
|
||||
// max_layer_height of the nozzle that prints it. first_layer_idx and below never combine: the
|
||||
// tower's first layer carries the brim.
|
||||
std::vector<char> combine_sparse_wipe_tower_layers(std::vector<float> &layer_height,
|
||||
const std::vector<char> &layer_is_sparse,
|
||||
const std::vector<float> &max_layer_height,
|
||||
size_t first_layer_idx);
|
||||
|
||||
// Applies the rule above to a planned tower. Either generator's plan fits: both carry height,
|
||||
// tool_changes and combined_away per layer, and index their filament parameters by tool.
|
||||
template<class PlanLayers, class FilamentParams>
|
||||
void combine_sparse_wipe_tower_plan(PlanLayers &plan, const FilamentParams &filpar, size_t first_layer_idx, size_t initial_tool)
|
||||
{
|
||||
const size_t n = plan.size();
|
||||
std::vector<float> heights(n);
|
||||
std::vector<char> sparse(n);
|
||||
std::vector<float> caps(n);
|
||||
|
||||
// A layer with no toolchange prints with the filament the layer below left loaded.
|
||||
size_t tool = initial_tool;
|
||||
for (const auto &layer : plan)
|
||||
if (! layer.tool_changes.empty()) {
|
||||
tool = layer.tool_changes.front().old_tool;
|
||||
break;
|
||||
}
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
heights[i] = plan[i].height;
|
||||
sparse[i] = plan[i].tool_changes.empty() ? 1 : 0;
|
||||
caps[i] = tool < filpar.size() ? filpar[tool].max_layer_height : 0.f;
|
||||
if (! plan[i].tool_changes.empty())
|
||||
tool = plan[i].tool_changes.back().new_tool;
|
||||
}
|
||||
|
||||
const std::vector<char> combined_away = combine_sparse_wipe_tower_layers(heights, sparse, caps, first_layer_idx);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
plan[i].height = heights[i];
|
||||
plan[i].combined_away = combined_away[i] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
||||
|
||||
@@ -1033,6 +1033,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
m_z_pos(0.f),
|
||||
m_bridging(float(config.wipe_tower_bridging)),
|
||||
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(config)),
|
||||
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(config)),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
m_infill_speed(default_region_config.sparse_infill_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
|
||||
@@ -1150,6 +1151,16 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
||||
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
|
||||
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
|
||||
|
||||
// Orca: max_layer_height is per nozzle, so read it through the filament->nozzle map rather than
|
||||
// by filament id. Zero means three quarters of the nozzle diameter, as in Slicing.cpp.
|
||||
{
|
||||
const std::vector<int> &filament_map = config.filament_map.values; // 1 based nozzle indices
|
||||
const size_t nozzle_idx = idx < filament_map.size() && filament_map[idx] > 0 ? size_t(filament_map[idx] - 1) : 0;
|
||||
const float max_layer_height = float(config.max_layer_height.get_at(nozzle_idx));
|
||||
m_filpar[idx].max_layer_height = max_layer_height > 0.f ? max_layer_height
|
||||
: 0.75f * float(config.nozzle_diameter.get_at(nozzle_idx));
|
||||
}
|
||||
|
||||
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
|
||||
if (max_vol_speed!= 0.f)
|
||||
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
|
||||
@@ -2103,7 +2114,9 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (! m_sparse_layers_skipped || toolchanges_on_layer || first_layer) {
|
||||
// A folded layer prints nothing, so it consumes nothing and adds no height of its own.
|
||||
const bool combined_away = m_layer_info != m_plan.end() && m_layer_info->combined_away;
|
||||
if ((! m_sparse_layers_skipped || toolchanges_on_layer || first_layer) && ! combined_away) {
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
m_current_height += m_layer_info->height;
|
||||
@@ -2435,6 +2448,10 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
if (m_plan.empty())
|
||||
return;
|
||||
|
||||
// Before planning: the layer heights this rewrites feed the extrusion flow of every later pass.
|
||||
if (m_sparse_layers_combined)
|
||||
combine_sparse_wipe_tower_plan(m_plan, m_filpar, m_first_layer_idx, m_current_tool);
|
||||
|
||||
plan_tower();
|
||||
#if 1
|
||||
for (int i=0;i<5;++i) {
|
||||
@@ -2533,6 +2550,10 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr);
|
||||
}
|
||||
|
||||
if (layer.combined_away)
|
||||
for (WipeTower::ToolChangeResult &tcr : layer_result)
|
||||
tcr.combined_away = true;
|
||||
|
||||
result.emplace_back(std::move(layer_result));
|
||||
|
||||
if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z)
|
||||
|
||||
@@ -200,6 +200,8 @@ public:
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
// Tallest layer this filament's nozzle can lay down; caps the sparse layer combination.
|
||||
float max_layer_height = 0.f;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -268,6 +270,7 @@ private:
|
||||
float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_sparse_layers_skipped = false;
|
||||
bool m_sparse_layers_combined = false;
|
||||
bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
@@ -368,6 +371,8 @@ private:
|
||||
float z; // z position of the layer
|
||||
float height; // layer height
|
||||
float depth; // depth of the layer based on all layers above
|
||||
// Folded into a later, thicker layer, so this one prints nothing at all.
|
||||
bool combined_away{false};
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
|
||||
@@ -2256,13 +2256,45 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
|
||||
// ORCA:
|
||||
// Inner Outer Inner wall ordering mode perimeter order optimisation functions
|
||||
|
||||
// Whether two Arachne lines touch: somewhere the gap between their centrelines is no more than the
|
||||
// touching distance there. Each junction of one line is measured against the segments of the other,
|
||||
// both ways, and the search stops at the first spot that touches.
|
||||
// Arachne varies line width to fill the region (e.g. the odd centre line of a narrow wall is wider
|
||||
// than nominal), so the touching distance is half the combined width at the closest points, not the
|
||||
// nominal spacing. Widths are taken locally so a line widened in one place (a wedge tip, a wall
|
||||
// transition) does not count as touching where it passes close to other perimeters. min_threshold keeps
|
||||
// the nominal spacing threshold as the lower bound.
|
||||
static bool arachne_lines_touch(const Arachne::ExtrusionLine &a, const Arachne::ExtrusionLine &b, double min_threshold)
|
||||
{
|
||||
auto one_way = [min_threshold](const Arachne::ExtrusionLine &from, const Arachne::ExtrusionLine &to) {
|
||||
for (const Arachne::ExtrusionJunction &j : from.junctions) {
|
||||
const Vec2d p = j.p.cast<double>();
|
||||
for (size_t k = 0; k + 1 < to.junctions.size(); ++k) {
|
||||
const Arachne::ExtrusionJunction &j0 = to.junctions[k];
|
||||
const Arachne::ExtrusionJunction &j1 = to.junctions[k + 1];
|
||||
const Vec2d s0 = j0.p.cast<double>();
|
||||
const Vec2d seg = j1.p.cast<double>() - s0;
|
||||
const double l2 = seg.squaredNorm();
|
||||
const double t = l2 > 0. ? std::clamp((p - s0).dot(seg) / l2, 0., 1.) : 0.;
|
||||
const double w = double(j0.w) + t * double(j1.w - j0.w); // width of `to` at the closest point
|
||||
const double touch_distance = std::max(min_threshold, 0.5 * (double(j.w) + w));
|
||||
if ((s0 + t * seg - p).norm() <= touch_distance)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return one_way(a, b) || one_way(b, a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds all perimeters touching a given set of reference lines, given as indexes.
|
||||
*
|
||||
* @param entities The list of PerimeterGeneratorArachneExtrusion entities.
|
||||
* @param referenceIndices A set of indices representing the reference points.
|
||||
* @param threshold_external The distance threshold to consider for proximity for a reference perimeter with inset index 0
|
||||
* @param threshold_internal The distance threshold to consider for proximity for a reference perimeter with inset index 1+
|
||||
* @param threshold_external The minimum touching distance for a reference perimeter with inset index 0
|
||||
* @param threshold_internal The minimum touching distance for a reference perimeter with inset index 1+
|
||||
* @param considered_inset_idx What perimeter inset index are we searching for (eg. if we are searching for first internal perimeters proximate to the current reference perimeter, this value should be set to 1 etc).
|
||||
* @return std::vector<int> A vector of indices representing the touching perimeters.
|
||||
*/
|
||||
@@ -2271,7 +2303,6 @@ std::vector<int> findAllTouchingPerimeters(const std::vector<PerimeterGeneratorA
|
||||
|
||||
for (const int refIdx : referenceIndices) {
|
||||
const auto& referenceEntity = entities[refIdx];
|
||||
Points referencePoints = Arachne::to_points(*referenceEntity.extrusion);
|
||||
for (size_t i = 0; i < entities.size(); ++i) {
|
||||
// Skip already considered references and the reference entity
|
||||
if (referenceIndices.count(i) > 0) continue;
|
||||
@@ -2282,15 +2313,9 @@ std::vector<int> findAllTouchingPerimeters(const std::vector<PerimeterGeneratorA
|
||||
continue; // skip if they dont match
|
||||
}
|
||||
|
||||
Points points = Arachne::to_points(*entity.extrusion);
|
||||
double distance = MultiPoint::minimumDistanceBetweenLinesDefinedByPoints(referencePoints, points);
|
||||
// Add to touchingIndices if within threshold distance
|
||||
size_t threshold=0;
|
||||
if(referenceEntity.extrusion->inset_idx == 0)
|
||||
threshold = threshold_external;
|
||||
else
|
||||
threshold = threshold_internal;
|
||||
if (distance <= threshold) {
|
||||
// Add to touchingIndices if the lines touch.
|
||||
const double threshold = double(referenceEntity.extrusion->inset_idx == 0 ? threshold_external : threshold_internal);
|
||||
if (arachne_lines_touch(*referenceEntity.extrusion, *entity.extrusion, threshold)) {
|
||||
touchingIndices.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_PerimeterGenerator_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "Layer.hpp"
|
||||
#include "Flow.hpp"
|
||||
@@ -105,6 +106,8 @@ public:
|
||||
bool has_fuzzy_hole = false;
|
||||
// Preserve construction order so overlap precedence remains deterministic.
|
||||
std::vector<std::pair<FuzzySkinConfig, ExPolygons>> regions_by_fuzzify;
|
||||
// Area resting on the layer below, where fuzzy skin is allowed. Unset means no restriction.
|
||||
std::optional<ExPolygons> fuzzy_supported_area;
|
||||
|
||||
PerimeterGenerator(
|
||||
// Input:
|
||||
|
||||
@@ -1199,6 +1199,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"enable_tower_interface_features",
|
||||
"enable_tower_interface_cooldown_during_tower",
|
||||
"wipe_tower_no_sparse_layers",
|
||||
"wipe_tower_sparse_layers_combination",
|
||||
"compatible_printers",
|
||||
"compatible_printers_condition",
|
||||
"inherits",
|
||||
|
||||
@@ -378,6 +378,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "wipe_tower_bridging"
|
||||
|| opt_key == "wipe_tower_extra_flow"
|
||||
|| opt_key == "wipe_tower_no_sparse_layers"
|
||||
|| opt_key == "wipe_tower_sparse_layers_combination"
|
||||
|| opt_key == "flush_volumes_matrix"
|
||||
|| opt_key == "prime_volume"
|
||||
|| opt_key == "flush_into_infill"
|
||||
|
||||
@@ -6705,6 +6705,20 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("wipe_tower_sparse_layers_combination", coBool);
|
||||
def->label = L("Combine sparse layers");
|
||||
def->tooltip = L("If enabled, consecutive layers on which the prime tower has no filament change are printed as a single "
|
||||
"thicker tower layer instead of one thin layer each, the same way infill combination merges sparse infill. "
|
||||
"The merged layer is printed at the top of the run, at the height of everything it covers.\n\n"
|
||||
"Only whole layers are merged, and never past the maximum layer height of the nozzle printing the tower "
|
||||
"(three quarters of the nozzle diameter when that is left at 0). Two or more layers therefore have to fit "
|
||||
"under that limit before anything changes at all: at a 0.2 mm layer height under a 0.3 mm maximum nothing "
|
||||
"is merged, while at 0.1 mm three layers become one.\n\n"
|
||||
"Unlike \"No sparse layers\" the tower keeps following the model, so the toolhead never has to reach down to it. "
|
||||
"Has no effect with \"No sparse layers\", smooth timelapse or clumping detection, which need a tower on every layer.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("single_extruder_multi_material_priming", coBool);
|
||||
def->label = L("Prime all printing extruders");
|
||||
def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print.");
|
||||
|
||||
@@ -163,6 +163,7 @@ inline bool is_smoothable_infill_pattern(InfillPattern pattern, int multiline =
|
||||
case ipGrid:
|
||||
case ipTriangles:
|
||||
case ipStars:
|
||||
case ipCubic:
|
||||
return multiline > 1;
|
||||
default:
|
||||
return false;
|
||||
@@ -1631,6 +1632,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionString, toolchange_cyclic_order))
|
||||
((ConfigOptionBool, toolchange_cyclic_first_layer))
|
||||
((ConfigOptionBool, wipe_tower_no_sparse_layers))
|
||||
((ConfigOptionBool, wipe_tower_sparse_layers_combination))
|
||||
((ConfigOptionString, change_filament_gcode))
|
||||
((ConfigOptionString, change_extrusion_role_gcode))
|
||||
((ConfigOptionString, process_change_extrusion_role_gcode))
|
||||
|
||||
@@ -819,7 +819,7 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
|
||||
m_scrolled_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL | wxHSCROLL);
|
||||
m_scrolled_window->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(20));
|
||||
|
||||
wxBoxSizer *title_sizer_h= new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer *title_sizer_v = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -1000,13 +1000,13 @@ AmsMapingPopup::AmsMapingPopup(wxWindow *parent, bool use_in_sync_dialog) :
|
||||
show_pos.x = screen_size.GetLeft();
|
||||
m_scrolled_window->SetMaxSize(wxSize(screen_size.GetWidth(), popup_height));
|
||||
m_scrolled_window->SetMinSize(wxSize(screen_size.GetWidth(), popup_height));
|
||||
m_scrolled_window->SetScrollRate(FromDIP(10), FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(FromDIP(10), FromDIP(20));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_scrolled_window->SetMaxSize(wxSize(popup_width, popup_height));
|
||||
m_scrolled_window->SetMinSize(wxSize(popup_width, popup_height));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(10));
|
||||
m_scrolled_window->SetScrollRate(0, FromDIP(20));
|
||||
}
|
||||
|
||||
//Vertical Direction Processing
|
||||
@@ -2117,7 +2117,7 @@ void AmsReplaceMaterialDialog::create()
|
||||
identical_filament->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#009688"))); // Orca: accent teal (not brand green)
|
||||
|
||||
m_scrollview_groups = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrollview_groups->SetScrollRate(5, 5);
|
||||
m_scrollview_groups->SetScrollRate(5, FromDIP(20));
|
||||
//m_scrollview_groups->SetMinSize(wxSize(400, 400));
|
||||
//m_scrollview_groups->SetMaxSize(wxSize(400, 400));
|
||||
m_scrollview_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -606,7 +606,7 @@ AuFolderPanel::AuFolderPanel(wxWindow *parent, AuxiliaryFolderType type, wxWindo
|
||||
wxBoxSizer *sizer_main = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
wxBoxSizer *sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
wxBoxSizer *sizer_top = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
|
||||
//show bind failed info
|
||||
m_sw_bind_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(450), FromDIP(300)), wxVSCROLL);
|
||||
m_sw_bind_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_bind_failed_info->SetScrollRate(5, 5);
|
||||
m_sw_bind_failed_info->SetScrollRate(5, FromDIP(20));
|
||||
m_sw_bind_failed_info->SetMinSize(wxSize(FromDIP(450), FromDIP(90)));
|
||||
m_sw_bind_failed_info->SetMaxSize(wxSize(FromDIP(450), FromDIP(90)));
|
||||
|
||||
|
||||
@@ -3521,7 +3521,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
cards->Show(m_box_mate, false, true);
|
||||
|
||||
m_form->FitInside();
|
||||
m_form->SetScrollRate(0, 10); // vertical only, like Prepare's sidebar: never scroll labels out
|
||||
m_form->SetScrollRate(0, FromDIP(20)); // vertical only, like Prepare's sidebar: never scroll labels out
|
||||
m_form->SetMinSize(wxSize(264, -1));
|
||||
|
||||
// Right column: a small view toolbar over the live 3D viewport that mirrors
|
||||
|
||||
@@ -115,7 +115,7 @@ HistoryWindow::HistoryWindow(wxWindow* parent, const std::vector<PACalibResult>&
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto scroll_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
scroll_window->SetScrollRate(5, 5);
|
||||
scroll_window->SetScrollRate(5, FromDIP(20));
|
||||
scroll_window->SetBackgroundColour(*wxWHITE);
|
||||
scroll_window->SetMinSize(HISTORY_WINDOW_SIZE);
|
||||
scroll_window->SetSize(HISTORY_WINDOW_SIZE);
|
||||
|
||||
@@ -207,7 +207,7 @@ SelectMObjectPopup::SelectMObjectPopup(wxWindow* parent)
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_LIST_SIZE, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetMinSize(SELECT_MACHINE_LIST_SIZE);
|
||||
m_scrolledWindow->SetScrollRate(0, 5);
|
||||
m_scrolledWindow->SetScrollRate(0, SELECT_MACHINE_ITEM_SIZE.y);
|
||||
auto m_sizxer_scrolledWindow = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolledWindow->SetSizer(m_sizxer_scrolledWindow);
|
||||
m_scrolledWindow->Layout();
|
||||
|
||||
@@ -81,7 +81,7 @@ CalibrationWizard::CalibrationWizard(wxWindow* parent, CalibMode mode, wxWindowI
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
wxBoxSizer* padding_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
@@ -834,7 +834,7 @@ void CaliPageSendingPanel::create(wxWindow* parent)
|
||||
|
||||
m_sw_print_failed_info = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(380), FromDIP(125)), wxVSCROLL);
|
||||
m_sw_print_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_print_failed_info->SetScrollRate(0, 5);
|
||||
m_sw_print_failed_info->SetScrollRate(0, FromDIP(20));
|
||||
m_sw_print_failed_info->SetMinSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
m_sw_print_failed_info->SetMaxSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
|
||||
|
||||
@@ -1046,6 +1046,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
|
||||
// Orca: both tower generators skip sparse layers, so this is not a wipe tower 2 exclusive.
|
||||
toggle_line("wipe_tower_no_sparse_layers", have_prime_tower);
|
||||
// Dropping the sparse layers outright leaves nothing to combine, so the two are exclusive.
|
||||
toggle_line("wipe_tower_sparse_layers_combination", have_prime_tower && !config->opt_bool("wipe_tower_no_sparse_layers"));
|
||||
|
||||
WipeTowerWallType wipe_tower_wall_type = config->opt_enum<WipeTowerWallType>("wipe_tower_wall_type");
|
||||
bool have_rib_wall = (wipe_tower_wall_type == WipeTowerWallType::wtwRib)&&have_prime_tower;
|
||||
|
||||
@@ -2710,7 +2710,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
// We can now enable scrolling on hscroll
|
||||
p->hscroll->SetScrollRate(30, 30);
|
||||
p->hscroll->SetScrollRate(30, FromDIP(20));
|
||||
|
||||
on_window_geometry(this, [this]() {
|
||||
p->init_dialog_size();
|
||||
|
||||
@@ -699,7 +699,7 @@ CreateFilamentPresetDialog::CreateFilamentPresetDialog(wxWindow *parent)
|
||||
m_scrolled_preset_panel = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrolled_preset_panel->SetMaxSize(wxSize(-1, FromDIP(350)));
|
||||
m_scrolled_preset_panel->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_preset_panel->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_panel->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolled_sizer->Add(create_item(FilamentOptionType::PRESET_FOR_PRINTER), 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, FromDIP(5));
|
||||
m_scrolled_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
|
||||
@@ -1593,7 +1593,7 @@ CreatePrinterPresetDialog::CreatePrinterPresetDialog(wxWindow *parent)
|
||||
|
||||
m_page1 = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_page1->SetBackgroundColour(*wxWHITE);
|
||||
m_page1->SetScrollRate(5, 5);
|
||||
m_page1->SetScrollRate(5, FromDIP(20));
|
||||
m_page2 = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); m_page2->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
create_printer_page1(m_page1);
|
||||
@@ -2652,7 +2652,7 @@ wxBoxSizer *CreatePrinterPresetDialog::create_presets_template_item(wxWindow *pa
|
||||
wxBoxSizer *vertical_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolled_preset_window = new wxScrolledWindow(parent);
|
||||
m_scrolled_preset_window->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_window->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_preset_window->SetBackgroundColour(*wxWHITE);
|
||||
//m_scrolled_preset_window->SetMinSize(wxSize(FromDIP(1500), FromDIP(-1)));
|
||||
m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(1500), FromDIP(-1)));
|
||||
@@ -4297,7 +4297,7 @@ wxBoxSizer *ExportConfigsDialog::create_select_printer(wxWindow *parent)
|
||||
optionSizer->SetMinSize(OPTION_SIZE);
|
||||
horizontal_sizer->Add(optionSizer, 0, wxEXPAND | wxALL, FromDIP(10));
|
||||
m_scrolled_preset_window = new wxScrolledWindow(parent);
|
||||
m_scrolled_preset_window->SetScrollRate(5, 5);
|
||||
m_scrolled_preset_window->SetScrollRate(5, FromDIP(20));
|
||||
m_scrolled_preset_window->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolled_preset_window->SetMaxSize(wxSize(FromDIP(660), FromDIP(400)));
|
||||
m_scrolled_preset_window->SetSize(wxSize(FromDIP(660), FromDIP(400)));
|
||||
@@ -4736,7 +4736,7 @@ wxBoxSizer *EditFilamentPresetDialog::create_preset_tree_sizer()
|
||||
{
|
||||
wxBoxSizer *filament_preset_tree_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_preset_tree_window = new wxScrolledWindow(this);
|
||||
m_preset_tree_window->SetScrollRate(5, 5);
|
||||
m_preset_tree_window->SetScrollRate(5, FromDIP(20));
|
||||
m_preset_tree_window->SetBackgroundColour(PRINTER_LIST_COLOUR);
|
||||
m_preset_tree_window->SetMinSize(wxSize(-1, FromDIP(400)));
|
||||
m_preset_tree_window->SetMaxSize(wxSize(-1, FromDIP(300)));
|
||||
|
||||
@@ -39,7 +39,7 @@ DeviceErrorDialog::DeviceErrorDialog(MachineObject* obj, wxWindow* parent, wxWin
|
||||
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
|
||||
m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scroll_area->SetScrollRate(0, 5);
|
||||
m_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
m_scroll_area->SetBackgroundColour(*wxWHITE);
|
||||
m_scroll_area->SetMinSize(wxSize(FromDIP(320), FromDIP(250)));
|
||||
|
||||
|
||||
@@ -7281,6 +7281,16 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
||||
m_last_w = w;
|
||||
m_last_h = h;
|
||||
|
||||
set_imgui_scaling();
|
||||
|
||||
this->request_extra_frame();
|
||||
|
||||
// ensures that this canvas is current
|
||||
_set_current();
|
||||
}
|
||||
|
||||
void GLCanvas3D::set_imgui_scaling()
|
||||
{
|
||||
float font_size = wxGetApp().em_unit();
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -7293,15 +7303,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
||||
#endif
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor());
|
||||
wxGetApp().imgui()->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor());
|
||||
#else
|
||||
imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f);
|
||||
wxGetApp().imgui()->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f);
|
||||
#endif
|
||||
|
||||
this->request_extra_frame();
|
||||
|
||||
// ensures that this canvas is current
|
||||
_set_current();
|
||||
}
|
||||
|
||||
BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model, bool include_plates) const
|
||||
|
||||
@@ -1299,6 +1299,8 @@ public:
|
||||
Vec3d _mouse_to_3d(const Point& mouse_pos, float* z = nullptr);
|
||||
|
||||
bool make_current_for_postinit();
|
||||
// Sizes ImGui's fonts and style for this canvas; the fonts are rebuilt when the size changes.
|
||||
void set_imgui_scaling();
|
||||
|
||||
private:
|
||||
bool _is_shown_on_screen() const;
|
||||
|
||||
@@ -456,8 +456,9 @@ void GLModel::init_from(const indexed_triangle_set& its)
|
||||
data.reserve_vertices(3 * its.indices.size());
|
||||
data.reserve_indices(3 * its.indices.size());
|
||||
|
||||
// Read user preference: smooth normals enabled
|
||||
const bool smooth_normals_enabled = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS);
|
||||
// CLI thumbnail generation can initialize models without a wx application.
|
||||
const bool smooth_normals_enabled = wxApp::GetInstance() != nullptr && wxGetApp().app_config != nullptr &&
|
||||
wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS);
|
||||
|
||||
if (smooth_normals_enabled) {
|
||||
// Use per-corner smooth normals (via IGL)
|
||||
|
||||
@@ -857,7 +857,12 @@ void GUI_App::post_init()
|
||||
slow_bootup = true;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", slow bootup, won't render gl here.";
|
||||
}
|
||||
if (!switch_to_3d) {
|
||||
// Starting on Home, the GL resources load at idle so Home paints first and Prepare is never
|
||||
// shown.
|
||||
const bool gl_at_idle = !starts_on_prepare() && is_editor();
|
||||
if (!switch_to_3d && gl_at_idle) {
|
||||
plater_->select_view_3D("3D");
|
||||
} else if (!switch_to_3d) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", begin load_gl_resources";
|
||||
#ifndef __linux__
|
||||
mainframe->Freeze();
|
||||
@@ -865,9 +870,6 @@ void GUI_App::post_init()
|
||||
plater_->canvas3D()->enable_render(false);
|
||||
mainframe->select_prepare_for_gl_init();
|
||||
plater_->select_view_3D("3D");
|
||||
// The first render happens before the queued new_project() sets the same view.
|
||||
plater_->get_camera().select_view("topfront");
|
||||
plater_->get_camera().requires_zoom_to_bed = true;
|
||||
//BBS init the opengl resource here
|
||||
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
|
||||
!plater_->canvas3D()->make_current_for_postinit()) {
|
||||
@@ -905,8 +907,6 @@ void GUI_App::post_init()
|
||||
}
|
||||
if (starts_on_prepare())
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
else if (is_editor())
|
||||
mainframe->select_tab(TAB_ID_HOME);
|
||||
#ifndef __linux__
|
||||
mainframe->Thaw();
|
||||
#endif
|
||||
@@ -3423,6 +3423,10 @@ bool GUI_App::on_init_inner()
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "create the main window";
|
||||
mainframe = new MainFrame();
|
||||
// The first render can happen as soon as the frame is shown, before the queued
|
||||
// new_project() sets the same view.
|
||||
plater_->get_camera().select_view("topfront");
|
||||
plater_->get_camera().requires_zoom_to_bed = true;
|
||||
if (is_editor()) {
|
||||
if (starts_on_prepare()) {
|
||||
mainframe->select_tab(TAB_ID_PREPARE);
|
||||
@@ -8201,10 +8205,12 @@ int GUI_App::input_idle_ms() const
|
||||
return int(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_last_input).count());
|
||||
}
|
||||
|
||||
// Every wxCommandEvent claims the user-input category, so only real mouse and key events count.
|
||||
// Every wxCommandEvent claims the user-input category, so only real mouse and key events count,
|
||||
// plus main window resizes, since a border drag produces no mouse events.
|
||||
int GUI_App::FilterEvent(wxEvent& event)
|
||||
{
|
||||
if (!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT))
|
||||
if ((!event.IsCommandEvent() && (event.GetEventCategory() & wxEVT_CATEGORY_USER_INPUT)) ||
|
||||
(event.GetEventType() == wxEVT_SIZE && event.GetEventObject() == mainframe))
|
||||
m_last_input = std::chrono::steady_clock::now();
|
||||
return Event_Skip;
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ public:
|
||||
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
|
||||
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
|
||||
bool is_recreating_gui() const { return m_is_recreating_gui; }
|
||||
// Milliseconds since the last mouse or keyboard event the app processed.
|
||||
// Milliseconds since the last mouse or keyboard event the app processed, or main window resize.
|
||||
int input_idle_ms() const;
|
||||
int FilterEvent(wxEvent& event) override;
|
||||
// The Preferences "Default page" choice, stored as its index: 0 Home, 1 Prepare.
|
||||
|
||||
@@ -2764,12 +2764,12 @@ ObjectTablePanel::ObjectTablePanel( wxWindow* parent, wxWindowID id, const wxPoi
|
||||
//m_object_grid->AssignTable(m_object_grid_table);
|
||||
|
||||
m_side_window = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(440),FromDIP(480)), wxVSCROLL);
|
||||
m_side_window->SetScrollRate( 0, 5 );
|
||||
m_side_window->SetScrollRate(0, FromDIP(20));
|
||||
m_page_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
//m_page_top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_side_window->SetBackgroundColour(wxColour(0xff, 0xff, 0xff));
|
||||
m_side_window->SetSizer(m_page_sizer);
|
||||
m_side_window->SetScrollbars(1, 20, 1, 2);
|
||||
m_side_window->SetScrollbars(1, FromDIP(20), 1, 2);
|
||||
//m_side_window->ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
|
||||
|
||||
//m_side_window->EnableScrolling(false, true);
|
||||
|
||||
@@ -247,7 +247,9 @@ int get_dpi_for_window(const wxWindow *window)
|
||||
|
||||
const HDC hdc = GetDC(hwnd);
|
||||
if (hdc == NULL) { return DPI_DEFAULT; }
|
||||
return GetDeviceCaps(hdc, LOGPIXELSX);
|
||||
const int dpi = GetDeviceCaps(hdc, LOGPIXELSX);
|
||||
ReleaseDC(hwnd, hdc); // GetDC's handle must be released, unlike GetWindowDC's
|
||||
return dpi;
|
||||
}
|
||||
#elif defined __linux__
|
||||
// TODO
|
||||
|
||||
@@ -171,7 +171,7 @@ HMSPanel::HMSPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wx
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(5, 5);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
|
||||
m_top_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/evtloop.h>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
@@ -23,12 +24,9 @@ constexpr int quiet_ms = 500;
|
||||
// queued meanwhile are handled first; a click waits at most a slice plus the unit that
|
||||
// overran it.
|
||||
constexpr int slice_ms = 40;
|
||||
// On GTK a due timer runs ahead of repaints and posted events, so the next slice waits a few ms.
|
||||
#ifdef __WXGTK__
|
||||
// Delay before the next slice; on GTK a due timer runs ahead of repaints and posted events,
|
||||
// and wxOSX rejects a 0 ms timer.
|
||||
constexpr int next_slice_ms = 5;
|
||||
#else
|
||||
constexpr int next_slice_ms = 0;
|
||||
#endif
|
||||
|
||||
// True when unhandled keyboard, button, touch or pen input is queued; only Windows can ask.
|
||||
bool input_pending()
|
||||
@@ -42,6 +40,13 @@ bool input_pending()
|
||||
#endif
|
||||
}
|
||||
|
||||
// True inside a wxYield(), where a slice would build pages in the middle of the code that yielded.
|
||||
bool yielding()
|
||||
{
|
||||
const wxEventLoopBase* loop = wxEventLoopBase::GetActive();
|
||||
return loop != nullptr && loop->IsYielding();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
IdleScheduler::IdleScheduler(std::function<int()> input_idle_ms) : m_input_idle_ms(std::move(input_idle_ms))
|
||||
@@ -69,7 +74,7 @@ void IdleScheduler::tick()
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
if (m_input_idle_ms() < quiet_ms || input_pending()) {
|
||||
if (m_input_idle_ms() < quiet_ms || input_pending() || yielding()) {
|
||||
start();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const Page& page)
|
||||
wxGetApp().UpdateDarkUI(scrollable_panel);
|
||||
const wxColour page_colour = StateColor::darkModeColorFor(*wxWHITE);
|
||||
scrollable_panel->SetBackgroundColour(page_colour);
|
||||
scrollable_panel->SetScrollRate(0, 20);
|
||||
scrollable_panel->SetScrollRate(0, FromDIP(20));
|
||||
const int page_width = FromDIP(PAGE_WIDTH);
|
||||
scrollable_panel->SetInitialSize(wxSize(page_width, FromDIP(450)));
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include <gtk/gtk.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#endif // __WXGTK__
|
||||
#include <slic3r/GUI/CreatePresetsDialog.hpp>
|
||||
|
||||
@@ -510,6 +511,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
wxQueueEvent(wxGetApp().plater(), new SimpleEvent(EVT_NOTICE_CHILDE_SIZE_CHANGED));
|
||||
|
||||
fit_tab_labels(); // ORCA on resize
|
||||
// Restarts the idle build so a hidden Prepare page is laid out at the new size.
|
||||
if (m_prebuild_started)
|
||||
m_idle.start();
|
||||
});
|
||||
|
||||
//BBS
|
||||
@@ -4008,13 +4012,81 @@ bool MainFrame::Show(bool show)
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool MainFrame::GLResourcesPrebuild::built() const
|
||||
{
|
||||
return m_frame.m_plater != nullptr && m_frame.m_plater->canvas3D()->is_initialized() &&
|
||||
m_frame.m_plater->get_partplate_list().icon_textures_loaded();
|
||||
}
|
||||
|
||||
bool MainFrame::GLResourcesPrebuild::build_step()
|
||||
{
|
||||
GLCanvas3D* canvas = m_frame.m_plater->canvas3D();
|
||||
#ifdef __WXGTK__
|
||||
// wx creates a GTK canvas's GL surface when the widget is realized, so the context can be
|
||||
// made current on it while hidden.
|
||||
gtk_widget_realize(canvas->get_wxglcanvas()->GetHandle());
|
||||
#endif
|
||||
if (!canvas->make_current_for_postinit()) {
|
||||
// The first render of the canvas loads everything instead.
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": cannot make the GL context current on the hidden canvas";
|
||||
m_failed = true;
|
||||
return false;
|
||||
}
|
||||
switch (m_step) {
|
||||
case 0:
|
||||
m_failed = !wxGetApp().init_opengl();
|
||||
break;
|
||||
case 1: {
|
||||
const Size size = canvas->get_canvas_size();
|
||||
wxGetApp().imgui()->set_display_size(float(std::max(1, size.get_width())), float(std::max(1, size.get_height())));
|
||||
canvas->set_imgui_scaling();
|
||||
// Builds the font atlas without leaving a frame open at the hidden canvas's size.
|
||||
wxGetApp().imgui()->new_frame();
|
||||
wxGetApp().imgui()->end_frame();
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
// One texture per unit until none remain.
|
||||
if (m_frame.m_plater->get_partplate_list().load_next_plate_texture())
|
||||
return true;
|
||||
break;
|
||||
case 3:
|
||||
m_failed = !canvas->init();
|
||||
break;
|
||||
default:
|
||||
// Runs after init(), which sets the color mode the icons are drawn for.
|
||||
m_frame.m_plater->get_partplate_list().load_icon_textures();
|
||||
return false;
|
||||
}
|
||||
++m_step;
|
||||
return !m_failed;
|
||||
}
|
||||
|
||||
bool MainFrame::PrepareLayoutPrebuild::built() const
|
||||
{
|
||||
// The book lays out the page it shows.
|
||||
const wxWindow* page = m_frame.m_tabpanel != nullptr ? m_frame.m_tabpanel->GetCurrentPage() : nullptr;
|
||||
return page == nullptr || page == m_frame.m_plater || page->GetSize() == m_laid_out_size;
|
||||
}
|
||||
|
||||
bool MainFrame::PrepareLayoutPrebuild::build_step()
|
||||
{
|
||||
// Sized as the book sizes the page it selects, so the selection finds nothing to lay out.
|
||||
const wxWindow* page = m_frame.m_tabpanel->GetCurrentPage();
|
||||
m_laid_out_size = page->GetSize();
|
||||
m_frame.m_plater->SetSize(page->GetRect());
|
||||
return false;
|
||||
}
|
||||
|
||||
// A page out of the book stays registered and is passed over; a negative order is never
|
||||
// registered.
|
||||
void MainFrame::prebuild_pages_when_idle()
|
||||
{
|
||||
m_idle.clear();
|
||||
m_idle.add(m_gl_prebuild);
|
||||
if (m_param_panel)
|
||||
m_idle.add(m_param_panel->settings_page_prebuild());
|
||||
m_idle.add(m_prepare_layout_prebuild);
|
||||
for (LazyBase* page : m_lazy_pages)
|
||||
if (page->prebuild_order() >= 0)
|
||||
m_idle.add(*page);
|
||||
|
||||
@@ -140,6 +140,38 @@ class MainFrame : public DPIFrame
|
||||
wxTimer* m_reset_title_text_colour_timer{ nullptr };
|
||||
IdleScheduler m_idle;
|
||||
bool m_prebuild_started{ false };
|
||||
// Loads the Prepare canvas's GL resources while its page is hidden.
|
||||
class GLResourcesPrebuild : public LazyBase
|
||||
{
|
||||
public:
|
||||
explicit GLResourcesPrebuild(MainFrame& frame) : m_frame(frame) {}
|
||||
const std::string& name() const override { return m_name; }
|
||||
bool built() const override;
|
||||
bool pending() const override { return !m_failed && !built(); }
|
||||
bool build_step() override;
|
||||
int prebuild_order() const override { return 0; }
|
||||
|
||||
private:
|
||||
MainFrame& m_frame;
|
||||
std::string m_name{ "gl_resources" };
|
||||
int m_step{ 0 };
|
||||
bool m_failed{ false };
|
||||
} m_gl_prebuild{ *this };
|
||||
// Lays out the hidden Prepare page at the size the book gives its pages.
|
||||
class PrepareLayoutPrebuild : public LazyBase
|
||||
{
|
||||
public:
|
||||
explicit PrepareLayoutPrebuild(MainFrame& frame) : m_frame(frame) {}
|
||||
const std::string& name() const override { return m_name; }
|
||||
bool built() const override;
|
||||
bool build_step() override;
|
||||
int prebuild_order() const override { return 0; }
|
||||
|
||||
private:
|
||||
MainFrame& m_frame;
|
||||
std::string m_name{ "prepare_layout" };
|
||||
wxSize m_laid_out_size;
|
||||
} m_prepare_layout_prebuild{ *this };
|
||||
// Every LazyPage, in and out of the book; prebuild_pages_when_idle() registers them.
|
||||
std::vector<LazyBase*> m_lazy_pages;
|
||||
// The latest EVT_LOAD_PRINTER_URL, applied when the web Device view is built.
|
||||
|
||||
@@ -1125,7 +1125,7 @@ wxBoxSizer* MixedFilamentDialog::create_recommendation_grid()
|
||||
outer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, FromDIP(4));
|
||||
|
||||
m_recommendation_scroll = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(116)));
|
||||
m_recommendation_scroll->SetScrollRate(0, 5);
|
||||
m_recommendation_scroll->SetScrollRate(0, FromDIP(20));
|
||||
m_recommendation_scroll->SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#F8F8F8")));
|
||||
|
||||
m_recommendation_grid = new wxWrapSizer(wxHORIZONTAL, wxREMOVE_LEADING_SPACES);
|
||||
|
||||
@@ -398,7 +398,7 @@ static void add_msg_content(wxWindow *parent,
|
||||
}
|
||||
wxScrolledWindow *scrolledWindow = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
scrolledWindow->SetScrollRate(0, 20);
|
||||
scrolledWindow->SetScrollRate(0, scrolledWindow->FromDIP(20));
|
||||
scrolledWindow->EnableScrolling(false, true);
|
||||
wxBoxSizer *sizer_scrolled = new wxBoxSizer(wxHORIZONTAL);
|
||||
Label *wrapped_text = new Label(scrolledWindow, font, msg, LB_AUTO_WRAP, wxSize(info_width, -1));
|
||||
|
||||
@@ -398,7 +398,7 @@ MultiMachineManagerPage::MultiMachineManagerPage(wxWindow* parent)
|
||||
|
||||
m_machine_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_machine_list->SetBackgroundColour(*wxWHITE);
|
||||
m_machine_list->SetScrollRate(0, 5);
|
||||
m_machine_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_machine_list->SetMinSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_machine_list->SetMaxSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ MultiMachinePickPage::MultiMachinePickPage(Plater* plater /*= nullptr*/)
|
||||
scroll_macine_list->SetMinSize(wxSize(FromDIP(400), FromDIP(10 * 30)));
|
||||
scroll_macine_list->SetMaxSize(wxSize(FromDIP(400), FromDIP(10 * 30)));
|
||||
scroll_macine_list->SetBackgroundColour(*wxWHITE);
|
||||
scroll_macine_list->SetScrollRate(0, 5);
|
||||
scroll_macine_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
|
||||
sizer_machine_list = new wxBoxSizer(wxVERTICAL);
|
||||
scroll_macine_list->SetSizer(sizer_machine_list);
|
||||
|
||||
@@ -675,7 +675,7 @@ LocalTaskManagerPage::LocalTaskManagerPage(wxWindow* parent)
|
||||
|
||||
m_task_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_task_list->SetBackgroundColour(*wxWHITE);
|
||||
m_task_list->SetScrollRate(0, 5);
|
||||
m_task_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_task_list->SetMinSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_task_list->SetMaxSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
@@ -1056,7 +1056,7 @@ CloudTaskManagerPage::CloudTaskManagerPage(wxWindow* parent)
|
||||
|
||||
m_task_list = new wxScrolledWindow(m_main_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_task_list->SetBackgroundColour(*wxWHITE);
|
||||
m_task_list->SetScrollRate(0, 5);
|
||||
m_task_list->SetScrollRate(0, FromDIP(DEVICE_ITEM_MAX_HEIGHT));
|
||||
m_task_list->SetMinSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
m_task_list->SetMaxSize(wxSize(FromDIP(CLOUD_TASK_ITEM_MAX_WIDTH), 10 * FromDIP(DEVICE_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ ObjColorPanel::ObjColorPanel(wxWindow *parent, Slic3r::ObjDialogInOut &in_out, c
|
||||
//new color table
|
||||
m_scrolledWindow = new wxScrolledWindow(m_page_simple, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(0, 20);
|
||||
m_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrolledWindow->EnableScrolling(false, true);
|
||||
m_scrolledWindow->ShowScrollbars(wxScrollbarVisibility::wxSHOW_SB_NEVER, wxScrollbarVisibility::wxSHOW_SB_DEFAULT);
|
||||
draw_new_table();
|
||||
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
bool split_multi_line{false};
|
||||
bool option_label_at_right{false};
|
||||
// BBS: new layout
|
||||
wxWindow * stb;
|
||||
wxWindow * stb{ nullptr };
|
||||
const wxString icon;
|
||||
const wxString title;
|
||||
bool m_labels_hidden{false};
|
||||
|
||||
@@ -391,7 +391,7 @@ ParamsPanel::ParamsPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, c
|
||||
m_page_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_page_view->SetSizer(m_page_sizer);
|
||||
m_page_view->SetScrollbars(1, 20, 1, 2);
|
||||
m_page_view->SetScrollbars(1, FromDIP(20), 1, 2);
|
||||
//m_page_view->SetScrollRate( 5, 5 );
|
||||
|
||||
if (m_mode_region)
|
||||
|
||||
@@ -122,8 +122,8 @@ class ParamsPanel : public wxPanel
|
||||
|
||||
wxPanel* m_current_tab { nullptr };
|
||||
|
||||
// Builds the selected page's option groups at idle; while no tab is selected yet,
|
||||
// its first unit selects the default one.
|
||||
// Builds the selected page's option groups at idle and then shows them for the mode;
|
||||
// while no tab is selected yet, its first unit selects the default one.
|
||||
class SettingsPagePrebuild : public LazyBase
|
||||
{
|
||||
public:
|
||||
|
||||
+126
-124
@@ -799,67 +799,8 @@ void PartPlate::render_logo(bool bottom, bool render_cali)
|
||||
{
|
||||
if (!m_partplate_list->render_bedtype_logo) {
|
||||
// render third-party printer texture logo
|
||||
if (m_partplate_list->m_logo_texture_filename.empty()) {
|
||||
m_partplate_list->m_logo_texture.reset();
|
||||
if (!m_partplate_list->load_logo_texture())
|
||||
return;
|
||||
}
|
||||
|
||||
//GLTexture* temp_texture = const_cast<GLTexture*>(&m_temp_texture);
|
||||
|
||||
if (m_partplate_list->m_logo_texture.get_id() == 0 || m_partplate_list->m_logo_texture.get_source() != m_partplate_list->m_logo_texture_filename) {
|
||||
m_partplate_list->m_logo_texture.reset();
|
||||
|
||||
if (boost::algorithm::iends_with(m_partplate_list->m_logo_texture_filename, ".svg")) {
|
||||
/*// use higher resolution images if graphic card and opengl version allow
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
|
||||
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
|
||||
if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
|
||||
render_default(bottom, false);
|
||||
return;
|
||||
}
|
||||
canvas.request_extra_frame();
|
||||
}*/
|
||||
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
if (!m_partplate_list->m_logo_texture.load_from_svg_file(m_partplate_list->m_logo_texture_filename, true, true, true, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(m_partplate_list->m_logo_texture_filename, ".png")) {
|
||||
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
|
||||
/* if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_logo_texture_filename) {
|
||||
if (!temp_texture->load_from_file(m_logo_texture_filename, false, GLTexture::None, false)) {
|
||||
render_default(bottom, false);
|
||||
return;
|
||||
}
|
||||
canvas.request_extra_frame();
|
||||
}*/
|
||||
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
if (!m_partplate_list->m_logo_texture.load_from_file(m_partplate_list->m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_partplate_list->m_logo_texture_filename;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not load logo texture from %1%, unsupported format") % m_partplate_list->m_logo_texture_filename;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (m_partplate_list->m_logo_texture.unsent_compressed_data_available()) {
|
||||
// sends to gpu the already available compressed levels of the main texture
|
||||
m_partplate_list->m_logo_texture.send_compressed_data_to_gpu();
|
||||
|
||||
// the temporary texture is not needed anymore, reset it
|
||||
//if (temp_texture->get_id() != 0)
|
||||
// temp_texture->reset();
|
||||
|
||||
//canvas.request_extra_frame();
|
||||
}
|
||||
|
||||
if (m_logo_triangles.is_initialized())
|
||||
render_logo_texture(m_partplate_list->m_logo_texture, m_logo_triangles, bottom);
|
||||
@@ -4232,6 +4173,7 @@ Vec2d PartPlateList::compute_shape_position(int index, int cols)
|
||||
//generate icon textures
|
||||
void PartPlateList::generate_icon_textures()
|
||||
{
|
||||
m_icon_textures_dark = m_is_dark;
|
||||
// use higher resolution images if graphic card and opengl version allow
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size(), icon_size = max_tex_size / 8;
|
||||
std::string path = resources_dir() + "/images/";
|
||||
@@ -4447,6 +4389,9 @@ void PartPlateList::release_icon_textures()
|
||||
PartPlateList::is_load_bedtype_textures = false;
|
||||
PartPlateList::is_load_extruder_only_area_textures = false;
|
||||
PartPlateList::is_load_cali_texture = false;
|
||||
m_next_bedtype_texture = 0;
|
||||
m_next_extruder_only_area_texture = 0;
|
||||
m_next_cali_texture = 0;
|
||||
for (int i = 0; i < btCount; i++) {
|
||||
for (auto& part: bed_texture_info[i].parts) {
|
||||
if (part.texture) {
|
||||
@@ -6002,12 +5947,7 @@ void PartPlateList::render(const Transform3d& view_matrix, const Transform3d& pr
|
||||
plate_hover_action = hover_id % PartPlate::GRABBER_COUNT;
|
||||
}
|
||||
|
||||
static bool last_dark_mode_status = m_is_dark;
|
||||
if (m_is_dark != last_dark_mode_status) {
|
||||
last_dark_mode_status = m_is_dark;
|
||||
generate_icon_textures();
|
||||
} else if(m_del_texture.get_id() == 0)
|
||||
generate_icon_textures();
|
||||
load_icon_textures();
|
||||
for (it = m_plate_list.begin(); it != m_plate_list.end(); it++) {
|
||||
int current_index = (*it)->get_index();
|
||||
if (only_current && (current_index != m_current_plate))
|
||||
@@ -6149,6 +6089,8 @@ bool PartPlateList::set_shapes(const Pointfs &shape,
|
||||
}
|
||||
is_load_bedtype_textures = false; //reload textures
|
||||
is_load_extruder_only_area_textures = false; // reload textures
|
||||
m_next_bedtype_texture = 0;
|
||||
m_next_extruder_only_area_texture = 0;
|
||||
calc_bounding_boxes();
|
||||
|
||||
update_logo_texture_filename(texture_filename);
|
||||
@@ -7089,53 +7031,120 @@ bool PartPlateList::init_extruder_only_area_info()
|
||||
return true;
|
||||
}
|
||||
|
||||
void PartPlateList::load_bedtype_textures()
|
||||
static GLint logo_texture_size()
|
||||
{
|
||||
if (PartPlateList::is_load_bedtype_textures) return;
|
||||
|
||||
init_bed_type_info();
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
for (int i = 0; i < (unsigned int)btCount; ++i) {
|
||||
for (int j = 0; j < bed_texture_info[i].parts.size(); j++) {
|
||||
std::string filename = resources_dir() + "/images/" + bed_texture_info[i].parts[j].filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
PartPlateList::bed_texture_info[i].parts[j].texture = new GLTexture();
|
||||
if (!PartPlateList::bed_texture_info[i].parts[j].texture->load_from_svg_file(filename, true, true, true, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
PartPlateList::is_load_bedtype_textures = true;
|
||||
return std::min<GLint>(OpenGLManager::get_gl_info().get_max_tex_size(), 2048);
|
||||
}
|
||||
|
||||
void PartPlateList::load_extruder_only_area_textures() {
|
||||
if (PartPlateList::is_load_extruder_only_area_textures) return;
|
||||
// Loads the texture of the next untried part across the parts of `infos`, in order, advancing
|
||||
// `next`; false once every part has been tried.
|
||||
static bool load_next_part_texture(PartPlateList::BedTextureInfo* infos, size_t count, size_t& next, bool compress_and_filter)
|
||||
{
|
||||
size_t k = next;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (k >= infos[i].parts.size()) {
|
||||
k -= infos[i].parts.size();
|
||||
continue;
|
||||
}
|
||||
++next;
|
||||
PartPlateList::BedTextureInfo::TexturePart& part = infos[i].parts[k];
|
||||
const std::string filename = resources_dir() + "/images/" + part.filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
part.texture = new GLTexture();
|
||||
if (!part.texture->load_from_svg_file(filename, true, compress_and_filter, compress_and_filter, logo_texture_size()))
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load texture from %1% failed!") % filename;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load texture from %1% failed!") % filename;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ok = init_extruder_only_area_info();
|
||||
if (!ok) {
|
||||
void PartPlateList::load_bedtype_textures()
|
||||
{
|
||||
while (load_next_bedtype_texture()) {}
|
||||
}
|
||||
|
||||
bool PartPlateList::load_next_bedtype_texture()
|
||||
{
|
||||
if (PartPlateList::is_load_bedtype_textures)
|
||||
return false;
|
||||
if (m_next_bedtype_texture == 0)
|
||||
init_bed_type_info();
|
||||
if (load_next_part_texture(bed_texture_info, btCount, m_next_bedtype_texture, true))
|
||||
return true;
|
||||
PartPlateList::is_load_bedtype_textures = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PartPlateList::load_logo_texture()
|
||||
{
|
||||
if (m_logo_texture_filename.empty()) {
|
||||
m_logo_texture.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_logo_texture.get_id() != 0 && m_logo_texture.get_source() == m_logo_texture_filename) {
|
||||
if (m_logo_texture.unsent_compressed_data_available())
|
||||
// sends to gpu the already available compressed levels of the main texture
|
||||
m_logo_texture.send_compressed_data_to_gpu();
|
||||
return true;
|
||||
}
|
||||
|
||||
m_logo_texture.reset();
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
if (boost::algorithm::iends_with(m_logo_texture_filename, ".svg")) {
|
||||
if (!m_logo_texture.load_from_svg_file(m_logo_texture_filename, true, true, true, logo_texture_size())) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_logo_texture_filename;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(m_logo_texture_filename, ".png")) {
|
||||
if (!m_logo_texture.load_from_file(m_logo_texture_filename, true, GLTexture::MultiThreaded, true)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % m_logo_texture_filename;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": can not load logo texture from %1%, unsupported format") % m_logo_texture_filename;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PartPlateList::load_icon_textures()
|
||||
{
|
||||
if (!icon_textures_loaded())
|
||||
generate_icon_textures();
|
||||
}
|
||||
|
||||
bool PartPlateList::load_next_plate_texture()
|
||||
{
|
||||
if (!render_bedtype_logo) {
|
||||
load_logo_texture();
|
||||
return false;
|
||||
}
|
||||
return load_next_bedtype_texture() || load_next_cali_texture() || load_next_extruder_only_area_texture();
|
||||
}
|
||||
|
||||
void PartPlateList::load_extruder_only_area_textures()
|
||||
{
|
||||
while (load_next_extruder_only_area_texture()) {}
|
||||
}
|
||||
|
||||
bool PartPlateList::load_next_extruder_only_area_texture()
|
||||
{
|
||||
if (PartPlateList::is_load_extruder_only_area_textures)
|
||||
return false;
|
||||
if (m_next_extruder_only_area_texture == 0 && !init_extruder_only_area_info()) {
|
||||
PartPlateList::is_load_extruder_only_area_textures = true;
|
||||
return;
|
||||
}
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
for (int i = 0; i < (unsigned int) ExtruderOnlyAreaType::btAreaCount; ++i) {
|
||||
for (int j = 0; j < extruder_only_area_info[i].parts.size(); j++) {
|
||||
std::string filename = resources_dir() + "/images/" + extruder_only_area_info[i].parts[j].filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
PartPlateList::extruder_only_area_info[i].parts[j].texture = new GLTexture();
|
||||
if (!PartPlateList::extruder_only_area_info[i].parts[j].texture->load_from_svg_file(filename, true, false, false, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load logo texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (load_next_part_texture(extruder_only_area_info, (size_t) ExtruderOnlyAreaType::btAreaCount, m_next_extruder_only_area_texture, false))
|
||||
return true;
|
||||
PartPlateList::is_load_extruder_only_area_textures = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PartPlateList::init_cali_texture_info()
|
||||
@@ -7150,26 +7159,19 @@ void PartPlateList::init_cali_texture_info()
|
||||
|
||||
void PartPlateList::load_cali_textures()
|
||||
{
|
||||
if (PartPlateList::is_load_cali_texture) return;
|
||||
while (load_next_cali_texture()) {}
|
||||
}
|
||||
|
||||
init_cali_texture_info();
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
GLint logo_tex_size = (max_tex_size < 2048) ? max_tex_size : 2048;
|
||||
for (int i = 0; i < (unsigned int)btCount; ++i) {
|
||||
for (int j = 0; j < cali_texture_info.parts.size(); j++) {
|
||||
std::string filename = resources_dir() + "/images/" + cali_texture_info.parts[j].filename;
|
||||
if (boost::filesystem::exists(filename)) {
|
||||
PartPlateList::cali_texture_info.parts[j].texture = new GLTexture();
|
||||
if (!PartPlateList::cali_texture_info.parts[j].texture->load_from_svg_file(filename, true, true, true, logo_tex_size)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load cali texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": load cali texture from %1% failed!") % filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool PartPlateList::load_next_cali_texture()
|
||||
{
|
||||
if (PartPlateList::is_load_cali_texture)
|
||||
return false;
|
||||
if (m_next_cali_texture == 0)
|
||||
init_cali_texture_info();
|
||||
if (load_next_part_texture(&cali_texture_info, 1, m_next_cali_texture, true))
|
||||
return true;
|
||||
PartPlateList::is_load_cali_texture = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PartPlateList::on_extruder_count_changed(int extruder_count)
|
||||
|
||||
@@ -644,6 +644,7 @@ class PartPlateList : public ObjectBase
|
||||
std::string m_hover_tooltip;
|
||||
|
||||
bool m_is_dark = false;
|
||||
bool m_icon_textures_dark = false;
|
||||
|
||||
int m_filament_count = 1;
|
||||
|
||||
@@ -943,12 +944,25 @@ public:
|
||||
bool calc_extruder_only_area(Rect &left_only_rect, Rect &right_only_rect);
|
||||
void init_bed_type_info();
|
||||
bool init_extruder_only_area_info();
|
||||
// Each load_*_textures() loads whatever of its set is not loaded yet; each load_next_*()
|
||||
// loads one texture and returns false once none remain.
|
||||
void load_bedtype_textures();
|
||||
bool load_next_bedtype_texture();
|
||||
void load_extruder_only_area_textures();
|
||||
bool load_next_extruder_only_area_texture();
|
||||
// Starts loading the printer's logo texture, or sends the levels compressed since; false when
|
||||
// there is no logo to draw.
|
||||
bool load_logo_texture();
|
||||
|
||||
void show_cali_texture(bool show = true);
|
||||
void init_cali_texture_info();
|
||||
void load_cali_textures();
|
||||
bool load_next_cali_texture();
|
||||
bool icon_textures_loaded() const { return m_del_texture.get_id() != 0 && m_icon_textures_dark == m_is_dark; }
|
||||
void load_icon_textures();
|
||||
// Loads the next bed-type, calibration or extruder-area texture, or the logo, which rendering
|
||||
// otherwise loads on first use; false once none remain.
|
||||
bool load_next_plate_texture();
|
||||
|
||||
void on_extruder_count_changed(int extruder_count);
|
||||
|
||||
@@ -960,6 +974,13 @@ public:
|
||||
BedTextureInfo bed_texture_info[btCount];
|
||||
BedTextureInfo cali_texture_info;
|
||||
BedTextureInfo extruder_only_area_info[(unsigned char) Slic3r::ExtruderOnlyAreaType::btAreaCount];
|
||||
|
||||
private:
|
||||
// The next part to load in each texture set, counted across the set's parts in order; reset
|
||||
// with the set's is_load_* flag.
|
||||
size_t m_next_bedtype_texture{ 0 };
|
||||
size_t m_next_cali_texture{ 0 };
|
||||
size_t m_next_extruder_only_area_texture{ 0 };
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
||||
@@ -148,7 +148,7 @@ PartSkipDialog::PartSkipDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _
|
||||
m_line->SetBackgroundColour(wxColor(238, 238, 238));
|
||||
|
||||
m_list_view = new wxScrolledWindow(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize(267, -1), wxHSCROLL | wxVSCROLL);
|
||||
m_list_view->SetScrollRate(5, 5);
|
||||
m_list_view->SetScrollRate(5, FromDIP(30));
|
||||
m_list_view->SetMinSize(wxSize(FromDIP(267), FromDIP(378)));
|
||||
m_list_view->SetMaxSize(wxSize(FromDIP(267), FromDIP(378)));
|
||||
m_list_view->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
@@ -3051,8 +3051,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
|
||||
// add filament content
|
||||
p->m_panel_filament_content = new wxScrolledWindow(p->m_filament_area_wrapper, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
p->m_panel_filament_content->SetScrollbars(0, 100, 1, 2);
|
||||
p->m_panel_filament_content->SetScrollRate(0, 5);
|
||||
p->m_panel_filament_content->SetScrollRate(0, FromDIP(20));
|
||||
//p->m_panel_filament_content->SetMaxSize(wxSize{-1, FromDIP(174)});
|
||||
p->m_panel_filament_content->SetBackgroundColour(wxColour(255, 255, 255));
|
||||
|
||||
@@ -3144,8 +3143,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
// 3) Mixed filament rows, in their own scroll area so a long mixed list does not
|
||||
// push the physical filament list off screen.
|
||||
p->m_mixed_scroll_area = new wxScrolledWindow(p->m_filament_area_wrapper, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
p->m_mixed_scroll_area->SetScrollbars(0, 100, 1, 2);
|
||||
p->m_mixed_scroll_area->SetScrollRate(0, 5);
|
||||
p->m_mixed_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
p->m_mixed_scroll_area->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
{
|
||||
auto* mix_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -3706,10 +3704,10 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
int select = -1;
|
||||
// ORCA get the actual nozzle diameter from printer config
|
||||
auto nozzle_dia = get_diameter_string(nozzle_diameter->values[extruder_index]);
|
||||
// Named variants such as "0.4 High Flow" share a physical diameter.
|
||||
// Named variants such as "0.4HS" and "0.4 High Flow" share a physical diameter.
|
||||
// Retain the variant selection unless the diameter was customized.
|
||||
const auto selected_variant =
|
||||
(diameter == nozzle_dia || boost::algorithm::starts_with(diameter, nozzle_dia + " ")) &&
|
||||
diameter.substr(0, diameter.find_first_not_of("0123456789.")) == nozzle_dia &&
|
||||
std::find(diameters.begin(), diameters.end(), diameter) != diameters.end() ? diameter : nozzle_dia;
|
||||
// ORCA try to add nozzle diameter from config if list is empty. fixes blank nozzle combo box when preset has no alias
|
||||
if(diameters[0].empty() && !nozzle_dia.empty()){
|
||||
|
||||
@@ -1475,7 +1475,7 @@ void PreferencesDialog::create()
|
||||
app_config = get_app_config();
|
||||
|
||||
m_parent = new MyscrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_parent->SetScrollRate(5, 5);
|
||||
m_parent->SetScrollRate(0, FromDIP(20));
|
||||
m_parent->SetBackgroundColour(*wxWHITE);
|
||||
|
||||
m_sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -32,7 +32,7 @@ PrintOptionsDialog::PrintOptionsDialog(wxWindow* parent)
|
||||
|
||||
|
||||
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(10));
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrollwindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrollwindow->SetMinSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
m_scrollwindow->SetMaxSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
|
||||
@@ -1225,7 +1225,7 @@ size_t PublishSettingsDialog::category_index_for(
|
||||
}
|
||||
|
||||
category.scroll = new wxScrolledWindow(category.page, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
category.scroll->SetScrollRate(0, 10);
|
||||
category.scroll->SetScrollRate(0, FromDIP(20));
|
||||
category.scroll->SetBackgroundColour(GetBackgroundColour());
|
||||
category.list_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
category.scroll->SetSizer(category.list_sizer);
|
||||
|
||||
@@ -76,7 +76,7 @@ ReleaseNoteDialog::ReleaseNoteDialog(Plater *plater /*= nullptr*/)
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(430)), wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(5, 5);
|
||||
m_vebview_release_note->SetScrollRate(5, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
m_vebview_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
|
||||
@@ -142,7 +142,7 @@ UpdatePluginDialog::UpdatePluginDialog(wxWindow* parent /*= nullptr*/)
|
||||
operation_tips->SetMaxSize(wxSize(FromDIP(260), -1));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(5, 5);
|
||||
m_vebview_release_note->SetScrollRate(5, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(260), FromDIP(150)));
|
||||
m_vebview_release_note->SetMaxSize(wxSize(FromDIP(260), FromDIP(150)));
|
||||
@@ -277,7 +277,7 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
m_simplebook_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
|
||||
m_scrollwindows_release_note = new wxScrolledWindow(m_simplebook_release_note, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(430)), wxVSCROLL);
|
||||
m_scrollwindows_release_note->SetScrollRate(5, 5);
|
||||
m_scrollwindows_release_note->SetScrollRate(5, FromDIP(20));
|
||||
m_scrollwindows_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
|
||||
//webview
|
||||
@@ -538,7 +538,7 @@ SecondaryCheckDialog::SecondaryCheckDialog(wxWindow* parent, wxWindowID id, cons
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(0, 5);
|
||||
m_vebview_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(*wxWHITE);
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(400), FromDIP(380)));
|
||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
@@ -823,7 +823,7 @@ PrintErrorDialog::PrintErrorDialog(wxWindow* parent, wxWindowID id, const wxStri
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(5));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(0, 5);
|
||||
m_vebview_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(*wxWHITE);
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(320), FromDIP(250)));
|
||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
@@ -1122,7 +1122,7 @@ ConfirmBeforeSendDialog::ConfirmBeforeSendDialog(wxWindow* parent, wxWindowID id
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
m_vebview_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_vebview_release_note->SetScrollRate(0, 5);
|
||||
m_vebview_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_vebview_release_note->SetBackgroundColour(*wxWHITE);
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(400), FromDIP(380)));
|
||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
|
||||
@@ -31,7 +31,7 @@ SafetyOptionsDialog::SafetyOptionsDialog(wxWindow* parent)
|
||||
SetSize(FromDIP(480),FromDIP(320));
|
||||
|
||||
m_scrollwindow = new wxScrolledWindow(this, wxID_ANY);
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(10));
|
||||
m_scrollwindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrollwindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrollwindow->SetMinSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
m_scrollwindow->SetMaxSize(wxSize(FromDIP(480), wxDefaultCoord));
|
||||
@@ -272,4 +272,4 @@ void SafetyOptionsDialog::show_idel_heating_toast(const wxString &text)
|
||||
m_idel_heating_toast_timer.StartOnce(3000);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -148,7 +148,7 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
|
||||
|
||||
wxBoxSizer* m_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scroll_area = new wxScrolledWindow(this);
|
||||
m_scroll_area->SetScrollRate(0, 20);
|
||||
m_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
m_scroll_area->SetBackgroundColour(m_colour_def_color);
|
||||
m_scroll_area->SetMinSize(wxSize(FromDIP(700), FromDIP(600)));
|
||||
m_scroll_area->SetMaxSize(wxSize(FromDIP(700), FromDIP(600)));
|
||||
@@ -707,7 +707,7 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
|
||||
//show bind failed info
|
||||
m_sw_print_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125)), wxVSCROLL);
|
||||
m_sw_print_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_print_failed_info->SetScrollRate(0, 5);
|
||||
m_sw_print_failed_info->SetScrollRate(0, FromDIP(20));
|
||||
m_sw_print_failed_info->SetMinSize(wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125)));
|
||||
m_sw_print_failed_info->SetMaxSize(wxSize(SELECT_MACHINE_DIALOG_SIMBOOK_SIZE2.x, FromDIP(125)));
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ SelectMachinePopup::SelectMachinePopup(wxWindow *parent)
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, SELECT_MACHINE_LIST_SIZE, wxHSCROLL | wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetMinSize(SELECT_MACHINE_LIST_SIZE);
|
||||
m_scrolledWindow->SetScrollRate(0, 5);
|
||||
m_scrolledWindow->SetScrollRate(0, SELECT_MACHINE_ITEM_SIZE.y);
|
||||
auto m_sizxer_scrolledWindow = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolledWindow->SetSizer(m_sizxer_scrolledWindow);
|
||||
m_scrolledWindow->Layout();
|
||||
|
||||
@@ -278,7 +278,7 @@ SendMultiMachinePage::SendMultiMachinePage(Plater* plater)
|
||||
|
||||
m_main_scroll = new ScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_main_scroll->SetBackgroundColour(*wxWHITE);
|
||||
m_main_scroll->SetScrollRate(5, 5);
|
||||
m_main_scroll->SetScrollRate(5, FromDIP(20));
|
||||
|
||||
m_sizer_body = new wxBoxSizer(wxVERTICAL);
|
||||
m_main_page = create_page();
|
||||
@@ -1353,7 +1353,7 @@ wxPanel* SendMultiMachinePage::create_page()
|
||||
|
||||
scroll_macine_list = new wxScrolledWindow(main_page, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(800), FromDIP(300)), wxHSCROLL | wxVSCROLL);
|
||||
scroll_macine_list->SetBackgroundColour(*wxWHITE);
|
||||
scroll_macine_list->SetScrollRate(5, 5);
|
||||
scroll_macine_list->SetScrollRate(5, FromDIP(SEND_ITEM_MAX_HEIGHT));
|
||||
scroll_macine_list->SetMinSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(SEND_ITEM_MAX_HEIGHT)));
|
||||
scroll_macine_list->SetMaxSize(wxSize(FromDIP(DEVICE_ITEM_MAX_WIDTH), 10 * FromDIP(SEND_ITEM_MAX_HEIGHT)));
|
||||
|
||||
|
||||
@@ -384,7 +384,7 @@ SendToPrinterDialog::SendToPrinterDialog(Plater *plater)
|
||||
//show bind failed info
|
||||
m_sw_print_failed_info = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(380), FromDIP(125)), wxVSCROLL);
|
||||
m_sw_print_failed_info->SetBackgroundColour(*wxWHITE);
|
||||
m_sw_print_failed_info->SetScrollRate(0, 5);
|
||||
m_sw_print_failed_info->SetScrollRate(0, FromDIP(20));
|
||||
m_sw_print_failed_info->SetMinSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
m_sw_print_failed_info->SetMaxSize(wxSize(FromDIP(380), FromDIP(125)));
|
||||
|
||||
|
||||
@@ -1302,7 +1302,7 @@ void PrintingTaskPanel::set_star_count(int star_count)
|
||||
StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style, const wxString &name)
|
||||
: wxScrolledWindow(parent, id, pos, size, wxHSCROLL | wxVSCROLL)
|
||||
{
|
||||
this->SetScrollRate(25, 25);
|
||||
SetScrollRate(25, FromDIP(20));
|
||||
Slic3r::DeviceManager* dev = Slic3r::GUI::wxGetApp().getDeviceManager();
|
||||
if (!dev) return;
|
||||
obj = dev->get_selected_machine();
|
||||
|
||||
@@ -700,7 +700,7 @@ SyncAmsInfoDialog::SyncAmsInfoDialog(wxWindow *parent, SyncInfo &info) :
|
||||
//wxBoxSizer *m_scroll_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scrolledWindow = new wxScrolledWindow(m_show_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetBackgroundColour(*wxWHITE);
|
||||
m_scrolledWindow->SetScrollRate(0, 20);
|
||||
m_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrolledWindow->SetMinSize(wxSize(-1, SyncAmsInfoDialogHeightMAX));
|
||||
m_scrolledWindow->SetMaxSize(wxSize(-1, SyncAmsInfoDialogHeightMAX));
|
||||
m_scrolledWindow->EnableScrolling(false,true);
|
||||
|
||||
+13
-2
@@ -3069,6 +3069,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("wipe_tower_rib_width", "multimaterial_settings_prime_tower#rib-width");
|
||||
optgroup->append_single_option_line("wipe_tower_fillet_wall", "multimaterial_settings_prime_tower#fillet-wall");
|
||||
optgroup->append_single_option_line("wipe_tower_no_sparse_layers", "multimaterial_settings_prime_tower#no-sparse-layers");
|
||||
optgroup->append_single_option_line("wipe_tower_sparse_layers_combination", "multimaterial_settings_prime_tower#combine-sparse-layers");
|
||||
optgroup->append_single_option_line("single_extruder_multi_material_priming", "multimaterial_settings_prime_tower");
|
||||
|
||||
optgroup = page->new_optgroup(L("Filament for Features"), L"param_filament_for_features");
|
||||
@@ -7165,12 +7166,18 @@ void Tab::restore_last_select_item()
|
||||
|
||||
bool Tab::page_build_pending() const
|
||||
{
|
||||
return m_active_page != nullptr && m_active_page->build_pending();
|
||||
return m_active_page != nullptr && (m_active_page->build_pending() || m_active_page->visibility_pending());
|
||||
}
|
||||
|
||||
bool Tab::page_build_step()
|
||||
{
|
||||
return m_active_page != nullptr && m_active_page->build_step(m_mode);
|
||||
if (m_active_page == nullptr)
|
||||
return false;
|
||||
if (m_active_page->build_pending())
|
||||
m_active_page->build_step(m_mode);
|
||||
else
|
||||
m_active_page->update_visibility(m_mode, true);
|
||||
return page_build_pending();
|
||||
}
|
||||
|
||||
void Tab::update_description_lines()
|
||||
@@ -8601,6 +8608,8 @@ void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibil
|
||||
}
|
||||
|
||||
m_show = ret_val;
|
||||
if (update_contolls_visibility)
|
||||
m_visibility_applied = true;
|
||||
#ifdef __WXMSW__
|
||||
if (!m_show) return;
|
||||
// BBS: fix field control position
|
||||
@@ -8654,6 +8663,7 @@ bool Page::activate_group(size_t i, ConfigOptionMode mode, std::function<void()>
|
||||
auto& group = m_optgroups[i];
|
||||
if (!group->activate(throw_if_canceled))
|
||||
return false;
|
||||
m_visibility_applied = false;
|
||||
m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), m_parent->FromDIP(5)); // ORCA use less margin on parameters section
|
||||
group->update_visibility(mode);
|
||||
#if HIDE_FIRST_SPLIT_LINE
|
||||
@@ -8688,6 +8698,7 @@ void Page::clear()
|
||||
for (auto group : m_optgroups)
|
||||
group->clear();
|
||||
m_page_title = NULL;
|
||||
m_visibility_applied = false;
|
||||
}
|
||||
|
||||
void Page::msw_rescale()
|
||||
|
||||
@@ -71,6 +71,7 @@ class Page: public std::enable_shared_from_this<Page>// : public wxScrolledWindo
|
||||
// BBS: new layout
|
||||
wxStaticText* m_page_title;
|
||||
bool m_show = true;
|
||||
bool m_visibility_applied = false;
|
||||
public:
|
||||
//BBS: GUI refactor
|
||||
Page(wxWindow* parent, const wxString& title, int iconID, wxPanel* tab_owner);
|
||||
@@ -98,6 +99,8 @@ public:
|
||||
bool build_pending() const;
|
||||
// Builds the next option group that has no controls yet; true while some remain.
|
||||
bool build_step(ConfigOptionMode mode);
|
||||
// Whether the controls have not been shown or hidden for a mode since they were built.
|
||||
bool visibility_pending() const { return !m_visibility_applied; }
|
||||
void clear();
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
@@ -443,8 +446,8 @@ public:
|
||||
// BBS: new layout
|
||||
void set_expanded(bool value);
|
||||
void restore_last_select_item();
|
||||
// page_build_pending() says whether the selected page has groups without controls, and
|
||||
// page_build_step() builds one.
|
||||
// page_build_pending() says whether the selected page has groups without controls or controls
|
||||
// not yet shown for the mode, and page_build_step() does the next of those.
|
||||
bool page_build_pending() const;
|
||||
bool page_build_step();
|
||||
|
||||
|
||||
@@ -534,7 +534,7 @@ public:
|
||||
|
||||
m_content = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_content->SetBackgroundColour(pop_bg);
|
||||
m_content->SetScrollRate(0, FromDIP(5));
|
||||
m_content->SetScrollRate(0, FromDIP(20));
|
||||
auto* outer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
const int pop_w = std::max(FromDIP(213), popup_width);
|
||||
@@ -2274,7 +2274,7 @@ void TextureImportDialog::build_mapping_panel(wxWindow* parent, wxSizer* sizer)
|
||||
|
||||
m_mapping_scroll = new wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition,
|
||||
wxSize(-1, FromDIP(300)));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(10));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(20));
|
||||
m_mapping_scroll->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
m_mapping_scroll->Bind(wxEVT_MOUSEWHEEL, &TextureImportDialog::dismiss_filament_popup_on_wheel, this);
|
||||
|
||||
@@ -4335,7 +4335,7 @@ void TextureImportDialog::on_dpi_changed(const wxRect&)
|
||||
|
||||
if (m_mapping_scroll) {
|
||||
m_mapping_scroll->SetMinSize(wxSize(-1, FromDIP(300)));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(10));
|
||||
m_mapping_scroll->SetScrollRate(0, FromDIP(20));
|
||||
}
|
||||
|
||||
if (m_btn_skip) {
|
||||
|
||||
@@ -942,7 +942,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
|
||||
m_sizer_tab->Add(m_table_top, 1, 0, 0);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(m_panel_tab, wxID_ANY, wxDefaultPosition, UNSAVE_CHANGE_DIALOG_SCROLL_WINDOW_SIZE, wxNO_BORDER|wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(0, 5);
|
||||
m_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
m_scrolledWindow->SetBackgroundColour(GREY200);
|
||||
m_sizer_bottom = new wxBoxSizer(wxVERTICAL);
|
||||
m_sizer_bottom->Add(m_scrolledWindow, 1, wxEXPAND, 0);
|
||||
|
||||
@@ -130,7 +130,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
|
||||
m_sizer_right->Add(0, 0, 1, wxTOP, FromDIP(15));
|
||||
|
||||
auto m_scrollwindw_release_note = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(430)),wxVSCROLL);
|
||||
m_scrollwindw_release_note->SetScrollRate(0, 5);
|
||||
m_scrollwindw_release_note->SetScrollRate(0, FromDIP(20));
|
||||
m_scrollwindw_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
m_scrollwindw_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_scrollwindw_release_note->SetWindowStyle(wxVSCROLL);
|
||||
|
||||
@@ -1629,7 +1629,7 @@ UpgradePanel::UpgradePanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
|
||||
auto m_main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_scrolledWindow = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL);
|
||||
m_scrolledWindow->SetScrollRate(5, 25);
|
||||
m_scrolledWindow->SetScrollRate(5, FromDIP(20));
|
||||
|
||||
m_machine_list_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ CheckList::CheckList(
|
||||
|
||||
s_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_scroll_area = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, scroll_style);
|
||||
m_scroll_area->SetScrollRate(0, 10);
|
||||
m_scroll_area->SetScrollRate(0, FromDIP(20));
|
||||
m_scroll_area->SetSizer(s_sizer);
|
||||
m_scroll_area->SetBackgroundColour(parent->GetBackgroundColour());
|
||||
m_scroll_area->Bind(wxEVT_RIGHT_DOWN, &CheckList::ShowMenu, this);
|
||||
@@ -223,4 +223,4 @@ void CheckList::ShowMenu(wxMouseEvent &evt)
|
||||
wxPoint screen_pos = src->ClientToScreen(evt.GetPosition());
|
||||
wxPoint local_pos = ScreenToClient(screen_pos);
|
||||
PopupMenu(&m, local_pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ bool ProgressDialog::Create(const wxString &title, const wxString &message, int
|
||||
m_sizer_main->Add(m_simplebook, 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(28));
|
||||
} else {
|
||||
m_msg_scrolledWindow = new wxScrolledWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL );
|
||||
m_msg_scrolledWindow->SetScrollRate(0,5);
|
||||
m_msg_scrolledWindow->SetScrollRate(0, FromDIP(20));
|
||||
wxBoxSizer* m_msg_sizer= new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_msg = new wxStaticText(m_msg_scrolledWindow, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(PROGRESSDIALOG_SIMPLEBOOK_SIZE.x, -1), 0);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <wx/webviewarchivehandler.h>
|
||||
#include <wx/webviewfshandler.h>
|
||||
#include <wx/weakref.h>
|
||||
#if wxUSE_WEBVIEW_EDGE
|
||||
#include <wx/msw/webview_edge.h>
|
||||
#elif defined(__WXMAC__)
|
||||
@@ -235,7 +236,9 @@ class FakeWebView : public wxWebView
|
||||
wxDEFINE_EVENT(EVT_WEBVIEW_RECREATED, wxCommandEvent);
|
||||
|
||||
static std::vector<wxWebView*> g_webviews;
|
||||
static std::vector<wxWebView*> g_delay_webviews;
|
||||
// Webviews waiting for their script handler while another one is added; adding it yields, so a
|
||||
// view can be destroyed while it waits.
|
||||
static std::vector<wxWeakRef<wxWebView>> g_delay_webviews;
|
||||
|
||||
class WebViewRef : public wxObjectRefData
|
||||
{
|
||||
@@ -340,8 +343,9 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
||||
addScriptMessageHandler(webView);
|
||||
while (!g_delay_webviews.empty()) {
|
||||
auto views = std::move(g_delay_webviews);
|
||||
for (auto wv : views)
|
||||
addScriptMessageHandler(wv);
|
||||
for (const wxWeakRef<wxWebView>& wv : views)
|
||||
if (wv)
|
||||
addScriptMessageHandler(wv.get());
|
||||
}
|
||||
}
|
||||
#ifndef __WIN32__
|
||||
|
||||
@@ -1222,7 +1222,7 @@ ImageTransientPopup::ImageTransientPopup( wxWindow *parent, bool scrolled, wxBit
|
||||
m_panel->SetSize(300, 300);
|
||||
|
||||
// And also actually enable them.
|
||||
m_panel->SetScrollRate(10, 10);
|
||||
m_panel->SetScrollRate(10, FromDIP(20));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user