mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-31 16:21:58 +08:00
Merge branch 'master_262' into et_spe1784_binary_gcode
This commit is contained in:
commit
1c26f0bf60
@ -15,7 +15,6 @@ our @EXPORT_OK = qw(
|
||||
|
||||
X Y Z
|
||||
convex_hull
|
||||
chained_path_from
|
||||
deg2rad
|
||||
rad2deg
|
||||
);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,26 @@
|
||||
src/libslic3r/ExtrusionRole.cpp
|
||||
src/libslic3r/Flow.cpp
|
||||
src/libslic3r/Format/3mf.cpp
|
||||
src/libslic3r/Format/AMF.cpp
|
||||
src/libslic3r/Format/SLAArchiveReader.cpp
|
||||
src/libslic3r/GCode/PostProcessor.cpp
|
||||
src/libslic3r/GCode.cpp
|
||||
src/libslic3r/miniz_extension.cpp
|
||||
src/libslic3r/Preset.cpp
|
||||
src/libslic3r/Print.cpp
|
||||
src/libslic3r/PrintBase.cpp
|
||||
src/libslic3r/PrintConfig.cpp
|
||||
src/libslic3r/PrintObject.cpp
|
||||
src/libslic3r/PrintObjectSlice.cpp
|
||||
src/libslic3r/SLA/Hollowing.cpp
|
||||
src/libslic3r/SLA/Pad.cpp
|
||||
src/libslic3r/SLAPrint.cpp
|
||||
src/libslic3r/SLAPrintSteps.cpp
|
||||
src/libslic3r/Utils.cpp
|
||||
src/libslic3r/Zipper.cpp
|
||||
src/slic3r/Config/Snapshot.cpp
|
||||
src/slic3r/GUI/AboutDialog.cpp
|
||||
src/slic3r/GUI/ArrangeSettingsDialogImgui.cpp
|
||||
src/slic3r/GUI/BackgroundSlicingProcess.cpp
|
||||
src/slic3r/GUI/BedShapeDialog.cpp
|
||||
src/slic3r/GUI/BedShapeDialog.hpp
|
||||
@ -18,7 +40,6 @@ src/slic3r/GUI/FileArchiveDialog.cpp
|
||||
src/slic3r/GUI/FirmwareDialog.cpp
|
||||
src/slic3r/GUI/GalleryDialog.cpp
|
||||
src/slic3r/GUI/GCodeViewer.cpp
|
||||
src/slic3r/GUI/GLCanvas3D.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
|
||||
@ -37,14 +58,15 @@ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp
|
||||
src/slic3r/GUI/GLCanvas3D.cpp
|
||||
src/slic3r/GUI/GUI.cpp
|
||||
src/slic3r/GUI/GUI_App.cpp
|
||||
src/slic3r/GUI/GUI_Init.cpp
|
||||
src/slic3r/GUI/GUI_Factories.cpp
|
||||
src/slic3r/GUI/GUI_Init.cpp
|
||||
src/slic3r/GUI/GUI_ObjectLayers.cpp
|
||||
src/slic3r/GUI/GUI_ObjectList.cpp
|
||||
src/slic3r/GUI/GUI_ObjectManipulation.cpp
|
||||
@ -53,10 +75,13 @@ src/slic3r/GUI/GUI_Preview.cpp
|
||||
src/slic3r/GUI/HintNotification.cpp
|
||||
src/slic3r/GUI/ImGuiWrapper.cpp
|
||||
src/slic3r/GUI/Jobs/ArrangeJob.cpp
|
||||
src/slic3r/GUI/Jobs/FillBedJob.cpp
|
||||
src/slic3r/GUI/Jobs/ArrangeJob2.cpp
|
||||
src/slic3r/GUI/Jobs/EmbossJob.cpp
|
||||
src/slic3r/GUI/Jobs/FillBedJob.cpp
|
||||
src/slic3r/GUI/Jobs/PlaterWorker.hpp
|
||||
src/slic3r/GUI/Jobs/RotoptimizeJob.hpp
|
||||
src/slic3r/GUI/Jobs/RotoptimizeJob.cpp
|
||||
src/slic3r/GUI/Jobs/SLAImportDialog.hpp
|
||||
src/slic3r/GUI/Jobs/SLAImportJob.cpp
|
||||
src/slic3r/GUI/KBShortcutsDialog.cpp
|
||||
src/slic3r/GUI/MainFrame.cpp
|
||||
@ -79,6 +104,7 @@ src/slic3r/GUI/SavePresetDialog.cpp
|
||||
src/slic3r/GUI/Search.cpp
|
||||
src/slic3r/GUI/Selection.cpp
|
||||
src/slic3r/GUI/SendSystemInfoDialog.cpp
|
||||
src/slic3r/GUI/SurfaceDrag.cpp
|
||||
src/slic3r/GUI/SysInfoDialog.cpp
|
||||
src/slic3r/GUI/Tab.cpp
|
||||
src/slic3r/GUI/Tab.hpp
|
||||
@ -86,34 +112,15 @@ src/slic3r/GUI/UnsavedChangesDialog.cpp
|
||||
src/slic3r/GUI/UpdateDialogs.cpp
|
||||
src/slic3r/GUI/WipeTowerDialog.cpp
|
||||
src/slic3r/GUI/wxExtensions.cpp
|
||||
src/slic3r/Utils/AstroBox.cpp
|
||||
src/slic3r/Utils/AppUpdater.cpp
|
||||
src/slic3r/Utils/AstroBox.cpp
|
||||
src/slic3r/Utils/Duet.cpp
|
||||
src/slic3r/Utils/FixModelByWin10.cpp
|
||||
src/slic3r/Utils/FlashAir.cpp
|
||||
src/slic3r/Utils/Http.cpp
|
||||
src/slic3r/Utils/MKS.cpp
|
||||
src/slic3r/Utils/Moonraker.cpp
|
||||
src/slic3r/Utils/OctoPrint.cpp
|
||||
src/slic3r/Utils/PresetUpdater.cpp
|
||||
src/slic3r/Utils/Http.cpp
|
||||
src/slic3r/Utils/Process.cpp
|
||||
src/slic3r/Utils/Repetier.cpp
|
||||
src/slic3r/Config/Snapshot.cpp
|
||||
src/libslic3r/GCode.cpp
|
||||
src/libslic3r/ExtrusionRole.cpp
|
||||
src/libslic3r/Flow.cpp
|
||||
src/libslic3r/Format/3mf.cpp
|
||||
src/libslic3r/Format/AMF.cpp
|
||||
src/libslic3r/Format/SLAArchiveReader.cpp
|
||||
src/libslic3r/GCode/PostProcessor.cpp
|
||||
src/libslic3r/miniz_extension.cpp
|
||||
src/libslic3r/Preset.cpp
|
||||
src/libslic3r/Print.cpp
|
||||
src/libslic3r/SLA/Pad.cpp
|
||||
src/libslic3r/SLA/Hollowing.cpp
|
||||
src/libslic3r/SLAPrint.cpp
|
||||
src/libslic3r/SLAPrintSteps.cpp
|
||||
src/libslic3r/Utils.cpp
|
||||
src/libslic3r/PrintBase.cpp
|
||||
src/libslic3r/PrintConfig.cpp
|
||||
src/libslic3r/Zipper.cpp
|
||||
src/libslic3r/PrintObject.cpp
|
||||
src/libslic3r/PrintObjectSlice.cpp
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -345,7 +345,7 @@ public:
|
||||
return dist;
|
||||
}
|
||||
|
||||
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
|
||||
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius) const
|
||||
{
|
||||
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
|
||||
}
|
||||
|
@ -490,8 +490,10 @@ static void make_inner_brim(const Print &print,
|
||||
|
||||
loops = union_pt_chained_outside_in(loops);
|
||||
std::reverse(loops.begin(), loops.end());
|
||||
extrusion_entities_append_loops(brim.entities, std::move(loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()),
|
||||
float(flow.width()), float(print.skirt_first_layer_height()));
|
||||
extrusion_entities_append_loops(brim.entities, std::move(loops),
|
||||
ExtrusionAttributes{
|
||||
ExtrusionRole::Skirt,
|
||||
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
|
||||
}
|
||||
|
||||
// Produce brim lines around those objects, that have the brim enabled.
|
||||
@ -672,7 +674,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
if (i + 1 == j && first_path.size() > 3 && first_path.front().x() == first_path.back().x() && first_path.front().y() == first_path.back().y()) {
|
||||
auto *loop = new ExtrusionLoop();
|
||||
brim.entities.emplace_back(loop);
|
||||
loop->paths.emplace_back(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
|
||||
loop->paths.emplace_back(ExtrusionAttributes{
|
||||
ExtrusionRole::Skirt,
|
||||
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
|
||||
Points &points = loop->paths.front().polyline.points;
|
||||
points.reserve(first_path.size());
|
||||
for (const ClipperLib_Z::IntPoint &pt : first_path)
|
||||
@ -683,7 +687,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
ExtrusionEntityCollection this_loop_trimmed;
|
||||
this_loop_trimmed.entities.reserve(j - i);
|
||||
for (; i < j; ++ i) {
|
||||
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())));
|
||||
this_loop_trimmed.entities.emplace_back(new ExtrusionPath({
|
||||
ExtrusionRole::Skirt,
|
||||
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } }));
|
||||
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
|
||||
Points &points = dynamic_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
|
||||
points.reserve(path.size());
|
||||
@ -699,7 +705,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
}
|
||||
}
|
||||
} else {
|
||||
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
|
||||
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops),
|
||||
ExtrusionAttributes{ ExtrusionRole::Skirt,
|
||||
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
|
||||
}
|
||||
|
||||
make_inner_brim(print, top_level_objects_with_brim, bottom_layers_expolygons, brim);
|
||||
|
@ -147,8 +147,12 @@ set(SLIC3R_SOURCES
|
||||
GCode/ConflictChecker.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode/ExtrusionProcessor.cpp
|
||||
GCode/ExtrusionProcessor.hpp
|
||||
GCode/FindReplace.cpp
|
||||
GCode/FindReplace.hpp
|
||||
GCode/GCodeWriter.cpp
|
||||
GCode/GCodeWriter.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
GCode/PostProcessor.hpp
|
||||
GCode/PressureEqualizer.cpp
|
||||
@ -161,10 +165,16 @@ set(SLIC3R_SOURCES
|
||||
GCode/SpiralVase.hpp
|
||||
GCode/SeamPlacer.cpp
|
||||
GCode/SeamPlacer.hpp
|
||||
GCode/SmoothPath.cpp
|
||||
GCode/SmoothPath.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
GCode/ToolOrdering.hpp
|
||||
GCode/Wipe.cpp
|
||||
GCode/Wipe.hpp
|
||||
GCode/WipeTower.cpp
|
||||
GCode/WipeTower.hpp
|
||||
GCode/WipeTowerIntegration.cpp
|
||||
GCode/WipeTowerIntegration.hpp
|
||||
GCode/GCodeProcessor.cpp
|
||||
GCode/GCodeProcessor.hpp
|
||||
GCode/AvoidCrossingPerimeters.cpp
|
||||
@ -175,10 +185,10 @@ set(SLIC3R_SOURCES
|
||||
GCodeReader.hpp
|
||||
# GCodeSender.cpp
|
||||
# GCodeSender.hpp
|
||||
GCodeWriter.cpp
|
||||
GCodeWriter.hpp
|
||||
Geometry.cpp
|
||||
Geometry.hpp
|
||||
Geometry/ArcWelder.cpp
|
||||
Geometry/ArcWelder.hpp
|
||||
Geometry/Bicubic.hpp
|
||||
Geometry/Circle.cpp
|
||||
Geometry/Circle.hpp
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "CustomGCode.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "Extruder.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Extruder.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include <cmath>
|
||||
@ -9,7 +10,7 @@
|
||||
#include <sstream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
|
||||
{
|
||||
this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval);
|
||||
@ -38,12 +39,12 @@ double ExtrusionPath::length() const
|
||||
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
|
||||
{
|
||||
for (const Polyline &polyline : polylines)
|
||||
collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
|
||||
collection->entities.emplace_back(new ExtrusionPath(polyline, this->attributes()));
|
||||
}
|
||||
|
||||
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
|
||||
polygons_append(out, offset(this->polyline, float(scale_(m_attributes.width/2)) + scaled_epsilon));
|
||||
}
|
||||
|
||||
void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
|
||||
@ -51,8 +52,8 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
|
||||
// Instantiating the Flow class to get the line spacing.
|
||||
// Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
|
||||
bool bridge = this->role().is_bridge();
|
||||
assert(! bridge || this->width == this->height);
|
||||
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
|
||||
assert(! bridge || m_attributes.width == m_attributes.height);
|
||||
auto flow = bridge ? Flow::bridging_flow(m_attributes.width, 0.f) : Flow(m_attributes.width, m_attributes.height, 0.f);
|
||||
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
|
||||
}
|
||||
|
||||
@ -87,7 +88,7 @@ double ExtrusionMultiPath::min_mm3_per_mm() const
|
||||
{
|
||||
double min_mm3_per_mm = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
|
||||
min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm());
|
||||
return min_mm3_per_mm;
|
||||
}
|
||||
|
||||
@ -112,21 +113,34 @@ Polyline ExtrusionMultiPath::as_polyline() const
|
||||
return out;
|
||||
}
|
||||
|
||||
bool ExtrusionLoop::make_clockwise()
|
||||
double ExtrusionLoop::area() const
|
||||
{
|
||||
bool was_ccw = this->polygon().is_counter_clockwise();
|
||||
if (was_ccw) this->reverse();
|
||||
return was_ccw;
|
||||
}
|
||||
|
||||
bool ExtrusionLoop::make_counter_clockwise()
|
||||
{
|
||||
bool was_cw = this->polygon().is_clockwise();
|
||||
if (was_cw) this->reverse();
|
||||
return was_cw;
|
||||
double a = 0;
|
||||
for (const ExtrusionPath &path : this->paths) {
|
||||
assert(path.size() >= 2);
|
||||
if (path.size() >= 2) {
|
||||
// Assumming that the last point of one path segment is repeated at the start of the following path segment.
|
||||
auto it = path.polyline.points.begin();
|
||||
Point prev = *it ++;
|
||||
for (; it != path.polyline.points.end(); ++ it) {
|
||||
a += cross2(prev.cast<double>(), it->cast<double>());
|
||||
prev = *it;
|
||||
}
|
||||
}
|
||||
}
|
||||
return a * 0.5;
|
||||
}
|
||||
|
||||
void ExtrusionLoop::reverse()
|
||||
{
|
||||
#if 0
|
||||
this->reverse_loop();
|
||||
#else
|
||||
throw Slic3r::LogicError("ExtrusionLoop::reverse() must NOT be called");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ExtrusionLoop::reverse_loop()
|
||||
{
|
||||
for (ExtrusionPath &path : this->paths)
|
||||
path.reverse();
|
||||
@ -248,8 +262,8 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const
|
||||
|
||||
// now split path_idx in two parts
|
||||
const ExtrusionPath &path = this->paths[path_idx];
|
||||
ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height);
|
||||
ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height);
|
||||
ExtrusionPath p1(path.attributes());
|
||||
ExtrusionPath p2(path.attributes());
|
||||
path.polyline.split_at(p, &p1.polyline, &p2.polyline);
|
||||
|
||||
if (this->paths.size() == 1) {
|
||||
@ -316,7 +330,7 @@ double ExtrusionLoop::min_mm3_per_mm() const
|
||||
{
|
||||
double min_mm3_per_mm = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
|
||||
min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm());
|
||||
return min_mm3_per_mm;
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExtrusionRole.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <numeric>
|
||||
|
||||
@ -55,28 +57,91 @@ public:
|
||||
virtual double total_volume() const = 0;
|
||||
};
|
||||
|
||||
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
|
||||
using ExtrusionEntitiesPtr = std::vector<ExtrusionEntity*>;
|
||||
|
||||
// Const reference for ordering extrusion entities without having to modify them.
|
||||
class ExtrusionEntityReference final
|
||||
{
|
||||
public:
|
||||
ExtrusionEntityReference() = delete;
|
||||
ExtrusionEntityReference(const ExtrusionEntity &extrusion_entity, bool flipped) :
|
||||
m_extrusion_entity(&extrusion_entity), m_flipped(flipped) {}
|
||||
ExtrusionEntityReference operator=(const ExtrusionEntityReference &rhs)
|
||||
{ m_extrusion_entity = rhs.m_extrusion_entity; m_flipped = rhs.m_flipped; return *this; }
|
||||
|
||||
const ExtrusionEntity& extrusion_entity() const { return *m_extrusion_entity; }
|
||||
template<typename Type>
|
||||
const Type* cast() const { return dynamic_cast<const Type*>(m_extrusion_entity); }
|
||||
bool flipped() const { return m_flipped; }
|
||||
|
||||
private:
|
||||
const ExtrusionEntity *m_extrusion_entity;
|
||||
bool m_flipped;
|
||||
};
|
||||
|
||||
using ExtrusionEntityReferences = std::vector<ExtrusionEntityReference>;
|
||||
|
||||
struct ExtrusionFlow
|
||||
{
|
||||
ExtrusionFlow() = default;
|
||||
ExtrusionFlow(double mm3_per_mm, float width, float height) :
|
||||
mm3_per_mm{ mm3_per_mm }, width{ width }, height{ height } {}
|
||||
ExtrusionFlow(const Flow &flow) :
|
||||
mm3_per_mm(flow.mm3_per_mm()), width(flow.width()), height(flow.height()) {}
|
||||
|
||||
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
|
||||
double mm3_per_mm{ -1. };
|
||||
// Width of the extrusion, used for visualization purposes.
|
||||
float width{ -1.f };
|
||||
// Height of the extrusion, used for visualization purposes.
|
||||
float height{ -1.f };
|
||||
};
|
||||
|
||||
inline bool operator==(const ExtrusionFlow &lhs, const ExtrusionFlow &rhs)
|
||||
{
|
||||
return lhs.mm3_per_mm == rhs.mm3_per_mm && lhs.width == rhs.width && lhs.height == rhs.height;
|
||||
}
|
||||
|
||||
struct OverhangAttributes {
|
||||
float start_distance_from_prev_layer;
|
||||
float end_distance_from_prev_layer;
|
||||
float proximity_to_curled_lines; //value between 0 and 1
|
||||
};
|
||||
|
||||
struct ExtrusionAttributes : ExtrusionFlow
|
||||
{
|
||||
ExtrusionAttributes() = default;
|
||||
ExtrusionAttributes(ExtrusionRole role) : role{ role } {}
|
||||
ExtrusionAttributes(ExtrusionRole role, const Flow &flow) : role{ role }, ExtrusionFlow{ flow } {}
|
||||
ExtrusionAttributes(ExtrusionRole role, const ExtrusionFlow &flow) : role{ role }, ExtrusionFlow{ flow } {}
|
||||
|
||||
// What is the role / purpose of this extrusion?
|
||||
ExtrusionRole role{ ExtrusionRole::None };
|
||||
// OVerhangAttributes are currently computed for perimeters if dynamic overhangs are enabled.
|
||||
// They are used to control fan and print speed in export.
|
||||
std::optional<OverhangAttributes> overhang_attributes;
|
||||
};
|
||||
|
||||
inline bool operator==(const ExtrusionAttributes &lhs, const ExtrusionAttributes &rhs)
|
||||
{
|
||||
return static_cast<const ExtrusionFlow&>(lhs) == static_cast<const ExtrusionFlow&>(rhs) &&
|
||||
lhs.role == rhs.role;
|
||||
}
|
||||
|
||||
class ExtrusionPath : public ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
Polyline polyline;
|
||||
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
|
||||
double mm3_per_mm;
|
||||
// Width of the extrusion, used for visualization purposes.
|
||||
float width;
|
||||
// Height of the extrusion, used for visualization purposes.
|
||||
float height;
|
||||
|
||||
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}
|
||||
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}
|
||||
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
|
||||
ExtrusionPath(ExtrusionRole role) : m_attributes{ role } {}
|
||||
ExtrusionPath(const ExtrusionAttributes &attributes) : m_attributes(attributes) {}
|
||||
ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), m_attributes(rhs.m_attributes) {}
|
||||
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), m_attributes(rhs.m_attributes) {}
|
||||
ExtrusionPath(const Polyline &polyline, const ExtrusionAttributes &attribs) : polyline(polyline), m_attributes(attribs) {}
|
||||
ExtrusionPath(Polyline &&polyline, const ExtrusionAttributes &attribs) : polyline(std::move(polyline)), m_attributes(attribs) {}
|
||||
|
||||
ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; }
|
||||
ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; }
|
||||
ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->polyline = rhs.polyline; m_attributes = rhs.m_attributes; return *this; }
|
||||
ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->polyline = std::move(rhs.polyline); m_attributes = rhs.m_attributes; return *this; }
|
||||
|
||||
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
@ -97,35 +162,46 @@ public:
|
||||
void clip_end(double distance);
|
||||
void simplify(double tolerance);
|
||||
double length() const override;
|
||||
ExtrusionRole role() const override { return m_role; }
|
||||
|
||||
const ExtrusionAttributes& attributes() const { return m_attributes; }
|
||||
ExtrusionRole role() const override { return m_attributes.role; }
|
||||
float width() const { return m_attributes.width; }
|
||||
float height() const { return m_attributes.height; }
|
||||
double mm3_per_mm() const { return m_attributes.mm3_per_mm; }
|
||||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() const override { return m_attributes.mm3_per_mm; }
|
||||
std::optional<OverhangAttributes>& overhang_attributes_mutable() { return m_attributes.overhang_attributes; }
|
||||
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
|
||||
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
|
||||
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
|
||||
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
|
||||
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
|
||||
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
||||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() const override { return this->mm3_per_mm; }
|
||||
Polyline as_polyline() const override { return this->polyline; }
|
||||
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
|
||||
void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
|
||||
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
|
||||
|
||||
Polyline as_polyline() const override { return this->polyline; }
|
||||
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
|
||||
void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
|
||||
double total_volume() const override { return m_attributes.mm3_per_mm * unscale<double>(length()); }
|
||||
|
||||
private:
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
|
||||
ExtrusionRole m_role;
|
||||
ExtrusionAttributes m_attributes;
|
||||
};
|
||||
|
||||
class ExtrusionPathOriented : public ExtrusionPath
|
||||
{
|
||||
public:
|
||||
ExtrusionPathOriented(ExtrusionRole role, double mm3_per_mm, float width, float height) : ExtrusionPath(role, mm3_per_mm, width, height) {}
|
||||
ExtrusionPathOriented(const ExtrusionAttributes &attribs) : ExtrusionPath(attribs) {}
|
||||
ExtrusionPathOriented(const Polyline &polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(polyline, attribs) {}
|
||||
ExtrusionPathOriented(Polyline &&polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(std::move(polyline), attribs) {}
|
||||
|
||||
ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); }
|
||||
@ -192,7 +268,8 @@ class ExtrusionLoop : public ExtrusionEntity
|
||||
public:
|
||||
ExtrusionPaths paths;
|
||||
|
||||
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
|
||||
ExtrusionLoop() = default;
|
||||
ExtrusionLoop(ExtrusionLoopRole role) : m_loop_role(role) {}
|
||||
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
|
||||
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
|
||||
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
|
||||
@ -204,16 +281,21 @@ public:
|
||||
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); }
|
||||
bool make_clockwise();
|
||||
bool make_counter_clockwise();
|
||||
void reverse() override;
|
||||
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
|
||||
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
|
||||
const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; }
|
||||
Polygon polygon() const;
|
||||
double length() const override;
|
||||
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
|
||||
void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled<double>(0.001));
|
||||
double area() const;
|
||||
bool is_counter_clockwise() const { return this->area() > 0; }
|
||||
bool is_clockwise() const { return this->area() < 0; }
|
||||
// Reverse shall never be called on ExtrusionLoop using a virtual function call, it is most likely never what one wants,
|
||||
// as this->can_reverse() returns false for an ExtrusionLoop.
|
||||
void reverse() override;
|
||||
// Used by PerimeterGenerator to reorient extrusion loops.
|
||||
void reverse_loop();
|
||||
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
|
||||
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
|
||||
const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; }
|
||||
Polygon polygon() const;
|
||||
double length() const override;
|
||||
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
|
||||
void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled<double>(0.001));
|
||||
struct ClosestPathPoint {
|
||||
size_t path_idx;
|
||||
size_t segment_idx;
|
||||
@ -259,59 +341,51 @@ public:
|
||||
#endif /* NDEBUG */
|
||||
|
||||
private:
|
||||
ExtrusionLoopRole m_loop_role;
|
||||
ExtrusionLoopRole m_loop_role{ elrDefault };
|
||||
};
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = polyline;
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(polyline, attributes);
|
||||
}
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = std::move(polyline);
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(std::move(polyline), attributes);
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (const Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
ExtrusionPath* extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
|
||||
dst.push_back(extrusion_path);
|
||||
extrusion_path->polyline = polyline;
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(can_reverse ? new ExtrusionPath(polyline, attributes) : new ExtrusionPathOriented(polyline, attributes));
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
|
||||
dst.push_back(extrusion_path);
|
||||
extrusion_path->polyline = std::move(polyline);
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(can_reverse ?
|
||||
new ExtrusionPath(std::move(polyline), attributes) :
|
||||
new ExtrusionPathOriented(std::move(polyline), attributes));
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + loops.size());
|
||||
for (Polygon &poly : loops) {
|
||||
if (poly.is_valid()) {
|
||||
ExtrusionPath path(role, mm3_per_mm, width, height);
|
||||
ExtrusionPath path(attributes);
|
||||
path.polyline.points = std::move(poly.points);
|
||||
path.polyline.points.push_back(path.polyline.points.front());
|
||||
dst.emplace_back(new ExtrusionLoop(std::move(path)));
|
||||
@ -320,22 +394,14 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons
|
||||
loops.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines) {
|
||||
if (polyline.is_valid()) {
|
||||
if (polyline.is_closed()) {
|
||||
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height);
|
||||
extrusion_path.polyline = std::move(polyline);
|
||||
dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path)));
|
||||
} else {
|
||||
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
|
||||
extrusion_path->polyline = std::move(polyline);
|
||||
dst.emplace_back(extrusion_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(polyline.is_closed() ?
|
||||
static_cast<ExtrusionEntity*>(new ExtrusionLoop(ExtrusionPath{ std::move(polyline), attributes })) :
|
||||
static_cast<ExtrusionEntity*>(new ExtrusionPath(std::move(polyline), attributes)));
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#if 0
|
||||
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
|
||||
{
|
||||
if (role != ExtrusionRole::Mixed) {
|
||||
@ -17,6 +18,7 @@ void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities,
|
||||
last);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
|
||||
: no_sort(false)
|
||||
@ -83,18 +85,6 @@ void ExtrusionEntityCollection::remove(size_t i)
|
||||
this->entities.erase(this->entities.begin() + i);
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role)
|
||||
{
|
||||
// Return a filtered copy of the collection.
|
||||
ExtrusionEntityCollection out;
|
||||
out.entities = filter_by_extrusion_role(extrusion_entities, role);
|
||||
// Clone the extrusion entities.
|
||||
for (auto &ptr : out.entities)
|
||||
ptr = ptr->clone();
|
||||
chain_and_reorder_extrusion_entities(out.entities, &start_near);
|
||||
return out;
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
for (const ExtrusionEntity *entity : this->entities)
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#if 0
|
||||
// Remove those items from extrusion_entities, that do not match role.
|
||||
// Do nothing if role is mixed.
|
||||
// Removed elements are NOT being deleted.
|
||||
@ -21,6 +22,7 @@ inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr
|
||||
filter_by_extrusion_role_in_place(out, role);
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
class ExtrusionEntityCollection : public ExtrusionEntity
|
||||
{
|
||||
@ -96,9 +98,6 @@ public:
|
||||
}
|
||||
void replace(size_t i, const ExtrusionEntity &entity);
|
||||
void remove(size_t i);
|
||||
static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed);
|
||||
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed) const
|
||||
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
|
||||
void reverse() override;
|
||||
const Point& first_point() const override { return this->entities.front()->first_point(); }
|
||||
const Point& last_point() const override { return this->entities.back()->last_point(); }
|
||||
|
@ -82,6 +82,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
|
||||
bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); }
|
||||
bool is_infill() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Infill); }
|
||||
bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
|
||||
bool is_sparse_infill() const { return this->is_infill() && ! this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
|
||||
bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); }
|
||||
bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); }
|
||||
|
||||
@ -89,6 +90,9 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
|
||||
bool is_support_base() const { return this->is_support() && ! this->is_external(); }
|
||||
bool is_support_interface() const { return this->is_support() && this->is_external(); }
|
||||
bool is_mixed() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Mixed); }
|
||||
|
||||
// Brim is currently marked as skirt.
|
||||
bool is_skirt() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Skirt); }
|
||||
};
|
||||
|
||||
// Special flags describing loop
|
||||
|
@ -957,9 +957,9 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const
|
||||
polyline.reserve(path.polyline.points.size());
|
||||
float scalex = float(viewport.size().x()) / float(bbox.size().x());
|
||||
float scaley = float(viewport.size().y()) / float(bbox.size().y());
|
||||
float w = scale_(path.width) * scalex;
|
||||
float w = scale_(path.width()) * scalex;
|
||||
//float h = scale_(path.height) * scalex;
|
||||
w = scale_(path.mm3_per_mm / path.height) * scalex;
|
||||
w = scale_(path.mm3_per_mm() / path.height()) * scalex;
|
||||
// printf("scalex: %f, scaley: %f\n", scalex, scaley);
|
||||
// printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
|
||||
for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {
|
||||
|
@ -558,8 +558,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
} else {
|
||||
extrusion_entities_append_paths(
|
||||
eec->entities, std::move(polylines),
|
||||
surface_fill.params.extrusion_role,
|
||||
flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height());
|
||||
ExtrusionAttributes{ surface_fill.params.extrusion_role,
|
||||
ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() }
|
||||
});
|
||||
}
|
||||
insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size()));
|
||||
}
|
||||
@ -904,8 +905,9 @@ void Layer::make_ironing()
|
||||
eec->no_sort = true;
|
||||
extrusion_entities_append_paths(
|
||||
eec->entities, std::move(polylines),
|
||||
ExtrusionRole::Ironing,
|
||||
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
|
||||
ExtrusionAttributes{ ExtrusionRole::Ironing,
|
||||
ExtrusionFlow{ flow_mm3_per_mm, extrusion_width, float(extrusion_height) }
|
||||
});
|
||||
insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size()));
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,18 +5,22 @@
|
||||
#include "JumpPointSearch.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "PlaceholderParser.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Geometry/ArcWelder.hpp"
|
||||
#include "GCode/AvoidCrossingPerimeters.hpp"
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
#include "GCode/FindReplace.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
#include "GCode/PressureEqualizer.hpp"
|
||||
#include "GCode/RetractWhenCrossingPerimeters.hpp"
|
||||
#include "GCode/SmoothPath.hpp"
|
||||
#include "GCode/SpiralVase.hpp"
|
||||
#include "GCode/ToolOrdering.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/Wipe.hpp"
|
||||
#include "GCode/WipeTowerIntegration.hpp"
|
||||
#include "GCode/SeamPlacer.hpp"
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
@ -26,7 +30,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "GCode/PressureEqualizer.hpp"
|
||||
//#include "GCode/PressureEqualizer.hpp"
|
||||
|
||||
#if ENABLE_BINARIZED_GCODE
|
||||
#include <LibBGCode/binarize/binarize.hpp>
|
||||
@ -35,7 +39,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
|
||||
namespace { struct Item; }
|
||||
struct PrintInstance;
|
||||
@ -45,71 +49,11 @@ public:
|
||||
bool enable;
|
||||
|
||||
OozePrevention() : enable(false) {}
|
||||
std::string pre_toolchange(GCode &gcodegen);
|
||||
std::string post_toolchange(GCode &gcodegen);
|
||||
std::string pre_toolchange(GCodeGenerator &gcodegen);
|
||||
std::string post_toolchange(GCodeGenerator &gcodegen);
|
||||
|
||||
private:
|
||||
int _get_temp(const GCode &gcodegen) const;
|
||||
};
|
||||
|
||||
class Wipe {
|
||||
public:
|
||||
bool enable;
|
||||
Polyline path;
|
||||
|
||||
Wipe() : enable(false) {}
|
||||
bool has_path() const { return ! this->path.empty(); }
|
||||
void reset_path() { this->path.clear(); }
|
||||
std::string wipe(GCode &gcodegen, bool toolchange);
|
||||
};
|
||||
|
||||
class WipeTowerIntegration {
|
||||
public:
|
||||
WipeTowerIntegration(
|
||||
const PrintConfig &print_config,
|
||||
const std::vector<WipeTower::ToolChangeResult> &priming,
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
|
||||
const WipeTower::ToolChangeResult &final_purge) :
|
||||
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
|
||||
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
|
||||
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
|
||||
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
|
||||
m_extruder_offsets(print_config.extruder_offset.values),
|
||||
m_priming(priming),
|
||||
m_tool_changes(tool_changes),
|
||||
m_final_purge(final_purge),
|
||||
m_layer_idx(-1),
|
||||
m_tool_change_idx(0)
|
||||
{}
|
||||
|
||||
std::string prime(GCode &gcodegen);
|
||||
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
|
||||
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
|
||||
std::string finalize(GCode &gcodegen);
|
||||
std::vector<float> used_filament_length() const;
|
||||
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
|
||||
// Left / right edges of the wipe tower, for the planning of wipe moves.
|
||||
const float m_left;
|
||||
const float m_right;
|
||||
const Vec2f m_wipe_tower_pos;
|
||||
const float m_wipe_tower_rotation;
|
||||
const std::vector<Vec2d> m_extruder_offsets;
|
||||
|
||||
// Reference to cached values at the Printer class.
|
||||
const std::vector<WipeTower::ToolChangeResult> &m_priming;
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
|
||||
const WipeTower::ToolChangeResult &m_final_purge;
|
||||
// Current layer index.
|
||||
int m_layer_idx;
|
||||
int m_tool_change_idx;
|
||||
double m_last_wipe_tower_print_z = 0.f;
|
||||
int _get_temp(const GCodeGenerator &gcodegen) const;
|
||||
};
|
||||
|
||||
class ColorPrintColors
|
||||
@ -133,9 +77,9 @@ struct LayerResult {
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
|
||||
};
|
||||
|
||||
class GCode {
|
||||
class GCodeGenerator {
|
||||
public:
|
||||
GCode() :
|
||||
GCodeGenerator() :
|
||||
m_origin(Vec2d::Zero()),
|
||||
m_enable_loop_clipping(true),
|
||||
m_enable_cooling_markers(false),
|
||||
@ -157,7 +101,7 @@ public:
|
||||
m_silent_time_estimator_enabled(false),
|
||||
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
|
||||
{}
|
||||
~GCode() = default;
|
||||
~GCodeGenerator() = default;
|
||||
|
||||
// throws std::runtime_exception on error,
|
||||
// throws CanceledException through print->throw_if_canceled().
|
||||
@ -169,9 +113,19 @@ public:
|
||||
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
|
||||
const Point& last_pos() const { return m_last_pos; }
|
||||
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
|
||||
Vec2d point_to_gcode(const Point &point) const;
|
||||
template<typename Derived>
|
||||
Vec2d point_to_gcode(const Eigen::MatrixBase<Derived> &point) const {
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector");
|
||||
return Vec2d(unscaled<double>(point.x()), unscaled<double>(point.y())) + m_origin
|
||||
- m_config.extruder_offset.get_at(m_writer.extruder()->id());
|
||||
}
|
||||
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
|
||||
Vec2d point_to_gcode_quantized(const Point &point) const;
|
||||
template<typename Derived>
|
||||
Vec2d point_to_gcode_quantized(const Eigen::MatrixBase<Derived> &point) const {
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode_quantized(): first parameter is not a 2D vector");
|
||||
Vec2d p = this->point_to_gcode(point);
|
||||
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
|
||||
}
|
||||
Point gcode_to_point(const Vec2d &point) const;
|
||||
const FullPrintConfig &config() const { return m_config; }
|
||||
const Layer* layer() const { return m_layer; }
|
||||
@ -258,6 +212,7 @@ private:
|
||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||
const ObjectsLayerToPrint &layers,
|
||||
const LayerTools &layer_tools,
|
||||
const GCode::SmoothPathCaches &smooth_path_caches,
|
||||
const bool last_layer,
|
||||
// Pairs of PrintObject index and its instance index.
|
||||
const std::vector<const PrintInstance*> *ordering,
|
||||
@ -272,6 +227,7 @@ private:
|
||||
const ToolOrdering &tool_ordering,
|
||||
const std::vector<const PrintInstance*> &print_object_instances_ordering,
|
||||
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
|
||||
const GCode::SmoothPathCache &smooth_path_cache_global,
|
||||
GCodeOutputStream &output_stream);
|
||||
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
|
||||
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
|
||||
@ -281,6 +237,7 @@ private:
|
||||
const ToolOrdering &tool_ordering,
|
||||
ObjectsLayerToPrint layers_to_print,
|
||||
const size_t single_object_idx,
|
||||
const GCode::SmoothPathCache &smooth_path_cache_global,
|
||||
GCodeOutputStream &output_stream);
|
||||
|
||||
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
|
||||
@ -288,10 +245,13 @@ private:
|
||||
void set_extruders(const std::vector<unsigned int> &extruder_ids);
|
||||
std::string preamble();
|
||||
std::string change_layer(coordf_t print_z);
|
||||
std::string extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
|
||||
const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed);
|
||||
|
||||
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
|
||||
struct InstanceToPrint
|
||||
{
|
||||
@ -325,12 +285,14 @@ private:
|
||||
const ObjectLayerToPrint &layer_to_print,
|
||||
// Container for extruder overrides (when wiping into object or infill).
|
||||
const LayerTools &layer_tools,
|
||||
// Optional smooth path interpolating extrusion polylines.
|
||||
const GCode::SmoothPathCache &smooth_path_cache,
|
||||
// Is any extrusion possibly marked as wiping extrusion?
|
||||
const bool is_anything_overridden,
|
||||
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
|
||||
const bool print_wipe_extrusions);
|
||||
|
||||
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
|
||||
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
|
||||
|
||||
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
|
||||
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
|
||||
@ -341,8 +303,6 @@ private:
|
||||
// Cache for custom seam enforcers/blockers for each layer.
|
||||
SeamPlacer m_seam_placer;
|
||||
|
||||
ExtrusionQualityEstimator m_extrusion_quality_estimator;
|
||||
|
||||
/* Origin of print coordinates expressed in unscaled G-code coordinates.
|
||||
This affects the input arguments supplied to the extrude*() and travel_to()
|
||||
methods. */
|
||||
@ -383,7 +343,7 @@ private:
|
||||
} m_placeholder_parser_integration;
|
||||
|
||||
OozePrevention m_ooze_prevention;
|
||||
Wipe m_wipe;
|
||||
GCode::Wipe m_wipe;
|
||||
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
|
||||
JPSPathFinder m_avoid_crossing_curled_overhangs;
|
||||
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
|
||||
@ -426,7 +386,7 @@ private:
|
||||
std::unique_ptr<SpiralVase> m_spiral_vase;
|
||||
std::unique_ptr<GCodeFindReplace> m_find_replace;
|
||||
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
|
||||
std::unique_ptr<WipeTowerIntegration> m_wipe_tower;
|
||||
std::unique_ptr<GCode::WipeTowerIntegration> m_wipe_tower;
|
||||
|
||||
// Heights (print_z) at which the skirt has already been extruded.
|
||||
std::vector<coordf_t> m_skirt_done;
|
||||
@ -442,7 +402,8 @@ private:
|
||||
// Processor
|
||||
GCodeProcessor m_processor;
|
||||
|
||||
std::string _extrude(const ExtrusionPath &path, const std::string_view description, double speed = -1);
|
||||
std::string _extrude(
|
||||
const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1);
|
||||
void print_machine_envelope(GCodeOutputStream &file, Print &print);
|
||||
void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
@ -451,8 +412,12 @@ private:
|
||||
// To control print speed of 1st object layer over raft interface.
|
||||
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
|
||||
|
||||
friend class Wipe;
|
||||
friend class WipeTowerIntegration;
|
||||
// Fill in cache of smooth paths for perimeters, fills and supports of the given object layers.
|
||||
// Based on params, the paths are either decimated to sparser polylines, or interpolated with circular arches.
|
||||
static void smooth_path_interpolate(const ObjectLayerToPrint &layers, const GCode::SmoothPathCache::InterpolationParameters ¶ms, GCode::SmoothPathCache &out);
|
||||
|
||||
friend class GCode::Wipe;
|
||||
friend class GCode::WipeTowerIntegration;
|
||||
friend class PressureEqualizer;
|
||||
};
|
||||
|
||||
|
@ -730,7 +730,7 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool need_wipe(const GCode &gcodegen,
|
||||
static bool need_wipe(const GCodeGenerator &gcodegen,
|
||||
const ExPolygons &lslices_offset,
|
||||
const std::vector<BoundingBox> &lslices_offset_bboxes,
|
||||
const EdgeGrid::Grid &grid_lslices_offset,
|
||||
@ -1167,7 +1167,7 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons
|
||||
}
|
||||
|
||||
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
|
||||
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
|
||||
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
|
||||
{
|
||||
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
|
||||
// Otherwise perform the path planning in the coordinate system of the active object.
|
||||
@ -1470,7 +1470,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary
|
||||
}
|
||||
|
||||
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
|
||||
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
|
||||
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
|
||||
{
|
||||
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
|
||||
// Otherwise perform the path planning in the coordinate system of the active object.
|
||||
|
@ -8,7 +8,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
class Layer;
|
||||
class Point;
|
||||
|
||||
@ -25,13 +25,13 @@ public:
|
||||
|
||||
void init_layer(const Layer &layer);
|
||||
|
||||
Polyline travel_to(const GCode& gcodegen, const Point& point)
|
||||
Polyline travel_to(const GCodeGenerator &gcodegen, const Point& point)
|
||||
{
|
||||
bool could_be_wipe_disabled;
|
||||
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
|
||||
}
|
||||
|
||||
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
|
||||
Polyline travel_to(const GCodeGenerator &gcodegen, const Point& point, bool* could_be_wipe_disabled);
|
||||
|
||||
struct Boundary {
|
||||
// Collection of boundaries used for detection of crossing perimeters for travels
|
||||
|
@ -43,7 +43,7 @@ public:
|
||||
void raise()
|
||||
{
|
||||
if (valid()) {
|
||||
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; }
|
||||
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height(); }
|
||||
_curPileIdx++;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
CoolingBuffer::CoolingBuffer(GCodeGenerator &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
{
|
||||
this->reset(gcodegen.writer().get_position());
|
||||
|
||||
@ -33,36 +33,45 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_t
|
||||
|
||||
void CoolingBuffer::reset(const Vec3d &position)
|
||||
{
|
||||
m_current_pos.assign(5, 0.f);
|
||||
m_current_pos[0] = float(position.x());
|
||||
m_current_pos[1] = float(position.y());
|
||||
m_current_pos[2] = float(position.z());
|
||||
m_current_pos[4] = float(m_config.travel_speed.value);
|
||||
assert(m_current_pos.size() == 5);
|
||||
m_current_pos[AxisIdx::X] = float(position.x());
|
||||
m_current_pos[AxisIdx::Y] = float(position.y());
|
||||
m_current_pos[AxisIdx::Z] = float(position.z());
|
||||
m_current_pos[AxisIdx::E] = 0.f;
|
||||
m_current_pos[AxisIdx::F] = float(m_config.travel_speed.value);
|
||||
m_fan_speed = -1;
|
||||
}
|
||||
|
||||
struct CoolingLine
|
||||
{
|
||||
enum Type {
|
||||
enum Type : uint32_t {
|
||||
TYPE_SET_TOOL = 1 << 0,
|
||||
TYPE_EXTRUDE_END = 1 << 1,
|
||||
TYPE_BRIDGE_FAN_START = 1 << 2,
|
||||
TYPE_BRIDGE_FAN_END = 1 << 3,
|
||||
TYPE_G0 = 1 << 4,
|
||||
TYPE_G1 = 1 << 5,
|
||||
TYPE_ADJUSTABLE = 1 << 6,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||
// G2 or G3: Arc interpolation
|
||||
TYPE_G2G3 = 1 << 6,
|
||||
TYPE_ADJUSTABLE = 1 << 7,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 8,
|
||||
// Arc interpolation, counter-clockwise.
|
||||
TYPE_G2G3_CCW = 1 << 9,
|
||||
// Arc interpolation, arc defined by IJ (offset of arc center from its start position).
|
||||
TYPE_G2G3_IJ = 1 << 10,
|
||||
// Arc interpolation, arc defined by R (arc radius, positive - smaller, negative - larger).
|
||||
TYPE_G2G3_R = 1 << 11,
|
||||
// The line sets a feedrate.
|
||||
TYPE_HAS_F = 1 << 8,
|
||||
TYPE_WIPE = 1 << 9,
|
||||
TYPE_G4 = 1 << 10,
|
||||
TYPE_G92 = 1 << 11,
|
||||
TYPE_HAS_F = 1 << 12,
|
||||
TYPE_WIPE = 1 << 13,
|
||||
TYPE_G4 = 1 << 14,
|
||||
TYPE_G92 = 1 << 15,
|
||||
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block
|
||||
// cannot have its speed adjusted. This should not happen (sic!).
|
||||
TYPE_ADJUSTABLE_EMPTY = 1 << 12,
|
||||
TYPE_ADJUSTABLE_EMPTY = 1 << 16,
|
||||
// Custom fan speed (introduced for overhang fan speed)
|
||||
TYPE_SET_FAN_SPEED = 1 << 13,
|
||||
TYPE_RESET_FAN_SPEED = 1 << 14,
|
||||
TYPE_SET_FAN_SPEED = 1 << 17,
|
||||
TYPE_RESET_FAN_SPEED = 1 << 18,
|
||||
};
|
||||
|
||||
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
|
||||
@ -324,7 +333,7 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b
|
||||
|
||||
// Parse the layer G-code for the moves, which could be adjusted.
|
||||
// Return the list of parsed lines, bucketed by an extruder.
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::array<float, 5> ¤t_pos) const
|
||||
{
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
|
||||
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
|
||||
@ -347,7 +356,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
// for a sequence of extrusion moves.
|
||||
size_t active_speed_modifier = size_t(-1);
|
||||
|
||||
std::vector<float> new_pos;
|
||||
std::array<float, AxisIdx::Count> new_pos;
|
||||
for (; *line_start != 0; line_start = line_end)
|
||||
{
|
||||
while (*line_end != '\n' && *line_end != 0)
|
||||
@ -362,12 +371,20 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
line.type = CoolingLine::TYPE_G0;
|
||||
else if (boost::starts_with(sline, "G1 "))
|
||||
line.type = CoolingLine::TYPE_G1;
|
||||
else if (boost::starts_with(sline, "G2 "))
|
||||
// Arc, clockwise.
|
||||
line.type = CoolingLine::TYPE_G2G3;
|
||||
else if (boost::starts_with(sline, "G3 "))
|
||||
// Arc, counter-clockwise.
|
||||
line.type = CoolingLine::TYPE_G2G3 | CoolingLine::TYPE_G2G3_CCW;
|
||||
else if (boost::starts_with(sline, "G92 "))
|
||||
line.type = CoolingLine::TYPE_G92;
|
||||
if (line.type) {
|
||||
// G0, G1 or G92
|
||||
// G0, G1, G2, G3 or G92
|
||||
// Initialize current_pos from new_pos, set IJKR to zero.
|
||||
std::fill(std::copy(std::begin(current_pos), std::end(current_pos), std::begin(new_pos)),
|
||||
std::end(new_pos), 0.f);
|
||||
// Parse the G-code line.
|
||||
new_pos = current_pos;
|
||||
for (auto c = sline.begin() + 3;;) {
|
||||
// Skip whitespaces.
|
||||
for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c);
|
||||
@ -376,21 +393,31 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
|
||||
// Parse the axis.
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
|
||||
(*c == extrusion_axis) ? AxisIdx::E : (*c == 'F') ? AxisIdx::F :
|
||||
(*c >= 'I' && *c <= 'K') ? int(AxisIdx::I) + (*c - 'I') :
|
||||
(*c == 'R') ? AxisIdx::R : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
//auto [pend, ec] =
|
||||
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
|
||||
if (axis == 4) {
|
||||
if (axis == AxisIdx::F) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
new_pos[AxisIdx::F] /= 60.f;
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0)
|
||||
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
|
||||
line.type |= CoolingLine::TYPE_HAS_F;
|
||||
}
|
||||
} else if (axis >= AxisIdx::I && axis <= AxisIdx::J)
|
||||
line.type |= CoolingLine::TYPE_G2G3_IJ;
|
||||
else if (axis == AxisIdx::R)
|
||||
line.type |= CoolingLine::TYPE_G2G3_R;
|
||||
}
|
||||
// Skip this word.
|
||||
for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c);
|
||||
}
|
||||
// If G2 or G3, then either center of the arc or radius has to be defined.
|
||||
assert(! (line.type & CoolingLine::TYPE_G2G3) ||
|
||||
(line.type & (CoolingLine::TYPE_G2G3_IJ | CoolingLine::TYPE_G2G3_R)));
|
||||
// Arc is defined either by IJ or by R, not by both.
|
||||
assert(! ((line.type & CoolingLine::TYPE_G2G3_IJ) && (line.type & CoolingLine::TYPE_G2G3_R)));
|
||||
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
|
||||
bool wipe = boost::contains(sline, ";_WIPE");
|
||||
if (external_perimeter)
|
||||
@ -402,23 +429,41 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
active_speed_modifier = adjustment->lines.size();
|
||||
}
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0) {
|
||||
// G0 or G1. Calculate the duration.
|
||||
// G0, G1, G2, G3. Calculate the duration.
|
||||
assert((line.type & CoolingLine::TYPE_G0) != 0 + (line.type & CoolingLine::TYPE_G1) != 0 + (line.type & CoolingLine::TYPE_G2G3) != 0 == 1);
|
||||
if (m_config.use_relative_e_distances.value)
|
||||
// Reset extruder accumulator.
|
||||
current_pos[3] = 0.f;
|
||||
current_pos[AxisIdx::E] = 0.f;
|
||||
float dif[4];
|
||||
for (size_t i = 0; i < 4; ++ i)
|
||||
dif[i] = new_pos[i] - current_pos[i];
|
||||
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
|
||||
float dxyz2 = dxy2 + dif[2] * dif[2];
|
||||
float dxy2;
|
||||
if (line.type & CoolingLine::TYPE_G2G3) {
|
||||
// Measure arc length.
|
||||
if (line.type & CoolingLine::TYPE_G2G3_IJ) {
|
||||
dxy2 = sqr(Geometry::ArcWelder::arc_length(
|
||||
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
|
||||
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
|
||||
Vec2d(current_pos[AxisIdx::X] + new_pos[AxisIdx::I], current_pos[AxisIdx::Y] + new_pos[AxisIdx::J]),
|
||||
line.type & CoolingLine::TYPE_G2G3_CCW));
|
||||
} else if (line.type & CoolingLine::TYPE_G2G3_R) {
|
||||
dxy2 = sqr(Geometry::ArcWelder::arc_length(
|
||||
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
|
||||
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
|
||||
double(new_pos[AxisIdx::R])));
|
||||
} else
|
||||
dxy2 = 0;
|
||||
} else
|
||||
dxy2 = sqr(dif[AxisIdx::X]) + sqr(dif[AxisIdx::Y]);
|
||||
float dxyz2 = dxy2 + sqr(dif[AxisIdx::Z]);
|
||||
if (dxyz2 > 0.f) {
|
||||
// Movement in xyz, calculate time from the xyz Euclidian distance.
|
||||
line.length = sqrt(dxyz2);
|
||||
} else if (std::abs(dif[3]) > 0.f) {
|
||||
} else if (std::abs(dif[AxisIdx::E]) > 0.f) {
|
||||
// Movement in the extruder axis.
|
||||
line.length = std::abs(dif[3]);
|
||||
line.length = std::abs(dif[AxisIdx::E]);
|
||||
}
|
||||
line.feedrate = new_pos[4];
|
||||
line.feedrate = new_pos[AxisIdx::F];
|
||||
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
|
||||
if (line.length > 0) {
|
||||
assert(line.feedrate > 0);
|
||||
@ -430,7 +475,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
assert(adjustment->min_print_speed >= 0);
|
||||
line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
|
||||
}
|
||||
if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
|
||||
if (active_speed_modifier < adjustment->lines.size() && (line.type & (CoolingLine::TYPE_G1 | CoolingLine::TYPE_G2G3))) {
|
||||
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
|
||||
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
|
||||
CoolingLine &sm = adjustment->lines[active_speed_modifier];
|
||||
@ -447,7 +492,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
line.type = 0;
|
||||
}
|
||||
}
|
||||
current_pos = std::move(new_pos);
|
||||
std::copy(std::begin(new_pos), std::begin(new_pos) + 5, std::begin(current_pos));
|
||||
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
|
||||
// Closing a block of non-zero length extrusion moves.
|
||||
line.type = CoolingLine::TYPE_EXTRUDE_END;
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
class Layer;
|
||||
struct PerExtruderAdjustments;
|
||||
|
||||
@ -22,7 +22,7 @@ struct PerExtruderAdjustments;
|
||||
//
|
||||
class CoolingBuffer {
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
CoolingBuffer(GCodeGenerator &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
|
||||
@ -31,7 +31,7 @@ public:
|
||||
|
||||
private:
|
||||
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::array<float, 5> ¤t_pos) const;
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
@ -40,9 +40,11 @@ private:
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// X,Y,Z,E,F
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
enum AxisIdx : int {
|
||||
X = 0, Y, Z, E, F, I, J, K, R, Count
|
||||
};
|
||||
std::array<float, 5> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
// Cached from GCodeWriter.
|
||||
@ -51,7 +53,7 @@ private:
|
||||
// Highest of m_extruder_ids plus 1.
|
||||
unsigned int m_num_extruders { 0 };
|
||||
const std::string m_toolchange_prefix;
|
||||
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// Referencs GCodeGenerator::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
|
||||
const PrintConfig &m_config;
|
||||
unsigned int m_current_extruder;
|
||||
|
216
src/libslic3r/GCode/ExtrusionProcessor.cpp
Normal file
216
src/libslic3r/GCode/ExtrusionProcessor.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
#include "ExtrusionProcessor.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r { namespace ExtrusionProcessor {
|
||||
|
||||
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
|
||||
{
|
||||
std::vector<ExtendedPoint> extended_points = estimate_points_properties<true, true, true, true>(path.polyline.points,
|
||||
unscaled_prev_layer, path.width());
|
||||
std::vector<std::pair<float, float>> calculated_distances(extended_points.size());
|
||||
|
||||
for (size_t i = 0; i < extended_points.size(); i++) {
|
||||
const ExtendedPoint &curr = extended_points[i];
|
||||
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
|
||||
|
||||
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
|
||||
float proximity_to_curled_lines = 0.0;
|
||||
const double dist_limit = 10.0 * path.width();
|
||||
{
|
||||
Vec2d middle = 0.5 * (curr.position + next.position);
|
||||
auto line_indices = prev_layer_curled_lines.all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
|
||||
if (!line_indices.empty()) {
|
||||
double len = (next.position - curr.position).norm();
|
||||
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle
|
||||
// of this long line
|
||||
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
|
||||
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
|
||||
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
|
||||
if (len > 8) {
|
||||
Vec2d dir = Vec2d(next.position - curr.position) / len;
|
||||
Vec2d right = Vec2d(-dir.y(), dir.x());
|
||||
|
||||
Polygon box_of_influence = {
|
||||
scaled(Vec2d(curr.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position - right * dist_limit)),
|
||||
scaled(Vec2d(curr.position - right * dist_limit)),
|
||||
};
|
||||
|
||||
double projected_lengths_sum = 0;
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
|
||||
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
|
||||
if (inside.empty())
|
||||
continue;
|
||||
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
|
||||
projected_lengths_sum += projected_length;
|
||||
}
|
||||
if (projected_lengths_sum < 0.4 * len) {
|
||||
line_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
|
||||
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
|
||||
float proximity = (1.0 - (distance_from_curled / dist_limit)) * (1.0 - (distance_from_curled / dist_limit)) *
|
||||
(line.curled_height / (path.height() * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
|
||||
proximity_to_curled_lines = std::max(proximity_to_curled_lines, proximity);
|
||||
}
|
||||
}
|
||||
}
|
||||
calculated_distances[i].first = std::max(curr.distance, next.distance);
|
||||
calculated_distances[i].second = proximity_to_curled_lines;
|
||||
}
|
||||
|
||||
ExtrusionPaths result;
|
||||
ExtrusionAttributes new_attrs = path.attributes();
|
||||
new_attrs.overhang_attributes = std::optional<OverhangAttributes>(
|
||||
{calculated_distances[0].first, calculated_distances[0].first, calculated_distances[0].second});
|
||||
result.emplace_back(new_attrs);
|
||||
result.back().polyline.append(Point::new_scale(extended_points[0].position));
|
||||
size_t sequence_start_index = 0;
|
||||
for (size_t i = 1; i < extended_points.size(); i++) {
|
||||
result.back().polyline.append(Point::new_scale(extended_points[i].position));
|
||||
result.back().overhang_attributes_mutable()->end_distance_from_prev_layer = extended_points[i].distance;
|
||||
|
||||
if (std::abs(calculated_distances[sequence_start_index].first - calculated_distances[i].first) < 0.001 * path.attributes().width &&
|
||||
std::abs(calculated_distances[sequence_start_index].second - calculated_distances[i].second) < 0.001) {
|
||||
// do not start new path, the attributes are similar enough
|
||||
// NOTE: a larger tolerance may be applied here. However, it makes the gcode preview much less smooth
|
||||
// (But it has very likely zero impact on the print quality.)
|
||||
} else if (i + 1 < extended_points.size()) { // do not start new path if this is last point!
|
||||
// start new path, parameters differ
|
||||
new_attrs.overhang_attributes->start_distance_from_prev_layer = calculated_distances[i].first;
|
||||
new_attrs.overhang_attributes->end_distance_from_prev_layer = calculated_distances[i].first;
|
||||
new_attrs.overhang_attributes->proximity_to_curled_lines = calculated_distances[i].second;
|
||||
sequence_start_index = i;
|
||||
result.emplace_back(new_attrs);
|
||||
result.back().polyline.append(Point::new_scale(extended_points[i].position));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(const ExtrusionEntityCollection *ecc,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
|
||||
{
|
||||
ExtrusionEntityCollection result{};
|
||||
result.no_sort = ecc->no_sort;
|
||||
for (const auto *e : ecc->entities) {
|
||||
if (auto *col = dynamic_cast<const ExtrusionEntityCollection *>(e)) {
|
||||
result.append(calculate_and_split_overhanging_extrusions(col, unscaled_prev_layer, prev_layer_curled_lines));
|
||||
} else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(e)) {
|
||||
ExtrusionLoop new_loop = *loop;
|
||||
new_loop.paths.clear();
|
||||
for (const ExtrusionPath &p : loop->paths) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
new_loop.paths.insert(new_loop.paths.end(), paths.begin(), paths.end());
|
||||
}
|
||||
result.append(new_loop);
|
||||
} else if (auto *mp = dynamic_cast<const ExtrusionMultiPath *>(e)) {
|
||||
ExtrusionMultiPath new_mp = *mp;
|
||||
new_mp.paths.clear();
|
||||
for (const ExtrusionPath &p : mp->paths) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
new_mp.paths.insert(new_mp.paths.end(), paths.begin(), paths.end());
|
||||
}
|
||||
result.append(new_mp);
|
||||
} else if (auto *op = dynamic_cast<const ExtrusionPathOriented *>(e)) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(*op, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
for (const ExtrusionPath &p : paths) {
|
||||
result.append(ExtrusionPathOriented(p.polyline, p.attributes()));
|
||||
}
|
||||
} else if (auto *p = dynamic_cast<const ExtrusionPath *>(e)) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(*p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
result.append(paths);
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Unknown extrusion entity type");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
|
||||
const FullPrintConfig &config,
|
||||
size_t extruder_id,
|
||||
float external_perim_reference_speed,
|
||||
float default_speed)
|
||||
{
|
||||
assert(attributes.overhang_attributes.has_value());
|
||||
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {
|
||||
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
|
||||
if (config.enable_dynamic_overhang_speeds) {
|
||||
overhangs_with_speeds = {{0, config.overhang_speed_0},
|
||||
{25, config.overhang_speed_1},
|
||||
{50, config.overhang_speed_2},
|
||||
{75, config.overhang_speed_3},
|
||||
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, ConfigOptionInts>> overhang_with_fan_speeds = {{100, ConfigOptionInts{0}}};
|
||||
if (config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
|
||||
overhang_with_fan_speeds = {{0, config.overhang_fan_speed_0},
|
||||
{25, config.overhang_fan_speed_1},
|
||||
{50, config.overhang_fan_speed_2},
|
||||
{75, config.overhang_fan_speed_3},
|
||||
{100, ConfigOptionInts{0}}};
|
||||
}
|
||||
|
||||
float speed_base = external_perim_reference_speed > 0 ? external_perim_reference_speed : default_speed;
|
||||
std::map<float, float> speed_sections;
|
||||
for (size_t i = 0; i < overhangs_with_speeds.size(); i++) {
|
||||
float distance = attributes.width * (1.0 - (overhangs_with_speeds[i].first / 100.0));
|
||||
float speed = overhangs_with_speeds[i].second.percent ? (speed_base * overhangs_with_speeds[i].second.value / 100.0) :
|
||||
overhangs_with_speeds[i].second.value;
|
||||
if (speed < EPSILON)
|
||||
speed = speed_base;
|
||||
speed_sections[distance] = speed;
|
||||
}
|
||||
|
||||
std::map<float, float> fan_speed_sections;
|
||||
for (size_t i = 0; i < overhang_with_fan_speeds.size(); i++) {
|
||||
float distance = attributes.width * (1.0 - (overhang_with_fan_speeds[i].first / 100.0));
|
||||
float fan_speed = overhang_with_fan_speeds[i].second.get_at(extruder_id);
|
||||
fan_speed_sections[distance] = fan_speed;
|
||||
}
|
||||
|
||||
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
|
||||
auto upper_dist = values.lower_bound(distance);
|
||||
if (upper_dist == values.end()) {
|
||||
return values.rbegin()->second;
|
||||
}
|
||||
if (upper_dist == values.begin()) {
|
||||
return upper_dist->second;
|
||||
}
|
||||
|
||||
auto lower_dist = std::prev(upper_dist);
|
||||
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
|
||||
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
|
||||
};
|
||||
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
float curled_base_speed = interpolate_speed(speed_sections,
|
||||
attributes.width * attributes.overhang_attributes->proximity_to_curled_lines);
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
|
||||
if (!config.enable_dynamic_overhang_speeds) {
|
||||
final_speed = -1;
|
||||
}
|
||||
if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
|
||||
fan_speed = -1;
|
||||
}
|
||||
|
||||
return {final_speed, fan_speed};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
@ -14,25 +14,29 @@
|
||||
#include "../Flow.hpp"
|
||||
#include "../Config.hpp"
|
||||
#include "../Line.hpp"
|
||||
#include "../Exception.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r { namespace ExtrusionProcessor {
|
||||
|
||||
struct ExtendedPoint
|
||||
{
|
||||
Vec2d position;
|
||||
float distance;
|
||||
float curvature;
|
||||
Vec2d position;
|
||||
float distance;
|
||||
float curvature;
|
||||
};
|
||||
|
||||
template<bool SCALED_INPUT, bool ADD_INTERSECTIONS, bool PREV_LAYER_BOUNDARY_OFFSET, bool SIGNED_DISTANCE, typename POINTS, typename L>
|
||||
@ -46,27 +50,30 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
using AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar;
|
||||
if (input_points.empty())
|
||||
return {};
|
||||
float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f;
|
||||
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
|
||||
float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f;
|
||||
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
|
||||
|
||||
std::vector<ExtendedPoint> points;
|
||||
points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
|
||||
|
||||
{
|
||||
ExtendedPoint start_point{maybe_unscale(input_points.front())};
|
||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
||||
start_point.distance = distance + boundary_offset;
|
||||
auto [distance, nearest_line,
|
||||
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
||||
start_point.distance = distance + boundary_offset;
|
||||
points.push_back(start_point);
|
||||
}
|
||||
for (size_t i = 1; i < input_points.size(); i++) {
|
||||
ExtendedPoint next_point{maybe_unscale(input_points[i])};
|
||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
||||
next_point.distance = distance + boundary_offset;
|
||||
auto [distance, nearest_line,
|
||||
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
||||
next_point.distance = distance + boundary_offset;
|
||||
|
||||
if (ADD_INTERSECTIONS &&
|
||||
((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) {
|
||||
const ExtendedPoint &prev_point = points.back();
|
||||
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
|
||||
const ExtendedPoint &prev_point = points.back();
|
||||
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
|
||||
L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
|
||||
for (const auto &intersection : intersections) {
|
||||
ExtendedPoint p{};
|
||||
p.position = intersection.first.template cast<double>();
|
||||
@ -79,47 +86,49 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
|
||||
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
|
||||
std::vector<ExtendedPoint> new_points;
|
||||
new_points.reserve(points.size()*2);
|
||||
new_points.reserve(points.size() * 2);
|
||||
new_points.push_back(points.front());
|
||||
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
|
||||
const ExtendedPoint &curr = points[point_idx];
|
||||
const ExtendedPoint &next = points[point_idx + 1];
|
||||
|
||||
if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) ||
|
||||
(next.distance > 0 && next.distance < boundary_offset + 2.0f)) {
|
||||
if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
|
||||
(next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
|
||||
double line_len = (next.position - curr.position).norm();
|
||||
if (line_len > 4.0f) {
|
||||
double a0 = std::clamp((curr.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a1 = std::clamp(1.0f - (next.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a0 = std::clamp((curr.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a1 = std::clamp(1.0f - (next.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double t0 = std::min(a0, a1);
|
||||
double t1 = std::max(a0, a1);
|
||||
|
||||
if (t0 < 1.0) {
|
||||
auto p0 = curr.position + t0 * (next.position - curr.position);
|
||||
auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
|
||||
auto p0 = curr.position + t0 * (next.position - curr.position);
|
||||
auto [p0_dist, p0_near_l,
|
||||
p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
|
||||
ExtendedPoint new_p{};
|
||||
new_p.position = p0;
|
||||
new_p.distance = float(p0_dist + boundary_offset);
|
||||
new_p.position = p0;
|
||||
new_p.distance = float(p0_dist + boundary_offset);
|
||||
new_points.push_back(new_p);
|
||||
}
|
||||
if (t1 > 0.0) {
|
||||
auto p1 = curr.position + t1 * (next.position - curr.position);
|
||||
auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
|
||||
auto p1 = curr.position + t1 * (next.position - curr.position);
|
||||
auto [p1_dist, p1_near_l,
|
||||
p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
|
||||
ExtendedPoint new_p{};
|
||||
new_p.position = p1;
|
||||
new_p.distance = float(p1_dist + boundary_offset);
|
||||
new_p.position = p1;
|
||||
new_p.distance = float(p1_dist + boundary_offset);
|
||||
new_points.push_back(new_p);
|
||||
}
|
||||
}
|
||||
}
|
||||
new_points.push_back(next);
|
||||
}
|
||||
points = new_points;
|
||||
points = std::move(new_points);
|
||||
}
|
||||
|
||||
if (max_line_length > 0) {
|
||||
std::vector<ExtendedPoint> new_points;
|
||||
new_points.reserve(points.size()*2);
|
||||
new_points.reserve(points.size() * 2);
|
||||
{
|
||||
for (size_t i = 0; i + 1 < points.size(); i++) {
|
||||
const ExtendedPoint &curr = points[i];
|
||||
@ -133,14 +142,14 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
auto [p_dist, p_near_l,
|
||||
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.cast<AABBScalar>());
|
||||
ExtendedPoint new_p{};
|
||||
new_p.position = pos;
|
||||
new_p.distance = float(p_dist + boundary_offset);
|
||||
new_p.position = pos;
|
||||
new_p.distance = float(p_dist + boundary_offset);
|
||||
new_points.push_back(new_p);
|
||||
}
|
||||
}
|
||||
new_points.push_back(points.back());
|
||||
}
|
||||
points = new_points;
|
||||
points = std::move(new_points);
|
||||
}
|
||||
|
||||
std::vector<float> angles_for_curvature(points.size());
|
||||
@ -212,144 +221,21 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
return points;
|
||||
}
|
||||
|
||||
struct ProcessedPoint
|
||||
{
|
||||
Point p;
|
||||
float speed = 1.0f;
|
||||
int fan_speed = 0;
|
||||
};
|
||||
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
|
||||
|
||||
class ExtrusionQualityEstimator
|
||||
{
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> prev_layer_boundaries;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> next_layer_boundaries;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
|
||||
const PrintObject *current_object;
|
||||
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(
|
||||
const ExtrusionEntityCollection *ecc,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
|
||||
|
||||
public:
|
||||
void set_current_object(const PrintObject *object) { current_object = object; }
|
||||
std::pair<float, float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
|
||||
const FullPrintConfig &config,
|
||||
size_t extruder_id,
|
||||
float external_perim_reference_speed,
|
||||
float default_speed);
|
||||
|
||||
void prepare_for_new_layer(const Layer *layer)
|
||||
{
|
||||
if (layer == nullptr)
|
||||
return;
|
||||
const PrintObject *object = layer->object();
|
||||
prev_layer_boundaries[object] = next_layer_boundaries[object];
|
||||
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
|
||||
prev_curled_extrusions[object] = next_curled_extrusions[object];
|
||||
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
|
||||
}
|
||||
|
||||
std::vector<ProcessedPoint> estimate_speed_from_extrusion_quality(
|
||||
const ExtrusionPath &path,
|
||||
const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
|
||||
const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
|
||||
size_t extruder_id,
|
||||
float ext_perimeter_speed,
|
||||
float original_speed)
|
||||
{
|
||||
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
|
||||
std::map<float, float> speed_sections;
|
||||
for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
|
||||
float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
|
||||
float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
|
||||
overhangs_w_speeds[i].second.value;
|
||||
if (speed < EPSILON) speed = speed_base;
|
||||
speed_sections[distance] = speed;
|
||||
}
|
||||
|
||||
std::map<float, float> fan_speed_sections;
|
||||
for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
|
||||
float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
|
||||
float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
|
||||
fan_speed_sections[distance] = fan_speed;
|
||||
}
|
||||
|
||||
std::vector<ExtendedPoint> extended_points =
|
||||
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width);
|
||||
|
||||
std::vector<ProcessedPoint> processed_points;
|
||||
processed_points.reserve(extended_points.size());
|
||||
for (size_t i = 0; i < extended_points.size(); i++) {
|
||||
const ExtendedPoint &curr = extended_points[i];
|
||||
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
|
||||
|
||||
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
|
||||
float artificial_distance_to_curled_lines = 0.0;
|
||||
const double dist_limit = 10.0 * path.width;
|
||||
{
|
||||
Vec2d middle = 0.5 * (curr.position + next.position);
|
||||
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
|
||||
if (!line_indices.empty()) {
|
||||
double len = (next.position - curr.position).norm();
|
||||
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
|
||||
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
|
||||
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
|
||||
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
|
||||
if (len > 8) {
|
||||
Vec2d dir = Vec2d(next.position - curr.position) / len;
|
||||
Vec2d right = Vec2d(-dir.y(), dir.x());
|
||||
|
||||
Polygon box_of_influence = {
|
||||
scaled(Vec2d(curr.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position - right * dist_limit)),
|
||||
scaled(Vec2d(curr.position - right * dist_limit)),
|
||||
};
|
||||
|
||||
double projected_lengths_sum = 0;
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
|
||||
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
|
||||
if (inside.empty())
|
||||
continue;
|
||||
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
|
||||
projected_lengths_sum += projected_length;
|
||||
}
|
||||
if (projected_lengths_sum < 0.4 * len) {
|
||||
line_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
|
||||
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
|
||||
float dist = path.width * (1.0 - (distance_from_curled / dist_limit)) *
|
||||
(1.0 - (distance_from_curled / dist_limit)) *
|
||||
(line.curled_height / (path.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
|
||||
artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
|
||||
auto upper_dist = values.lower_bound(distance);
|
||||
if (upper_dist == values.end()) {
|
||||
return values.rbegin()->second;
|
||||
}
|
||||
if (upper_dist == values.begin()) {
|
||||
return upper_dist->second;
|
||||
}
|
||||
|
||||
auto lower_dist = std::prev(upper_dist);
|
||||
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
|
||||
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
|
||||
};
|
||||
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance),
|
||||
interpolate_speed(speed_sections, next.distance));
|
||||
float curled_base_speed = interpolate_speed(speed_sections, artificial_distance_to_curled_lines);
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance),
|
||||
interpolate_speed(fan_speed_sections, next.distance));
|
||||
|
||||
processed_points.push_back({scaled(curr.position), final_speed, int(fan_speed)});
|
||||
}
|
||||
return processed_points;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
||||
|
||||
#endif // slic3r_ExtrusionProcessor_hpp_
|
||||
|
@ -4,8 +4,9 @@
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode/GCodeWriter.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/Geometry/ArcWelder.hpp"
|
||||
#include "GCodeProcessor.hpp"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
@ -47,8 +48,6 @@ static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
|
||||
// taken from PrusaResearch.ini - [printer:Original Prusa i3 MK2.5 MMU2]
|
||||
static const std::vector<std::string> DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" };
|
||||
|
||||
static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!";
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
|
||||
@ -2598,13 +2597,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
if (line.has_e()) g1_axes[E] = (double)line.e();
|
||||
std::optional<double> g1_feedrate = std::nullopt;
|
||||
if (line.has_f()) g1_feedrate = (double)line.f();
|
||||
std::optional<std::string> g1_cmt = std::nullopt;
|
||||
if (!line.comment().empty()) g1_cmt = line.comment();
|
||||
|
||||
process_G1(g1_axes, g1_feedrate, g1_cmt);
|
||||
process_G1(g1_axes, g1_feedrate);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, std::optional<std::string> cmt)
|
||||
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, G1DiscretizationOrigin origin)
|
||||
{
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
@ -2687,7 +2683,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
m_height = m_forced_height;
|
||||
else if (m_layer_id == 0)
|
||||
m_height = m_first_layer_height + m_z_offset;
|
||||
else if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) {
|
||||
else if (origin == G1DiscretizationOrigin::G1) {
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0)
|
||||
m_height = m_end_position[Z] - m_extruded_last_z;
|
||||
}
|
||||
@ -2698,7 +2694,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0))
|
||||
m_end_position[Z] = m_height;
|
||||
|
||||
if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG)
|
||||
if (origin == G1DiscretizationOrigin::G1)
|
||||
m_extruded_last_z = m_end_position[Z];
|
||||
m_options_z_corrector.update(m_height);
|
||||
|
||||
@ -2928,18 +2924,48 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
}
|
||||
|
||||
// store move
|
||||
store_move_vertex(type, cmt.has_value() && *cmt == INTERNAL_G2G3_TAG);
|
||||
store_move_vertex(type, origin == G1DiscretizationOrigin::G2G3);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise)
|
||||
{
|
||||
if (!line.has('I') || !line.has('J'))
|
||||
enum class EFitting { None, IJ, R };
|
||||
const EFitting fitting = line.has('R') ? EFitting::R : (line.has('I') && line.has('J')) ? EFitting::IJ : EFitting::None;
|
||||
|
||||
if (fitting == EFitting::None)
|
||||
return;
|
||||
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
|
||||
|
||||
AxisCoords end_position = m_start_position;
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
|
||||
}
|
||||
|
||||
// relative center
|
||||
Vec3f rel_center = Vec3f::Zero();
|
||||
if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y()))
|
||||
return;
|
||||
#ifndef _NDEBUG
|
||||
double radius = 0.0;
|
||||
#endif // _NDEBUG
|
||||
if (fitting == EFitting::R) {
|
||||
float r;
|
||||
if (!line.has_value('R', r) || r == 0.0f)
|
||||
return;
|
||||
#ifndef _NDEBUG
|
||||
radius = (double)std::abs(r);
|
||||
#endif // _NDEBUG
|
||||
const Vec2f start_pos((float)m_start_position[X], (float)m_start_position[Y]);
|
||||
const Vec2f end_pos((float)end_position[X], (float)end_position[Y]);
|
||||
const Vec2f c = Geometry::ArcWelder::arc_center(start_pos, end_pos, r, !clockwise);
|
||||
rel_center.x() = c.x() - m_start_position[X];
|
||||
rel_center.y() = c.y() - m_start_position[Y];
|
||||
}
|
||||
else {
|
||||
if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y()))
|
||||
return;
|
||||
}
|
||||
|
||||
// scale center, if needed
|
||||
if (m_units == EUnits::Inches)
|
||||
@ -2975,15 +3001,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// arc center
|
||||
arc.center = arc.start + rel_center.cast<double>();
|
||||
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
|
||||
|
||||
AxisCoords end_position = m_start_position;
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
|
||||
}
|
||||
|
||||
// arc end endpoint
|
||||
arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]);
|
||||
|
||||
@ -2992,6 +3009,8 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// what to do ???
|
||||
}
|
||||
|
||||
assert(fitting != EFitting::R || std::abs(radius - arc.start_radius()) < EPSILON);
|
||||
|
||||
// updates feedrate from line
|
||||
std::optional<float> feedrate;
|
||||
if (line.has_f())
|
||||
@ -3051,9 +3070,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
g1_feedrate = (double)*feedrate;
|
||||
if (extrusion.has_value())
|
||||
g1_axes[E] = target[E];
|
||||
std::optional<std::string> g1_cmt = INTERNAL_G2G3_TAG;
|
||||
|
||||
process_G1(g1_axes, g1_feedrate, g1_cmt);
|
||||
process_G1(g1_axes, g1_feedrate, G1DiscretizationOrigin::G2G3);
|
||||
};
|
||||
|
||||
// calculate arc segments
|
||||
@ -3062,8 +3079,13 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// https://github.com/prusa3d/Prusa-Firmware/blob/MK3/Firmware/motion_control.cpp
|
||||
|
||||
// segments count
|
||||
#if 0
|
||||
static const double MM_PER_ARC_SEGMENT = 1.0;
|
||||
const size_t segments = std::max<size_t>(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1);
|
||||
#else
|
||||
static const double gcode_arc_tolerance = 0.0125;
|
||||
const size_t segments = Geometry::ArcWelder::arc_discretization_steps(arc.start_radius(), std::abs(arc.angle), gcode_arc_tolerance);
|
||||
#endif
|
||||
|
||||
const double inv_segment = 1.0 / double(segments);
|
||||
const double theta_per_segment = arc.angle * inv_segment;
|
||||
|
@ -60,9 +60,13 @@ namespace Slic3r {
|
||||
time = 0.0f;
|
||||
travel_time = 0.0f;
|
||||
custom_gcode_times.clear();
|
||||
custom_gcode_times.shrink_to_fit();
|
||||
moves_times.clear();
|
||||
moves_times.shrink_to_fit();
|
||||
roles_times.clear();
|
||||
roles_times.shrink_to_fit();
|
||||
layers_times.clear();
|
||||
layers_times.shrink_to_fit();
|
||||
}
|
||||
};
|
||||
|
||||
@ -80,6 +84,7 @@ namespace Slic3r {
|
||||
m.reset();
|
||||
}
|
||||
volumes_per_color_change.clear();
|
||||
volumes_per_color_change.shrink_to_fit();
|
||||
volumes_per_extruder.clear();
|
||||
used_filaments_per_role.clear();
|
||||
cost_per_extruder.clear();
|
||||
@ -708,8 +713,12 @@ namespace Slic3r {
|
||||
// Move
|
||||
void process_G0(const GCodeReader::GCodeLine& line);
|
||||
void process_G1(const GCodeReader::GCodeLine& line);
|
||||
enum class G1DiscretizationOrigin {
|
||||
G1,
|
||||
G2G3,
|
||||
};
|
||||
void process_G1(const std::array<std::optional<double>, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt },
|
||||
std::optional<double> feedrate = std::nullopt, std::optional<std::string> cmt = std::nullopt);
|
||||
std::optional<double> feedrate = std::nullopt, G1DiscretizationOrigin origin = G1DiscretizationOrigin::G1);
|
||||
|
||||
// Arc Move
|
||||
void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise);
|
||||
|
@ -1,10 +1,12 @@
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
#include "../CustomGCode.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <assert.h>
|
||||
#include <string_view>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <boost/spirit/include/karma.hpp>
|
||||
@ -13,6 +15,8 @@
|
||||
#define FLAVOR_IS(val) this->config.gcode_flavor == val
|
||||
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// static
|
||||
@ -90,17 +94,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
|
||||
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
|
||||
return {};
|
||||
|
||||
std::string code, comment;
|
||||
std::string_view code, comment;
|
||||
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
|
||||
code = "M109";
|
||||
comment = "set temperature and wait for it to be reached";
|
||||
code = "M109"sv;
|
||||
comment = "set temperature and wait for it to be reached"sv;
|
||||
} else {
|
||||
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
|
||||
code = "G10";
|
||||
code = "G10"sv;
|
||||
} else {
|
||||
code = "M104";
|
||||
code = "M104"sv;
|
||||
}
|
||||
comment = "set temperature";
|
||||
comment = "set temperature"sv;
|
||||
}
|
||||
|
||||
std::ostringstream gcode;
|
||||
@ -130,22 +134,22 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
|
||||
std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
|
||||
{
|
||||
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached))
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
m_last_bed_temperature = temperature;
|
||||
m_last_bed_temperature_reached = wait;
|
||||
|
||||
std::string code, comment;
|
||||
std::string_view code, comment;
|
||||
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
|
||||
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
|
||||
code = "M109";
|
||||
code = "M109"sv;
|
||||
} else {
|
||||
code = "M190";
|
||||
code = "M190"sv;
|
||||
}
|
||||
comment = "set bed temperature and wait for it to be reached";
|
||||
comment = "set bed temperature and wait for it to be reached"sv;
|
||||
} else {
|
||||
code = "M140";
|
||||
comment = "set bed temperature";
|
||||
code = "M140"sv;
|
||||
comment = "set bed temperature"sv;
|
||||
}
|
||||
|
||||
std::ostringstream gcode;
|
||||
@ -176,7 +180,7 @@ std::string GCodeWriter::set_acceleration_internal(Acceleration type, unsigned i
|
||||
|
||||
auto& last_value = separate_travel ? m_last_travel_acceleration : m_last_acceleration ;
|
||||
if (acceleration == 0 || acceleration == last_value)
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
last_value = acceleration;
|
||||
|
||||
@ -245,7 +249,7 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id)
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const
|
||||
std::string GCodeWriter::set_speed(double F, const std::string_view comment, const std::string_view cooling_marker) const
|
||||
{
|
||||
assert(F > 0.);
|
||||
assert(F < 100000.);
|
||||
@ -257,10 +261,9 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
|
||||
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
|
||||
{
|
||||
m_pos.x() = point.x();
|
||||
m_pos.y() = point.y();
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xy(point);
|
||||
@ -269,7 +272,40 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment)
|
||||
std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) < 1200.);
|
||||
assert(std::abs(ij.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_ij(ij);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(radius) >= 0.001);
|
||||
assert(std::abs(radius) < 1800.);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_radius(radius);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
|
||||
{
|
||||
// FIXME: This function was not being used when travel_speed_z was separated (bd6badf).
|
||||
// Calculation of feedrate was not updated accordingly. If you want to use
|
||||
@ -302,7 +338,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
|
||||
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
|
||||
{
|
||||
/* If target Z is lower than current Z but higher than nominal Z
|
||||
we don't perform the move but we only adjust the nominal Z by
|
||||
@ -321,7 +357,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
|
||||
return this->_travel_to_z(z, comment);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
|
||||
std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment)
|
||||
{
|
||||
m_pos.z() = z;
|
||||
|
||||
@ -348,10 +384,12 @@ bool GCodeWriter::will_move_z(double z) const
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
|
||||
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment)
|
||||
{
|
||||
m_pos.x() = point.x();
|
||||
m_pos.y() = point.y();
|
||||
assert(dE != 0);
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xy(point);
|
||||
@ -360,8 +398,47 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
assert(dE != 0);
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) < 1200.);
|
||||
assert(std::abs(ij.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_ij(ij);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment)
|
||||
{
|
||||
assert(dE != 0);
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(radius) >= 0.001);
|
||||
assert(std::abs(radius) < 1800.);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_radius(radius);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
#if 0
|
||||
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
|
||||
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment)
|
||||
{
|
||||
m_pos = point;
|
||||
m_lifted = 0;
|
||||
@ -397,8 +474,11 @@ std::string GCodeWriter::retract_for_toolchange(bool before_wipe)
|
||||
);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment)
|
||||
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(length) < 1000.0);
|
||||
assert(std::abs(restart_extra) < 1000.0);
|
||||
|
||||
/* If firmware retraction is enabled, we use a fake value of 1
|
||||
since we ignore the actual configured retract_length which
|
||||
might be 0, in which case the retraction logic gets skipped. */
|
@ -1,13 +1,15 @@
|
||||
#ifndef slic3r_GCodeWriter_hpp_
|
||||
#define slic3r_GCodeWriter_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "../libslic3r.h"
|
||||
#include "../Extruder.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "CoolingBuffer.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <charconv>
|
||||
#include "Extruder.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "GCode/CoolingBuffer.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -56,13 +58,17 @@ public:
|
||||
// printed with the same extruder.
|
||||
std::string toolchange_prefix() const;
|
||||
std::string toolchange(unsigned int extruder_id);
|
||||
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
|
||||
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
|
||||
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
|
||||
std::string travel_to_z(double z, const std::string &comment = std::string());
|
||||
std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const;
|
||||
std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
|
||||
std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {});
|
||||
std::string travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment = {});
|
||||
std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {});
|
||||
std::string travel_to_z(double z, const std::string_view comment = {});
|
||||
bool will_move_z(double z) const;
|
||||
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
|
||||
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
|
||||
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {});
|
||||
std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment);
|
||||
std::string extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment);
|
||||
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {});
|
||||
std::string retract(bool before_wipe = false);
|
||||
std::string retract_for_toolchange(bool before_wipe = false);
|
||||
std::string unretract();
|
||||
@ -113,8 +119,8 @@ private:
|
||||
Print
|
||||
};
|
||||
|
||||
std::string _travel_to_z(double z, const std::string &comment);
|
||||
std::string _retract(double length, double restart_extra, const std::string &comment);
|
||||
std::string _travel_to_z(double z, const std::string_view comment);
|
||||
std::string _retract(double length, double restart_extra, const std::string_view comment);
|
||||
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
|
||||
};
|
||||
|
||||
@ -152,6 +158,10 @@ public:
|
||||
static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
|
||||
static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
|
||||
static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); }
|
||||
static Vec2d quantize(const Vec2d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec3d quantize(const Vec3d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
|
||||
|
||||
void emit_axis(const char axis, const double v, size_t digits);
|
||||
|
||||
@ -170,7 +180,20 @@ public:
|
||||
this->emit_axis('Z', z, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_e(const std::string &axis, double v) {
|
||||
void emit_ij(const Vec2d &point) {
|
||||
if (point.x() != 0)
|
||||
this->emit_axis('I', point.x(), XYZF_EXPORT_DIGITS);
|
||||
if (point.y() != 0)
|
||||
this->emit_axis('J', point.y(), XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
// Positive radius means a smaller arc,
|
||||
// negative radius means a larger arc.
|
||||
void emit_radius(const double radius) {
|
||||
this->emit_axis('R', radius, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_e(const std::string_view axis, double v) {
|
||||
if (! axis.empty()) {
|
||||
// not gcfNoExtrusion
|
||||
this->emit_axis(axis[0], v, E_EXPORT_DIGITS);
|
||||
@ -181,12 +204,12 @@ public:
|
||||
this->emit_axis('F', speed, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_string(const std::string &s) {
|
||||
strncpy(ptr_err.ptr, s.c_str(), s.size());
|
||||
void emit_string(const std::string_view s) {
|
||||
strncpy(ptr_err.ptr, s.data(), s.size());
|
||||
ptr_err.ptr += s.size();
|
||||
}
|
||||
|
||||
void emit_comment(bool allow_comments, const std::string &comment) {
|
||||
void emit_comment(bool allow_comments, const std::string_view comment) {
|
||||
if (allow_comments && ! comment.empty()) {
|
||||
*ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' ';
|
||||
this->emit_string(comment);
|
||||
@ -210,14 +233,25 @@ public:
|
||||
GCodeG1Formatter() {
|
||||
this->buf[0] = 'G';
|
||||
this->buf[1] = '1';
|
||||
this->buf_end = buf + buflen;
|
||||
this->ptr_err.ptr = this->buf + 2;
|
||||
this->ptr_err.ptr += 2;
|
||||
}
|
||||
|
||||
GCodeG1Formatter(const GCodeG1Formatter&) = delete;
|
||||
GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete;
|
||||
};
|
||||
|
||||
class GCodeG2G3Formatter : public GCodeFormatter {
|
||||
public:
|
||||
GCodeG2G3Formatter(bool ccw) {
|
||||
this->buf[0] = 'G';
|
||||
this->buf[1] = ccw ? '3' : '2';
|
||||
this->ptr_err.ptr += 2;
|
||||
}
|
||||
|
||||
GCodeG2G3Formatter(const GCodeG2G3Formatter&) = delete;
|
||||
GCodeG2G3Formatter& operator=(const GCodeG2G3Formatter&) = delete;
|
||||
};
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
||||
#endif /* slic3r_GCodeWriter_hpp_ */
|
@ -30,7 +30,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
|
||||
{
|
||||
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
|
||||
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width())));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
@ -44,7 +44,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width()))));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
@ -58,7 +58,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width()))));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
|
@ -1484,7 +1484,7 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
|
||||
}
|
||||
}
|
||||
|
||||
void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first,
|
||||
Point SeamPlacer::place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first,
|
||||
const Point &last_pos) const {
|
||||
using namespace SeamPlacerImpl;
|
||||
const PrintObject *po = layer->object();
|
||||
@ -1587,7 +1587,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
|
||||
//lastly, for internal perimeters, do the staggering if requested
|
||||
if (po->config().staggered_inner_seams && loop.length() > 0.0) {
|
||||
//fix depth, it is sometimes strongly underestimated
|
||||
depth = std::max(loop.paths[projected_point.path_idx].width, depth);
|
||||
depth = std::max(loop.paths[projected_point.path_idx].width(), depth);
|
||||
|
||||
while (depth > 0.0f) {
|
||||
auto next_point = get_next_loop_point(projected_point);
|
||||
@ -1605,14 +1605,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
|
||||
}
|
||||
}
|
||||
|
||||
// Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
|
||||
// thus empty path segments will not be produced by G-code export.
|
||||
if (!loop.split_at_vertex(seam_point, scaled<double>(0.0015))) {
|
||||
// The point is not in the original loop.
|
||||
// Insert it.
|
||||
loop.split_at(seam_point, true);
|
||||
}
|
||||
|
||||
return seam_point;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -141,7 +141,7 @@ public:
|
||||
|
||||
void init(const Print &print, std::function<void(void)> throw_if_canceled_func);
|
||||
|
||||
void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
|
||||
Point place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
|
||||
|
||||
private:
|
||||
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info);
|
||||
|
257
src/libslic3r/GCode/SmoothPath.cpp
Normal file
257
src/libslic3r/GCode/SmoothPath.cpp
Normal file
@ -0,0 +1,257 @@
|
||||
#include "SmoothPath.hpp"
|
||||
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
// Length of a smooth path.
|
||||
double length(const SmoothPath &path)
|
||||
{
|
||||
double l = 0;
|
||||
for (const SmoothPathElement &el : path)
|
||||
l += Geometry::ArcWelder::path_length<double>(el.path);
|
||||
return l;
|
||||
}
|
||||
|
||||
// Returns true if the smooth path is longer than a threshold.
|
||||
bool longer_than(const SmoothPath &path, double length)
|
||||
{
|
||||
for (const SmoothPathElement &el : path) {
|
||||
for (auto it = std::next(el.path.begin()); it != el.path.end(); ++ it) {
|
||||
length -= Geometry::ArcWelder::segment_length<double>(*std::prev(it), *it);
|
||||
if (length < 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return length < 0;
|
||||
}
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance)
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement &el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++ it; it != end; ++ it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
distance -= sqrt(lsqr);
|
||||
} else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(- angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failed.
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance)
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement& el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++it; it != end; ++it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
distance -= sqrt(lsqr);
|
||||
}
|
||||
else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(-angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failed.
|
||||
return {};
|
||||
}
|
||||
|
||||
double clip_end(SmoothPath &path, double distance)
|
||||
{
|
||||
while (! path.empty() && distance > 0) {
|
||||
Geometry::ArcWelder::Path &p = path.back().path;
|
||||
distance = clip_end(p, distance);
|
||||
if (p.empty()) {
|
||||
path.pop_back();
|
||||
} else {
|
||||
// Trailing path was trimmed and it is valid.
|
||||
assert(path.back().path.size() > 1);
|
||||
assert(distance == 0);
|
||||
// Distance to go is zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Return distance to go after the whole smooth path was trimmed to zero.
|
||||
return distance;
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
double tolerance = params.tolerance;
|
||||
if (path.role().is_sparse_infill())
|
||||
// Use 3x lower resolution than the object fine detail for sparse infill.
|
||||
tolerance *= 3.;
|
||||
else if (path.role().is_support())
|
||||
// Use 4x lower resolution than the object fine detail for support.
|
||||
tolerance *= 4.;
|
||||
else if (path.role().is_skirt())
|
||||
// Brim is currently marked as skirt.
|
||||
// Use 4x lower resolution than the object fine detail for skirt & brim.
|
||||
tolerance *= 4.;
|
||||
m_cache[&path.polyline] = Slic3r::Geometry::ArcWelder::fit_path(path.polyline.points, tolerance, params.fit_circle_tolerance);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : multi_path.paths)
|
||||
this->interpolate_add(path, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : loop.paths)
|
||||
this->interpolate_add(path, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionEntity *ee : eec) {
|
||||
if (ee->is_collection())
|
||||
this->interpolate_add(*static_cast<const ExtrusionEntityCollection*>(ee), params);
|
||||
else if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ee); path)
|
||||
this->interpolate_add(*path, params);
|
||||
else if (const ExtrusionMultiPath *multi_path = dynamic_cast<const ExtrusionMultiPath*>(ee); multi_path)
|
||||
this->interpolate_add(*multi_path, params);
|
||||
else if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop*>(ee); loop)
|
||||
this->interpolate_add(*loop, params);
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const Polyline *pl) const
|
||||
{
|
||||
auto it = m_cache.find(pl);
|
||||
return it == m_cache.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const ExtrusionPath &path) const
|
||||
{
|
||||
return this->resolve(&path.polyline);
|
||||
}
|
||||
|
||||
Geometry::ArcWelder::Path SmoothPathCache::resolve_or_fit(const ExtrusionPath &path, bool reverse, double tolerance) const
|
||||
{
|
||||
Geometry::ArcWelder::Path out;
|
||||
if (const Geometry::ArcWelder::Path *cached = this->resolve(path); cached)
|
||||
out = *cached;
|
||||
else
|
||||
out = Geometry::ArcWelder::fit_polyline(path.polyline.points, tolerance);
|
||||
if (reverse)
|
||||
Geometry::ArcWelder::reverse(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const
|
||||
{
|
||||
SmoothPath out;
|
||||
out.reserve(paths.size());
|
||||
if (reverse) {
|
||||
for (auto it = paths.crbegin(); it != paths.crend(); ++ it)
|
||||
out.push_back({ it->attributes(), this->resolve_or_fit(*it, true, resolution) });
|
||||
} else {
|
||||
for (auto it = paths.cbegin(); it != paths.cend(); ++ it)
|
||||
out.push_back({ it->attributes(), this->resolve_or_fit(*it, false, resolution) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionMultiPath &multipath, bool reverse, double resolution) const
|
||||
{
|
||||
return this->resolve_or_fit(multipath.paths, reverse, resolution);
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam(
|
||||
const ExtrusionLoop &loop, const bool reverse, const double resolution,
|
||||
const Point &seam_point, const double seam_point_merge_distance_threshold) const
|
||||
{
|
||||
SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution);
|
||||
assert(! out.empty());
|
||||
if (! out.empty()) {
|
||||
// Find a closest point on a vector of smooth paths.
|
||||
Geometry::ArcWelder::PathSegmentProjection proj;
|
||||
int proj_path = -1;
|
||||
for (const SmoothPathElement &el : out)
|
||||
if (Geometry::ArcWelder::PathSegmentProjection this_proj = Geometry::ArcWelder::point_to_path_projection(el.path, seam_point, proj.distance2);
|
||||
this_proj.valid()) {
|
||||
// Found a better (closer) projection.
|
||||
assert(this_proj.distance2 < proj.distance2);
|
||||
assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size());
|
||||
proj = this_proj;
|
||||
proj_path = &el - out.data();
|
||||
if (proj.distance2 == 0)
|
||||
// There will be no better split point found than one with zero distance.
|
||||
break;
|
||||
}
|
||||
assert(proj_path >= 0);
|
||||
// Split the path at the closest point.
|
||||
Geometry::ArcWelder::Path &path = out[proj_path].path;
|
||||
std::pair<Geometry::ArcWelder::Path, Geometry::ArcWelder::Path> split = Geometry::ArcWelder::split_at(
|
||||
path, proj, seam_point_merge_distance_threshold);
|
||||
if (split.second.empty()) {
|
||||
std::rotate(out.begin(), out.begin() + proj_path + 1, out.end());
|
||||
assert(out.back().path == split.first);
|
||||
} else {
|
||||
ExtrusionAttributes attr = out[proj_path].path_attributes;
|
||||
std::rotate(out.begin(), out.begin() + proj_path, out.end());
|
||||
out.front().path = std::move(split.second);
|
||||
if (! split.first.empty()) {
|
||||
if (out.back().path_attributes == attr) {
|
||||
// Merge with the last segment.
|
||||
out.back().path.insert(out.back().path.end(), std::next(split.first.begin()), split.first.end());
|
||||
} else
|
||||
out.push_back({ attr, std::move(split.first) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
87
src/libslic3r/GCode/SmoothPath.hpp
Normal file
87
src/libslic3r/GCode/SmoothPath.hpp
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef slic3r_GCode_SmoothPath_hpp_
|
||||
#define slic3r_GCode_SmoothPath_hpp_
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../Geometry/ArcWelder.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExtrusionEntityCollection;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
struct SmoothPathElement
|
||||
{
|
||||
ExtrusionAttributes path_attributes;
|
||||
Geometry::ArcWelder::Path path;
|
||||
};
|
||||
|
||||
using SmoothPath = std::vector<SmoothPathElement>;
|
||||
|
||||
// Length of a smooth path.
|
||||
double length(const SmoothPath &path);
|
||||
// Returns true if the smooth path is longer than a threshold.
|
||||
bool longer_than(const SmoothPath &path, const double length);
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance);
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance);
|
||||
|
||||
// Clip end of a smooth path, for seam hiding.
|
||||
double clip_end(SmoothPath &path, double distance);
|
||||
|
||||
class SmoothPathCache
|
||||
{
|
||||
public:
|
||||
struct InterpolationParameters {
|
||||
double tolerance;
|
||||
double fit_circle_tolerance;
|
||||
};
|
||||
|
||||
void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms);
|
||||
|
||||
const Geometry::ArcWelder::Path* resolve(const Polyline *pl) const;
|
||||
const Geometry::ArcWelder::Path* resolve(const ExtrusionPath &path) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
Geometry::ArcWelder::Path resolve_or_fit(const ExtrusionPath &path, bool reverse, double resolution) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
SmoothPath resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const;
|
||||
SmoothPath resolve_or_fit(const ExtrusionMultiPath &path, bool reverse, double resolution) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
SmoothPath resolve_or_fit_split_with_seam(
|
||||
const ExtrusionLoop &path, const bool reverse, const double resolution,
|
||||
const Point &seam_point, const double seam_point_merge_distance_threshold) const;
|
||||
|
||||
private:
|
||||
ankerl::unordered_dense::map<const Polyline*, Geometry::ArcWelder::Path> m_cache;
|
||||
};
|
||||
|
||||
// Encapsulates references to global and layer local caches of smooth extrusion paths.
|
||||
class SmoothPathCaches final
|
||||
{
|
||||
public:
|
||||
SmoothPathCaches() = delete;
|
||||
SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) :
|
||||
m_global(&global), m_layer_local(&layer_local) {}
|
||||
SmoothPathCaches operator=(const SmoothPathCaches &rhs)
|
||||
{ m_global = rhs.m_global; m_layer_local = rhs.m_layer_local; return *this; }
|
||||
|
||||
const SmoothPathCache& global() const { return *m_global; }
|
||||
const SmoothPathCache& layer_local() const { return *m_layer_local; }
|
||||
|
||||
private:
|
||||
const SmoothPathCache *m_global;
|
||||
const SmoothPathCache *m_layer_local;
|
||||
};
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_SmoothPath_hpp_
|
252
src/libslic3r/GCode/Wipe.cpp
Normal file
252
src/libslic3r/GCode/Wipe.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
#include "Wipe.hpp"
|
||||
#include "../GCode.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extruders)
|
||||
{
|
||||
this->reset_path();
|
||||
|
||||
// Calculate maximum wipe length to accumulate by the wipe cache.
|
||||
// Paths longer than wipe_xy should never be needed for the wipe move.
|
||||
double wipe_xy = 0;
|
||||
const bool multimaterial = extruders.size() > 1;
|
||||
for (auto id : extruders)
|
||||
if (config.wipe.get_at(id)) {
|
||||
// Wipe length to extrusion ratio.
|
||||
const double xy_to_e = this->calc_xy_to_e_ratio(config, id);
|
||||
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length.get_at(id));
|
||||
if (multimaterial)
|
||||
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length_toolchange.get_at(id));
|
||||
}
|
||||
|
||||
if (wipe_xy == 0)
|
||||
this->disable();
|
||||
else
|
||||
this->enable(wipe_xy);
|
||||
}
|
||||
|
||||
void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
{
|
||||
this->reset_path();
|
||||
|
||||
if (this->enabled() && ! path.empty()) {
|
||||
if (reversed) {
|
||||
m_path = std::move(path.back().path);
|
||||
Geometry::ArcWelder::reverse(m_path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.rbegin()); len < m_wipe_len_max && it != path.rend(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.back().point);
|
||||
if (m_path.back().point != it->path.back().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend());
|
||||
}
|
||||
} else {
|
||||
m_path = std::move(path.front().path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.begin()); len < m_wipe_len_max && it != path.end(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.front().point);
|
||||
if (m_path.back().point != it->path.front().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_path.empty() || m_path.size() > 1);
|
||||
}
|
||||
|
||||
std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
|
||||
{
|
||||
std::string gcode;
|
||||
const Extruder &extruder = *gcodegen.writer().extruder();
|
||||
static constexpr const std::string_view wipe_retract_comment = "wipe and retract"sv;
|
||||
|
||||
// Remaining quantized retraction length.
|
||||
if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length());
|
||||
retract_length > 0 && this->has_path()) {
|
||||
// Delayed emitting of a wipe start tag.
|
||||
bool wiped = false;
|
||||
const double wipe_speed = this->calc_wipe_speed(gcodegen.writer().config);
|
||||
auto start_wipe = [&wiped, &gcode, &gcodegen, wipe_speed](){
|
||||
if (! wiped) {
|
||||
wiped = true;
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n";
|
||||
gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE"sv : ""sv);
|
||||
}
|
||||
};
|
||||
const double xy_to_e = this->calc_xy_to_e_ratio(gcodegen.writer().config, extruder.id());
|
||||
auto wipe_linear = [&gcode, &gcodegen, &retract_length, xy_to_e](const Vec2d &prev_quantized, Vec2d &p) {
|
||||
Vec2d p_quantized = GCodeFormatter::quantize(p);
|
||||
if (p_quantized == prev_quantized) {
|
||||
p = p_quantized;
|
||||
return false;
|
||||
}
|
||||
double segment_length = (p_quantized - prev_quantized).norm();
|
||||
// Quantize E axis as it is to be extruded as a whole segment.
|
||||
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
|
||||
bool done = false;
|
||||
if (dE > retract_length - EPSILON) {
|
||||
if (dE > retract_length + EPSILON)
|
||||
// Shorten the segment.
|
||||
p = GCodeFormatter::quantize(Vec2d(prev_quantized + (p - prev_quantized) * (retract_length / dE)));
|
||||
else
|
||||
p = p_quantized;
|
||||
dE = retract_length;
|
||||
done = true;
|
||||
} else
|
||||
p = p_quantized;
|
||||
gcode += gcodegen.writer().extrude_to_xy(p, -dE, wipe_retract_comment);
|
||||
retract_length -= dE;
|
||||
return done;
|
||||
};
|
||||
const bool emit_radius = gcodegen.config().arc_fitting == ArcFittingType::EmitRadius;
|
||||
auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e, emit_radius](
|
||||
const Vec2d &prev_quantized, Vec2d &p, double radius_in, const bool ccw) {
|
||||
Vec2d p_quantized = GCodeFormatter::quantize(p);
|
||||
if (p_quantized == prev_quantized) {
|
||||
p = p_quantized;
|
||||
return false;
|
||||
}
|
||||
// Only quantize radius if emitting it directly into G-code. Otherwise use the exact radius for calculating the IJ values.
|
||||
double radius = emit_radius ? GCodeFormatter::quantize_xyzf(radius_in) : radius_in;
|
||||
Vec2d center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius), ccw);
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius));
|
||||
assert(angle > 0);
|
||||
double segment_length = angle * std::abs(radius);
|
||||
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
|
||||
bool done = false;
|
||||
if (dE > retract_length - EPSILON) {
|
||||
if (dE > retract_length + EPSILON) {
|
||||
// Shorten the segment. Recalculate the arc from the unquantized end coordinate.
|
||||
center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p.cast<double>(), double(radius), ccw);
|
||||
angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p.cast<double>(), double(radius));
|
||||
segment_length = angle * std::abs(radius);
|
||||
dE = xy_to_e * segment_length;
|
||||
p = GCodeFormatter::quantize(
|
||||
Vec2d(center + Eigen::Rotation2D((ccw ? angle : -angle) * (retract_length / dE)) * (prev_quantized - center)));
|
||||
} else
|
||||
p = p_quantized;
|
||||
dE = retract_length;
|
||||
done = true;
|
||||
} else
|
||||
p = p_quantized;
|
||||
assert(dE > 0);
|
||||
if (emit_radius) {
|
||||
gcode += gcodegen.writer().extrude_to_xy_G2G3R(p, radius, ccw, -dE, wipe_retract_comment);
|
||||
} else {
|
||||
// Calculate quantized IJ circle center offset.
|
||||
gcode += gcodegen.writer().extrude_to_xy_G2G3IJ(
|
||||
p, GCodeFormatter::quantize(Vec2d(center - prev_quantized)), ccw, -dE, wipe_retract_comment);
|
||||
}
|
||||
retract_length -= dE;
|
||||
return done;
|
||||
};
|
||||
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
|
||||
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
|
||||
auto it = this->path().begin();
|
||||
Vec2d p = gcodegen.point_to_gcode(it->point + m_offset);
|
||||
++ it;
|
||||
bool done = false;
|
||||
if (p != prev) {
|
||||
start_wipe();
|
||||
done = wipe_linear(prev, p);
|
||||
}
|
||||
if (! done) {
|
||||
prev = p;
|
||||
auto end = this->path().end();
|
||||
for (; it != end && ! done; ++ it) {
|
||||
p = gcodegen.point_to_gcode(it->point + m_offset);
|
||||
if (p != prev) {
|
||||
start_wipe();
|
||||
if (it->linear() ?
|
||||
wipe_linear(prev, p) :
|
||||
wipe_arc(prev, p, unscaled<double>(it->radius), it->ccw()))
|
||||
break;
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wiped) {
|
||||
// add tag for processor
|
||||
assert(p == GCodeFormatter::quantize(p));
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
|
||||
gcodegen.set_last_pos(gcodegen.gcode_to_point(p));
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent wiping again on the same path.
|
||||
this->reset_path();
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Make a little move inwards before leaving loop after path was extruded,
|
||||
// thus the current extruder position is at the end of a path and the path
|
||||
// may not be closed in case the loop was clipped to hide a seam.
|
||||
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length)
|
||||
{
|
||||
assert(! path.empty());
|
||||
assert(path.front().path.size() >= 2);
|
||||
assert(path.back().path.size() >= 2);
|
||||
|
||||
// Heuristics for estimating whether there is a chance that the wipe move will fit inside a small perimeter
|
||||
// or that the wipe move direction could be calculated with reasonable accuracy.
|
||||
if (longer_than(path, 2.5 * wipe_length)) {
|
||||
// The print head will be moved away from path end inside the island.
|
||||
Point p_current = path.back().path.back().point;
|
||||
Point p_next = path.front().path.front().point;
|
||||
Point p_prev;
|
||||
{
|
||||
// Is the seam hiding gap large enough already?
|
||||
double l = wipe_length - (p_next - p_current).cast<double>().norm();
|
||||
if (l > 0) {
|
||||
// Not yet.
|
||||
std::optional<Point> n = sample_path_point_at_distance_from_start(path, l);
|
||||
assert(n);
|
||||
if (! n)
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
return {};
|
||||
}
|
||||
if (std::optional<Point> p = sample_path_point_at_distance_from_end(path, wipe_length); p)
|
||||
p_prev = *p;
|
||||
else
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
return {};
|
||||
}
|
||||
// Detect angle between last and first segment.
|
||||
// The side depends on the original winding order of the polygon (left for contours, right for holes).
|
||||
double angle_inside = angle(p_next - p_current, p_prev - p_current);
|
||||
assert(angle_inside >= -M_PI && angle_inside <= M_PI);
|
||||
// 3rd of this angle will be taken, thus make the angle monotonic before interpolation.
|
||||
if (is_hole) {
|
||||
if (angle_inside > 0)
|
||||
angle_inside -= 2.0 * M_PI;
|
||||
} else {
|
||||
if (angle_inside < 0)
|
||||
angle_inside += 2.0 * M_PI;
|
||||
}
|
||||
// Rotate the forward segment inside by 1/3 of the wedge angle.
|
||||
auto v_rotated = Eigen::Rotation2D(angle_inside) * (p_next - p_current).cast<double>().normalized();
|
||||
return std::make_optional<Point>(p_current + (v_rotated * wipe_length).cast<coord_t>());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
73
src/libslic3r/GCode/Wipe.hpp
Normal file
73
src/libslic3r/GCode/Wipe.hpp
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef slic3r_GCode_Wipe_hpp_
|
||||
#define slic3r_GCode_Wipe_hpp_
|
||||
|
||||
#include "SmoothPath.hpp"
|
||||
|
||||
#include "../Geometry/ArcWelder.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeGenerator;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
class Wipe {
|
||||
public:
|
||||
using Path = Slic3r::Geometry::ArcWelder::Path;
|
||||
|
||||
Wipe() = default;
|
||||
|
||||
void init(const PrintConfig &config, const std::vector<unsigned int> &extruders);
|
||||
void enable(double wipe_len_max) { m_enabled = true; m_wipe_len_max = wipe_len_max; }
|
||||
void disable() { m_enabled = false; }
|
||||
bool enabled() const { return m_enabled; }
|
||||
|
||||
const Path& path() const { return m_path; }
|
||||
bool has_path() const { assert(m_path.empty() || m_path.size() > 1); return ! m_path.empty(); }
|
||||
void reset_path() { m_path.clear(); m_offset = Point::Zero(); }
|
||||
void set_path(const Path &path) {
|
||||
assert(path.empty() || path.size() > 1);
|
||||
this->reset_path();
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = path;
|
||||
}
|
||||
void set_path(Path &&path) {
|
||||
assert(path.empty() || path.size() > 1);
|
||||
this->reset_path();
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = std::move(path);
|
||||
}
|
||||
void set_path(SmoothPath &&path, bool reversed);
|
||||
void offset_path(const Point &v) { m_offset += v; }
|
||||
|
||||
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
|
||||
|
||||
// Reduce feedrate a bit; travel speed is often too high to move on existing material.
|
||||
// Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
|
||||
static double calc_wipe_speed(const GCodeConfig &config) { return config.travel_speed.value * 0.8; }
|
||||
// Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
|
||||
// due to rounding (TODO: test and/or better math for this).
|
||||
static double calc_xy_to_e_ratio(const GCodeConfig &config, unsigned int extruder_id)
|
||||
{ return 0.95 * floor(config.retract_speed.get_at(extruder_id) + 0.5) / calc_wipe_speed(config); }
|
||||
|
||||
private:
|
||||
bool m_enabled{ false };
|
||||
// Maximum length of a path to accumulate. Only wipes shorter than this threshold will be requested.
|
||||
double m_wipe_len_max{ 0. };
|
||||
Path m_path;
|
||||
// Offset from m_path to the current PrintObject active.
|
||||
Point m_offset{ Point::Zero() };
|
||||
};
|
||||
|
||||
// Make a little move inwards before leaving loop.
|
||||
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length);
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_Wipe_hpp_
|
@ -1,5 +1,5 @@
|
||||
#ifndef WipeTower_
|
||||
#define WipeTower_
|
||||
#ifndef slic3r_GCode_WipeTower_hpp_
|
||||
#define slic3r_GCode_WipeTower_hpp_
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
@ -411,4 +411,4 @@ private:
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
||||
#endif // slic3r_GCode_WipeTower_hpp_
|
||||
|
251
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal file
251
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
#include "WipeTowerIntegration.hpp"
|
||||
|
||||
#include "../GCode.hpp"
|
||||
#include "../libslic3r.h"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
static inline Point wipe_tower_point_to_object_point(GCodeGenerator &gcodegen, const Vec2f& wipe_tower_pt)
|
||||
{
|
||||
return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
|
||||
{
|
||||
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
|
||||
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
|
||||
|
||||
std::string gcode;
|
||||
|
||||
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
|
||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
|
||||
auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
|
||||
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
|
||||
out += m_wipe_tower_pos;
|
||||
return out;
|
||||
};
|
||||
|
||||
Vec2f start_pos = tcr.start_pos;
|
||||
Vec2f end_pos = tcr.end_pos;
|
||||
if (! tcr.priming) {
|
||||
start_pos = transform_wt_pt(start_pos);
|
||||
end_pos = transform_wt_pt(end_pos);
|
||||
}
|
||||
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||
|
||||
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
|
||||
|
||||
gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't).
|
||||
|
||||
double current_z = gcodegen.writer().get_position().z();
|
||||
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
||||
z = current_z;
|
||||
|
||||
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
|
||||
const bool will_go_down = ! is_approx(z, current_z);
|
||||
const bool is_ramming = (gcodegen.config().single_extruder_multi_material && ! tcr.priming)
|
||||
|| (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool));
|
||||
const bool should_travel_to_tower = tcr.force_travel // wipe tower says so
|
||||
|| ! needs_toolchange // this is just finishing the tower with no toolchange
|
||||
|| is_ramming;
|
||||
if (should_travel_to_tower) {
|
||||
gcode += gcodegen.retract();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(
|
||||
wipe_tower_point_to_object_point(gcodegen, start_pos),
|
||||
ExtrusionRole::Mixed,
|
||||
"Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
// the tool without travelling to the tower.
|
||||
}
|
||||
|
||||
if (will_go_down) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
std::string toolchange_gcode_str;
|
||||
std::string deretraction_str;
|
||||
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
|
||||
if (is_ramming)
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower)
|
||||
deretraction_str = gcodegen.unretract();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Insert the toolchange and deretraction gcode into the generated gcode.
|
||||
DynamicConfig config;
|
||||
config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
|
||||
config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
|
||||
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
|
||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
if (! toolchange_gcode_str.empty() && toolchange_gcode_str.back() != '\n')
|
||||
toolchange_gcode_str += '\n';
|
||||
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
if (!is_approx(z, current_z)) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
else {
|
||||
// Prepare a future wipe.
|
||||
// Convert to a smooth path.
|
||||
Geometry::ArcWelder::Path path;
|
||||
path.reserve(tcr.wipe_path.size());
|
||||
std::transform(tcr.wipe_path.begin(), tcr.wipe_path.end(), std::back_inserter(path),
|
||||
[&gcodegen, &transform_wt_pt](const Vec2f &wipe_pt) {
|
||||
return Geometry::ArcWelder::Segment{ wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)) };
|
||||
});
|
||||
// Pass to the wipe cache.
|
||||
gcodegen.m_wipe.set_path(std::move(path));
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
|
||||
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
|
||||
std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
|
||||
{
|
||||
Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
|
||||
|
||||
std::istringstream gcode_str(tcr.gcode);
|
||||
std::string gcode_out;
|
||||
std::string line;
|
||||
Vec2f pos = tcr.start_pos;
|
||||
Vec2f transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
|
||||
Vec2f old_pos(-1000.1f, -1000.1f);
|
||||
|
||||
while (gcode_str) {
|
||||
std::getline(gcode_str, line); // we read the gcode line by line
|
||||
|
||||
// All G1 commands should be translated and rotated. X and Y coords are
|
||||
// only pushed to the output when they differ from last time.
|
||||
// WT generator can override this by appending the never_skip_tag
|
||||
if (boost::starts_with(line, "G1 ")) {
|
||||
bool never_skip = false;
|
||||
auto it = line.find(WipeTower::never_skip_tag());
|
||||
if (it != std::string::npos) {
|
||||
// remove the tag and remember we saw it
|
||||
never_skip = true;
|
||||
line.erase(it, it + WipeTower::never_skip_tag().size());
|
||||
}
|
||||
std::ostringstream line_out;
|
||||
std::istringstream line_str(line);
|
||||
line_str >> std::noskipws; // don't skip whitespace
|
||||
char ch = 0;
|
||||
line_str >> ch >> ch; // read the "G1"
|
||||
while (line_str >> ch) {
|
||||
if (ch == 'X' || ch == 'Y')
|
||||
line_str >> (ch == 'X' ? pos.x() : pos.y());
|
||||
else
|
||||
line_out << ch;
|
||||
}
|
||||
|
||||
transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
|
||||
|
||||
if (transformed_pos != old_pos || never_skip) {
|
||||
line = line_out.str();
|
||||
boost::trim_left(line); // Remove leading spaces
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3) << "G1";
|
||||
if (transformed_pos.x() != old_pos.x() || never_skip)
|
||||
oss << " X" << transformed_pos.x() - extruder_offset.x();
|
||||
if (transformed_pos.y() != old_pos.y() || never_skip)
|
||||
oss << " Y" << transformed_pos.y() - extruder_offset.y();
|
||||
if (! line.empty())
|
||||
oss << " ";
|
||||
line = oss.str() + line;
|
||||
old_pos = transformed_pos;
|
||||
}
|
||||
}
|
||||
|
||||
gcode_out += line + "\n";
|
||||
|
||||
// If this was a toolchange command, we should change current extruder offset
|
||||
if (line == "[toolchange_gcode]") {
|
||||
extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
|
||||
|
||||
// If the extruder offset changed, add an extra move so everything is continuous
|
||||
if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3)
|
||||
<< "G1 X" << transformed_pos.x() - extruder_offset.x()
|
||||
<< " Y" << transformed_pos.y() - extruder_offset.y()
|
||||
<< "\n";
|
||||
gcode_out += oss.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode_out;
|
||||
}
|
||||
|
||||
|
||||
std::string WipeTowerIntegration::prime(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
for (const WipeTower::ToolChangeResult& tcr : m_priming) {
|
||||
if (! tcr.extrusions.empty())
|
||||
gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer)
|
||||
{
|
||||
std::string gcode;
|
||||
assert(m_layer_idx >= 0);
|
||||
if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
|
||||
if (m_layer_idx < (int)m_tool_changes.size()) {
|
||||
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
||||
throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
|
||||
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && m_layer_idx != 0);
|
||||
if (m_tool_change_idx == 0 && !ignore_sparse)
|
||||
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
|
||||
}
|
||||
|
||||
if (!ignore_sparse) {
|
||||
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
|
||||
m_last_wipe_tower_print_z = wipe_tower_z;
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
|
||||
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
|
||||
gcode += gcodegen.change_layer(m_final_purge.print_z);
|
||||
gcode += append_tcr(gcodegen, m_final_purge, -1);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
65
src/libslic3r/GCode/WipeTowerIntegration.hpp
Normal file
65
src/libslic3r/GCode/WipeTowerIntegration.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
#ifndef slic3r_GCode_WipeTowerIntegration_hpp_
|
||||
#define slic3r_GCode_WipeTowerIntegration_hpp_
|
||||
|
||||
#include "WipeTower.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeGenerator;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
class WipeTowerIntegration {
|
||||
public:
|
||||
WipeTowerIntegration(
|
||||
const PrintConfig &print_config,
|
||||
const std::vector<WipeTower::ToolChangeResult> &priming,
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
|
||||
const WipeTower::ToolChangeResult &final_purge) :
|
||||
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
|
||||
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
|
||||
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
|
||||
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
|
||||
m_extruder_offsets(print_config.extruder_offset.values),
|
||||
m_priming(priming),
|
||||
m_tool_changes(tool_changes),
|
||||
m_final_purge(final_purge),
|
||||
m_layer_idx(-1),
|
||||
m_tool_change_idx(0)
|
||||
{}
|
||||
|
||||
std::string prime(GCodeGenerator &gcodegen);
|
||||
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
|
||||
std::string tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer);
|
||||
std::string finalize(GCodeGenerator &gcodegen);
|
||||
std::vector<float> used_filament_length() const;
|
||||
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
|
||||
// Left / right edges of the wipe tower, for the planning of wipe moves.
|
||||
const float m_left;
|
||||
const float m_right;
|
||||
const Vec2f m_wipe_tower_pos;
|
||||
const float m_wipe_tower_rotation;
|
||||
const std::vector<Vec2d> m_extruder_offsets;
|
||||
|
||||
// Reference to cached values at the Printer class.
|
||||
const std::vector<WipeTower::ToolChangeResult> &m_priming;
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
|
||||
const WipeTower::ToolChangeResult &m_final_purge;
|
||||
// Current layer index.
|
||||
int m_layer_idx;
|
||||
int m_tool_change_idx;
|
||||
double m_last_wipe_tower_print_z = 0.f;
|
||||
};
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_WipeTowerIntegration_hpp_
|
674
src/libslic3r/Geometry/ArcWelder.cpp
Normal file
674
src/libslic3r/Geometry/ArcWelder.cpp
Normal file
@ -0,0 +1,674 @@
|
||||
// The following code for merging circles into arches originates from https://github.com/FormerLurker/ArcWelderLib
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Arc Welder: Anti-Stutter Library
|
||||
//
|
||||
// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
|
||||
// This reduces file size and the number of gcodes per second.
|
||||
//
|
||||
// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
|
||||
//
|
||||
// Copyright(C) 2021 - Brad Hochgesang
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This program is free software : you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
//
|
||||
// You can contact the author at the following email address:
|
||||
// FormerLurker@pm.me
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "ArcWelder.hpp"
|
||||
#include "Circle.hpp"
|
||||
|
||||
#include "../MultiPoint.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r { namespace Geometry { namespace ArcWelder {
|
||||
|
||||
Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation)
|
||||
{
|
||||
Vec2d center = arc_center(p1.cast<double>(), p2.cast<double>(), radius, ccw);
|
||||
double angle = arc_angle(p1.cast<double>(), p2.cast<double>(), radius);
|
||||
assert(angle > 0);
|
||||
|
||||
double r = std::abs(radius);
|
||||
size_t num_steps = arc_discretization_steps(r, angle, deviation);
|
||||
double angle_step = angle / num_steps;
|
||||
|
||||
Points out;
|
||||
out.reserve(num_steps + 1);
|
||||
out.emplace_back(p1);
|
||||
if (! ccw)
|
||||
angle_step *= -1.;
|
||||
for (size_t i = 1; i < num_steps; ++ i)
|
||||
out.emplace_back(p1.rotated(angle_step * i, center.cast<coord_t>()));
|
||||
out.emplace_back(p2);
|
||||
return out;
|
||||
}
|
||||
|
||||
struct Circle
|
||||
{
|
||||
Point center;
|
||||
double radius;
|
||||
};
|
||||
|
||||
// Interpolate three points with a circle.
|
||||
// Returns false if the three points are collinear or if the radius is bigger than maximum allowed radius.
|
||||
static std::optional<Circle> try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius)
|
||||
{
|
||||
if (auto center = Slic3r::Geometry::try_circle_center(p1.cast<double>(), p2.cast<double>(), p3.cast<double>(), SCALED_EPSILON); center) {
|
||||
Point c = center->cast<coord_t>();
|
||||
if (double r = sqrt(double((c - p1).cast<int64_t>().squaredNorm())); r <= max_radius)
|
||||
return std::make_optional<Circle>({ c, float(r) });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Returns a closest point on the segment.
|
||||
// Returns false if the closest point is not inside the segment, but at its boundary.
|
||||
static bool foot_pt_on_segment(const Point &p1, const Point &p2, const Point &pt, Point &out)
|
||||
{
|
||||
Vec2i64 v21 = (p2 - p1).cast<int64_t>();
|
||||
int64_t l2 = v21.squaredNorm();
|
||||
if (l2 > int64_t(SCALED_EPSILON)) {
|
||||
if (int64_t t = (pt - p1).cast<int64_t>().dot(v21);
|
||||
t >= int64_t(SCALED_EPSILON) && t < l2 - int64_t(SCALED_EPSILON)) {
|
||||
out = p1 + ((double(t) / double(l2)) * v21.cast<double>()).cast<coord_t>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// The segment is short or the closest point is an end point.
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool circle_approximation_sufficient(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance)
|
||||
{
|
||||
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
|
||||
assert(std::abs((*begin - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(end - begin >= 3);
|
||||
|
||||
// Test the 1st point.
|
||||
if (double distance_from_center = (*begin - circle.center).cast<double>().norm();
|
||||
std::abs(distance_from_center - circle.radius) > tolerance)
|
||||
return false;
|
||||
|
||||
for (auto it = std::next(begin); it != end; ++ it) {
|
||||
if (double distance_from_center = (*it - circle.center).cast<double>().norm();
|
||||
std::abs(distance_from_center - circle.radius) > tolerance)
|
||||
return false;
|
||||
Point closest_point;
|
||||
if (foot_pt_on_segment(*std::prev(it), *it, circle.center, closest_point)) {
|
||||
if (double distance_from_center = (closest_point - circle.center).cast<double>().norm();
|
||||
std::abs(distance_from_center - circle.radius) > tolerance)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool get_deviation_sum_squared(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance, double &total_deviation)
|
||||
{
|
||||
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
|
||||
assert(std::abs((*begin - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(end - begin >= 3);
|
||||
|
||||
total_deviation = 0;
|
||||
|
||||
const double tolerance2 = sqr(tolerance);
|
||||
for (auto it = std::next(begin); std::next(it) != end; ++ it)
|
||||
if (double deviation2 = sqr((*it - circle.center).cast<double>().norm() - circle.radius); deviation2 > tolerance2)
|
||||
return false;
|
||||
else
|
||||
total_deviation += deviation2;
|
||||
|
||||
for (auto it = begin; std::next(it) != end; ++ it) {
|
||||
Point closest_point;
|
||||
if (foot_pt_on_segment(*it, *std::next(it), circle.center, closest_point)) {
|
||||
if (double deviation2 = sqr((closest_point - circle.center).cast<double>().norm() - circle.radius); deviation2 > tolerance2)
|
||||
return false;
|
||||
else
|
||||
total_deviation += deviation2;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::optional<Circle> try_create_circle(const Points::const_iterator begin, const Points::const_iterator end, const double max_radius, const double tolerance)
|
||||
{
|
||||
std::optional<Circle> out;
|
||||
size_t size = end - begin;
|
||||
if (size == 3) {
|
||||
out = try_create_circle(*begin, *std::next(begin), *std::prev(end), max_radius);
|
||||
if (out && ! circle_approximation_sufficient(*out, begin, end, tolerance))
|
||||
out.reset();
|
||||
} else {
|
||||
#if 0
|
||||
size_t ipivot = size / 2;
|
||||
// Take a center difference of points at the center of the path.
|
||||
//FIXME does it really help? For short arches, the linear interpolation may be
|
||||
Point pivot = (size % 2 == 0) ? (*(begin + ipivot) + *(begin + ipivot - 1)) / 2 :
|
||||
(*(begin + ipivot - 1) + *(begin + ipivot + 1)) / 2;
|
||||
if (std::optional<Circle> circle = try_create_circle(*begin, pivot, *std::prev(end), max_radius);
|
||||
circle && circle_approximation_sufficient(*circle, begin, end, tolerance))
|
||||
return circle;
|
||||
#endif
|
||||
// Find the circle with the least deviation, if one exists.
|
||||
double least_deviation = std::numeric_limits<double>::max();
|
||||
double current_deviation;
|
||||
for (auto it = std::next(begin); std::next(it) != end; ++ it)
|
||||
if (std::optional<Circle> circle = try_create_circle(*begin, *it, *std::prev(end), max_radius);
|
||||
circle && get_deviation_sum_squared(*circle, begin, end, tolerance, current_deviation)) {
|
||||
if (current_deviation < least_deviation) {
|
||||
out = circle;
|
||||
least_deviation = current_deviation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ported from ArcWelderLib/ArcWelder/segmented/shape.h class "arc"
|
||||
class Arc {
|
||||
public:
|
||||
Point start_point;
|
||||
Point end_point;
|
||||
Point center;
|
||||
double radius;
|
||||
Orientation direction { Orientation::Unknown };
|
||||
};
|
||||
|
||||
static inline int sign(const int64_t i)
|
||||
{
|
||||
return i > 0 ? 1 : i < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
// Return orientation of a polyline with regard to the center.
|
||||
// Successive points are expected to take less than a PI angle step.
|
||||
Orientation arc_orientation(
|
||||
const Point ¢er,
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end)
|
||||
{
|
||||
assert(end - begin >= 3);
|
||||
// Assumption: Two successive points of a single segment span an angle smaller than PI.
|
||||
Vec2i64 vstart = (*begin - center).cast<int64_t>();
|
||||
Vec2i64 vprev = vstart;
|
||||
int arc_dir = 0;
|
||||
for (auto it = std::next(begin); it != end; ++ it) {
|
||||
Vec2i64 v = (*it - center).cast<int64_t>();
|
||||
int dir = sign(cross2(vprev, v));
|
||||
if (dir == 0) {
|
||||
// Ignore radial segments.
|
||||
} else if (arc_dir * dir < 0) {
|
||||
// The path turns back and overextrudes. Such path is likely invalid, but the arc interpolation should
|
||||
// rather maintain such an invalid path instead of covering it up.
|
||||
// Don't replace such a path with an arc.
|
||||
return {};
|
||||
} else {
|
||||
// Success, either establishing the direction for the first time, or moving in the same direction as the last time.
|
||||
arc_dir = dir;
|
||||
vprev = v;
|
||||
}
|
||||
}
|
||||
return arc_dir == 0 ?
|
||||
// All points are radial wrt. the center, this is unexpected.
|
||||
Orientation::Unknown :
|
||||
// Arc is valid, either CCW or CW.
|
||||
arc_dir > 0 ? Orientation::CCW : Orientation::CW;
|
||||
}
|
||||
|
||||
static inline std::optional<Arc> try_create_arc_impl(
|
||||
const Circle &circle,
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end,
|
||||
const double tolerance,
|
||||
const double path_tolerance_percent)
|
||||
{
|
||||
assert(end - begin >= 3);
|
||||
// Assumption: Two successive points of a single segment span an angle smaller than PI.
|
||||
Orientation orientation = arc_orientation(circle.center, begin, end);
|
||||
if (orientation == Orientation::Unknown)
|
||||
return {};
|
||||
|
||||
Vec2i64 vstart = (*begin - circle.center).cast<int64_t>();
|
||||
Vec2i64 vend = (*std::prev(end) - circle.center).cast<int64_t>();
|
||||
double angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend)));
|
||||
if (orientation == Orientation::CW)
|
||||
angle *= -1.;
|
||||
if (angle < 0)
|
||||
angle += 2. * M_PI;
|
||||
assert(angle >= 0. && angle < 2. * M_PI + EPSILON);
|
||||
|
||||
// Check the length against the original length.
|
||||
// This can trigger simply due to the differing path lengths
|
||||
// but also could indicate that the vector calculation above
|
||||
// got wrong direction
|
||||
const double arc_length = circle.radius * angle;
|
||||
const double approximate_length = length(begin, end);
|
||||
assert(approximate_length > 0);
|
||||
const double arc_length_difference_relative = (arc_length - approximate_length) / approximate_length;
|
||||
|
||||
if (std::fabs(arc_length_difference_relative) >= path_tolerance_percent) {
|
||||
return {};
|
||||
} else {
|
||||
assert(circle_approximation_sufficient(circle, begin, end, tolerance + SCALED_EPSILON));
|
||||
return std::make_optional<Arc>(Arc{
|
||||
*begin,
|
||||
*std::prev(end),
|
||||
circle.center,
|
||||
angle > M_PI ? - circle.radius : circle.radius,
|
||||
orientation
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::optional<Arc> try_create_arc(
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end,
|
||||
double max_radius = default_scaled_max_radius,
|
||||
double tolerance = default_scaled_resolution,
|
||||
double path_tolerance_percent = default_arc_length_percent_tolerance)
|
||||
{
|
||||
std::optional<Circle> circle = try_create_circle(begin, end, max_radius, tolerance);
|
||||
if (! circle)
|
||||
return {};
|
||||
return try_create_arc_impl(*circle, begin, end, tolerance, path_tolerance_percent);
|
||||
}
|
||||
|
||||
float arc_angle(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f ¢er_pos, bool is_ccw)
|
||||
{
|
||||
if ((end_pos - start_pos).squaredNorm() < sqr(1e-6)) {
|
||||
// If start equals end, full circle is considered.
|
||||
return float(2. * M_PI);
|
||||
} else {
|
||||
Vec2f v1 = start_pos - center_pos;
|
||||
Vec2f v2 = end_pos - center_pos;
|
||||
if (! is_ccw)
|
||||
std::swap(v1, v2);
|
||||
float radian = atan2(cross2(v1, v2), v1.dot(v2));
|
||||
return radian < 0 ? float(2. * M_PI) + radian : radian;
|
||||
}
|
||||
}
|
||||
|
||||
float arc_length(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f ¢er_pos, bool is_ccw)
|
||||
{
|
||||
return (center_pos - start_pos).norm() * arc_angle(start_pos, end_pos, center_pos, is_ccw);
|
||||
}
|
||||
|
||||
// Reduces polyline in the <begin, end) range in place,
|
||||
// returns the new end iterator.
|
||||
static inline Segments::iterator douglas_peucker_in_place(Segments::iterator begin, Segments::iterator end, const double tolerance)
|
||||
{
|
||||
return douglas_peucker<int64_t>(begin, end, begin, tolerance, [](const Segment &s) { return s.point; });
|
||||
}
|
||||
|
||||
Path fit_path(const Points &src, double tolerance, double fit_circle_percent_tolerance)
|
||||
{
|
||||
assert(tolerance >= 0);
|
||||
assert(fit_circle_percent_tolerance >= 0);
|
||||
|
||||
Path out;
|
||||
out.reserve(src.size());
|
||||
if (tolerance <= 0 || src.size() <= 2) {
|
||||
// No simplification, just convert.
|
||||
std::transform(src.begin(), src.end(), std::back_inserter(out), [](const Point &p) -> Segment { return { p }; });
|
||||
} else if (fit_circle_percent_tolerance <= 0) {
|
||||
// Convert and simplify to a polyline.
|
||||
std::transform(src.begin(), src.end(), std::back_inserter(out), [](const Point &p) -> Segment { return { p }; });
|
||||
out.erase(douglas_peucker_in_place(out.begin(), out.end(), tolerance), out.end());
|
||||
} else {
|
||||
// Perform simplification & fitting.
|
||||
// Index of the start of a last polyline, which has not yet been decimated.
|
||||
int begin_pl_idx = 0;
|
||||
out.push_back({ src.front(), 0.f });
|
||||
for (auto it = std::next(src.begin()); it != src.end();) {
|
||||
// Minimum 2 additional points required for circle fitting.
|
||||
auto begin = std::prev(it);
|
||||
auto end = std::next(it);
|
||||
assert(end <= src.end());
|
||||
std::optional<Arc> arc;
|
||||
while (end != src.end()) {
|
||||
auto next_end = std::next(end);
|
||||
if (std::optional<Arc> this_arc = try_create_arc(
|
||||
begin, next_end,
|
||||
ArcWelder::default_scaled_max_radius,
|
||||
tolerance, fit_circle_percent_tolerance);
|
||||
this_arc) {
|
||||
assert(this_arc->direction != Orientation::Unknown);
|
||||
arc = this_arc;
|
||||
end = next_end;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if (arc) {
|
||||
// If there is a trailing polyline, decimate it first before saving a new arc.
|
||||
if (out.size() - begin_pl_idx > 2) {
|
||||
// Decimating linear segmens only.
|
||||
assert(std::all_of(out.begin() + begin_pl_idx + 1, out.end(), [](const Segment &seg) { return seg.linear(); }));
|
||||
out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end());
|
||||
assert(out.back().linear());
|
||||
}
|
||||
// Save the index of an end of the new circle segment, which may become the first point of a possible future polyline.
|
||||
begin_pl_idx = int(out.size());
|
||||
// This will be the next point to try to add.
|
||||
it = end;
|
||||
// Add the new arc.
|
||||
assert(*begin == arc->start_point);
|
||||
assert(*std::prev(it) == arc->end_point);
|
||||
assert(out.back().point == arc->start_point);
|
||||
out.push_back({ arc->end_point, float(arc->radius), arc->direction });
|
||||
#if 0
|
||||
// Verify that all the source points are at tolerance distance from the interpolated path.
|
||||
{
|
||||
const Segment &seg_start = *std::prev(std::prev(out.end()));
|
||||
const Segment &seg_end = out.back();
|
||||
const Vec2d center = arc_center(seg_start.point.cast<double>(), seg_end.point.cast<double>(), double(seg_end.radius), seg_end.ccw());
|
||||
assert(seg_start.point == *begin);
|
||||
assert(seg_end.point == *std::prev(end));
|
||||
assert(arc_orientation(center.cast<coord_t>(), begin, end) == arc->direction);
|
||||
for (auto it = std::next(begin); it != end; ++ it) {
|
||||
Point ptstart = *std::prev(it);
|
||||
Point ptend = *it;
|
||||
Point closest_point;
|
||||
if (foot_pt_on_segment(ptstart, ptend, center.cast<coord_t>(), closest_point)) {
|
||||
double distance_from_center = (closest_point.cast<double>() - center).norm();
|
||||
assert(std::abs(distance_from_center - std::abs(seg_end.radius)) < tolerance + SCALED_EPSILON);
|
||||
}
|
||||
Vec2d v = (ptend - ptstart).cast<double>();
|
||||
double len = v.norm();
|
||||
auto num_segments = std::min<size_t>(10, ceil(2. * len / fit_circle_percent_tolerance));
|
||||
for (size_t i = 0; i < num_segments; ++ i) {
|
||||
Point p = ptstart + (v * (double(i) / double(num_segments))).cast<coord_t>();
|
||||
assert(i == 0 || inside_arc_wedge(seg_start.point.cast<double>(), seg_end.point.cast<double>(), center, seg_end.radius > 0, seg_end.ccw(), p.cast<double>()));
|
||||
double d2 = sqr((p.cast<double>() - center).norm() - std::abs(seg_end.radius));
|
||||
assert(d2 < sqr(tolerance + SCALED_EPSILON));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// Arc is not valid, append a linear segment.
|
||||
out.push_back({ *it ++ });
|
||||
}
|
||||
}
|
||||
if (out.size() - begin_pl_idx > 2)
|
||||
// Do the final polyline decimation.
|
||||
out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end());
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Verify that all the source points are at tolerance distance from the interpolated path.
|
||||
for (auto it = std::next(src.begin()); it != src.end(); ++ it) {
|
||||
Point start = *std::prev(it);
|
||||
Point end = *it;
|
||||
Vec2d v = (end - start).cast<double>();
|
||||
double len = v.norm();
|
||||
auto num_segments = std::min<size_t>(10, ceil(2. * len / fit_circle_percent_tolerance));
|
||||
for (size_t i = 0; i <= num_segments; ++ i) {
|
||||
Point p = start + (v * (double(i) / double(num_segments))).cast<coord_t>();
|
||||
PathSegmentProjection proj = point_to_path_projection(out, p);
|
||||
assert(proj.valid());
|
||||
assert(proj.distance2 < sqr(tolerance + SCALED_EPSILON));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void reverse(Path &path)
|
||||
{
|
||||
if (path.size() > 1) {
|
||||
auto prev = path.begin();
|
||||
for (auto it = std::next(prev); it != path.end(); ++ it) {
|
||||
prev->radius = it->radius;
|
||||
prev->orientation = it->orientation == Orientation::CCW ? Orientation::CW : Orientation::CCW;
|
||||
prev = it;
|
||||
}
|
||||
path.back().radius = 0;
|
||||
std::reverse(path.begin(), path.end());
|
||||
}
|
||||
}
|
||||
|
||||
double clip_start(Path &path, const double len)
|
||||
{
|
||||
reverse(path);
|
||||
double remaining = clip_end(path, len);
|
||||
reverse(path);
|
||||
// Return remaining distance to go.
|
||||
return remaining;
|
||||
}
|
||||
|
||||
double clip_end(Path &path, double distance)
|
||||
{
|
||||
while (distance > 0) {
|
||||
Segment &last = path.back();
|
||||
path.pop_back();
|
||||
if (path.empty())
|
||||
break;
|
||||
if (last.linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (path.back().point - last.point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance)) {
|
||||
path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast<coord_t>() });
|
||||
// Length to go is zero.
|
||||
return 0;
|
||||
}
|
||||
distance -= sqrt(lsqr);
|
||||
} else {
|
||||
// Circular segment
|
||||
double angle = arc_angle(path.back().point.cast<double>(), last.point.cast<double>(), last.radius);
|
||||
double len = std::abs(last.radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
if (last.ccw())
|
||||
angle *= -1.;
|
||||
path.push_back({
|
||||
last.point.rotated(angle * (distance / len),
|
||||
arc_center(path.back().point.cast<double>(), last.point.cast<double>(), double(last.radius), last.ccw()).cast<coord_t>()),
|
||||
last.radius, last.orientation });
|
||||
// Length to go is zero.
|
||||
return 0;
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
}
|
||||
|
||||
// Return remaining distance to go.
|
||||
assert(distance >= 0);
|
||||
return distance;
|
||||
}
|
||||
|
||||
PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2)
|
||||
{
|
||||
assert(path.size() != 1);
|
||||
// initialized to "invalid" state.
|
||||
PathSegmentProjection out;
|
||||
out.distance2 = search_radius2;
|
||||
if (path.size() < 2 || path.front().point == point) {
|
||||
// First point is the closest point.
|
||||
if (path.empty()) {
|
||||
// No closest point available.
|
||||
} else if (const Point p0 = path.front().point; p0 == point) {
|
||||
out.segment_id = 0;
|
||||
out.point = p0;
|
||||
out.distance2 = 0;
|
||||
} else if (double d2 = (p0 - point).cast<double>().squaredNorm(); d2 < out.distance2) {
|
||||
out.segment_id = 0;
|
||||
out.point = p0;
|
||||
out.distance2 = d2;
|
||||
}
|
||||
} else {
|
||||
assert(path.size() >= 2);
|
||||
// min_point_it will contain an end point of a segment with a closest projection found
|
||||
// or path.cbegin() if no such closest projection closer than search_radius2 was found.
|
||||
auto min_point_it = path.cbegin();
|
||||
Point prev = path.front().point;
|
||||
for (auto it = std::next(path.cbegin()); it != path.cend(); ++ it) {
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Point proj;
|
||||
// distance_to_squared() will possibly return the start or end point of a line segment.
|
||||
if (double d2 = line_alg::distance_to_squared(Line(prev, it->point), point, &proj); d2 < out.distance2) {
|
||||
out.point = proj;
|
||||
out.distance2 = d2;
|
||||
min_point_it = it;
|
||||
}
|
||||
} else {
|
||||
// Circular arc
|
||||
Vec2i64 center = arc_center(prev.cast<double>(), it->point.cast<double>(), double(it->radius), it->ccw()).cast<int64_t>();
|
||||
// Test whether point is inside the wedge.
|
||||
Vec2i64 v1 = prev.cast<int64_t>() - center;
|
||||
Vec2i64 v2 = it->point.cast<int64_t>() - center;
|
||||
Vec2i64 vp = point.cast<int64_t>() - center;
|
||||
if (inside_arc_wedge_vectors(v1, v2, it->radius > 0, it->ccw(), vp)) {
|
||||
// Distance of the radii.
|
||||
const auto r = double(std::abs(it->radius));
|
||||
const auto rtest = sqrt(double(vp.squaredNorm()));
|
||||
if (double d2 = sqr(rtest - r); d2 < out.distance2) {
|
||||
if (rtest > SCALED_EPSILON)
|
||||
// Project vp to the arc.
|
||||
out.point = center.cast<coord_t>() + (vp.cast<double>() * (r / rtest)).cast<coord_t>();
|
||||
else
|
||||
// Test point is very close to the center of the radius. Any point of the arc is the closest.
|
||||
// Pick the start.
|
||||
out.point = prev;
|
||||
out.distance2 = d2;
|
||||
out.center = center.cast<coord_t>();
|
||||
min_point_it = it;
|
||||
}
|
||||
} else {
|
||||
// Distance to the start point.
|
||||
if (double d2 = double((v1 - vp).squaredNorm()); d2 < out.distance2) {
|
||||
out.point = prev;
|
||||
out.distance2 = d2;
|
||||
min_point_it = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = it->point;
|
||||
}
|
||||
if (! path.back().linear()) {
|
||||
// Calculate distance to the end point.
|
||||
if (double d2 = (path.back().point - point).cast<double>().squaredNorm(); d2 < out.distance2) {
|
||||
out.point = path.back().point;
|
||||
out.distance2 = d2;
|
||||
min_point_it = std::prev(path.end());
|
||||
}
|
||||
}
|
||||
// If a new closes point was found, it is closer than search_radius2.
|
||||
assert((min_point_it == path.cbegin()) == (out.distance2 == search_radius2));
|
||||
// Output is not valid yet.
|
||||
assert(! out.valid());
|
||||
if (min_point_it != path.cbegin()) {
|
||||
// Make it valid by setting the segment.
|
||||
out.segment_id = std::prev(min_point_it) - path.begin();
|
||||
assert(out.valid());
|
||||
}
|
||||
}
|
||||
|
||||
assert(! out.valid() || (out.segment_id >= 0 && out.segment_id < path.size()));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length)
|
||||
{
|
||||
assert(proj.valid());
|
||||
assert(! proj.valid() || (proj.segment_id >= 0 && proj.segment_id < path.size()));
|
||||
assert(path.size() > 1);
|
||||
std::pair<Path, Path> out;
|
||||
if (! proj.valid() || proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point))
|
||||
out.first = path;
|
||||
else if (proj.segment_id == 0 && proj.point == path.front().point)
|
||||
out.second = path;
|
||||
else {
|
||||
// Path will likely be split to two pieces.
|
||||
assert(proj.valid() && proj.segment_id >= 0 && proj.segment_id + 1 < path.size());
|
||||
const Segment &start = path[proj.segment_id];
|
||||
const Segment &end = path[proj.segment_id + 1];
|
||||
bool split_segment = true;
|
||||
int split_segment_id = proj.segment_id;
|
||||
if (int64_t d2 = (proj.point - start.point).cast<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
|
||||
split_segment = false;
|
||||
int64_t d22 = (proj.point - end.point).cast<int64_t>().squaredNorm();
|
||||
if (d22 < d2)
|
||||
// Split at the end of the segment.
|
||||
++ split_segment_id;
|
||||
} else if (int64_t d2 = (proj.point - end.point).cast<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
|
||||
++ split_segment_id;
|
||||
split_segment = false;
|
||||
}
|
||||
if (split_segment) {
|
||||
out.first.assign(path.begin(), path.begin() + split_segment_id + 2);
|
||||
out.second.assign(path.begin() + split_segment_id, path.end());
|
||||
assert(out.first[out.first.size() - 2] == start);
|
||||
assert(out.first.back() == end);
|
||||
assert(out.second.front() == start);
|
||||
assert(out.second[1] == end);
|
||||
assert(out.first.size() + out.second.size() == path.size() + 2);
|
||||
assert(out.first.back().radius == out.second[1].radius);
|
||||
out.first.back().point = proj.point;
|
||||
out.second.front().point = proj.point;
|
||||
if (end.radius < 0) {
|
||||
// A large arc (> PI) was split.
|
||||
// At least one of the two arches that were created by splitting the original arch will become smaller.
|
||||
// Make the radii of those arches that became < PI positive.
|
||||
// In case of a projection onto an arc, proj.center should be filled in and valid.
|
||||
auto vstart = (start.point - proj.center).cast<int64_t>();
|
||||
auto vend = (end.point - proj.center).cast<int64_t>();
|
||||
auto vproj = (proj.point - proj.center).cast<int64_t>();
|
||||
if ((cross2(vstart, vproj) > 0) == end.ccw())
|
||||
// Make the radius of a minor arc positive.
|
||||
out.first.back().radius *= -1.f;
|
||||
if ((cross2(vproj, vend) > 0) == end.ccw())
|
||||
// Make the radius of a minor arc positive.
|
||||
out.second[1].radius *= -1.f;
|
||||
assert(out.first.size() > 1);
|
||||
assert(out.second.size() > 1);
|
||||
out.second.front().radius = 0;
|
||||
}
|
||||
} else {
|
||||
assert(split_segment_id >= 0 && split_segment_id < path.size());
|
||||
if (split_segment_id + 1 == int(path.size()))
|
||||
out.first = path;
|
||||
else if (split_segment_id == 0)
|
||||
out.second = path;
|
||||
else {
|
||||
// Split at the start of proj.segment_id.
|
||||
out.first.assign(path.begin(), path.begin() + split_segment_id + 1);
|
||||
out.second.assign(path.begin() + split_segment_id, path.end());
|
||||
assert(out.first.size() + out.second.size() == path.size() + 1);
|
||||
assert(out.first.back() == (split_segment_id == proj.segment_id ? start : end));
|
||||
assert(out.second.front() == (split_segment_id == proj.segment_id ? start : end));
|
||||
assert(out.first.size() > 1);
|
||||
assert(out.second.size() > 1);
|
||||
out.second.front().radius = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length)
|
||||
{
|
||||
return split_at(path, point_to_path_projection(path, point), min_segment_length);
|
||||
}
|
||||
|
||||
} } } // namespace Slic3r::Geometry::ArcWelder
|
324
src/libslic3r/Geometry/ArcWelder.hpp
Normal file
324
src/libslic3r/Geometry/ArcWelder.hpp
Normal file
@ -0,0 +1,324 @@
|
||||
#ifndef slic3r_Geometry_ArcWelder_hpp_
|
||||
#define slic3r_Geometry_ArcWelder_hpp_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "../Point.hpp"
|
||||
|
||||
namespace Slic3r { namespace Geometry { namespace ArcWelder {
|
||||
|
||||
// Calculate center point of an arc given two points and a radius.
|
||||
// positive radius: take shorter arc
|
||||
// negative radius: take longer arc
|
||||
// radius must NOT be zero!
|
||||
template<typename Derived, typename Derived2, typename Float>
|
||||
inline Eigen::Matrix<Float, 2, 1, Eigen::DontAlign> arc_center(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const Float radius,
|
||||
const bool is_ccw)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_center(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_center(): second parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_center(): Both vectors must be of the same type.");
|
||||
static_assert(std::is_same<typename Derived::Scalar, Float>::value, "arc_center(): Radius must be of the same type as the vectors.");
|
||||
assert(radius != 0);
|
||||
using Vector = Eigen::Matrix<Float, 2, 1, Eigen::DontAlign>;
|
||||
auto v = end_pos - start_pos;
|
||||
Float q2 = v.squaredNorm();
|
||||
assert(q2 > 0);
|
||||
Float t2 = sqr(radius) / q2 - Float(.25f);
|
||||
// If the start_pos and end_pos are nearly antipodal, t2 may become slightly negative.
|
||||
// In that case return a centroid of start_point & end_point.
|
||||
Float t = t2 > 0 ? sqrt(t2) : Float(0);
|
||||
auto mid = Float(0.5) * (start_pos + end_pos);
|
||||
Vector vp{ -v.y() * t, v.x() * t };
|
||||
return (radius > Float(0)) == is_ccw ? (mid + vp).eval() : (mid - vp).eval();
|
||||
}
|
||||
|
||||
// Calculate angle of an arc given two points and a radius.
|
||||
// Returned angle is in the range <0, 2 PI)
|
||||
// positive radius: take shorter arc
|
||||
// negative radius: take longer arc
|
||||
// radius must NOT be zero!
|
||||
template<typename Derived, typename Derived2>
|
||||
inline typename Derived::Scalar arc_angle(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const typename Derived::Scalar radius)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_angle(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_angle(): second parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_angle(): Both vectors must be of the same type.");
|
||||
assert(radius != 0);
|
||||
using Float = typename Derived::Scalar;
|
||||
Float a = Float(0.5) * (end_pos - start_pos).norm() / radius;
|
||||
return radius > Float(0) ?
|
||||
// acute angle:
|
||||
(a > Float( 1.) ? Float(M_PI) : Float(2.) * std::asin(a)) :
|
||||
// obtuse angle:
|
||||
(a < Float(-1.) ? Float(M_PI) : Float(2. * M_PI) + Float(2.) * std::asin(a));
|
||||
}
|
||||
|
||||
// Calculate positive length of an arc given two points and a radius.
|
||||
// positive radius: take shorter arc
|
||||
// negative radius: take longer arc
|
||||
// radius must NOT be zero!
|
||||
template<typename Derived, typename Derived2>
|
||||
inline typename Derived::Scalar arc_length(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const typename Derived::Scalar radius)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_length(): Both vectors must be of the same type.");
|
||||
assert(radius != 0);
|
||||
return arc_angle(start_pos, end_pos, radius) * std::abs(radius);
|
||||
}
|
||||
|
||||
// Calculate positive length of an arc given two points, center and orientation.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
inline typename Derived::Scalar arc_length(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const Eigen::MatrixBase<Derived3> ¢er_pos,
|
||||
const bool ccw)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): third parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value, "arc_length(): All third points must be of the same type.");
|
||||
using Float = typename Derived::Scalar;
|
||||
auto vstart = start_pos - center_pos;
|
||||
auto vend = end_pos - center_pos;
|
||||
Float radius = vstart.norm();
|
||||
Float angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend)));
|
||||
if (! ccw)
|
||||
angle *= Float(-1.);
|
||||
if (angle < 0)
|
||||
angle += Float(2. * M_PI);
|
||||
assert(angle >= Float(0.) && angle < Float(2. * M_PI + EPSILON));
|
||||
return angle * radius;
|
||||
}
|
||||
|
||||
// Test whether a point is inside a wedge of an arc.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
inline bool inside_arc_wedge_vectors(
|
||||
const Eigen::MatrixBase<Derived> &start_vec,
|
||||
const Eigen::MatrixBase<Derived2> &end_vec,
|
||||
const bool shorter_arc,
|
||||
const bool ccw,
|
||||
const Eigen::MatrixBase<Derived3> &query_vec)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): start_vec is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): end_vec is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): query_vec is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value, "inside_arc_wedge_vectors(): All vectors must be of the same type.");
|
||||
return shorter_arc ?
|
||||
// Smaller (convex) wedge.
|
||||
(ccw ?
|
||||
cross2(start_vec, query_vec) > 0 && cross2(query_vec, end_vec) > 0 :
|
||||
cross2(start_vec, query_vec) < 0 && cross2(query_vec, end_vec) < 0) :
|
||||
// Larger (concave) wedge.
|
||||
(ccw ?
|
||||
cross2(end_vec, query_vec) < 0 || cross2(query_vec, start_vec) < 0 :
|
||||
cross2(end_vec, query_vec) > 0 || cross2(query_vec, start_vec) > 0);
|
||||
}
|
||||
|
||||
template<typename Derived, typename Derived2, typename Derived3, typename Derived4>
|
||||
inline bool inside_arc_wedge(
|
||||
const Eigen::MatrixBase<Derived> &start_pt,
|
||||
const Eigen::MatrixBase<Derived2> &end_pt,
|
||||
const Eigen::MatrixBase<Derived3> ¢er_pt,
|
||||
const bool shorter_arc,
|
||||
const bool ccw,
|
||||
const Eigen::MatrixBase<Derived4> &query_pt)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): center_pt is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived4::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived4::Scalar>::value, "inside_arc_wedge(): All vectors must be of the same type.");
|
||||
return inside_arc_wedge_vectors(start_pt - center_pt, end_pt - center_pt, shorter_arc, ccw, query_pt - center_pt);
|
||||
}
|
||||
|
||||
template<typename Derived, typename Derived2, typename Derived3, typename Float>
|
||||
inline bool inside_arc_wedge(
|
||||
const Eigen::MatrixBase<Derived> &start_pt,
|
||||
const Eigen::MatrixBase<Derived2> &end_pt,
|
||||
const Float radius,
|
||||
const bool ccw,
|
||||
const Eigen::MatrixBase<Derived3> &query_pt)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, Float>::value, "inside_arc_wedge(): All vectors + radius must be of the same type.");
|
||||
return inside_arc_wedge(start_pt, end_pt,
|
||||
arc_center(start_pt, end_pt, radius, ccw),
|
||||
radius > 0, ccw, query_pt);
|
||||
}
|
||||
|
||||
// Return number of linear segments necessary to interpolate arc of a given positive radius and positive angle to satisfy
|
||||
// maximum deviation of an interpolating polyline from an analytic arc.
|
||||
template<typename FloatType>
|
||||
size_t arc_discretization_steps(const FloatType radius, const FloatType angle, const FloatType deviation)
|
||||
{
|
||||
assert(radius > 0);
|
||||
assert(angle > 0);
|
||||
assert(angle <= FloatType(2. * M_PI));
|
||||
assert(deviation > 0);
|
||||
|
||||
FloatType d = radius - deviation;
|
||||
return d < EPSILON ?
|
||||
// Radius smaller than deviation.
|
||||
( // Acute angle: a single segment interpolates the arc with sufficient accuracy.
|
||||
angle < M_PI ||
|
||||
// Obtuse angle: Test whether the furthest point (center) of an arc is closer than deviation to the center of a line segment.
|
||||
radius * (FloatType(1.) + cos(M_PI - FloatType(.5) * angle)) < deviation ?
|
||||
// Single segment is sufficient
|
||||
1 :
|
||||
// Two segments are necessary, the middle point is at the center of the arc.
|
||||
2) :
|
||||
size_t(ceil(angle / (2. * acos(d / radius))));
|
||||
}
|
||||
|
||||
// Discretize arc given the radius, orientation and maximum deviation from the arc.
|
||||
// Returned polygon starts with p1, ends with p2 and it is discretized to guarantee the maximum deviation.
|
||||
Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation);
|
||||
|
||||
// 1.2m diameter, maximum given by coord_t
|
||||
static constexpr const double default_scaled_max_radius = scaled<double>(600.);
|
||||
// 0.05mm
|
||||
static constexpr const double default_scaled_resolution = scaled<double>(0.05);
|
||||
// 5 percent
|
||||
static constexpr const double default_arc_length_percent_tolerance = 0.05;
|
||||
|
||||
enum class Orientation : unsigned char {
|
||||
Unknown,
|
||||
CCW,
|
||||
CW,
|
||||
};
|
||||
|
||||
// Returns orientation of a polyline with regard to the center.
|
||||
// Successive points are expected to take less than a PI angle step.
|
||||
// Returns Orientation::Unknown if the orientation with regard to the center
|
||||
// is not monotonous.
|
||||
Orientation arc_orientation(
|
||||
const Point ¢er,
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end);
|
||||
|
||||
// Single segment of a smooth path.
|
||||
struct Segment
|
||||
{
|
||||
// End point of a linear or circular segment.
|
||||
// Start point is provided by the preceding segment.
|
||||
Point point;
|
||||
// Radius of a circular segment. Positive - take the shorter arc. Negative - take the longer arc. Zero - linear segment.
|
||||
float radius{ 0.f };
|
||||
// CCW or CW. Ignored for zero radius (linear segment).
|
||||
Orientation orientation{ Orientation::CCW };
|
||||
|
||||
bool linear() const { return radius == 0; }
|
||||
bool ccw() const { return orientation == Orientation::CCW; }
|
||||
bool cw() const { return orientation == Orientation::CW; }
|
||||
};
|
||||
|
||||
inline bool operator==(const Segment &lhs, const Segment &rhs) {
|
||||
return lhs.point == rhs.point && lhs.radius == rhs.radius && lhs.orientation == rhs.orientation;
|
||||
}
|
||||
|
||||
using Segments = std::vector<Segment>;
|
||||
using Path = Segments;
|
||||
|
||||
// Interpolate polyline path with a sequence of linear / circular segments given the interpolation tolerance.
|
||||
// Only convert to polyline if zero tolerance.
|
||||
// Convert to polyline and decimate polyline if zero fit_circle_percent_tolerance.
|
||||
// Path fitting is inspired with the arc fitting algorithm in
|
||||
// Arc Welder: Anti-Stutter Library by Brad Hochgesang FormerLurker@pm.me
|
||||
// https://github.com/FormerLurker/ArcWelderLib
|
||||
Path fit_path(const Points &points, double tolerance, double fit_circle_percent_tolerance);
|
||||
|
||||
// Decimate polyline into a smooth path structure using Douglas-Peucker polyline decimation algorithm.
|
||||
inline Path fit_polyline(const Points &points, double tolerance) { return fit_path(points, tolerance, 0.); }
|
||||
|
||||
template<typename FloatType>
|
||||
inline FloatType segment_length(const Segment &start, const Segment &end)
|
||||
{
|
||||
return end.linear() ?
|
||||
(end.point - start.point).cast<FloatType>().norm() :
|
||||
arc_length(start.point.cast<FloatType>(), end.point.cast<FloatType>(), FloatType(end.radius));
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline FloatType path_length(const Path &path)
|
||||
{
|
||||
FloatType len = 0;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
len += segment_length<FloatType>(path[i - 1], path[i]);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Estimate minimum path length of a segment cheaply without having to calculate center of an arc and it arc length.
|
||||
// Used for caching a smooth path chunk that is certainly longer than a threshold.
|
||||
inline int64_t estimate_min_segment_length(const Segment &start, const Segment &end)
|
||||
{
|
||||
if (end.linear() || end.radius > 0) {
|
||||
// Linear segment or convex wedge, take the larger X or Y component.
|
||||
Point v = (end.point - start.point).cwiseAbs();
|
||||
return std::max(v.x(), v.y());
|
||||
} else {
|
||||
// Arc with angle > PI.
|
||||
// Returns estimate of PI * r
|
||||
return - int64_t(3) * int64_t(end.radius);
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate minimum path length cheaply without having to calculate center of an arc and it arc length.
|
||||
// Used for caching a smooth path chunk that is certainly longer than a threshold.
|
||||
inline int64_t estimate_path_length(const Path &path)
|
||||
{
|
||||
int64_t len = 0;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
len += Geometry::ArcWelder::estimate_min_segment_length(path[i - 1], path[i]);
|
||||
return len;
|
||||
}
|
||||
|
||||
void reverse(Path &path);
|
||||
|
||||
// Clip start / end of a smooth path by len.
|
||||
// If path is shorter than len, remaining path length to trim will be returned.
|
||||
double clip_start(Path &path, const double len);
|
||||
double clip_end(Path &path, const double len);
|
||||
|
||||
struct PathSegmentProjection
|
||||
{
|
||||
// Start segment of a projection on the path.
|
||||
size_t segment_id { std::numeric_limits<size_t>::max() };
|
||||
// Projection of the point on the segment.
|
||||
Point point { 0, 0 };
|
||||
// If the point lies on an arc, the arc center is cached here.
|
||||
Point center { 0, 0 };
|
||||
// Square of a distance of the projection.
|
||||
double distance2 { std::numeric_limits<double>::max() };
|
||||
|
||||
bool valid() const { return this->segment_id != std::numeric_limits<size_t>::max(); }
|
||||
};
|
||||
// Returns closest segment and a parameter along the closest segment of a path to a point.
|
||||
PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2 = std::numeric_limits<double>::max());
|
||||
// Split a path into two paths at a segment point. Snap to an existing point if the projection of "point is closer than min_segment_length.
|
||||
std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length);
|
||||
// Split a path into two paths at a point closest to "point". Snap to an existing point if the projection of "point is closer than min_segment_length.
|
||||
std::pair<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length);
|
||||
|
||||
} } } // namespace Slic3r::Geometry::ArcWelder
|
||||
|
||||
#endif // slic3r_Geometry_ArcWelder_hpp_
|
@ -17,9 +17,14 @@ Point circle_center_taubin_newton(const Points::const_iterator& input_begin, con
|
||||
return Point::new_scale(center.x(), center.y());
|
||||
}
|
||||
|
||||
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
|
||||
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
|
||||
/// lie on.
|
||||
// Robust and accurate algebraic circle fit, which works well even if data points are observed only within a small arc.
|
||||
// The method was proposed by G. Taubin in
|
||||
// "Estimation Of Planar Curves, Surfaces And Nonplanar Space Curves Defined By Implicit Equations,
|
||||
// With Applications To Edge And Range Image Segmentation", IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991)."
|
||||
// This particular implementation was adapted from
|
||||
// "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126"
|
||||
// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
|
||||
// lie on.
|
||||
Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
// calculate the centroid of the data set
|
||||
|
@ -9,10 +9,17 @@ namespace Slic3r { namespace Geometry {
|
||||
|
||||
// https://en.wikipedia.org/wiki/Circumscribed_circle
|
||||
// Circumcenter coordinates, Cartesian coordinates
|
||||
template<typename Vector>
|
||||
Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, typename Vector::Scalar epsilon)
|
||||
// In case the three points are collinear, returns their centroid.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "circle_center(): 1st point is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "circle_center(): 2nd point is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "circle_center(): 3rd point is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
|
||||
"circle_center(): All three points must be of the same type.");
|
||||
using Scalar = typename Derived::Scalar;
|
||||
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
|
||||
Vector b = bsrc - a;
|
||||
Vector c = csrc - a;
|
||||
Scalar lb = b.squaredNorm();
|
||||
@ -30,6 +37,32 @@ Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, ty
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Circumscribed_circle
|
||||
// Circumcenter coordinates, Cartesian coordinates
|
||||
// Returns no value if the three points are collinear.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
std::optional<Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign>> try_circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon)
|
||||
{
|
||||
static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "try_circle_center(): 1st point is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "try_circle_center(): 2nd point is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "try_circle_center(): 3rd point is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
|
||||
"try_circle_center(): All three points must be of the same type.");
|
||||
using Scalar = typename Derived::Scalar;
|
||||
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
|
||||
Vector b = bsrc - a;
|
||||
Vector c = csrc - a;
|
||||
Scalar lb = b.squaredNorm();
|
||||
Scalar lc = c.squaredNorm();
|
||||
if (Scalar d = b.x() * c.y() - b.y() * c.x(); std::abs(d) < epsilon) {
|
||||
// The three points are collinear.
|
||||
return {};
|
||||
} else {
|
||||
Vector v = lc * b - lb * c;
|
||||
return std::make_optional<Vector>(a + Vector(- v.y(), v.x()) / (2 * d));
|
||||
}
|
||||
}
|
||||
|
||||
// 2D circle defined by its center and squared radius
|
||||
template<typename Vector>
|
||||
struct CircleSq {
|
||||
@ -65,7 +98,7 @@ struct Circle {
|
||||
Vector center;
|
||||
Scalar radius;
|
||||
|
||||
Circle() {}
|
||||
Circle() = default;
|
||||
Circle(const Vector ¢er, const Scalar radius) : center(center), radius(radius) {}
|
||||
Circle(const Vector &a, const Vector &b) : center(Scalar(0.5) * (a + b)) { radius = (a - center).norm(); }
|
||||
Circle(const Vector &a, const Vector &b, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); }
|
||||
|
@ -72,7 +72,7 @@ public:
|
||||
int get_num_of_planes() const;
|
||||
const std::vector<int>& get_plane_triangle_indices(int idx) const;
|
||||
const std::vector<SurfaceFeature>& get_plane_features(unsigned int plane_id);
|
||||
const TriangleMesh& get_mesh() const;
|
||||
const indexed_triangle_set& get_its() const;
|
||||
|
||||
private:
|
||||
void update_planes();
|
||||
@ -80,7 +80,7 @@ private:
|
||||
|
||||
std::vector<PlaneData> m_planes;
|
||||
std::vector<size_t> m_face_to_plane;
|
||||
TriangleMesh m_mesh;
|
||||
indexed_triangle_set m_its;
|
||||
};
|
||||
|
||||
|
||||
@ -89,7 +89,7 @@ private:
|
||||
|
||||
|
||||
MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
|
||||
: m_mesh(its)
|
||||
: m_its(its)
|
||||
{
|
||||
update_planes();
|
||||
|
||||
@ -104,14 +104,12 @@ MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
|
||||
|
||||
void MeasuringImpl::update_planes()
|
||||
{
|
||||
m_planes.clear();
|
||||
|
||||
// Now we'll go through all the facets and append Points of facets sharing the same normal.
|
||||
// This part is still performed in mesh coordinate system.
|
||||
const size_t num_of_facets = m_mesh.its.indices.size();
|
||||
const size_t num_of_facets = m_its.indices.size();
|
||||
m_face_to_plane.resize(num_of_facets, size_t(-1));
|
||||
const std::vector<Vec3f> face_normals = its_face_normals(m_mesh.its);
|
||||
const std::vector<Vec3i> face_neighbors = its_face_neighbors(m_mesh.its);
|
||||
const std::vector<Vec3f> face_normals = its_face_normals(m_its);
|
||||
const std::vector<Vec3i> face_neighbors = its_face_neighbors(m_its);
|
||||
std::vector<int> facet_queue(num_of_facets, 0);
|
||||
int facet_queue_cnt = 0;
|
||||
const stl_normal* normal_ptr = nullptr;
|
||||
@ -121,6 +119,10 @@ void MeasuringImpl::update_planes()
|
||||
return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001);
|
||||
};
|
||||
|
||||
m_planes.clear();
|
||||
m_planes.reserve(num_of_facets / 5); // empty plane data object is quite lightweight, let's save the initial reallocations
|
||||
|
||||
|
||||
// First go through all the triangles and fill in m_planes vector. For each "plane"
|
||||
// detected on the model, it will contain list of facets that are part of it.
|
||||
// We will also fill in m_face_to_plane, which contains index into m_planes
|
||||
@ -132,7 +134,7 @@ void MeasuringImpl::update_planes()
|
||||
facet_queue[facet_queue_cnt ++] = seed_facet_idx;
|
||||
normal_ptr = &face_normals[seed_facet_idx];
|
||||
m_face_to_plane[seed_facet_idx] = m_planes.size();
|
||||
m_planes.emplace_back();
|
||||
m_planes.emplace_back();
|
||||
break;
|
||||
}
|
||||
if (seed_facet_idx == num_of_facets)
|
||||
@ -160,16 +162,21 @@ void MeasuringImpl::update_planes()
|
||||
assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); }));
|
||||
|
||||
// Now we will walk around each of the planes and save vertices which form the border.
|
||||
SurfaceMesh sm(m_mesh.its);
|
||||
for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) {
|
||||
const auto& facets = m_planes[plane_id].facets;
|
||||
m_planes[plane_id].borders.clear();
|
||||
const SurfaceMesh sm(m_its);
|
||||
|
||||
const auto& face_to_plane = m_face_to_plane;
|
||||
auto& planes = m_planes;
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_planes.size()),
|
||||
[&planes, &face_to_plane, &face_neighbors, &sm](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t plane_id = range.begin(); plane_id != range.end(); ++plane_id) {
|
||||
|
||||
const auto& facets = planes[plane_id].facets;
|
||||
planes[plane_id].borders.clear();
|
||||
std::vector<std::array<bool, 3>> visited(facets.size(), {false, false, false});
|
||||
|
||||
|
||||
|
||||
for (int face_id=0; face_id<int(facets.size()); ++face_id) {
|
||||
assert(m_face_to_plane[facets[face_id]] == plane_id);
|
||||
assert(face_to_plane[facets[face_id]] == plane_id);
|
||||
|
||||
for (int edge_id=0; edge_id<3; ++edge_id) {
|
||||
// Every facet's edge which has a neighbor from a different plane is
|
||||
@ -177,7 +184,7 @@ void MeasuringImpl::update_planes()
|
||||
int neighbor_idx = face_neighbors[facets[face_id]][edge_id];
|
||||
if (neighbor_idx == -1)
|
||||
goto PLANE_FAILURE;
|
||||
if (visited[face_id][edge_id] || (int)m_face_to_plane[neighbor_idx] == plane_id) {
|
||||
if (visited[face_id][edge_id] || (int)face_to_plane[neighbor_idx] == plane_id) {
|
||||
visited[face_id][edge_id] = true;
|
||||
continue;
|
||||
}
|
||||
@ -188,8 +195,9 @@ void MeasuringImpl::update_planes()
|
||||
|
||||
// he is the first halfedge on the border. Now walk around and append the points.
|
||||
//const Halfedge_index he_orig = he;
|
||||
m_planes[plane_id].borders.emplace_back();
|
||||
std::vector<Vec3d>& last_border = m_planes[plane_id].borders.back();
|
||||
planes[plane_id].borders.emplace_back();
|
||||
std::vector<Vec3d>& last_border = planes[plane_id].borders.back();
|
||||
last_border.reserve(4);
|
||||
last_border.emplace_back(sm.point(sm.source(he)).cast<double>());
|
||||
//Vertex_index target = sm.target(he);
|
||||
const Halfedge_index he_start = he;
|
||||
@ -210,7 +218,7 @@ void MeasuringImpl::update_planes()
|
||||
// Remember all halfedges we saw to break out of such infinite loops.
|
||||
boost::container::small_vector<Halfedge_index, 10> he_seen;
|
||||
|
||||
while ( (int)m_face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
|
||||
while ( (int)face_to_plane[sm.face(he)] == plane_id && he != he_orig) {
|
||||
he_seen.emplace_back(he);
|
||||
he = sm.next_around_target(he);
|
||||
if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end())
|
||||
@ -241,7 +249,7 @@ void MeasuringImpl::update_planes()
|
||||
} while (he != he_start);
|
||||
|
||||
if (last_border.size() == 1)
|
||||
m_planes[plane_id].borders.pop_back();
|
||||
planes[plane_id].borders.pop_back();
|
||||
else {
|
||||
assert(last_border.front() == last_border.back());
|
||||
last_border.pop_back();
|
||||
@ -251,8 +259,9 @@ void MeasuringImpl::update_planes()
|
||||
continue; // There was no failure.
|
||||
|
||||
PLANE_FAILURE:
|
||||
m_planes[plane_id].borders.clear();
|
||||
}
|
||||
planes[plane_id].borders.clear();
|
||||
}});
|
||||
m_planes.shrink_to_fit();
|
||||
}
|
||||
|
||||
|
||||
@ -581,9 +590,9 @@ const std::vector<SurfaceFeature>& MeasuringImpl::get_plane_features(unsigned in
|
||||
return m_planes[plane_id].surface_features;
|
||||
}
|
||||
|
||||
const TriangleMesh& MeasuringImpl::get_mesh() const
|
||||
const indexed_triangle_set& MeasuringImpl::get_its() const
|
||||
{
|
||||
return this->m_mesh;
|
||||
return this->m_its;
|
||||
}
|
||||
|
||||
|
||||
@ -626,9 +635,9 @@ const std::vector<SurfaceFeature>& Measuring::get_plane_features(unsigned int pl
|
||||
return priv->get_plane_features(plane_id);
|
||||
}
|
||||
|
||||
const TriangleMesh& Measuring::get_mesh() const
|
||||
const indexed_triangle_set& Measuring::get_its() const
|
||||
{
|
||||
return priv->get_mesh();
|
||||
return priv->get_its();
|
||||
}
|
||||
|
||||
const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true };
|
||||
|
@ -109,7 +109,7 @@ public:
|
||||
const std::vector<SurfaceFeature>& get_plane_features(unsigned int plane_id) const;
|
||||
|
||||
// Returns the mesh used for measuring
|
||||
const TriangleMesh& get_mesh() const;
|
||||
const indexed_triangle_set& get_its() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<MeasuringImpl> priv;
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
#include "SVG.hpp"
|
||||
#include <Eigen/Dense>
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -103,100 +103,6 @@ bool MultiPoint::remove_duplicate_points()
|
||||
return false;
|
||||
}
|
||||
|
||||
Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance)
|
||||
{
|
||||
Points result_pts;
|
||||
auto tolerance_sq = int64_t(sqr(tolerance));
|
||||
if (! pts.empty()) {
|
||||
const Point *anchor = &pts.front();
|
||||
size_t anchor_idx = 0;
|
||||
const Point *floater = &pts.back();
|
||||
size_t floater_idx = pts.size() - 1;
|
||||
result_pts.reserve(pts.size());
|
||||
result_pts.emplace_back(*anchor);
|
||||
if (anchor_idx != floater_idx) {
|
||||
assert(pts.size() > 1);
|
||||
std::vector<size_t> dpStack;
|
||||
dpStack.reserve(pts.size());
|
||||
dpStack.emplace_back(floater_idx);
|
||||
for (;;) {
|
||||
int64_t max_dist_sq = 0;
|
||||
size_t furthest_idx = anchor_idx;
|
||||
// find point furthest from line seg created by (anchor, floater) and note it
|
||||
{
|
||||
const Point a = *anchor;
|
||||
const Point f = *floater;
|
||||
const Vec2i64 v = (f - a).cast<int64_t>();
|
||||
if (const int64_t l2 = v.squaredNorm(); l2 == 0) {
|
||||
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i)
|
||||
if (int64_t dist_sq = (pts[i] - a).cast<int64_t>().squaredNorm(); dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest_idx = i;
|
||||
}
|
||||
} else {
|
||||
const double dl2 = double(l2);
|
||||
const Vec2d dv = v.cast<double>();
|
||||
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) {
|
||||
const Point p = pts[i];
|
||||
const Vec2i64 va = (p - a).template cast<int64_t>();
|
||||
const int64_t t = va.dot(v);
|
||||
int64_t dist_sq;
|
||||
if (t <= 0) {
|
||||
dist_sq = va.squaredNorm();
|
||||
} else if (t >= l2) {
|
||||
dist_sq = (p - f).cast<int64_t>().squaredNorm();
|
||||
} else {
|
||||
const Vec2i64 w = ((double(t) / dl2) * dv).cast<int64_t>();
|
||||
dist_sq = (w - va).squaredNorm();
|
||||
}
|
||||
if (dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove point if less than tolerance
|
||||
if (max_dist_sq <= tolerance_sq) {
|
||||
result_pts.emplace_back(*floater);
|
||||
anchor_idx = floater_idx;
|
||||
anchor = floater;
|
||||
assert(dpStack.back() == floater_idx);
|
||||
dpStack.pop_back();
|
||||
if (dpStack.empty())
|
||||
break;
|
||||
floater_idx = dpStack.back();
|
||||
} else {
|
||||
floater_idx = furthest_idx;
|
||||
dpStack.emplace_back(floater_idx);
|
||||
}
|
||||
floater = &pts[floater_idx];
|
||||
}
|
||||
}
|
||||
assert(result_pts.front() == pts.front());
|
||||
assert(result_pts.back() == pts.back());
|
||||
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
BoundingBox bbox(pts);
|
||||
BoundingBox bbox2(result_pts);
|
||||
bbox.merge(bbox2);
|
||||
SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox);
|
||||
if (pts.front() == pts.back())
|
||||
svg.draw(Polygon(pts), "black");
|
||||
else
|
||||
svg.draw(Polyline(pts), "black");
|
||||
if (result_pts.front() == result_pts.back())
|
||||
svg.draw(Polygon(result_pts), "green", scale_(0.1));
|
||||
else
|
||||
svg.draw(Polyline(result_pts), "green", scale_(0.1));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return result_pts;
|
||||
}
|
||||
|
||||
// Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825
|
||||
// thanks to @fuchstraumer
|
||||
/*
|
||||
@ -219,7 +125,7 @@ struct vis_node{
|
||||
// other node if it's area is less than the other node's area
|
||||
bool operator<(const vis_node& other) { return (this->area < other.area); }
|
||||
};
|
||||
Points MultiPoint::visivalingam(const Points& pts, const double& tolerance)
|
||||
Points MultiPoint::visivalingam(const Points &pts, const double tolerance)
|
||||
{
|
||||
// Make sure there's enough points in "pts" to bother with simplification.
|
||||
assert(pts.size() >= 2);
|
||||
|
@ -12,6 +12,123 @@ namespace Slic3r {
|
||||
class BoundingBox;
|
||||
class BoundingBox3;
|
||||
|
||||
// Reduces polyline in the <begin, end) range, outputs into the output iterator.
|
||||
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
|
||||
template<typename SquareLengthType, typename InputIterator, typename OutputIterator, typename PointGetter>
|
||||
inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter)
|
||||
{
|
||||
using InputIteratorCategory = typename std::iterator_traits<InputIterator>::iterator_category;
|
||||
static_assert(std::is_base_of_v<std::input_iterator_tag, InputIteratorCategory>);
|
||||
using Vector = Eigen::Matrix<SquareLengthType, 2, 1, Eigen::DontAlign>;
|
||||
if (begin != end) {
|
||||
// Supporting in-place reduction and the data type may be generic, thus we are always making a copy of the point value before there is a chance
|
||||
// to override input by moving the data to the output.
|
||||
auto a = point_getter(*begin);
|
||||
*out ++ = std::move(*begin);
|
||||
if (auto next = std::next(begin); next == end) {
|
||||
// Single point input only.
|
||||
} else if (std::next(next) == end) {
|
||||
// Two points input.
|
||||
*out ++ = std::move(*next);
|
||||
} else {
|
||||
const auto tolerance_sq = SquareLengthType(sqr(tolerance));
|
||||
InputIterator anchor = begin;
|
||||
InputIterator floater = std::prev(end);
|
||||
std::vector<InputIterator> dpStack;
|
||||
if constexpr (std::is_base_of_v<std::random_access_iterator_tag, InputIteratorCategory>)
|
||||
dpStack.reserve(end - begin);
|
||||
dpStack.emplace_back(floater);
|
||||
auto f = point_getter(*floater);
|
||||
for (;;) {
|
||||
assert(anchor != floater);
|
||||
bool take_floater = false;
|
||||
InputIterator furthest = anchor;
|
||||
if (std::next(anchor) == floater) {
|
||||
// Two point segment. Accept the floater.
|
||||
take_floater = true;
|
||||
} else {
|
||||
SquareLengthType max_dist_sq = 0;
|
||||
// Find point furthest from line seg created by (anchor, floater) and note it.
|
||||
const Vector v = (f - a).template cast<SquareLengthType>();
|
||||
if (const SquareLengthType l2 = v.squaredNorm(); l2 == 0) {
|
||||
// Zero length segment, find the furthest point between anchor and floater.
|
||||
for (auto it = std::next(anchor); it != floater; ++ it)
|
||||
if (SquareLengthType dist_sq = (point_getter(*it) - a).template cast<SquareLengthType>().squaredNorm();
|
||||
dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest = it;
|
||||
}
|
||||
} else {
|
||||
// Find Find the furthest point from the line <anchor, floater>.
|
||||
const double dl2 = double(l2);
|
||||
const Vec2d dv = v.template cast<double>();
|
||||
for (auto it = std::next(anchor); it != floater; ++ it) {
|
||||
const auto p = point_getter(*it);
|
||||
const Vector va = (p - a).template cast<SquareLengthType>();
|
||||
const SquareLengthType t = va.dot(v);
|
||||
SquareLengthType dist_sq;
|
||||
if (t <= 0) {
|
||||
dist_sq = va.squaredNorm();
|
||||
} else if (t >= l2) {
|
||||
dist_sq = (p - f).template cast<SquareLengthType>().squaredNorm();
|
||||
} else if (double dt = double(t) / dl2; dt <= 0) {
|
||||
dist_sq = va.squaredNorm();
|
||||
} else if (dt >= 1.) {
|
||||
dist_sq = (p - f).template cast<SquareLengthType>().squaredNorm();
|
||||
} else {
|
||||
const Vector w = (dt * dv).cast<SquareLengthType>();
|
||||
dist_sq = (w - va).squaredNorm();
|
||||
}
|
||||
if (dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove point if less than tolerance
|
||||
take_floater = max_dist_sq <= tolerance_sq;
|
||||
}
|
||||
if (take_floater) {
|
||||
// The points between anchor and floater are close to the <anchor, floater> line.
|
||||
// Drop the points between them.
|
||||
a = f;
|
||||
*out ++ = std::move(*floater);
|
||||
anchor = floater;
|
||||
assert(dpStack.back() == floater);
|
||||
dpStack.pop_back();
|
||||
if (dpStack.empty())
|
||||
break;
|
||||
floater = dpStack.back();
|
||||
f = point_getter(*floater);
|
||||
} else {
|
||||
// The furthest point is too far from the segment <anchor, floater>.
|
||||
// Divide recursively.
|
||||
floater = furthest;
|
||||
f = point_getter(*floater);
|
||||
dpStack.emplace_back(floater);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Reduces polyline in the <begin, end) range, outputs into the output iterator.
|
||||
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
|
||||
template<typename OutputIterator>
|
||||
inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance)
|
||||
{
|
||||
return douglas_peucker<int64_t>(begin, end, out, tolerance, [](const Point &p) { return p; });
|
||||
}
|
||||
|
||||
inline Points douglas_peucker(const Points &src, const double tolerance)
|
||||
{
|
||||
Points out;
|
||||
out.reserve(src.size());
|
||||
douglas_peucker(src.begin(), src.end(), std::back_inserter(out), tolerance);
|
||||
return out;
|
||||
}
|
||||
|
||||
class MultiPoint
|
||||
{
|
||||
public:
|
||||
@ -81,8 +198,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static Points douglas_peucker(const Points &points, const double tolerance);
|
||||
static Points visivalingam(const Points& pts, const double& tolerance);
|
||||
static Points douglas_peucker(const Points &src, const double tolerance) { return Slic3r::douglas_peucker(src, tolerance); }
|
||||
static Points visivalingam(const Points &src, const double tolerance);
|
||||
|
||||
inline auto begin() { return points.begin(); }
|
||||
inline auto begin() const { return points.begin(); }
|
||||
@ -119,16 +236,20 @@ extern BoundingBox get_extents(const MultiPoint &mp);
|
||||
extern BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||
extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
|
||||
|
||||
inline double length(const Points &pts) {
|
||||
inline double length(const Points::const_iterator begin, const Points::const_iterator end) {
|
||||
double total = 0;
|
||||
if (! pts.empty()) {
|
||||
auto it = pts.begin();
|
||||
for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev)
|
||||
if (begin != end) {
|
||||
auto it = begin;
|
||||
for (auto it_prev = it ++; it != end; ++ it, ++ it_prev)
|
||||
total += (*it - *it_prev).cast<double>().norm();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
inline double length(const Points &pts) {
|
||||
return length(pts.begin(), pts.end());
|
||||
}
|
||||
|
||||
inline double area(const Points &polygon) {
|
||||
double area = 0.;
|
||||
for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++)
|
||||
|
@ -119,20 +119,18 @@ ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickP
|
||||
|
||||
const double w = fmax(line.a_width, line.b_width);
|
||||
const Flow new_flow = (role.is_bridge() && flow.bridge()) ? flow : flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
|
||||
if (path.polyline.points.empty()) {
|
||||
path.polyline.append(line.a);
|
||||
path.polyline.append(line.b);
|
||||
if (path.empty()) {
|
||||
// Convert from spacing to extrusion width based on the extrusion model
|
||||
// of a square extrusion ended with semi circles.
|
||||
path = { ExtrusionAttributes{ path.role(), new_flow } };
|
||||
path.polyline.append(line.a);
|
||||
path.polyline.append(line.b);
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf(" filling %f gap\n", flow.width);
|
||||
#endif
|
||||
path.mm3_per_mm = new_flow.mm3_per_mm();
|
||||
path.width = new_flow.width();
|
||||
path.height = new_flow.height();
|
||||
} else {
|
||||
assert(path.width >= EPSILON);
|
||||
thickness_delta = scaled<double>(fabs(path.width - new_flow.width()));
|
||||
assert(path.width() >= EPSILON);
|
||||
thickness_delta = scaled<double>(fabs(path.width() - new_flow.width()));
|
||||
if (thickness_delta <= merge_tolerance) {
|
||||
// the width difference between this line and the current flow
|
||||
// (of the previous line) width is within the accepted tolerance
|
||||
@ -325,10 +323,12 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
|
||||
extrusion_paths_append(
|
||||
paths,
|
||||
intersection_pl({ polygon }, lower_slices_polygons_clipped),
|
||||
role_normal,
|
||||
is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
|
||||
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
|
||||
float(params.layer_height));
|
||||
ExtrusionAttributes{
|
||||
role_normal,
|
||||
ExtrusionFlow{ is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
|
||||
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
|
||||
float(params.layer_height)
|
||||
} });
|
||||
|
||||
// get overhang paths by checking what parts of this loop fall
|
||||
// outside the grown lower slices (thus where the distance between
|
||||
@ -336,23 +336,26 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
|
||||
extrusion_paths_append(
|
||||
paths,
|
||||
diff_pl({ polygon }, lower_slices_polygons_clipped),
|
||||
role_overhang,
|
||||
params.mm3_per_mm_overhang,
|
||||
params.overhang_flow.width(),
|
||||
params.overhang_flow.height());
|
||||
ExtrusionAttributes{
|
||||
role_overhang,
|
||||
ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() }
|
||||
});
|
||||
|
||||
// Reapply the nearest point search for starting point.
|
||||
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
|
||||
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
|
||||
} else {
|
||||
ExtrusionPath path(role_normal);
|
||||
path.polyline = polygon.split_at_first_point();
|
||||
path.mm3_per_mm = is_external ? params.ext_mm3_per_mm : params.mm3_per_mm;
|
||||
path.width = is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width();
|
||||
path.height = float(params.layer_height);
|
||||
paths.push_back(path);
|
||||
paths.emplace_back(polygon.split_at_first_point(),
|
||||
ExtrusionAttributes{
|
||||
role_normal,
|
||||
ExtrusionFlow{
|
||||
is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
|
||||
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
|
||||
float(params.layer_height)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
coll.append(ExtrusionLoop(std::move(paths), loop_role));
|
||||
}
|
||||
|
||||
@ -383,11 +386,13 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
|
||||
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
|
||||
coll.entities[idx.first] = nullptr;
|
||||
if (loop.is_contour) {
|
||||
eloop->make_counter_clockwise();
|
||||
if (eloop->is_clockwise())
|
||||
eloop->reverse_loop();
|
||||
out.append(std::move(children.entities));
|
||||
out.entities.emplace_back(eloop);
|
||||
} else {
|
||||
eloop->make_clockwise();
|
||||
if (eloop->is_counter_clockwise())
|
||||
eloop->reverse_loop();
|
||||
out.entities.emplace_back(eloop);
|
||||
out.append(std::move(children.entities));
|
||||
}
|
||||
@ -603,10 +608,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
|
||||
if (extrusion->is_closed) {
|
||||
ExtrusionLoop extrusion_loop(std::move(paths));
|
||||
// Restore the orientation of the extrusion loop.
|
||||
if (pg_extrusion.is_contour)
|
||||
extrusion_loop.make_counter_clockwise();
|
||||
else
|
||||
extrusion_loop.make_clockwise();
|
||||
if (pg_extrusion.is_contour == extrusion_loop.is_clockwise())
|
||||
extrusion_loop.reverse_loop();
|
||||
|
||||
for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) {
|
||||
assert(it->polyline.points.size() >= 2);
|
||||
@ -960,11 +963,9 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
|
||||
if (perimeter_polygon.empty()) { // fill possible gaps of single extrusion width
|
||||
Polygons shrinked = intersection(offset(prev, -0.3 * overhang_flow.scaled_spacing()), expanded_overhang_to_cover);
|
||||
if (!shrinked.empty()) {
|
||||
if (!shrinked.empty())
|
||||
extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
|
||||
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
|
||||
overhang_flow.height());
|
||||
}
|
||||
ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow });
|
||||
|
||||
Polylines fills;
|
||||
ExPolygons gap = shrinked.empty() ? offset_ex(prev, overhang_flow.scaled_spacing() * 0.5) : to_expolygons(shrinked);
|
||||
@ -975,14 +976,12 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
|
||||
if (!fills.empty()) {
|
||||
fills = intersection_pl(fills, shrinked_overhang_to_cover);
|
||||
extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()),
|
||||
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
|
||||
overhang_flow.height());
|
||||
ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow });
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
|
||||
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
|
||||
overhang_flow.height());
|
||||
ExtrusionAttributes{ExtrusionRole::OverhangPerimeter, overhang_flow });
|
||||
}
|
||||
|
||||
if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; }
|
||||
|
@ -47,14 +47,12 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
|
||||
|
||||
void Point::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
double cur_x = (double)(*this)(0);
|
||||
double cur_y = (double)(*this)(1);
|
||||
double s = ::sin(angle);
|
||||
double c = ::cos(angle);
|
||||
double dx = cur_x - (double)center(0);
|
||||
double dy = cur_y - (double)center(1);
|
||||
(*this)(0) = (coord_t)round( (double)center(0) + c * dx - s * dy );
|
||||
(*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx );
|
||||
Vec2d cur = this->cast<double>();
|
||||
double s = ::sin(angle);
|
||||
double c = ::cos(angle);
|
||||
auto d = cur - center.cast<double>();
|
||||
this->x() = fast_round_up<coord_t>(center.x() + c * d.x() - s * d.y());
|
||||
this->y() = fast_round_up<coord_t>(center.y() + s * d.x() + c * d.y());
|
||||
}
|
||||
|
||||
bool has_duplicate_points(Points &&pts)
|
||||
|
@ -166,11 +166,12 @@ public:
|
||||
Point(const Point &rhs) { *this = rhs; }
|
||||
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {}
|
||||
// This constructor allows you to construct Point from Eigen expressions
|
||||
// This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions.
|
||||
template<typename OtherDerived>
|
||||
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
|
||||
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
|
||||
static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
|
||||
static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
|
||||
template<typename OtherDerived>
|
||||
static Point new_scale(const Eigen::MatrixBase<OtherDerived> &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
|
||||
|
||||
// This method allows you to assign Eigen expressions to MyVectorType
|
||||
template<typename OtherDerived>
|
||||
|
@ -146,8 +146,10 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
|
||||
}
|
||||
|
||||
if (this->points.front() == point) {
|
||||
//FIXME why is p1 NOT empty as in the case above?
|
||||
*p1 = { point };
|
||||
*p2 = *this;
|
||||
return;
|
||||
}
|
||||
|
||||
auto min_dist2 = std::numeric_limits<double>::max();
|
||||
|
@ -460,7 +460,8 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
|
||||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "arc_fitting", "arc_fitting_tolerance",
|
||||
"wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
|
||||
"mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
|
||||
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
|
||||
|
@ -906,20 +906,31 @@ void Print::process()
|
||||
name_tbb_thread_pool_threads_set_locale();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->make_perimeters();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->infill();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->ironing();
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_objects.size(), 1), [this](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t idx = range.begin(); idx < range.end(); ++idx) {
|
||||
m_objects[idx]->make_perimeters();
|
||||
m_objects[idx]->infill();
|
||||
m_objects[idx]->ironing();
|
||||
}
|
||||
}, tbb::simple_partitioner());
|
||||
|
||||
// The following step writes to m_shared_regions, it should not run in parallel.
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->generate_support_spots();
|
||||
// check data from previous step, format the error message(s) and send alert to ui
|
||||
// this also has to be done sequentially.
|
||||
alert_when_supports_needed();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->generate_support_material();
|
||||
for (PrintObject *obj : m_objects)
|
||||
obj->estimate_curled_extrusions();
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_objects.size(), 1), [this](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t idx = range.begin(); idx < range.end(); ++idx) {
|
||||
PrintObject &obj = *m_objects[idx];
|
||||
obj.generate_support_material();
|
||||
obj.estimate_curled_extrusions();
|
||||
obj.calculate_overhanging_perimeters();
|
||||
}
|
||||
}, tbb::simple_partitioner());
|
||||
|
||||
if (this->set_started(psWipeTower)) {
|
||||
m_wipe_tower_data.clear();
|
||||
m_tool_ordering.clear();
|
||||
@ -1003,7 +1014,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
this->set_status(90, message);
|
||||
|
||||
// Create GCode on heap, it has quite a lot of data.
|
||||
std::unique_ptr<GCode> gcode(new GCode);
|
||||
std::unique_ptr<GCodeGenerator> gcode(new GCodeGenerator);
|
||||
gcode->do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
|
||||
if (m_conflict_result.has_value())
|
||||
@ -1119,13 +1130,15 @@ void Print::_make_skirt()
|
||||
}
|
||||
// Extrude the skirt loop.
|
||||
ExtrusionLoop eloop(elrSkirt);
|
||||
eloop.paths.emplace_back(ExtrusionPath(
|
||||
ExtrusionPath(
|
||||
eloop.paths.emplace_back(
|
||||
ExtrusionAttributes{
|
||||
ExtrusionRole::Skirt,
|
||||
(float)mm3_per_mm, // this will be overridden at G-code export time
|
||||
flow.width(),
|
||||
(float)first_layer_height // this will be overridden at G-code export time
|
||||
)));
|
||||
ExtrusionFlow{
|
||||
float(mm3_per_mm), // this will be overridden at G-code export time
|
||||
flow.width(),
|
||||
float(first_layer_height) // this will be overridden at G-code export time
|
||||
}
|
||||
});
|
||||
eloop.paths.back().polyline = loop.split_at_first_point();
|
||||
m_skirt.append(eloop);
|
||||
if (m_config.min_skirt_length.value > 0) {
|
||||
@ -1653,8 +1666,8 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
|
||||
}
|
||||
|
||||
|
||||
ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh);
|
||||
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner };
|
||||
ExtrusionPath path({ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner },
|
||||
ExtrusionAttributes{ ExtrusionRole::WipeTower, ExtrusionFlow{ 0.0, 0.0, lh } });
|
||||
paths.push_back({ path });
|
||||
|
||||
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
class Layer;
|
||||
class ModelObject;
|
||||
class Print;
|
||||
@ -67,7 +67,7 @@ enum PrintStep : unsigned int {
|
||||
|
||||
enum PrintObjectStep : unsigned int {
|
||||
posSlice, posPerimeters, posPrepareInfill,
|
||||
posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount,
|
||||
posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters, posCount,
|
||||
};
|
||||
|
||||
// A PrintRegion object represents a group of volumes to print
|
||||
@ -376,6 +376,7 @@ private:
|
||||
void generate_support_spots();
|
||||
void generate_support_material();
|
||||
void estimate_curled_extrusions();
|
||||
void calculate_overhanging_perimeters();
|
||||
|
||||
void slice_volumes();
|
||||
// Has any support (not counting the raft).
|
||||
@ -714,7 +715,7 @@ private:
|
||||
Polygons m_sequential_print_clearance_contours;
|
||||
|
||||
// To allow GCode to set the Print's GCodeExport step status.
|
||||
friend class GCode;
|
||||
friend class GCodeGenerator;
|
||||
// To allow GCodeProcessor to emit warnings.
|
||||
friend class GCodeProcessor;
|
||||
// Allow PrintObject to access m_mutex and m_cancel_callback.
|
||||
|
@ -34,6 +34,13 @@ static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values &
|
||||
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values() { return s_keys_map_##NAME; } \
|
||||
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names() { return s_keys_names_##NAME; }
|
||||
|
||||
static const t_config_enum_values s_keys_map_ArcFittingType {
|
||||
{ "disabled", int(ArcFittingType::Disabled) },
|
||||
{ "emit_center", int(ArcFittingType::EmitCenter) },
|
||||
{ "emit_radius", int(ArcFittingType::EmitRadius) }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ArcFittingType)
|
||||
|
||||
static t_config_enum_values s_keys_map_PrinterTechnology {
|
||||
{ "FFF", ptFFF },
|
||||
{ "SLA", ptSLA }
|
||||
@ -397,6 +404,27 @@ void PrintConfigDef::init_fff_params()
|
||||
{
|
||||
ConfigOptionDef* def;
|
||||
|
||||
def = this->add("arc_fitting", coEnum);
|
||||
def->label = L("Arc fitting");
|
||||
def->tooltip = L("Enable this to get a G-code file which has G2 and G3 moves. "
|
||||
"And the fitting tolerance is same with resolution");
|
||||
def->set_enum<ArcFittingType>({
|
||||
{ "disabled", "Disabled" },
|
||||
{ "emit_center", "Enabled: G2/3 I J" },
|
||||
{ "emit_radius", "Enabled: G2/3 R" }
|
||||
});
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<ArcFittingType>(ArcFittingType::Disabled));
|
||||
|
||||
def = this->add("arc_fitting_tolerance", coFloatOrPercent);
|
||||
def->label = L("Arc fitting tolerance");
|
||||
def->sidetext = L("mm or %");
|
||||
def->tooltip = L("When using the arc_fitting option, allow the curve to deviate a cetain % from the collection of strait paths.\n"
|
||||
"Can be a mm value or a percentage of the current extrusion width.");
|
||||
def->mode = comAdvanced;
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloatOrPercent(5, true));
|
||||
|
||||
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
|
||||
const int max_temp = 1500;
|
||||
def = this->add("avoid_crossing_curled_overhangs", coBool);
|
||||
|
@ -30,6 +30,12 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ArcFittingType {
|
||||
Disabled,
|
||||
EmitCenter,
|
||||
EmitRadius,
|
||||
};
|
||||
|
||||
enum GCodeFlavor : unsigned char {
|
||||
gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit,
|
||||
gcfSmoothie, gcfNoExtrusion,
|
||||
@ -141,6 +147,7 @@ enum class GCodeThumbnailsFormat {
|
||||
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
|
||||
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
|
||||
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ArcFittingType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage)
|
||||
@ -671,6 +678,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
PRINT_CONFIG_CLASS_DEFINE(
|
||||
GCodeConfig,
|
||||
|
||||
((ConfigOptionEnum<ArcFittingType>, arc_fitting))
|
||||
((ConfigOptionFloatOrPercent, arc_fitting_tolerance))
|
||||
((ConfigOptionBool, autoemit_temperature_commands))
|
||||
((ConfigOptionString, before_layer_gcode))
|
||||
((ConfigOptionString, between_objects_gcode))
|
||||
|
@ -3,7 +3,9 @@
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "GCode/ExtrusionProcessor.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
@ -533,6 +535,65 @@ void PrintObject::estimate_curled_extrusions()
|
||||
}
|
||||
}
|
||||
|
||||
void PrintObject::calculate_overhanging_perimeters()
|
||||
{
|
||||
if (this->set_started(posCalculateOverhangingPerimeters)) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - start";
|
||||
m_print->set_status(89, _u8L("Calculating overhanging perimeters"));
|
||||
std::vector<unsigned int> extruders;
|
||||
std::unordered_set<const PrintRegion *> regions_with_dynamic_speeds;
|
||||
for (const PrintRegion *pr : this->print()->m_print_regions) {
|
||||
if (pr->config().enable_dynamic_overhang_speeds.getBool()) {
|
||||
regions_with_dynamic_speeds.insert(pr);
|
||||
}
|
||||
extruders.clear();
|
||||
pr->collect_object_printing_extruders(*this->print(), extruders);
|
||||
auto cfg = this->print()->config();
|
||||
if (std::any_of(extruders.begin(), extruders.end(),
|
||||
[&cfg](unsigned int extruder_id) { return cfg.enable_dynamic_fan_speeds.get_at(extruder_id); })) {
|
||||
regions_with_dynamic_speeds.insert(pr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!regions_with_dynamic_speeds.empty()) {
|
||||
std::unordered_map<size_t, AABBTreeLines::LinesDistancer<CurledLine>> curled_lines;
|
||||
std::unordered_map<size_t, AABBTreeLines::LinesDistancer<Linef>> unscaled_polygons_lines;
|
||||
for (const Layer *l : this->layers()) {
|
||||
curled_lines[l->id()] = AABBTreeLines::LinesDistancer<CurledLine>{l->curled_lines};
|
||||
unscaled_polygons_lines[l->id()] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(l->lslices)};
|
||||
}
|
||||
curled_lines[size_t(-1)] = {};
|
||||
unscaled_polygons_lines[size_t(-1)] = {};
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this, &curled_lines, &unscaled_polygons_lines,
|
||||
®ions_with_dynamic_speeds](
|
||||
const tbb::blocked_range<size_t> &range) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
auto l = m_layers[layer_idx];
|
||||
if (l->id() == 0) { // first layer, do not split
|
||||
continue;
|
||||
}
|
||||
for (LayerRegion *layer_region : l->regions()) {
|
||||
if (regions_with_dynamic_speeds.find(layer_region->m_region) == regions_with_dynamic_speeds.end()) {
|
||||
continue;
|
||||
}
|
||||
size_t prev_layer_id = l->lower_layer ? l->lower_layer->id() : size_t(-1);
|
||||
layer_region->m_perimeters =
|
||||
ExtrusionProcessor::calculate_and_split_overhanging_extrusions(&layer_region->m_perimeters,
|
||||
unscaled_polygons_lines[prev_layer_id],
|
||||
curled_lines[l->id()]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - end";
|
||||
}
|
||||
this->set_done(posCalculateOverhangingPerimeters);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const
|
||||
{
|
||||
@ -644,7 +705,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "first_layer_extrusion_width"
|
||||
|| opt_key == "perimeter_extrusion_width"
|
||||
|| opt_key == "infill_overlap"
|
||||
|| opt_key == "external_perimeters_first") {
|
||||
|| opt_key == "external_perimeters_first"
|
||||
|| opt_key == "arc_fitting"
|
||||
|| opt_key == "arc_fitting_tolerance") {
|
||||
steps.emplace_back(posPerimeters);
|
||||
} else if (
|
||||
opt_key == "gap_fill_enabled"
|
||||
@ -845,7 +908,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
||||
|
||||
// propagate to dependent steps
|
||||
if (step == posPerimeters) {
|
||||
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions });
|
||||
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
} else if (step == posPrepareInfill) {
|
||||
invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch});
|
||||
@ -854,7 +917,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
} else if (step == posSlice) {
|
||||
invalidated |= this->invalidate_steps({posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch,
|
||||
posSupportMaterial, posEstimateCurledExtrusions});
|
||||
posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters});
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
m_slicing_params.valid = false;
|
||||
} else if (step == posSupportMaterial) {
|
||||
|
@ -1006,16 +1006,20 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy2(SegmentEndPointFunc
|
||||
return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(const std::vector<ExtrusionEntity*> &entities, const Point *start_near, const bool reversed)
|
||||
{
|
||||
auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
|
||||
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
|
||||
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near);
|
||||
auto segment_end_point = [&entities, reversed](size_t idx, bool first_point) -> const Point& { return first_point == reversed ? entities[idx]->last_point() : entities[idx]->first_point(); };
|
||||
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
|
||||
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(
|
||||
segment_end_point, could_reverse, entities.size(), start_near);
|
||||
for (std::pair<size_t, bool> &segment : out) {
|
||||
ExtrusionEntity *ee = entities[segment.first];
|
||||
if (ee->is_loop())
|
||||
// Ignore reversals for loops, as the start point equals the end point.
|
||||
segment.second = false;
|
||||
else if (reversed)
|
||||
// Input was already reversed.
|
||||
segment.second = ! segment.second;
|
||||
// Is can_reverse() respected by the reversals?
|
||||
assert(ee->can_reverse() || ! segment.second);
|
||||
}
|
||||
@ -1041,6 +1045,33 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
|
||||
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
|
||||
}
|
||||
|
||||
ExtrusionEntityReferences chain_extrusion_references(const std::vector<ExtrusionEntity*> &entities, const Point *start_near, const bool reversed)
|
||||
{
|
||||
const std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(entities, start_near, reversed);
|
||||
ExtrusionEntityReferences out;
|
||||
out.reserve(chain.size());
|
||||
for (const std::pair<size_t, bool> &idx : chain) {
|
||||
assert(entities[idx.first] != nullptr);
|
||||
out.push_back({ *entities[idx.first], idx.second });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollection &eec, const Point *start_near, const bool reversed)
|
||||
{
|
||||
if (eec.no_sort) {
|
||||
ExtrusionEntityReferences out;
|
||||
out.reserve(eec.entities.size());
|
||||
for (const ExtrusionEntity *ee : eec.entities) {
|
||||
assert(ee != nullptr);
|
||||
// Never reverse a loop.
|
||||
out.push_back({ *ee, ! ee->is_loop() && reversed });
|
||||
}
|
||||
return out;
|
||||
} else
|
||||
return chain_extrusion_references(eec.entities, start_near, reversed);
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near)
|
||||
{
|
||||
auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };
|
||||
|
@ -18,13 +18,25 @@ namespace Slic3r {
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
// Used by chain_expolygons()
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
|
||||
// Used to give layer islands a print order.
|
||||
std::vector<size_t> chain_expolygons(const ExPolygons &expolygons, Point *start_near = nullptr);
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||
// Chain extrusion entities by a shortest distance. Returns the ordered extrusions together with a "reverse" flag.
|
||||
// Set input "reversed" to true if the vector of "entities" is to be considered to be reversed once already.
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(const std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr, const bool reversed = false);
|
||||
// Reorder & reverse extrusion entities in place based on the "chain" ordering.
|
||||
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
|
||||
// Reorder & reverse extrusion entities in place.
|
||||
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||
|
||||
// Chain extrusion entities by a shortest distance. Returns the ordered extrusions together with a "reverse" flag.
|
||||
// Set input "reversed" to true if the vector of "entities" is to be considered to be reversed.
|
||||
ExtrusionEntityReferences chain_extrusion_references(const std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr, const bool reversed = false);
|
||||
// The same as above, respect eec.no_sort flag.
|
||||
ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollection &eec, const Point *start_near = nullptr, const bool reversed = false);
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
|
||||
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain);
|
||||
void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
|
||||
|
@ -485,8 +485,7 @@ static inline void fill_expolygon_generate_paths(
|
||||
extrusion_entities_append_paths(
|
||||
dst,
|
||||
std::move(polylines),
|
||||
role,
|
||||
flow.mm3_per_mm(), flow.width(), flow.height());
|
||||
{ role, flow });
|
||||
}
|
||||
|
||||
static inline void fill_expolygons_generate_paths(
|
||||
@ -644,7 +643,7 @@ static inline void tree_supports_generate_paths(
|
||||
ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width());
|
||||
if (level2.size() == 1) {
|
||||
Polylines polylines;
|
||||
extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(),
|
||||
extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow },
|
||||
// Disable reversal of the path, always start with the anchor, always print CCW.
|
||||
false);
|
||||
expoly = level2.front();
|
||||
@ -750,7 +749,7 @@ static inline void tree_supports_generate_paths(
|
||||
}
|
||||
|
||||
ExtrusionEntitiesPtr &out = eec ? eec->entities : dst;
|
||||
extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(),
|
||||
extrusion_entities_append_paths(out, std::move(polylines), { ExtrusionRole::SupportMaterial, flow },
|
||||
// Disable reversal of the path, always start with the anchor, always print CCW.
|
||||
false);
|
||||
if (eec) {
|
||||
@ -794,7 +793,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
|
||||
eec->no_sort = true;
|
||||
}
|
||||
ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst;
|
||||
extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
|
||||
extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow });
|
||||
// Fill in the rest.
|
||||
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
|
||||
if (no_sort && ! eec->empty())
|
||||
@ -1106,7 +1105,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact
|
||||
extrusion_entities_append_paths(
|
||||
top_contact_layer.extrusions,
|
||||
std::move(loop_lines),
|
||||
ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height());
|
||||
{ ExtrusionRole::SupportMaterialInterface, flow });
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
@ -1148,24 +1147,19 @@ static void modulate_extrusion_by_overlapping_layers(
|
||||
ExtrusionPath *extrusion_path_template = dynamic_cast<ExtrusionPath*>(extrusions_in_out.front());
|
||||
assert(extrusion_path_template != nullptr);
|
||||
ExtrusionRole extrusion_role = extrusion_path_template->role();
|
||||
float extrusion_width = extrusion_path_template->width;
|
||||
float extrusion_width = extrusion_path_template->width();
|
||||
|
||||
struct ExtrusionPathFragment
|
||||
{
|
||||
ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {};
|
||||
ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {};
|
||||
|
||||
ExtrusionFlow flow;
|
||||
Polylines polylines;
|
||||
double mm3_per_mm;
|
||||
float width;
|
||||
float height;
|
||||
};
|
||||
|
||||
// Split the extrusions by the overlapping layers, reduce their extrusion rate.
|
||||
// The last path_fragment is from this_layer.
|
||||
std::vector<ExtrusionPathFragment> path_fragments(
|
||||
n_overlapping_layers + 1,
|
||||
ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height));
|
||||
ExtrusionPathFragment{ extrusion_path_template->attributes() });
|
||||
// Don't use it, it will be released.
|
||||
extrusion_path_template = nullptr;
|
||||
|
||||
@ -1242,8 +1236,8 @@ static void modulate_extrusion_by_overlapping_layers(
|
||||
path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming);
|
||||
// Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
|
||||
assert(this_layer.print_z > overlapping_layer.print_z);
|
||||
frag.height = float(this_layer.print_z - overlapping_layer.print_z);
|
||||
frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm();
|
||||
frag.flow.height = float(this_layer.print_z - overlapping_layer.print_z);
|
||||
frag.flow.mm3_per_mm = Flow(frag.flow.width, frag.flow.height, -1.f).mm3_per_mm();
|
||||
#ifdef SLIC3R_DEBUG
|
||||
svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
|
||||
#endif /* SLIC3R_DEBUG */
|
||||
@ -1326,15 +1320,14 @@ static void modulate_extrusion_by_overlapping_layers(
|
||||
ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back();
|
||||
if (path != nullptr) {
|
||||
// Verify whether the path is compatible with the current fragment.
|
||||
assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm);
|
||||
if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) {
|
||||
assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm);
|
||||
if (path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm)
|
||||
path = nullptr;
|
||||
}
|
||||
// Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer.
|
||||
}
|
||||
if (path == nullptr) {
|
||||
// Allocate a new path.
|
||||
multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height));
|
||||
multipath.paths.emplace_back(ExtrusionAttributes{ extrusion_role, frag.flow });
|
||||
path = &multipath.paths.back();
|
||||
}
|
||||
// The Clipper library may flip the order of the clipped polylines arbitrarily.
|
||||
@ -1369,8 +1362,8 @@ static void modulate_extrusion_by_overlapping_layers(
|
||||
}
|
||||
// If there are any non-consumed fragments, add them separately.
|
||||
//FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected.
|
||||
for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment)
|
||||
extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height);
|
||||
for (ExtrusionPathFragment &fragment : path_fragments)
|
||||
extrusion_entities_append_paths(extrusions_in_out, std::move(fragment.polylines), { extrusion_role, fragment.flow });
|
||||
}
|
||||
|
||||
// Support layer that is covered by some form of dense interface.
|
||||
|
@ -1023,7 +1023,7 @@ namespace SupportMaterialInternal {
|
||||
assert(expansion_scaled >= 0.f);
|
||||
for (const ExtrusionPath &ep : loop.paths)
|
||||
if (ep.role() == ExtrusionRole::OverhangPerimeter && ! ep.polyline.empty()) {
|
||||
float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled;
|
||||
float exp = 0.5f * (float)scale_(ep.width()) + expansion_scaled;
|
||||
if (ep.is_closed()) {
|
||||
if (ep.size() >= 3) {
|
||||
// This is a complete loop.
|
||||
|
@ -327,10 +327,10 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) {
|
||||
return {};
|
||||
}
|
||||
const float flow_width = get_flow_width(layer_region, entity->role());
|
||||
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, true, true>(entity->as_polyline().points,
|
||||
prev_layer_boundary, flow_width,
|
||||
params.bridge_distance);
|
||||
const float flow_width = get_flow_width(layer_region, entity->role());
|
||||
std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
|
||||
ExtrusionProcessor::estimate_points_properties<true, true, true, true>(entity->as_polyline().points, prev_layer_boundary,
|
||||
flow_width, params.bridge_distance);
|
||||
|
||||
std::vector<ExtrusionLine> lines_out;
|
||||
lines_out.reserve(annotated_points.size());
|
||||
@ -339,8 +339,8 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
std::optional<Vec2d> bridging_dir{};
|
||||
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
|
||||
SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor :
|
||||
SupportPointCause::LongBridge;
|
||||
@ -382,19 +382,19 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
|
||||
const float flow_width = get_flow_width(layer_region, entity->role());
|
||||
// Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
|
||||
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
|
||||
prev_layer_lines, flow_width,
|
||||
params.bridge_distance);
|
||||
std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
|
||||
ExtrusionProcessor::estimate_points_properties<true, true, false, false>(entity->as_polyline().points, prev_layer_lines,
|
||||
flow_width, params.bridge_distance);
|
||||
|
||||
std::vector<ExtrusionLine> lines_out;
|
||||
lines_out.reserve(annotated_points.size());
|
||||
float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) :
|
||||
0.0f;
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
float line_len = (prev_point.position - curr_point.position).norm();
|
||||
ExtrusionLine line_out{prev_point.position.cast<float>(), curr_point.position.cast<float>(), line_len, entity};
|
||||
ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
float line_len = (prev_point.position - curr_point.position).norm();
|
||||
ExtrusionLine line_out{prev_point.position.cast<float>(), curr_point.position.cast<float>(), line_len, entity};
|
||||
|
||||
Vec2f middle = 0.5 * (line_out.a + line_out.b);
|
||||
auto [middle_distance, bottom_line_idx, x] = prev_layer_lines.distance_from_lines_extra<false>(middle);
|
||||
@ -1107,12 +1107,13 @@ void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width,
|
||||
Polygon pol(pl.points);
|
||||
pol.make_counter_clockwise();
|
||||
|
||||
auto annotated_points = estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines, flow_width);
|
||||
auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines,
|
||||
flow_width);
|
||||
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtendedPoint &b = annotated_points[i];
|
||||
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
|
||||
const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
|
||||
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
|
||||
extrusion};
|
||||
|
||||
Vec2f middle = 0.5 * (line_out.a + line_out.b);
|
||||
@ -1182,11 +1183,13 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms)
|
||||
Points extrusion_pts;
|
||||
extrusion->collect_points(extrusion_pts);
|
||||
float flow_width = get_flow_width(layer_region, extrusion->role());
|
||||
auto annotated_points = estimate_points_properties<true, true, false, false>(extrusion_pts, prev_layer_lines, flow_width,
|
||||
params.bridge_distance);
|
||||
auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(extrusion_pts,
|
||||
prev_layer_lines,
|
||||
flow_width,
|
||||
params.bridge_distance);
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtendedPoint &b = annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
|
||||
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
|
||||
extrusion};
|
||||
|
||||
@ -1196,7 +1199,8 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms)
|
||||
prev_layer_lines.get_line(bottom_line_idx);
|
||||
|
||||
// correctify the distance sign using slice polygons
|
||||
float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f;
|
||||
float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 0.5f * flow_width) < 0.0f ? -1.0f :
|
||||
1.0f;
|
||||
|
||||
line_out.curled_up_height = estimate_curled_up_height(middle_distance * sign, 0.5 * (a.curvature + b.curvature),
|
||||
l->height, flow_width, bottom_line.curled_up_height, params);
|
||||
|
@ -1442,8 +1442,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, flo
|
||||
polyline.remove_duplicate_points();
|
||||
polyline.translate(copy);
|
||||
const Lines lines = polyline.lines();
|
||||
std::vector<double> widths(lines.size(), extrusion_path.width);
|
||||
std::vector<double> heights(lines.size(), extrusion_path.height);
|
||||
std::vector<double> widths(lines.size(), extrusion_path.width());
|
||||
std::vector<double> heights(lines.size(), extrusion_path.height());
|
||||
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
|
||||
}
|
||||
|
||||
@ -1459,8 +1459,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, flo
|
||||
polyline.translate(copy);
|
||||
const Lines lines_this = polyline.lines();
|
||||
append(lines, lines_this);
|
||||
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
|
||||
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
|
||||
widths.insert(widths.end(), lines_this.size(), extrusion_path.width());
|
||||
heights.insert(heights.end(), lines_this.size(), extrusion_path.height());
|
||||
}
|
||||
thick_lines_to_verts(lines, widths, heights, true, print_z, geometry);
|
||||
}
|
||||
@ -1477,8 +1477,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_mult
|
||||
polyline.translate(copy);
|
||||
const Lines lines_this = polyline.lines();
|
||||
append(lines, lines_this);
|
||||
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
|
||||
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
|
||||
widths.insert(widths.end(), lines_this.size(), extrusion_path.width());
|
||||
heights.insert(heights.end(), lines_this.size(), extrusion_path.height());
|
||||
}
|
||||
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
|
||||
}
|
||||
|
@ -92,8 +92,14 @@ void ArrangeSettingsDialogImgui::render(float pos_x, float pos_y)
|
||||
settings.xl_align));
|
||||
}
|
||||
|
||||
// TRN ArrangeDialog
|
||||
if (m_imgui->combo(_L("Geometry handling"),
|
||||
{_u8L("Fast"), _u8L("Balanced"), _u8L("Full complexity")},
|
||||
// TRN ArrangeDialog: Type of "Geometry handling"
|
||||
{_u8L("Fast"),
|
||||
// TRN ArrangeDialog: Type of "Geometry handling"
|
||||
_u8L("Balanced"),
|
||||
// TRN ArrangeDialog: Type of "Geometry handling"
|
||||
_u8L("Accurate")},
|
||||
settings.geom_handling)) {
|
||||
if (settings.geom_handling >= 0 &&
|
||||
settings.geom_handling < ArrangeSettingsView::ghCount)
|
||||
|
@ -341,6 +341,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
toggle_field("min_feature_size", have_arachne);
|
||||
toggle_field("min_bead_width", have_arachne);
|
||||
toggle_field("thin_walls", !have_arachne);
|
||||
|
||||
bool has_arc_fitting = config->opt_enum<ArcFittingType>("arc_fitting") != ArcFittingType::Disabled;
|
||||
toggle_field("arc_fitting_tolerance", has_arc_fitting);
|
||||
}
|
||||
|
||||
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)
|
||||
|
@ -960,8 +960,8 @@ void GUI_App::init_app_config()
|
||||
{
|
||||
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
|
||||
|
||||
SetAppName(SLIC3R_APP_KEY);
|
||||
// SetAppName(SLIC3R_APP_KEY "-alpha");
|
||||
// SetAppName(SLIC3R_APP_KEY);
|
||||
SetAppName(SLIC3R_APP_KEY "-alpha");
|
||||
// SetAppName(SLIC3R_APP_KEY "-beta");
|
||||
|
||||
|
||||
|
@ -356,7 +356,10 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||
const GLVolume* volume = selection.get_first_volume();
|
||||
const double min_z = get_volume_min_z(*volume);
|
||||
if (!is_world_coordinates()) {
|
||||
const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
|
||||
Vec3d diff = volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
|
||||
if (is_local_coordinates())
|
||||
diff = volume->get_volume_transformation().get_matrix_no_offset().inverse() * diff;
|
||||
diff = m_cache.position - diff;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
|
||||
change_position_value(0, diff.x());
|
||||
@ -942,7 +945,7 @@ void ObjectManipulation::change_position_value(int axis, double value)
|
||||
selection.setup_cache();
|
||||
TransformationType trafo_type;
|
||||
trafo_type.set_relative();
|
||||
switch (get_coordinates_type())
|
||||
switch (m_coordinates_type)
|
||||
{
|
||||
case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
|
||||
case ECoordinatesType::Local: { trafo_type.set_local(); break; }
|
||||
@ -952,7 +955,7 @@ void ObjectManipulation::change_position_value(int axis, double value)
|
||||
canvas->do_move(L("Set Position"));
|
||||
|
||||
m_cache.position = position;
|
||||
m_cache.position_rounded(axis) = DBL_MAX;
|
||||
m_cache.position_rounded(axis) = DBL_MAX;
|
||||
this->UpdateAndShow(true);
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u
|
||||
|
||||
std::string GLGizmoBase::get_action_snapshot_name() const
|
||||
{
|
||||
return _u8L("Gizmo action");
|
||||
return "Gizmo action";
|
||||
}
|
||||
|
||||
void GLGizmoBase::set_hover_id(int id)
|
||||
|
@ -194,7 +194,8 @@ GLGizmoCut3D::GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename,
|
||||
|
||||
std::map<const wchar_t, std::string> connetor_types = {
|
||||
{ImGui::PlugMarker , _u8L("Plug") },
|
||||
{ImGui::DowelMarker, _u8L("Dowel") },
|
||||
{ImGui::DowelMarker, _u8L("Dowel") },
|
||||
//TRN Connectors type next to "Plug" and "Dowel"
|
||||
{ImGui::SnapMarker, _u8L("Snap") },
|
||||
};
|
||||
for (auto connector : connetor_types) {
|
||||
@ -2679,7 +2680,7 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
|
||||
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": ");
|
||||
render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance);
|
||||
render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance);
|
||||
render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f);
|
||||
render_groove_angle_input(m_labels_map["Flap Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f);
|
||||
render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/CameraUtils.hpp"
|
||||
#include "slic3r/GUI/Jobs/EmbossJob.hpp"
|
||||
#include "slic3r/GUI/Jobs/CreateFontNameImageJob.hpp"
|
||||
@ -2631,7 +2632,7 @@ void GLGizmoEmboss::draw_style_list() {
|
||||
trunc_name = ImGuiWrapper::trunc(current_name, max_style_name_width);
|
||||
}
|
||||
|
||||
std::string title = _u8L("Styles");
|
||||
std::string title = _u8L("Style");
|
||||
if (m_style_manager.exist_stored_style())
|
||||
ImGui::Text("%s", title.c_str());
|
||||
else
|
||||
@ -2863,7 +2864,6 @@ bool GLGizmoEmboss::revertible(const std::string &name,
|
||||
else
|
||||
ImGuiWrapper::text(name);
|
||||
|
||||
bool result = draw();
|
||||
// render revert changes button
|
||||
if (changed) {
|
||||
ImGuiWindow *window = ImGui::GetCurrentWindow();
|
||||
@ -2876,7 +2876,7 @@ bool GLGizmoEmboss::revertible(const std::string &name,
|
||||
ImGui::SetTooltip("%s", undo_tooltip.c_str());
|
||||
window->DC.CursorPosPrevLine.x = prev_x; // set back previous position
|
||||
}
|
||||
return result;
|
||||
return draw();
|
||||
}
|
||||
|
||||
|
||||
@ -3188,7 +3188,7 @@ void GLGizmoEmboss::draw_advanced()
|
||||
if (per_glyph) {
|
||||
ImGui::SetTooltip("%s", _u8L("Set global orientation for whole text.").c_str());
|
||||
} else {
|
||||
ImGui::SetTooltip("%s", _u8L("Set position and orientation per Glyph.").c_str());
|
||||
ImGui::SetTooltip("%s", _u8L("Set position and orientation per glyph.").c_str());
|
||||
if (!m_text_lines.is_init())
|
||||
reinit_text_lines();
|
||||
}
|
||||
@ -3201,28 +3201,28 @@ void GLGizmoEmboss::draw_advanced()
|
||||
ImGui::SameLine(gui_cfg->advanced_input_offset);
|
||||
if (align.first==FontProp::HorizontalAlign::left) draw(get_icon(icons, IconType::align_horizontal_left, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_left)) { align.first=FontProp::HorizontalAlign::left; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set left alignment").c_str());
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Left", "Alignment"), "Alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::center) draw(get_icon(icons, IconType::align_horizontal_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_center)) { align.first=FontProp::HorizontalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set horizont center alignment").c_str());
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Center", "Alignment"), "Alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.first==FontProp::HorizontalAlign::right) draw(get_icon(icons, IconType::align_horizontal_right, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_horizontal_right)) { align.first=FontProp::HorizontalAlign::right; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set right alignment").c_str());
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Right", "Alignment"), "Alignment").c_str());
|
||||
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::top) draw(get_icon(icons, IconType::align_vertical_top, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_top)) { align.second=FontProp::VerticalAlign::top; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set top alignment").c_str());
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Top", "Alignment"), "Alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::center) draw(get_icon(icons, IconType::align_vertical_center, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_center)) { align.second=FontProp::VerticalAlign::center; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set vertical center alignment").c_str());
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Middle", "Alignment"), "Alignment").c_str());
|
||||
ImGui::SameLine();
|
||||
if (align.second==FontProp::VerticalAlign::bottom) draw(get_icon(icons, IconType::align_vertical_bottom, IconState::hovered));
|
||||
else if (draw_button(icons, IconType::align_vertical_bottom)) { align.second=FontProp::VerticalAlign::bottom; is_change = true; }
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set bottom alignment").c_str());
|
||||
else if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _CTX_utf8(L_CONTEXT("Bottom", "Alignment"), "Alignment").c_str());
|
||||
return is_change;
|
||||
};
|
||||
const FontProp::Align * def_align = stored_style ? &stored_style->prop.align : nullptr;
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include <wx/clipbrd.h>
|
||||
|
||||
namespace Slic3r {
|
||||
@ -96,6 +98,8 @@ static GLModel::Geometry init_plane_data(const indexed_triangle_set& its, const
|
||||
{
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
|
||||
init_data.reserve_indices(3 * triangle_indices.size());
|
||||
init_data.reserve_vertices(3 * triangle_indices.size());
|
||||
unsigned int i = 0;
|
||||
for (int idx : triangle_indices) {
|
||||
const Vec3f& v0 = its.vertices[its.indices[idx][0]];
|
||||
@ -648,7 +652,7 @@ void GLGizmoMeasure::on_render()
|
||||
const auto [idx, normal, point] = m_curr_feature->get_plane();
|
||||
if (m_last_plane_idx != idx) {
|
||||
m_last_plane_idx = idx;
|
||||
const indexed_triangle_set& its = m_measuring->get_mesh().its;
|
||||
const indexed_triangle_set& its = m_measuring->get_its();
|
||||
const std::vector<int>& plane_triangles = m_measuring->get_plane_triangle_indices(idx);
|
||||
GLModel::Geometry init_data = init_plane_data(its, plane_triangles);
|
||||
m_plane.reset();
|
||||
@ -1036,11 +1040,19 @@ void GLGizmoMeasure::update_if_needed()
|
||||
{
|
||||
auto update_plane_models_cache = [this](const indexed_triangle_set& its) {
|
||||
m_plane_models_cache.clear();
|
||||
for (int idx = 0; idx < m_measuring->get_num_of_planes(); ++idx) {
|
||||
m_plane_models_cache.emplace_back(GLModel());
|
||||
GLModel::Geometry init_data = init_plane_data(its, m_measuring->get_plane_triangle_indices(idx));
|
||||
m_plane_models_cache.back().init_from(std::move(init_data));
|
||||
}
|
||||
m_plane_models_cache.resize(m_measuring->get_num_of_planes(), GLModel());
|
||||
|
||||
auto& plane_models_cache = m_plane_models_cache;
|
||||
const auto& measuring = m_measuring;
|
||||
|
||||
//for (int idx = 0; idx < m_measuring->get_num_of_planes(); ++idx) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_measuring->get_num_of_planes()),
|
||||
[&plane_models_cache, &measuring, &its](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t idx = range.begin(); idx != range.end(); ++idx) {
|
||||
GLModel::Geometry init_data = init_plane_data(its, measuring->get_plane_triangle_indices(idx));
|
||||
plane_models_cache[idx].init_from(std::move(init_data));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
auto do_update = [this, update_plane_models_cache](const std::vector<VolumeCacheItem>& volumes_cache, const Selection& selection) {
|
||||
@ -1059,8 +1071,8 @@ void GLGizmoMeasure::update_if_needed()
|
||||
}
|
||||
|
||||
m_measuring.reset(new Measure::Measuring(composite_mesh.its));
|
||||
update_plane_models_cache(m_measuring->get_mesh().its);
|
||||
m_raycaster.reset(new MeshRaycaster(std::make_shared<const TriangleMesh>(m_measuring->get_mesh())));
|
||||
update_plane_models_cache(m_measuring->get_its());
|
||||
m_raycaster.reset(new MeshRaycaster(std::make_shared<const TriangleMesh>(composite_mesh)));
|
||||
m_volumes_cache = volumes_cache;
|
||||
};
|
||||
|
||||
|
@ -82,7 +82,7 @@ class PlaterWorker: public Worker {
|
||||
if (eptr) try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (std::exception &e) {
|
||||
show_error(m_plater, _L("An unexpected error occured: ") + e.what());
|
||||
show_error(m_plater, _L("An unexpected error occured") + ": " + e.what());
|
||||
eptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ public:
|
||||
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
|
||||
|
||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _L("Choose SLA archive") + ":",
|
||||
get_readers_wildcard(),
|
||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
@ -114,9 +114,9 @@ public:
|
||||
szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5);
|
||||
|
||||
static const std::vector<wxString> qual_choices = {
|
||||
_(L("Accurate")),
|
||||
_(L("Balanced")),
|
||||
_(L("Quick"))
|
||||
_L("Accurate"),
|
||||
_L("Balanced"),
|
||||
_L("Fast")
|
||||
};
|
||||
|
||||
m_quality_dropdown = new wxComboBox(
|
||||
|
@ -32,19 +32,6 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform)
|
||||
: position(transform.get_offset())
|
||||
, rotation(transform.get_rotation())
|
||||
, scaling_factor(transform.get_scaling_factor())
|
||||
, mirror(transform.get_mirror())
|
||||
, full_matrix(transform.get_matrix())
|
||||
, transform(transform)
|
||||
, rotation_matrix(transform.get_rotation_matrix())
|
||||
, scale_matrix(transform.get_scaling_factor_matrix())
|
||||
, mirror_matrix(transform.get_mirror_matrix())
|
||||
{
|
||||
}
|
||||
|
||||
Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform)
|
||||
: m_volume(volume_transform)
|
||||
, m_instance(instance_transform)
|
||||
@ -952,8 +939,8 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor
|
||||
}
|
||||
else {
|
||||
Vec3d relative_disp = displacement;
|
||||
if (transformation_type.instance())
|
||||
relative_disp = volume_data.get_instance_scale_matrix().inverse() * relative_disp;
|
||||
if (transformation_type.world() && transformation_type.instance())
|
||||
relative_disp = volume_data.get_instance_transform().get_scaling_factor_matrix().inverse() * relative_disp;
|
||||
|
||||
transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(relative_disp), m_cache.dragging_center);
|
||||
}
|
||||
|
@ -57,46 +57,15 @@ public:
|
||||
private:
|
||||
struct VolumeCache
|
||||
{
|
||||
private:
|
||||
struct TransformCache
|
||||
{
|
||||
Vec3d position{ Vec3d::Zero() };
|
||||
Vec3d rotation{ Vec3d::Zero() };
|
||||
Vec3d scaling_factor{ Vec3d::Ones() };
|
||||
Vec3d mirror{ Vec3d::Ones() };
|
||||
Transform3d rotation_matrix{ Transform3d::Identity() };
|
||||
Transform3d scale_matrix{ Transform3d::Identity() };
|
||||
Transform3d mirror_matrix{ Transform3d::Identity() };
|
||||
Transform3d full_matrix{ Transform3d::Identity() };
|
||||
Geometry::Transformation transform;
|
||||
|
||||
TransformCache() = default;
|
||||
explicit TransformCache(const Geometry::Transformation& transform);
|
||||
};
|
||||
|
||||
TransformCache m_volume;
|
||||
TransformCache m_instance;
|
||||
|
||||
public:
|
||||
VolumeCache() = default;
|
||||
VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform);
|
||||
|
||||
const Vec3d& get_volume_position() const { return m_volume.position; }
|
||||
const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; }
|
||||
const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; }
|
||||
const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; }
|
||||
const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; }
|
||||
const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; }
|
||||
const Geometry::Transformation& get_volume_transform() const { return m_volume; }
|
||||
const Geometry::Transformation& get_instance_transform() const { return m_instance; }
|
||||
|
||||
const Vec3d& get_instance_position() const { return m_instance.position; }
|
||||
const Vec3d& get_instance_rotation() const { return m_instance.rotation; }
|
||||
const Vec3d& get_instance_scaling_factor() const { return m_instance.scaling_factor; }
|
||||
const Vec3d& get_instance_mirror() const { return m_instance.mirror; }
|
||||
const Transform3d& get_instance_rotation_matrix() const { return m_instance.rotation_matrix; }
|
||||
const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; }
|
||||
const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; }
|
||||
const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; }
|
||||
const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; }
|
||||
private:
|
||||
Geometry::Transformation m_volume;
|
||||
Geometry::Transformation m_instance;
|
||||
};
|
||||
|
||||
public:
|
||||
|
@ -33,7 +33,7 @@ bool on_mouse_surface_drag(const wxMouseEvent &mouse_event,
|
||||
// Fix when click right button
|
||||
if (surface_drag.has_value() && !mouse_event.Dragging()) {
|
||||
// write transformation from UI into model
|
||||
canvas.do_move(L("Surface move"));
|
||||
canvas.do_move(L("Move over surface"));
|
||||
|
||||
// allow moving with object again
|
||||
canvas.enable_moving(true);
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode/GCodeWriter.hpp"
|
||||
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
@ -1649,6 +1649,8 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("slicing_mode");
|
||||
optgroup->append_single_option_line("resolution");
|
||||
optgroup->append_single_option_line("gcode_resolution");
|
||||
optgroup->append_single_option_line("arc_fitting");
|
||||
optgroup->append_single_option_line("arc_fitting_tolerance");
|
||||
optgroup->append_single_option_line("xy_size_compensation");
|
||||
optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487");
|
||||
|
||||
|
95
t/geometry.t
95
t/geometry.t
@ -1,95 +0,0 @@
|
||||
use Test::More;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
plan tests => 10;
|
||||
|
||||
BEGIN {
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use local::lib "$FindBin::Bin/../local-lib";
|
||||
}
|
||||
|
||||
use Slic3r;
|
||||
use Slic3r::Geometry qw(PI
|
||||
chained_path_from epsilon scale);
|
||||
|
||||
{
|
||||
# this test was failing on Windows (GH #1950)
|
||||
my $polygon = Slic3r::Polygon->new(
|
||||
[207802834,-57084522],[196528149,-37556190],[173626821,-25420928],[171285751,-21366123],
|
||||
[118673592,-21366123],[116332562,-25420928],[93431208,-37556191],[82156517,-57084523],
|
||||
[129714478,-84542120],[160244873,-84542120],
|
||||
);
|
||||
my $point = Slic3r::Point->new(95706562, -57294774);
|
||||
ok $polygon->contains_point($point), 'contains_point';
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
my $polygons = [
|
||||
Slic3r::Polygon->new( # contour, ccw
|
||||
[45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800],
|
||||
[43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600],
|
||||
[75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500],
|
||||
[107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300],
|
||||
[82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500],
|
||||
[285273900, 461246400], [254081000, 515273900],
|
||||
|
||||
),
|
||||
Slic3r::Polygon->new( # hole, cw
|
||||
[75000000, 502014700], [87251500, 499577600], [97637800, 492637800], [104577600, 482251500],
|
||||
[107014700, 470000000], [104577600, 457748400], [97637800, 447362100], [87251500, 440422300],
|
||||
[75000000, 437985300], [62748400, 440422300], [52362100, 447362100], [45422300, 457748400],
|
||||
[42985300, 470000000], [45422300, 482251500], [52362100, 492637800], [62748400, 499577600],
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
my $polygon = Slic3r::Polygon->new([0, 0], [10, 0], [5, 5]);
|
||||
my $result = $polygon->split_at_index(1);
|
||||
is ref($result), 'Slic3r::Polyline', 'split_at_index returns polyline';
|
||||
is_deeply $result->pp, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index';
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
#{
|
||||
# my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]);
|
||||
# $bb->scale(2);
|
||||
# is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly';
|
||||
#}
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
# if chained_path() works correctly, these points should be joined with no diagonal paths
|
||||
# (thus 26 units long)
|
||||
my @points = map Slic3r::Point->new_scale(@$_), [26,26],[52,26],[0,26],[26,52],[26,0],[0,52],[52,52],[52,0];
|
||||
my @ordered = @points[@{chained_path_from(\@points, $points[0])}];
|
||||
ok !(grep { abs($ordered[$_]->distance_to($ordered[$_+1]) - scale 26) > epsilon } 0..$#ordered-1), 'chained_path';
|
||||
}
|
||||
|
||||
#==========================================================
|
||||
|
||||
{
|
||||
my $line = Slic3r::Line->new([0, 0], [20, 0]);
|
||||
is +Slic3r::Point->new(10, 10)->distance_to_line($line), 10, 'distance_to';
|
||||
is +Slic3r::Point->new(50, 0)->distance_to_line($line), 30, 'distance_to';
|
||||
is +Slic3r::Point->new(0, 0)->distance_to_line($line), 0, 'distance_to';
|
||||
is +Slic3r::Point->new(20, 0)->distance_to_line($line), 0, 'distance_to';
|
||||
is +Slic3r::Point->new(10, 0)->distance_to_line($line), 0, 'distance_to';
|
||||
}
|
||||
|
||||
{
|
||||
my $triangle = Slic3r::Polygon->new(
|
||||
[16000170,26257364], [714223,461012], [31286371,461008],
|
||||
);
|
||||
my $simplified = $triangle->simplify(250000)->[0];
|
||||
is scalar(@$simplified), 3, 'triangle is never simplified to less than 3 points';
|
||||
}
|
||||
|
||||
__END__
|
@ -14,7 +14,7 @@
|
||||
using namespace Slic3r;
|
||||
|
||||
std::unique_ptr<CoolingBuffer> make_cooling_buffer(
|
||||
GCode &gcode,
|
||||
GCodeGenerator &gcode,
|
||||
const DynamicPrintConfig &config = DynamicPrintConfig{},
|
||||
const std::vector<unsigned int> &extruder_ids = { 0 })
|
||||
{
|
||||
@ -65,7 +65,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
|
||||
const double print_time = 100. / (3000. / 60.);
|
||||
//FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s.
|
||||
config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } });
|
||||
GCode gcodegen;
|
||||
GCodeGenerator gcodegen;
|
||||
auto buffer = make_cooling_buffer(gcodegen, config);
|
||||
std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true);
|
||||
bool speed_not_altered = gcode.find("F3000") != gcode.npos;
|
||||
@ -83,7 +83,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
|
||||
// Print time of gcode.
|
||||
const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.);
|
||||
config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } });
|
||||
GCode gcodegen;
|
||||
GCodeGenerator gcodegen;
|
||||
auto buffer = make_cooling_buffer(gcodegen, config);
|
||||
std::string gcode = buffer->process_layer(gcode_src, 0, true);
|
||||
THEN("speed is altered when elapsed time is lower than slowdown threshold") {
|
||||
@ -106,7 +106,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
|
||||
{ "fan_below_layer_time" , int(print_time1 * 0.88) },
|
||||
{ "slowdown_below_layer_time" , int(print_time1 * 0.99) }
|
||||
});
|
||||
GCode gcodegen;
|
||||
GCodeGenerator gcodegen;
|
||||
auto buffer = make_cooling_buffer(gcodegen, config);
|
||||
std::string gcode = buffer->process_layer(gcode1, 0, true);
|
||||
bool fan_not_activated = gcode.find("M106") == gcode.npos;
|
||||
@ -119,7 +119,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
|
||||
{ "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } },
|
||||
{ "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } }
|
||||
});
|
||||
GCode gcodegen;
|
||||
GCodeGenerator gcodegen;
|
||||
auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 });
|
||||
std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true);
|
||||
THEN("fan is activated for the 1st tool") {
|
||||
@ -134,7 +134,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
|
||||
WHEN("G-code block 2") {
|
||||
THEN("slowdown is computed on all objects printing at the same Z") {
|
||||
config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } });
|
||||
GCode gcodegen;
|
||||
GCodeGenerator gcodegen;
|
||||
auto buffer = make_cooling_buffer(gcodegen, config);
|
||||
std::string gcode = buffer->process_layer(gcode2, 0, true);
|
||||
bool ok = gcode.find("F3000") != gcode.npos;
|
||||
@ -145,7 +145,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
|
||||
{ "fan_below_layer_time", int(print_time2 * 0.65) },
|
||||
{ "slowdown_below_layer_time", int(print_time2 * 0.7) }
|
||||
});
|
||||
GCode gcodegen;
|
||||
GCodeGenerator gcodegen;
|
||||
auto buffer = make_cooling_buffer(gcodegen, config);
|
||||
// use an elapsed time which is < the threshold but greater than it when summed twice
|
||||
std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);
|
||||
@ -158,7 +158,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
|
||||
{ "fan_below_layer_time", int(print_time2 + 1) },
|
||||
{ "slowdown_below_layer_time", int(print_time2 + 1) }
|
||||
});
|
||||
GCode gcodegen;
|
||||
GCodeGenerator gcodegen;
|
||||
auto buffer = make_cooling_buffer(gcodegen, config);
|
||||
// use an elapsed time which is < the threshold but greater than it when summed twice
|
||||
std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);
|
||||
|
@ -21,7 +21,7 @@ static inline Slic3r::Point random_point(float LO=-50, float HI=50)
|
||||
// build a sample extrusion entity collection with random start and end points.
|
||||
static Slic3r::ExtrusionPath random_path(size_t length = 20, float LO = -50, float HI = 50)
|
||||
{
|
||||
ExtrusionPath t { ExtrusionRole::Perimeter, 1.0, 1.0, 1.0 };
|
||||
ExtrusionPath t{ ExtrusionAttributes{ ExtrusionRole::Perimeter, ExtrusionFlow{ 1.0, 1.0, 1.0 } } };
|
||||
for (size_t j = 0; j < length; ++ j)
|
||||
t.polyline.append(random_point(LO, HI));
|
||||
return t;
|
||||
@ -37,9 +37,8 @@ static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20
|
||||
|
||||
SCENARIO("ExtrusionPath", "[ExtrusionEntity]") {
|
||||
GIVEN("Simple path") {
|
||||
Slic3r::ExtrusionPath path{ ExtrusionRole::ExternalPerimeter };
|
||||
path.polyline = { { 100, 100 }, { 200, 100 }, { 200, 200 } };
|
||||
path.mm3_per_mm = 1.;
|
||||
Slic3r::ExtrusionPath path{ { { 100, 100 }, { 200, 100 }, { 200, 200 } },
|
||||
ExtrusionAttributes{ ExtrusionRole::ExternalPerimeter, ExtrusionFlow{ 1., -1.f, -1.f } } };
|
||||
THEN("first point") {
|
||||
REQUIRE(path.first_point() == path.polyline.front());
|
||||
}
|
||||
@ -52,10 +51,7 @@ SCENARIO("ExtrusionPath", "[ExtrusionEntity]") {
|
||||
|
||||
static ExtrusionPath new_extrusion_path(const Polyline &polyline, ExtrusionRole role, double mm3_per_mm)
|
||||
{
|
||||
ExtrusionPath path(role);
|
||||
path.polyline = polyline;
|
||||
path.mm3_per_mm = 1.;
|
||||
return path;
|
||||
return { polyline, ExtrusionAttributes{ role, ExtrusionFlow{ mm3_per_mm, -1.f, -1.f } } };
|
||||
}
|
||||
|
||||
SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
|
||||
@ -67,6 +63,7 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
|
||||
loop.paths.emplace_back(new_extrusion_path(square.split_at_first_point(), ExtrusionRole::ExternalPerimeter, 1.));
|
||||
THEN("polygon area") {
|
||||
REQUIRE(loop.polygon().area() == Approx(square.area()));
|
||||
REQUIRE(loop.area() == Approx(square.area()));
|
||||
}
|
||||
THEN("loop length") {
|
||||
REQUIRE(loop.length() == Approx(square.length()));
|
||||
@ -110,6 +107,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
|
||||
loop.paths.emplace_back(new_extrusion_path(polyline1, ExtrusionRole::ExternalPerimeter, 1.));
|
||||
loop.paths.emplace_back(new_extrusion_path(polyline2, ExtrusionRole::OverhangPerimeter, 1.));
|
||||
|
||||
THEN("area") {
|
||||
REQUIRE(loop.area() == Approx(loop.polygon().area()));
|
||||
}
|
||||
double tot_len = polyline1.length() + polyline2.length();
|
||||
THEN("length") {
|
||||
REQUIRE(loop.length() == Approx(tot_len));
|
||||
@ -212,6 +212,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
|
||||
loop.paths.emplace_back(new_extrusion_path(polyline3, ExtrusionRole::ExternalPerimeter, 1.));
|
||||
loop.paths.emplace_back(new_extrusion_path(polyline4, ExtrusionRole::OverhangPerimeter, 1.));
|
||||
double len = loop.length();
|
||||
THEN("area") {
|
||||
REQUIRE(loop.area() == Approx(loop.polygon().area()));
|
||||
}
|
||||
WHEN("splitting at vertex") {
|
||||
Point point(4821067, 9321068);
|
||||
if (! loop.split_at_vertex(point))
|
||||
@ -234,6 +237,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
|
||||
Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 },
|
||||
{ 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } },
|
||||
ExtrusionRole::ExternalPerimeter, 1.));
|
||||
THEN("area") {
|
||||
REQUIRE(loop.area() == Approx(loop.polygon().area()));
|
||||
}
|
||||
double len = loop.length();
|
||||
THEN("split_at() preserves total length") {
|
||||
loop.split_at({ 15896783, 15868739 }, false, 0);
|
||||
@ -378,23 +384,27 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
|
||||
REQUIRE(chained == test.chained);
|
||||
ExtrusionEntityCollection unchained_extrusions;
|
||||
extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained,
|
||||
ExtrusionRole::InternalInfill, 0., 0.4f, 0.3f);
|
||||
ExtrusionAttributes{ ExtrusionRole::InternalInfill, ExtrusionFlow{ 0., 0.4f, 0.3f } });
|
||||
THEN("Chaining works") {
|
||||
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point);
|
||||
REQUIRE(chained_extrusions.entities.size() == test.chained.size());
|
||||
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) {
|
||||
ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point);
|
||||
REQUIRE(chained_extrusions.size() == test.chained.size());
|
||||
for (size_t i = 0; i < chained_extrusions.size(); ++ i) {
|
||||
const Points &p1 = test.chained[i].points;
|
||||
const Points &p2 = dynamic_cast<const ExtrusionPath*>(chained_extrusions.entities[i])->polyline.points;
|
||||
Points p2 = chained_extrusions[i].cast<ExtrusionPath>()->polyline.points;
|
||||
if (chained_extrusions[i].flipped())
|
||||
std::reverse(p2.begin(), p2.end());
|
||||
REQUIRE(p1 == p2);
|
||||
}
|
||||
}
|
||||
THEN("Chaining produces no change with no_sort") {
|
||||
unchained_extrusions.no_sort = true;
|
||||
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point);
|
||||
REQUIRE(chained_extrusions.entities.size() == test.unchained.size());
|
||||
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) {
|
||||
ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point);
|
||||
REQUIRE(chained_extrusions.size() == test.unchained.size());
|
||||
for (size_t i = 0; i < chained_extrusions.size(); ++ i) {
|
||||
const Points &p1 = test.unchained[i].points;
|
||||
const Points &p2 = dynamic_cast<const ExtrusionPath*>(chained_extrusions.entities[i])->polyline.points;
|
||||
Points p2 = chained_extrusions[i].cast<ExtrusionPath>()->polyline.points;
|
||||
if (chained_extrusions[i].flipped())
|
||||
std::reverse(p2.begin(), p2.end());
|
||||
REQUIRE(p1 == p2);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("Origin manipulation", "[GCode]") {
|
||||
Slic3r::GCode gcodegen;
|
||||
Slic3r::GCodeGenerator gcodegen;
|
||||
WHEN("set_origin to (10,0)") {
|
||||
gcodegen.set_origin(Vec2d(10,0));
|
||||
REQUIRE(gcodegen.origin() == Vec2d(10, 0));
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode/GCodeWriter.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
|
@ -6,6 +6,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_aabbindirect.cpp
|
||||
test_kdtreeindirect.cpp
|
||||
test_arachne.cpp
|
||||
test_arc_welder.cpp
|
||||
test_clipper_offset.cpp
|
||||
test_clipper_utils.cpp
|
||||
test_color.cpp
|
||||
|
264
tests/libslic3r/test_arc_welder.cpp
Normal file
264
tests/libslic3r/test_arc_welder.cpp
Normal file
@ -0,0 +1,264 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <libslic3r/Geometry/ArcWelder.hpp>
|
||||
#include <libslic3r/libslic3r.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("arc basics", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("arc from { 2000.f, 1000.f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 2000.f, 1000.f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
THEN("90 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.5 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.5 * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx(1.5 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * 1.5 * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
|
||||
}
|
||||
}
|
||||
WHEN("arc from { 1707.11f, 1707.11f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 1707.11f, 1707.11f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
|
||||
// Center on the other side of the CCW arch.
|
||||
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
|
||||
THEN("45 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.25 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.25 * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("45 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
}
|
||||
THEN("315 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx((2. - 0.25) * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 0.25) * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("315 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
}
|
||||
}
|
||||
WHEN("arc from { 1866.f, 1500.f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 1866.f, 1500.f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
|
||||
// Center on the other side of the CCW arch.
|
||||
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
|
||||
THEN("60 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, r), float(M_PI / 3.), 0.001f));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * M_PI / 3.).epsilon(0.001));
|
||||
}
|
||||
THEN("60 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
}
|
||||
THEN("300 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, - r), float((2. - 1./3.) * M_PI), 0.001f));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 1. / 3.) * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("300 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc discretization", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point center = Point::new_scale(1., 1.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center, resolution, radius](const Point &p1, const Point &p2, const float r, const bool ccw) {
|
||||
Vec2f c = ArcWelder::arc_center(p1.cast<float>(), p2.cast<float>(), r, ccw);
|
||||
REQUIRE((p1.cast<float>() - c).norm() == Approx(radius));
|
||||
REQUIRE((c - center.cast<float>()).norm() == Approx(0.));
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
REQUIRE(pts.size() >= 2);
|
||||
REQUIRE(pts.front() == p1);
|
||||
REQUIRE(pts.back() == p2);
|
||||
for (const Point &p : pts)
|
||||
REQUIRE(std::abs((p.cast<double>() - c.cast<double>()).norm() - double(radius)) < double(resolution + SCALED_EPSILON));
|
||||
};
|
||||
THEN("90 degrees arc, CCW") {
|
||||
test(p1, p2, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
test(p2, p1, - radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
test(p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
test(p1, p2, - radius, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc fitting", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point center = Point::new_scale(1., 1.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center, resolution](const Point &p1, const Point &p2, const float r, const bool ccw) {
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
|
||||
REQUIRE(path.size() == 2);
|
||||
REQUIRE(path.front().point == p1);
|
||||
REQUIRE(path.front().radius == 0.f);
|
||||
REQUIRE(path.back().point == p2);
|
||||
REQUIRE(path.back().radius == Approx(r));
|
||||
REQUIRE(path.back().ccw() == ccw);
|
||||
};
|
||||
THEN("90 degrees arc, CCW is fitted") {
|
||||
test(p1, p2, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW is fitted") {
|
||||
test(p2, p1, - radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW is fitted") {
|
||||
test(p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW is fitted") {
|
||||
test(p1, p2, - radius, false);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }, another arc from { 2, 1 } to { 0, 2 }, tangentially connected") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point p3 = Point::new_scale(0., 3.);
|
||||
const Point center1 = Point::new_scale(1., 1.);
|
||||
const Point center2 = Point::new_scale(1., 3.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center1, center2, resolution](const Point &p1, const Point &p2, const Point &p3, const float r, const bool ccw) {
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
{
|
||||
Points pts2 = ArcWelder::arc_discretize(p2, p3, - r, ! ccw, resolution);
|
||||
REQUIRE(pts.back() == pts2.front());
|
||||
pts.insert(pts.end(), std::next(pts2.begin()), pts2.end());
|
||||
}
|
||||
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
|
||||
REQUIRE(path.size() == 3);
|
||||
REQUIRE(path.front().point == p1);
|
||||
REQUIRE(path.front().radius == 0.f);
|
||||
REQUIRE(path[1].point == p2);
|
||||
REQUIRE(path[1].radius == Approx(r));
|
||||
REQUIRE(path[1].ccw() == ccw);
|
||||
REQUIRE(path.back().point == p3);
|
||||
REQUIRE(path.back().radius == Approx(- r));
|
||||
REQUIRE(path.back().ccw() == ! ccw);
|
||||
};
|
||||
THEN("90 degrees arches, CCW are fitted") {
|
||||
test(p1, p2, p3, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW is fitted") {
|
||||
test(p3, p2, p1, -radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW is fitted") {
|
||||
test(p3, p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW is fitted") {
|
||||
test(p1, p2, p3, -radius, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc wedge test", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("test point inside wedge, arc from { 2, 1 } to { 1, 2 }") {
|
||||
const int64_t s = 1000000;
|
||||
const Vec2i64 p1{ 2 * s, s };
|
||||
const Vec2i64 p2{ s, 2 * s };
|
||||
const Vec2i64 center{ s, s };
|
||||
const int64_t radius{ s };
|
||||
auto test = [center](
|
||||
// Arc data
|
||||
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
|
||||
// Test data
|
||||
const Vec2i64 &ptest, const bool ptest_inside) {
|
||||
const Vec2d c = ArcWelder::arc_center(p1.cast<double>(), p2.cast<double>(), double(r), ccw);
|
||||
REQUIRE(is_approx(c, center.cast<double>()));
|
||||
REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside);
|
||||
REQUIRE(ArcWelder::inside_arc_wedge(p1.cast<double>(), p2.cast<double>(), double(r), ccw, ptest.cast<double>()) == ptest_inside);
|
||||
};
|
||||
auto test_quadrants = [center, test](
|
||||
// Arc data
|
||||
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
|
||||
// Test data
|
||||
const Vec2i64 &ptest1, const bool ptest_inside1,
|
||||
const Vec2i64 &ptest2, const bool ptest_inside2,
|
||||
const Vec2i64 &ptest3, const bool ptest_inside3,
|
||||
const Vec2i64 &ptest4, const bool ptest_inside4) {
|
||||
test(p1, p2, r, ccw, ptest1 + center, ptest_inside1);
|
||||
test(p1, p2, r, ccw, ptest2 + center, ptest_inside2);
|
||||
test(p1, p2, r, ccw, ptest3 + center, ptest_inside3);
|
||||
test(p1, p2, r, ccw, ptest4 + center, ptest_inside4);
|
||||
};
|
||||
THEN("90 degrees arc, CCW") {
|
||||
test_quadrants(p1, p2, radius, true,
|
||||
Vec2i64{ s, s }, true,
|
||||
Vec2i64{ s, - s }, false,
|
||||
Vec2i64{ - s, s }, false,
|
||||
Vec2i64{ - s, - s }, false);
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
test_quadrants(p2, p1, -radius, true,
|
||||
Vec2i64{ s, s }, false,
|
||||
Vec2i64{ s, - s }, true,
|
||||
Vec2i64{ - s, s }, true,
|
||||
Vec2i64{ - s, - s }, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
test_quadrants(p2, p1, radius, false,
|
||||
Vec2i64{ s, s }, true,
|
||||
Vec2i64{ s, - s }, false,
|
||||
Vec2i64{ - s, s }, false,
|
||||
Vec2i64{ - s, - s }, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
test_quadrants(p1, p2, -radius, false,
|
||||
Vec2i64{ s, s }, false,
|
||||
Vec2i64{ s, - s }, true,
|
||||
Vec2i64{ - s, s }, true,
|
||||
Vec2i64{ - s, - s }, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -84,16 +84,16 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") {
|
||||
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
|
||||
// this test was failing on Windows (GH #1950)
|
||||
Slic3r::Polygon polygon(Points({
|
||||
Point(207802834,-57084522),
|
||||
Point(196528149,-37556190),
|
||||
Point(173626821,-25420928),
|
||||
Point(171285751,-21366123),
|
||||
Point(118673592,-21366123),
|
||||
Point(116332562,-25420928),
|
||||
Point(93431208,-37556191),
|
||||
Point(82156517,-57084523),
|
||||
Point(129714478,-84542120),
|
||||
Point(160244873,-84542120)
|
||||
{207802834,-57084522},
|
||||
{196528149,-37556190},
|
||||
{173626821,-25420928},
|
||||
{171285751,-21366123},
|
||||
{118673592,-21366123},
|
||||
{116332562,-25420928},
|
||||
{93431208,-37556191},
|
||||
{82156517,-57084523},
|
||||
{129714478,-84542120},
|
||||
{160244873,-84542120}
|
||||
}));
|
||||
Point point(95706562, -57294774);
|
||||
REQUIRE(polygon.contains(point));
|
||||
@ -196,6 +196,41 @@ TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
|
||||
REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, 3 points", "[Geometry]") {
|
||||
WHEN("Three points make a circle") {
|
||||
double s1 = scaled<double>(1.);
|
||||
THEN("circle_center(): A center point { 0, 0 } is returned") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("circle_center(): A center point { 0, 0 } is returned for points in reverse") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): A center point { 0, 0 } is returned") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(center);
|
||||
REQUIRE(is_approx(*center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): A center point { 0, 0 } is returned for points in reverse") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(center);
|
||||
REQUIRE(is_approx(*center, Vec2d(0, 0)));
|
||||
}
|
||||
}
|
||||
WHEN("Three points are collinear") {
|
||||
double s1 = scaled<double>(1.);
|
||||
THEN("circle_center(): A center point { 2, 0 } is returned") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(2. * s1, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): Fails for collinear points") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(! center);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
|
||||
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
|
||||
Vec2d expected_center(-6, 0);
|
||||
@ -310,6 +345,7 @@ SCENARIO("Path chaining", "[Geometry]") {
|
||||
GIVEN("A path") {
|
||||
Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
|
||||
THEN("Chained with no diagonals (thus 26 units long)") {
|
||||
// if chain_points() works correctly, these points should be joined with no diagonal paths
|
||||
std::vector<Points::size_type> indices = chain_points(points);
|
||||
for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
|
||||
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user