mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Merge from main and fix merge conflicts
This commit is contained in:
@@ -280,6 +280,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);
|
||||
|
||||
@@ -1630,6 +1633,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;
|
||||
|
||||
@@ -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
|
||||
@@ -374,6 +379,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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -149,6 +149,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
|
||||
|
||||
@@ -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.;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ®ion) { 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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 ¶ms, 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 ¶ms, 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 ¶ms, 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
|
||||
|
||||
@@ -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 ¶ms, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user