Merge remote-tracking branch 'prfork/cad-mainline' into cad-mainline

This commit is contained in:
Tommaso Bianchi
2026-08-20 17:45:15 +02:00
99 changed files with 3854 additions and 838 deletions
+19
View File
@@ -289,6 +289,9 @@ void AppConfig::set_defaults()
set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap));
}
// The getter already defaults, parses and clamps; write back what it resolves to.
set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(get_plugin_pages_visible_count()));
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
@@ -1646,6 +1649,22 @@ void AppConfig::set_network_plugin_version(const std::string& version)
set(SETTING_NETWORK_PLUGIN_VERSION, version);
}
int AppConfig::get_plugin_pages_visible_count() const
{
std::string value = get(SETTING_PLUGIN_PAGES_VISIBLE_COUNT);
if (value.empty())
return PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT;
int visible_count = PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT;
try {
visible_count = std::stoi(value);
}
catch (...) {
return PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT;
}
return std::clamp(visible_count, PLUGIN_PAGES_VISIBLE_COUNT_MIN, PLUGIN_PAGES_VISIBLE_COUNT_MAX);
}
std::vector<std::string> AppConfig::get_skipped_network_versions() const
{
std::vector<std::string> result;
+9
View File
@@ -41,6 +41,11 @@ using namespace nlohmann;
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
#define SETTING_PLUGIN_PAGES_VISIBLE_COUNT "plugin_pages_visible_count"
#define PLUGIN_PAGES_VISIBLE_COUNT_MIN 1
#define PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT 5
#define PLUGIN_PAGES_VISIBLE_COUNT_MAX 10
#if defined(_WIN32) || defined(_WIN64)
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
#else
@@ -381,6 +386,10 @@ public:
std::string get_network_plugin_version() const;
void set_network_plugin_version(const std::string& version);
// Number of plugin pages shown as fixed tabs before the rest are collapsed into a
// dropdown on the last tab.
int get_plugin_pages_visible_count() const;
std::vector<std::string> get_skipped_network_versions() const;
void add_skipped_network_version(const std::string& version);
bool is_network_version_skipped(const std::string& version) const;
+2
View File
@@ -8,6 +8,8 @@
namespace Slic3r {
template BoundingBoxBase<Point, Points>::BoundingBoxBase(const Points &points);
template void BoundingBoxBase<Point, Points>::construct<0, BoundingBox, Points::const_iterator>(BoundingBox&, Points::const_iterator, Points::const_iterator);
template void BoundingBoxBase<Point, Points>::construct<1, BoundingBox, Points::const_iterator>(BoundingBox&, Points::const_iterator, Points::const_iterator);
template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points);
template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points);
+2
View File
@@ -154,6 +154,8 @@ set(lisbslic3r_sources
Fill/FillConcentric.hpp
Fill/FillConcentricInternal.cpp
Fill/FillConcentricInternal.hpp
Fill/FillCornerSmoothing.cpp
Fill/FillCornerSmoothing.hpp
Fill/Fill.cpp
Fill/FillCrossHatch.cpp
Fill/FillCrossHatch.hpp
+2
View File
@@ -2982,6 +2982,8 @@ public:
const double & opt_float(const t_config_option_key &opt_key, unsigned int idx) const;
double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloatsNullable>(opt_key)->get_at(idx); }
const double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloatsNullable *>(this->option(opt_key))->get_at(idx); }
FloatOrPercent & opt_float_or_percent_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloatsOrPercentsNullable>(opt_key)->get_at(idx); }
const FloatOrPercent & opt_float_or_percent_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(this->option(opt_key))->get_at(idx); }
int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
+3 -3
View File
@@ -970,9 +970,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
region_config.sparse_infill_rotate_template.value);
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
// Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
// FillHilbertCurve::generate clamps and validates the value itself.
if (params.pattern == ipHilbertCurve)
// Orca: the smoothing factor only applies to the sparse infill patterns that
// implement it. The fills clamp and validate the value themselves.
if (is_smoothable_infill_pattern(params.pattern, params.multiline))
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
} else {
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
+4
View File
@@ -2,6 +2,7 @@
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillBase.hpp"
#include "FillCornerSmoothing.hpp"
#include "Fill3DHoneycomb.hpp"
namespace Slic3r {
@@ -271,6 +272,9 @@ void Fill3DHoneycomb::_fill_surface_single(
for (Polyline &pl : polylines){
pl.translate(bb.min);
pl.simplify(5 * spacing); // simplify to 5x line width
// Orca: round the corners of the octahedral wave. The layers where the wave degenerates to a
// straight line have no corner to round.
smooth_polyline_corners(pl, params.smooth_factor, scaled<double>(params.resolution));
}
// Apply multiline offset if needed
+22 -1
View File
@@ -5,6 +5,7 @@
#include "Arachne/WallToolPaths.hpp"
#include "FillConcentric.hpp"
#include "FillCornerSmoothing.hpp"
#include <libslic3r/ShortestPath.hpp>
namespace Slic3r {
@@ -32,12 +33,32 @@ void FillConcentric::_fill_surface_single(
Polygons loops = to_polygons(contracted);
ExPolygons last { std::move(contracted) };
ExPolygons last { contracted };
while (! last.empty()) {
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
append(loops, to_polygons(last));
}
// Orca: round the corners of the loops. Unlike the other patterns these are never clipped to the
// fill region - they are its offsets - so a corner may only be rounded where the curve replacing it
// stays inside. Rounding cuts toward the inside of the turn, which around a hole, at a concave
// feature or across a thin region is outside the fill and would put the extrusion over a wall.
// The reach is capped at half the distance between two loops as well: a loop is as long as the
// object, and a corner cut by half of its side would swallow the neighbouring loops.
auto corner_stays_inside = [&contracted](const Vec2d &from, const Vec2d &to) {
// The straight chord between the ends of the curve is the deepest the curve can cut.
for (const double t : { 0.25, 0.5, 0.75 }) {
const Vec2d sample = from + t * (to - from);
const Point point(coord_t(sample.x()), coord_t(sample.y()));
if (std::none_of(contracted.begin(), contracted.end(),
[&point](const ExPolygon &region) { return region.contains(point); }))
return false;
}
return true;
};
smooth_polygons_corners(loops, params.smooth_factor, scaled<double>(params.resolution), 0.5 * distance,
corner_stays_inside);
// generate paths from the outermost to the innermost, to avoid
// adhesion problems of the first central tiny loops
loops = union_pt_chained_outside_in(loops);
+226
View File
@@ -0,0 +1,226 @@
#include <array>
#include "FillCornerSmoothing.hpp"
namespace Slic3r {
// Turns sharper than this are left untouched: both ends of the curve replacing such a corner nearly
// coincide, so the corner would be rounded into a degenerate loop instead of a hairpin.
static constexpr const double min_smoothed_turn_cosine = -0.9;
// The control points are expressed in the (incoming, outgoing) basis of the corner, which is not
// orthonormal for turns other than a right angle.
using QuinticBezier = std::array<Vec2d, 6>;
static bool is_bezier_flat(const QuinticBezier &curve, const Vec2d &incoming, const Vec2d &outgoing, const double deviation)
{
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
// comparing squared values avoids a square root.
auto in_plane = [&incoming, &outgoing](const Vec2d &c) { return c.x() * incoming + c.y() * outgoing; };
const Vec2d chord = in_plane(curve.back() - curve.front());
const double chord_length_sq = chord.squaredNorm();
const double max_cross_sq = deviation * deviation * chord_length_sq;
for (size_t i = 1; i + 1 < curve.size(); ++i) {
const Vec2d offset = in_plane(curve[i] - curve.front());
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
if (cross * cross > max_cross_sq)
return false;
}
return true;
}
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
{
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
// control point to the left half and one to the right half; the latter is filled backwards to keep
// both resulting control polygons in their original parameter direction.
QuinticBezier subdivision = curve;
left.front() = subdivision.front();
right.back() = subdivision.back();
for (size_t level = 1; level < curve.size(); ++level) {
for (size_t i = 0; i + level < curve.size(); ++i)
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
left[level] = subdivision.front();
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
}
}
static void flatten_bezier(
const QuinticBezier &curve, const Vec2d &incoming, const Vec2d &outgoing, const double deviation, std::vector<Vec2d> &output)
{
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
static constexpr size_t max_depth = 16;
std::vector<QuinticBezier> subcurves(2);
subdivide_bezier(curve, subcurves[0], subcurves[1]);
for (size_t depth = 1; depth < max_depth; ++depth) {
bool all_flat = true;
for (const QuinticBezier &c : subcurves)
if (!is_bezier_flat(c, incoming, outgoing, deviation)) {
all_flat = false;
break;
}
if (all_flat)
break;
std::vector<QuinticBezier> finer(subcurves.size() * 2);
for (size_t i = 0; i < subcurves.size(); ++i)
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
subcurves = std::move(finer);
}
// The curve start is deliberately omitted so it can be shared with the straight leg feeding into it.
output.clear();
output.reserve(subcurves.size());
for (const QuinticBezier &c : subcurves)
output.emplace_back(c.back());
}
const std::vector<Vec2d>& CornerSmoother::curve_coefficients(
const double corner_distance, const Vec2d &incoming, const Vec2d &outgoing)
{
const double cosine = incoming.dot(outgoing);
// Corners of the same size and turn angle are congruent, so they flatten identically. An infill
// path walks over the very same corner over and over again, the Hilbert curve over a single one.
if (m_has_cached_coefficients && corner_distance == m_cached_distance && cosine == m_cached_cosine)
return m_cached_coefficients;
// One canonical corner running from -corner_distance along the incoming leg to corner_distance
// along the outgoing one. At each end, the first three control points are collinear and equally
// spaced: the tangent follows the adjoining straight leg and the second derivative is zero. The
// endpoint curvature is therefore zero, giving G2 joins to both legs.
const double d = corner_distance;
const QuinticBezier corner_curve {{
{-d, 0.}, {-0.7 * d, 0.}, {-0.4 * d, 0.}, {0., 0.4 * d}, {0., 0.7 * d}, {0., d}
}};
// Retain a finite positive tolerance if the smoother was set up with an invalid one.
const double deviation = m_tolerance > 0. && std::isfinite(m_tolerance) ? m_tolerance : EPSILON;
flatten_bezier(corner_curve, incoming, outgoing, deviation, m_cached_coefficients);
m_cached_distance = corner_distance;
m_cached_cosine = cosine;
m_has_cached_coefficients = true;
return m_cached_coefficients;
}
void CornerSmoother::round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next)
{
m_corner_points.clear();
const Vec2d incoming_leg = corner - previous;
const Vec2d outgoing_leg = next - corner;
const double incoming_length = incoming_leg.norm();
const double outgoing_length = outgoing_leg.norm();
if (incoming_length < EPSILON || outgoing_length < EPSILON) {
m_corner_points.emplace_back(corner);
return;
}
const Vec2d incoming = incoming_leg / incoming_length;
const Vec2d outgoing = outgoing_leg / outgoing_length;
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
// A collinear vertex is no corner at all, and a hairpin cannot be rounded, see above.
if (std::abs(cross) < EPSILON || incoming.dot(outgoing) < min_smoothed_turn_cosine) {
m_corner_points.emplace_back(corner);
return;
}
// Consuming at most half of the shorter leg keeps the curves of two adjacent corners apart.
double corner_distance = m_corner_distance_ratio * std::min(incoming_length, outgoing_length);
if (m_max_corner_distance > 0.)
corner_distance = std::min(corner_distance, m_max_corner_distance);
const Vec2d curve_start = corner - corner_distance * incoming;
const Vec2d curve_end = corner + corner_distance * outgoing;
if (m_corner_filter && !m_corner_filter(curve_start, curve_end)) {
m_corner_points.emplace_back(corner);
return;
}
const std::vector<Vec2d> &coefficients = curve_coefficients(corner_distance, incoming, outgoing);
m_corner_points.reserve(coefficients.size() + 1);
m_corner_points.emplace_back(curve_start);
for (const Vec2d &coefficient : coefficients)
m_corner_points.emplace_back(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
}
// Rounds the corners of a scaled point sequence. A polygon closes implicitly, so all of its vertices
// are corners; a polyline is an open path that keeps both of its ends, even where they coincide - a
// path returning to where it started retraces its way back and is not a loop.
static Points smooth_corners(const Points &points, const bool polygon, CornerSmoother &smoother)
{
// A polygon has no free ends, so its first vertex is a corner like any other. Rounding it takes
// feeding the smoother the last vertex first, whose own output point is then dropped again.
size_t skip = polygon ? 1 : 0;
Points smoothed;
smoothed.reserve(2 * points.size());
auto emit = [&smoothed, &skip](const Vec2d &point) {
if (skip > 0) {
--skip;
return;
}
smoothed.emplace_back(coord_t(std::floor(point.x() + 0.5)), coord_t(std::floor(point.y() + 0.5)));
};
if (polygon)
smoother.push(points.back().cast<double>(), emit);
for (const Point &point : points)
smoother.push(point.cast<double>(), emit);
if (polygon)
// Wrap the first vertex around, so that the last one is a corner as well.
smoother.push(points.front().cast<double>(), emit);
smoother.flush(emit);
if (polygon)
// The flushed point is the wrapped first vertex, which a polygon does not store.
smoothed.pop_back();
return smoothed;
}
void smooth_polyline_corners(Polyline &polyline, const double smooth_factor, const double tolerance,
const double max_corner_distance, const CornerFilter &corner_filter)
{
CornerSmoother smoother(smooth_factor, tolerance, max_corner_distance, corner_filter);
if (!smoother.enabled() || polyline.size() < 3)
return;
polyline.points = smooth_corners(polyline.points, false, smoother);
// Rounding back to the integer grid may collapse neighbouring samples of a curve.
polyline.remove_duplicate_points();
}
void smooth_polylines_corners(Polylines &polylines, const double smooth_factor, const double tolerance,
const double max_corner_distance, const CornerFilter &corner_filter)
{
if (sanitize_smooth_factor(smooth_factor) == 0.)
return;
for (Polyline &polyline : polylines)
smooth_polyline_corners(polyline, smooth_factor, tolerance, max_corner_distance, corner_filter);
}
void smooth_polygons_corners(Polygons &polygons, const double smooth_factor, const double tolerance,
const double max_corner_distance, const CornerFilter &corner_filter)
{
CornerSmoother smoother(smooth_factor, tolerance, max_corner_distance, corner_filter);
if (!smoother.enabled())
return;
for (Polygon &polygon : polygons) {
if (polygon.size() < 3)
continue;
polygon.points = smooth_corners(polygon.points, true, smoother);
polygon.remove_duplicate_points();
// The curves of the first and of the last corner may have met on the segment they share. A
// polygon closes implicitly, so it must not repeat its first vertex at the end.
if (polygon.points.size() > 1 && polygon.points.front() == polygon.points.back())
polygon.points.pop_back();
}
}
} // namespace Slic3r
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <functional>
#include <vector>
#include "../libslic3r.h"
#include "../Point.hpp"
#include "../Polygon.hpp"
#include "../Polyline.hpp"
namespace Slic3r {
// Orca: NaN or infinite factors disable the smoothing, everything else is clamped to <0, 1>.
inline double sanitize_smooth_factor(double smooth_factor)
{
return std::isfinite(smooth_factor) ? std::clamp(smooth_factor, 0., 1.) : 0.;
}
// Decides whether a corner may be replaced by the curve that leaves the path at `from` and rejoins it
// at `to`, both in the coordinate system of the pushed points. Rounding cuts toward the inside of the
// turn, so a path that is not clipped to the fill region afterwards needs this to stay inside it.
using CornerFilter = std::function<bool(const Vec2d &from, const Vec2d &to)>;
// Orca: Replaces the sharp vertices of an infill path with curves that join the adjoining straight
// legs with a continuous curvature, so the toolhead does not have to stop in every corner.
// Points are pushed one by one, because the plane path fills produce their path on the fly, and
// every point of the smoothed path is handed over to the caller supplied emit callback.
// Fully smoothed adjacent corners meet at the midpoint of the segment they share, so the emitted
// points may collapse onto each other once rounded to the integer grid of the caller. Dropping such
// duplicates is left to the caller, which is the only one knowing that grid.
class CornerSmoother
{
public:
// tolerance is the maximum chordal deviation of the flattened curves, in the units of the pushed
// points. max_corner_distance caps how far a curve may reach along a leg, in the same units; it
// bounds how far a rounded corner moves away from the original path, which matters where the legs
// are much longer than the spacing of the pattern. Zero leaves the reach uncapped.
CornerSmoother(double smooth_factor, double tolerance, double max_corner_distance = 0.,
CornerFilter corner_filter = {})
: m_corner_distance_ratio(0.5 * sanitize_smooth_factor(smooth_factor)), m_tolerance(tolerance),
m_max_corner_distance(max_corner_distance), m_corner_filter(std::move(corner_filter))
{}
bool enabled() const { return m_corner_distance_ratio > 0.; }
template<typename Emit> void push(const Vec2d &point, Emit &emit)
{
if (m_pending == 0) {
emit(point);
m_previous = point;
} else if (m_pending > 1) {
round_corner(m_previous, m_corner, point);
for (const Vec2d &corner_point : m_corner_points)
emit(corner_point);
m_previous = m_corner;
}
m_corner = point;
m_pending = std::min(m_pending + 1, 2);
}
// Emits the last point of the path and prepares the smoother for a new one.
template<typename Emit> void flush(Emit &emit)
{
if (m_pending > 1)
emit(m_corner);
m_pending = 0;
}
private:
// Fills m_corner_points with the points replacing the corner vertex.
void round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next);
// Flattens the canonical corner curve of the given size and turn into coordinates of the
// (incoming, outgoing) basis of the corner. Cached, as an infill path repeats the same corner.
const std::vector<Vec2d>& curve_coefficients(double corner_distance, const Vec2d &incoming, const Vec2d &outgoing);
// Fraction of the shorter adjoining segment consumed on each side of a corner. Half of a segment
// is the maximum, otherwise the curves of two adjacent corners would overlap.
const double m_corner_distance_ratio;
const double m_tolerance;
const double m_max_corner_distance;
const CornerFilter m_corner_filter;
std::vector<Vec2d> m_corner_points;
// Cached flattening of the last corner, valid for corners of the same size and turn angle.
std::vector<Vec2d> m_cached_coefficients;
double m_cached_distance { 0. };
double m_cached_cosine { 0. };
bool m_has_cached_coefficients { false };
Vec2d m_previous { Vec2d::Zero() };
Vec2d m_corner { Vec2d::Zero() };
// Number of points held back: none, the first point of a path, or a corner candidate.
int m_pending { 0 };
};
// Rounds the corners of already scaled paths in place. Paths of less than three points are left alone.
// Both ends of a polyline are kept where they are, even when they coincide: such a path retraces its
// way back and joining its ends would turn it into a loop. See CornerSmoother for max_corner_distance.
void smooth_polyline_corners(Polyline &polyline, double smooth_factor, double tolerance,
double max_corner_distance = 0., const CornerFilter &corner_filter = {});
void smooth_polylines_corners(Polylines &polylines, double smooth_factor, double tolerance,
double max_corner_distance = 0., const CornerFilter &corner_filter = {});
// Polygons close implicitly, so every one of their vertices is a corner.
void smooth_polygons_corners(Polygons &polygons, double smooth_factor, double tolerance,
double max_corner_distance = 0., const CornerFilter &corner_filter = {});
} // namespace Slic3r
+4
View File
@@ -3,6 +3,7 @@
#include "../Surface.hpp"
#include <cmath>
#include "FillBase.hpp"
#include "FillCornerSmoothing.hpp"
#include "FillCrossHatch.hpp"
namespace Slic3r {
@@ -205,6 +206,9 @@ void FillCrossHatch ::_fill_surface_single(
// shift the pattern to the actual space
for (Polyline &pl : polylines) { pl.translate(bb.min); }
// Orca: round the corners of the transition layers. The repeat layers are straight lines and stay as they are.
smooth_polylines_corners(polylines, params.smooth_factor, scaled<double>(params.resolution));
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
+4
View File
@@ -2,6 +2,7 @@
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillCornerSmoothing.hpp"
#include "FillHoneycomb.hpp"
namespace Slic3r {
@@ -70,6 +71,9 @@ void FillHoneycomb::_fill_surface_single(
}
p.rotate(-direction.first, m.hex_center);
p.simplify(5 * spacing); // simplify to 5x line width
// Orca: round the corners of the honeycomb cells. Done before the clipping, so that the
// curves are cut by the region boundary just like the sharp path would be.
smooth_polyline_corners(p, params.smooth_factor, scaled<double>(params.resolution));
all_polylines.push_back(p);
}
}
+14
View File
@@ -2,6 +2,7 @@
#include "../Print.hpp"
#include "../ShortestPath.hpp"
#include "FillBase.hpp"
#include "FillCornerSmoothing.hpp"
#include "FillLightning.hpp"
#include "Lightning/Generator.hpp"
@@ -17,6 +18,19 @@ void Filler::_fill_surface_single(
const Layer &layer = generator->getTreesForLayer(this->layer_id);
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap));
// Orca: round the turns of the branches. Hairpins are left sharp, as they cannot be rounded, and
// the reach is capped: cutting a corner moves the branch, and a branch is as long as the object
// rather than as long as one cell of a pattern, so half of a leg would merge it with its neighbour
// instead of rounding the turn between them. Half the distance between two branches keeps them
// apart. With more than one line per infill wall the branches are printed as outlines drawn around
// them, and the outlines of branches that run into each other merge into a single one; moving a
// branch by more than a fraction of its printed width breaks such an outline up into separate
// loops, so that width bounds the reach as well.
const double branch_width = scaled<double>(this->spacing) * params.multiline;
const double branch_spacing = branch_width / std::max(double(params.density), EPSILON);
const double max_reach = 0.5 * (params.multiline > 1 ? branch_width : branch_spacing);
smooth_polylines_corners(fill_lines, params.smooth_factor, scaled<double>(params.resolution), max_reach);
// Apply multiline offset if needed
multiline_fill(fill_lines, params, spacing);
+59 -147
View File
@@ -2,6 +2,7 @@
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillCornerSmoothing.hpp"
#include "FillPlanePath.hpp"
namespace Slic3r {
@@ -288,145 +289,60 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x,
}
}
using QuinticBezier = std::array<Vec2d, 6>;
static bool is_bezier_flat(const QuinticBezier &curve, const double deviation)
{
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
// comparing squared values avoids a square root.
const Vec2d chord = curve.back() - curve.front();
const double chord_length_sq = chord.squaredNorm();
const double max_cross_sq = deviation * deviation * chord_length_sq;
for (size_t i = 1; i + 1 < curve.size(); ++i) {
const Vec2d offset = curve[i] - curve.front();
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
if (cross * cross > max_cross_sq)
return false;
}
return true;
}
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
{
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
// control point to the left half and one to the right half; the latter is filled backwards to keep
// both resulting control polygons in their original parameter direction.
QuinticBezier subdivision = curve;
left.front() = subdivision.front();
right.back() = subdivision.back();
for (size_t level = 1; level < curve.size(); ++level) {
for (size_t i = 0; i + level < curve.size(); ++i)
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
left[level] = subdivision.front();
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
}
}
static void flatten_bezier(const QuinticBezier &curve, const double deviation, std::vector<Vec2d> &output)
{
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
static constexpr size_t max_depth = 16;
std::vector<QuinticBezier> subcurves(2);
subdivide_bezier(curve, subcurves[0], subcurves[1]);
for (size_t depth = 1; depth < max_depth; ++depth) {
bool all_flat = true;
for (const QuinticBezier &c : subcurves)
if (!is_bezier_flat(c, deviation)) {
all_flat = false;
break;
}
if (all_flat)
break;
std::vector<QuinticBezier> finer(subcurves.size() * 2);
for (size_t i = 0; i < subcurves.size(); ++i)
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
subcurves = std::move(finer);
}
// The curve start is deliberately omitted so consecutive curve pieces can share it without duplication.
output.reserve(output.size() + subcurves.size());
for (const QuinticBezier &c : subcurves)
output.emplace_back(c.back());
}
// Rounds the corners of the generated path on its way to the infill output.
template<typename Output>
static void generate_smooth_hilbert_curve(
coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const double corner_distance, Output &output)
class SmoothingPolylineOutput
{
// A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
// generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
// clipper or the later region intersection removes the padded part of the traversal.
size_t sz = 2;
const size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
while (sz < sz0)
sz <<= 1;
public:
SmoothingPolylineOutput(Output &output, const double smooth_factor, const double tolerance)
: m_output(output), m_smoother(smooth_factor, tolerance) {}
const size_t point_count = sz * sz;
output.reserve(point_count);
void reserve(size_t n) { m_output.reserve(n); }
void add_point(const Vec2d &pt) { auto emit = emitter(); m_smoother.push(pt, emit); }
// The smoother holds back the last point of the path until it knows there is no corner left to round.
void finish() { auto emit = emitter(); m_smoother.flush(emit); }
// The caller normalizes resolution to the unit Hilbert grid; retain a finite positive tolerance
// if this helper is invoked with an invalid resolution.
const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
// Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
// At each end, the first three control points are collinear and equally spaced: the tangent follows
// the adjoining straight leg and the second derivative is zero. The endpoint curvature is therefore
// zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
// it only once to the requested chordal-deviation tolerance.
const QuinticBezier corner_curve {{
{-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
{0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
}};
std::vector<Vec2d> curve_coefficients;
flatten_bezier(corner_curve, deviation, curve_coefficients);
auto translated_point = [min_x, min_y](size_t idx) {
Point p = hilbert_n_to_xy(idx);
return Point(p.x() + min_x, p.y() + min_y);
};
auto to_vec2d = [](const Point &p) { return Vec2d(double(p.x()), double(p.y())); };
bool has_last_output = false;
Vec2d last_output;
// Fully smoothed adjacent corners may meet at the same segment midpoint. Suppress such duplicates
// to avoid emitting zero-length extrusion segments.
auto add_point = [&output, &has_last_output, &last_output](const Vec2d &point) {
if (!has_last_output || point.x() != last_output.x() || point.y() != last_output.y()) {
output.add_point(point);
last_output = point;
has_last_output = true;
}
};
Vec2d previous = to_vec2d(translated_point(0));
Vec2d corner = to_vec2d(translated_point(1));
add_point(previous);
// Replace each non-collinear Hilbert vertex by the canonical curve expressed in the local basis of
// its incoming and outgoing unit vectors. Collinear vertices remain part of the straight polyline.
for (size_t i = 1; i + 1 < point_count; ++i) {
const Vec2d next = to_vec2d(translated_point(i + 1));
const Vec2d incoming = (corner - previous).normalized();
const Vec2d outgoing = (next - corner).normalized();
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
if (std::abs(cross) < EPSILON) {
add_point(corner);
} else {
add_point(corner - corner_distance * incoming);
for (const Vec2d &coefficient : curve_coefficients)
add_point(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
}
previous = corner;
corner = next;
private:
// The curves of two adjacent corners meet at the midpoint of the segment they share, where they
// may round to the very same output point. Drop those, they would be zero length extrusions.
auto emitter()
{
return [this](const Vec2d &pt) {
const Point snapped = m_output.scaled(pt);
if (m_has_last_snapped && snapped == m_last_snapped)
return;
m_last_snapped = snapped;
m_has_last_snapped = true;
m_output.add_point(pt);
};
}
add_point(corner);
Output &m_output;
CornerSmoother m_smoother;
Point m_last_snapped { Point::Zero() };
bool m_has_last_snapped { false };
};
// Runs the path generator against the concrete output type, optionally through the corner smoother.
// The outputs do not share a virtual add_point(), so the type has to be resolved here.
template<typename GenerateFn>
static void generate_path(InfillPolylineOutput &output, const FillParams &params, const double resolution, GenerateFn generate)
{
const double smooth_factor = sanitize_smooth_factor(params.smooth_factor);
auto run = [smooth_factor, resolution, &generate](auto &out) {
if (smooth_factor == 0.) {
generate(out);
} else {
SmoothingPolylineOutput<std::remove_reference_t<decltype(out)>> smoothing(out, smooth_factor, resolution);
generate(smoothing);
smoothing.finish();
}
};
if (output.clips())
run(static_cast<InfillPolylineClipper&>(output));
else
run(output);
}
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
@@ -440,19 +356,8 @@ void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coo
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const FillParams &params, InfillPolylineOutput &output)
{
const double smooth_factor = std::isfinite(params.smooth_factor) ?
std::clamp(params.smooth_factor, 0., 1.) : 0.;
if (smooth_factor == 0.) {
this->generate(min_x, min_y, max_x, max_y, resolution, output);
return;
}
const double corner_distance = 0.5 * smooth_factor;
if (output.clips())
generate_smooth_hilbert_curve(
min_x, min_y, max_x, max_y, resolution, corner_distance, static_cast<InfillPolylineClipper&>(output));
else
generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
generate_path(output, params, resolution,
[min_x, min_y, max_x, max_y](auto &out) { generate_hilbert_curve(min_x, min_y, max_x, max_y, out); });
}
template<typename Output>
@@ -495,4 +400,11 @@ void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, c
generate_octagram_spiral(min_x, min_y, max_x, max_y, output);
}
void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const FillParams &params, InfillPolylineOutput &output)
{
generate_path(output, params, resolution,
[min_x, min_y, max_x, max_y](auto &out) { generate_octagram_spiral(min_x, min_y, max_x, max_y, out); });
}
} // namespace Slic3r
+4 -2
View File
@@ -21,10 +21,10 @@ public:
void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); }
Points&& result() { return std::move(m_out); }
virtual bool clips() const { return false; }
protected:
// The output grid the generated points are snapped to.
const Point scaled(const Vec2d& fpt) const { return { coord_t(floor(fpt.x() * m_scale_out + 0.5)), coord_t(floor(fpt.y() * m_scale_out + 0.5)) }; }
protected:
// Output polyline.
Points m_out;
@@ -93,6 +93,8 @@ public:
protected:
bool centered() const override { return true; }
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const FillParams &params, InfillPolylineOutput &output) override;
};
} // namespace Slic3r
+5
View File
@@ -18,6 +18,7 @@
#include "../ShortestPath.hpp"
#include "../VariableWidth.hpp"
#include "FillCornerSmoothing.hpp"
#include "FillRectilinear.hpp"
// #define SLIC3R_DEBUG
@@ -3364,6 +3365,10 @@ bool FillRectilinear::fill_surface_trapezoidal(
for (Polyline &pl : polylines)
pl.translate(rotate_vector.second);
// Orca: round the corners of the trapezoids. The straight base lines of the triangular family
// have no corner to round.
smooth_polylines_corners(polylines, params.smooth_factor, scaled<double>(params.resolution));
// Apply multiline fill
multiline_fill(polylines, params, spacing);
+1
View File
@@ -6726,6 +6726,7 @@ void GCode::append_full_config(const Print &print, std::string &str)
"farthest_point_timelapse"sv,
"compatible_printers"sv,
"compatible_prints"sv,
"filament_colour_type"sv,
"print_host"sv,
"print_host_webui"sv,
"printhost_apikey"sv,
+127 -21
View File
@@ -19,6 +19,7 @@
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <functional>
#include <limits>
#include <numeric>
#include <unordered_map>
@@ -39,7 +40,11 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
const AABBTreeLines::LinesDistancer<L>& unscaled_prev_layer,
float flow_width,
float max_line_length = -1.0f,
float min_distance = -1.0f)
float min_distance = -1.0f,
// Maps an overhang distance onto the speed it will be printed at. Interior sampling
// needs it to tell which of the points it could add would change the G-code, and is
// skipped without it.
const std::function<float(float)>& distance_to_speed = {})
{
bool looped = input_points.front() == input_points.back();
std::function<size_t(size_t,size_t)> get_prev_index = [](size_t idx, size_t count) {
@@ -120,6 +125,107 @@ std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS&
points.push_back(next_point);
}
// ORCA: Interior sampling
// The passes below infer the support under a span from its endpoints alone, so an interior that is supported
// differently from both ends is invisible to them: the outer perimeter of an overhang whose ends are caged by
// full height walls reads as supported along its whole length. Probe the interior, keep the samples the
// endpoint interpolation fails to predict, and bisect either side of each one, so a span that is only partly
// unsupported gets points where its support actually changes instead of one reading spread across all of it.
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS && min_distance > 0 && distance_to_speed) {
// Probe at least this densely before treating matching samples as evidence that a span is uniform. The
// segmentation pass below only splits lines of 2mm or more, and every pass here drops points closer
// together than min_spacing, so finer discovery would not produce a more precise speed transition.
const double max_probe_spacing = std::max(2., 4. * min_spacing);
// A backstop for that length test, which on a non-finite length would never be met.
constexpr int max_bisection_depth = 10;
// Whether two readings are interchangeable. A segment is printed at the lower of the speeds its ends
// read, so a sample that agrees on speed with what is already known cannot change the G-code, whatever
// its distance says. The distances themselves are far too coarse a stand-in for this: the speed sections
// interpolate, so readings a small fraction of min_distance apart can still be tens of mm/s apart.
// The tolerance matches the one GCode.cpp applies when it decides a path has a variable speed at all.
auto same_speed = [&distance_to_speed](float a, float b) {
return std::abs(distance_to_speed(a) - distance_to_speed(b)) <= 1.f;
};
// Whether the first reading is printed slower than the second, once they are known to differ.
auto prints_slower = [&distance_to_speed](float a, float b) { return distance_to_speed(a) < distance_to_speed(b); };
// Part of a segment still to bisect: its positions along the segment and bisections left.
struct Subspan { double t0, t1; int depth; };
std::vector<ExtendedPoint<L::Dim>> sampled_points; // Populated lazily, on the first insertion
std::vector<std::pair<double, float>> interior; // Samples of one segment, keyed by position along it
std::vector<Subspan> pending;
for (size_t point_idx = 0; point_idx + 1 < points.size(); ++point_idx) {
const ExtendedPoint<L::Dim>& curr = points[point_idx];
const ExtendedPoint<L::Dim>& next = points[point_idx + 1];
const Vec step = next.position - curr.position;
const double line_len = step.norm();
interior.clear();
if (line_len >= max_probe_spacing)
pending.push_back({0., 1., max_bisection_depth});
while (!pending.empty()) {
const Subspan subspan = pending.back();
pending.pop_back();
if (subspan.depth <= 0 || (subspan.t1 - subspan.t0) * line_len < max_probe_spacing)
continue;
const double t = 0.5 * (subspan.t0 + subspan.t1);
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
(curr.position + t * step).template cast<AABBScalar>());
const float sampled = float(distance + boundary_offset);
interior.emplace_back(t, sampled);
pending.push_back({subspan.t0, t, subspan.depth - 1});
pending.push_back({t, subspan.t1, subspan.depth - 1});
}
if (!interior.empty()) {
std::sort(interior.begin(), interior.end(),
[](const std::pair<double, float>& l, const std::pair<double, float>& r) { return l.first < r.first; });
// Coarse probing keeps every sample it took until this pass can see which ones bracket a speed
// transition. Matching samples cannot be discarded during discovery: one may be the last
// supported point before a narrow unsupported pocket found by a later probe.
size_t kept = 0;
for (size_t i = 0; i < interior.size(); ++i) {
const float sample = interior[i].second;
const bool at_start = kept == 0; // Nothing kept yet, so the segment's own start precedes it
const bool at_end = i + 1 == interior.size(); // And nothing follows the last sample but the segment's end
const float before = at_start ? curr.distance : interior[kept - 1].second;
const float after = at_end ? next.distance : interior[i + 1].second;
// A sample is worth a point in the path only where it prints at a different speed from the
// readings either side of it. Differing from one of the segment's own ends is not enough on
// its own where the sample is the faster of the two: the segmentation pass below already
// ends the slowdown an end reads, at a distance taken from how far out that end is rather
// than from wherever bisection happened to stop, and a point here would leave the span
// beside the end too short for that pass to run at all. Support an end cannot account for,
// where the interior is the slower reading, is exactly what this pass is here to find.
const bool worth_before = !same_speed(sample, before) && (!at_start || prints_slower(sample, before));
const bool worth_after = !same_speed(sample, after) && (!at_end || prints_slower(sample, after));
if (worth_before || worth_after)
interior[kept++] = interior[i];
}
interior.resize(kept);
}
if (!interior.empty() && sampled_points.empty()) {
sampled_points.reserve(points.size() + 8);
sampled_points.assign(points.begin(), points.begin() + point_idx + 1);
}
if (!sampled_points.empty()) {
// Only a sub-span of max_probe_spacing or more is ever bisected, so these sit at least
// 2 * min_spacing apart, and need none of the filtering the passes either side of this one do.
for (const auto& [t, distance] : interior)
sampled_points.push_back({curr.position + t * step, distance});
sampled_points.push_back(next);
}
}
if (!sampled_points.empty())
points = std::move(sampled_points);
}
// Segmentation handling
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
std::vector<ExtendedPoint<L::Dim>> new_points;
@@ -362,9 +468,28 @@ public:
smallest_distance_with_lower_speed=-1.f;
// Orca: Pass to the point properties estimator the smallest ovehang distance that triggers a slowdown (smallest_distance_with_lower_speed)
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
float final_speed;
if (distance <= speed_sections.front().first) {
final_speed = original_speed;
} else if (distance >= speed_sections.back().first) {
final_speed = speed_sections.back().second;
} else {
size_t section_idx = 0;
while (distance > speed_sections[section_idx + 1].first) {
section_idx++;
}
float t = (distance - speed_sections[section_idx].first) /
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
t = std::clamp(t, 0.0f, 1.0f);
final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
}
return round(final_speed);
};
std::vector<ExtendedPoint<3>> extended_points =
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width, -1,
smallest_distance_with_lower_speed);
smallest_distance_with_lower_speed, calculate_speed);
const auto width_inv = 1.0f / path.width;
std::vector<ProcessedPoint> processed_points;
processed_points.reserve(extended_points.size());
@@ -423,25 +548,6 @@ public:
}
}
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
float final_speed;
if (distance <= speed_sections.front().first) {
final_speed = original_speed;
} else if (distance >= speed_sections.back().first) {
final_speed = speed_sections.back().second;
} else {
size_t section_idx = 0;
while (distance > speed_sections[section_idx + 1].first) {
section_idx++;
}
float t = (distance - speed_sections[section_idx].first) /
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
t = std::clamp(t, 0.0f, 1.0f);
final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
}
return round(final_speed);
};
float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance));
// ORCA: Clamp resulting speed to lowest of calculated speed based on the overhang values and the current speed
// Fixes bug where resulting overhang speed is higher than the current speed due to (for example) volumetric flow limits.
+2 -2
View File
@@ -3246,9 +3246,9 @@ double Model::findMaxSpeed(const ModelObject* object) {
if (objectKey == "outer_wall_speed")
externalPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
if (objectKey == "small_perimeter_speed")
smallPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
smallPerimeterSpeedObj = object->config.get().opt_float_or_percent_nullable(objectKey, 0).get_abs_value(externalPerimeterSpeedObj);
if (objectKey == "small_support_perimeter_speed")
smallSupportPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
smallSupportPerimeterSpeedObj = object->config.get().opt_float_or_percent_nullable(objectKey, 0).get_abs_value(supportSpeedObj);
}
objMaxSpeed = std::max(perimeterSpeedObj, std::max(externalPerimeterSpeedObj, std::max(infillSpeedObj, std::max(solidInfillSpeedObj, std::max(topSolidInfillSpeedObj, std::max(supportSpeedObj, std::max(smallPerimeterSpeedObj, std::max(smallSupportPerimeterSpeedObj, objMaxSpeed))))))));
if (objMaxSpeed <= 0) objMaxSpeed = 250.;
-1
View File
@@ -49,7 +49,6 @@ static std::vector<std::string> s_project_options {
"filament_multi_colour",
"wipe_tower_x",
"wipe_tower_y",
"wipe_tower_rotation_angle",
"curr_bed_type",
"flush_multiplier",
// Fast-purge mode: project-level purge control, inert at Default.
+13 -4
View File
@@ -3469,9 +3469,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("sparse_infill_smooth_factor", coPercent);
def->label = L("Sparse infill smooth factor");
def->category = L("Strength");
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original right-angle path, "
"while 100% produces the largest possible curves between adjacent infill lines. "
"Currently applies only to the Hilbert Curve.");
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original sharp path, "
"while 100% produces the largest possible curves between adjacent infill lines.");
def->sidetext = "%";
def->min = 0;
def->max = 100;
@@ -10426,6 +10425,16 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
}
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride)
{
// A single-value object or region override applies to every nozzle variant.
std::vector<int> indices = variant_index;
if (source.size() == 1 && !source.is_nil(0))
std::fill(indices.begin(), indices.end(), 0);
target.set_to_index(&source, indices, stride);
}
//used for object/region config
//use the smallest of multiple to single
@@ -11503,7 +11512,7 @@ void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPr
else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(opt_src);
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(opt_dest);
opt_vec_src->set_to_index(opt_vec_dest, variant_index, stride);
set_variant_override(*opt_vec_src, *opt_vec_dest, variant_index, stride);
}
}
}
+75
View File
@@ -146,6 +146,29 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
}
}
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
// than one line per infill wall; a single line makes them plain crossing lines with nothing to round.
inline bool is_smoothable_infill_pattern(InfillPattern pattern, int multiline = 1)
{
switch (pattern) {
case ipHilbertCurve:
case ipOctagramSpiral:
case ipLightning:
case ipHoneycomb:
case ip3DHoneycomb:
case ipConcentric:
case ipCrossHatch:
return true;
case ipGrid:
case ipTriangles:
case ipStars:
return multiline > 1;
default:
return false;
}
}
enum class IroningType {
NoIroning,
TopSurfaces,
@@ -842,6 +865,9 @@ extern std::set<std::string> printer_options_with_variant_1;
extern std::set<std::string> printer_options_with_variant_2;
extern std::set<std::string> empty_options;
void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
const std::vector<int> &variant_index, int stride = 1);
extern std::set<std::string> filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector<int> variant_index, std::set<std::string>& key_set1, int stride = 1);
@@ -2394,6 +2420,55 @@ static void set_flush_volumes_matrix(std::vector<T> &out_matrix, const std::vect
}
}
template<class T>
static bool has_zero_flush_volume_for_used_filaments(const std::vector<T> &fv_matrix,
const std::vector<T> &flush_multipliers,
const std::vector<int> &used_filaments)
{
if (used_filaments.size() < 2 || flush_multipliers.empty())
return false;
if (fv_matrix.size() % flush_multipliers.size() != 0)
return false;
const size_t matrix_len = fv_matrix.size() / flush_multipliers.size();
const size_t row_len = size_t(std::sqrt(double(matrix_len)));
if (row_len < 2 || row_len * row_len != matrix_len)
return false;
std::vector<int> filtered_filaments;
filtered_filaments.reserve(used_filaments.size());
for (int filament_id : used_filaments) {
if (filament_id <= 0 || filament_id > int(row_len))
continue;
if (std::find(filtered_filaments.begin(), filtered_filaments.end(), filament_id) == filtered_filaments.end())
filtered_filaments.push_back(filament_id);
}
if (filtered_filaments.size() < 2)
return false;
for (T multiplier : flush_multipliers) {
if (multiplier == 0)
return true;
}
for (size_t nozzle_idx = 0; nozzle_idx < flush_multipliers.size(); nozzle_idx++) {
const size_t block_offset = nozzle_idx * matrix_len;
for (int from_id : filtered_filaments) {
for (int to_id : filtered_filaments) {
if (from_id == to_id)
continue;
const size_t matrix_idx = block_offset + size_t(from_id - 1) * row_len + size_t(to_id - 1);
if (matrix_idx < fv_matrix.size() && fv_matrix[matrix_idx] == 0)
return true;
}
}
}
return false;
}
size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id);
} // namespace Slic3r
+1 -1
View File
@@ -3812,7 +3812,7 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr
else {
ConfigOptionVectorBase* opt_vec_src = static_cast<ConfigOptionVectorBase*>(my_opt);
const ConfigOptionVectorBase* opt_vec_dest = static_cast<const ConfigOptionVectorBase*>(it->second.get());
opt_vec_src->set_to_index(opt_vec_dest, variant_index, 1);
set_variant_override(*opt_vec_src, *opt_vec_dest, variant_index);
}
}
}