mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 03:41:31 +00:00
Merge branch 'main' into pr/Noisyfox/13712
This commit is contained in:
@@ -266,10 +266,44 @@ std::vector<WaveSeed> wave_seeds(
|
||||
//(front.z() < 0 && back.z() < 0));
|
||||
// Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created.
|
||||
(front.z() < 0 || back.z() < 0));
|
||||
// However, with complex geometry, both endpoints may coincide with existing polygon
|
||||
// vertices (z >= 0), which is handled below.
|
||||
|
||||
if (front != back && front.z() >= 0 && back.z() >= 0) {
|
||||
// Very rare case when both endpoints intersect boundary ExPolygons in existing points.
|
||||
// So the ZFillFunction callback hasn't been called.
|
||||
// Both endpoints coincide with existing polygon vertices, so the
|
||||
// ZFillFunction callback was never called. With complex geometry
|
||||
// this is common because source and boundary contours share many
|
||||
// vertices. Determine src_id / boundary_id from Z coordinates
|
||||
// (and fall back to an AABB-tree point-in-polygon test when a
|
||||
// boundary ID is not directly available).
|
||||
coord_t src_z = -1, boundary_z = -1;
|
||||
// Scan all path points for the information we need.
|
||||
for (const ClipperLib_Z::IntPoint &point : path) {
|
||||
if (point.z() >= idx_boundary_end && point.z() < idx_src_end && src_z < 0)
|
||||
src_z = point.z();
|
||||
else if (point.z() >= idx_boundary_begin && point.z() < idx_boundary_end && boundary_z < 0)
|
||||
boundary_z = point.z();
|
||||
if (src_z >= 0 && boundary_z >= 0)
|
||||
break;
|
||||
}
|
||||
if (src_z >= 0) {
|
||||
uint32_t src_id = uint32_t(src_z - idx_boundary_end);
|
||||
if (boundary_z >= 0) {
|
||||
out.push_back({ src_id, uint32_t(boundary_z - 1), ClipperZUtils::from_zpath(path) });
|
||||
} else {
|
||||
// Source ID known but boundary unknown – use AABB tree.
|
||||
if (aabb_tree.empty())
|
||||
aabb_tree = build_aabb_tree_over_expolygons(boundary);
|
||||
int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y()));
|
||||
if (boundary_id >= 0)
|
||||
out.push_back({ src_id, uint32_t(boundary_id), ClipperZUtils::from_zpath(path) });
|
||||
}
|
||||
++ iseed;
|
||||
continue;
|
||||
}
|
||||
// Unable to determine source ID – drop the segment.
|
||||
continue;
|
||||
} else
|
||||
if (front == back && (front.z() < idx_boundary_end)) {
|
||||
|
||||
@@ -93,6 +93,11 @@ bool AppConfig::get_stealth_mode()
|
||||
return get_bool("stealth_mode");
|
||||
}
|
||||
|
||||
bool AppConfig::get_hide_login_side_panel()
|
||||
{
|
||||
return get_bool("hide_login_side_panel");
|
||||
}
|
||||
|
||||
void AppConfig::reset()
|
||||
{
|
||||
m_storage.clear();
|
||||
@@ -259,6 +264,24 @@ void AppConfig::set_defaults()
|
||||
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
|
||||
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
|
||||
|
||||
if (get(SETTING_OPENGL_REALISTIC_MODE).empty())
|
||||
set_bool(SETTING_OPENGL_REALISTIC_MODE, false);
|
||||
|
||||
if (get(SETTING_OPENGL_REALISTIC_PHONG).empty())
|
||||
set_bool(SETTING_OPENGL_REALISTIC_PHONG, true);
|
||||
|
||||
if (get(SETTING_OPENGL_SHADING_MODEL).empty())
|
||||
set(SETTING_OPENGL_SHADING_MODEL, "gouraud");
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS, false);
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_SMOOTH_NORMALS).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_SMOOTH_NORMALS, false);
|
||||
|
||||
if (get(SETTING_OPENGL_PHONG_SSAO).empty())
|
||||
set_bool(SETTING_OPENGL_PHONG_SSAO, false);
|
||||
|
||||
if (get("export_sources_full_pathnames").empty())
|
||||
set_bool("export_sources_full_pathnames", false);
|
||||
|
||||
@@ -321,6 +344,9 @@ void AppConfig::set_defaults()
|
||||
if (get("developer_mode").empty())
|
||||
set_bool("developer_mode", false);
|
||||
|
||||
if (get("show_unsupported_presets").empty())
|
||||
set_bool("show_unsupported_presets", false);
|
||||
|
||||
if (get("enable_ssl_for_mqtt").empty())
|
||||
set_bool("enable_ssl_for_mqtt", true);
|
||||
|
||||
@@ -347,6 +373,9 @@ void AppConfig::set_defaults()
|
||||
if (get("stealth_mode").empty()) {
|
||||
set_bool("stealth_mode", false);
|
||||
}
|
||||
if (get("hide_login_side_panel").empty()) {
|
||||
set_bool("hide_login_side_panel", false);
|
||||
}
|
||||
if (get("allow_abnormal_storage").empty()) {
|
||||
set_bool("allow_abnormal_storage", false);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ using namespace nlohmann;
|
||||
#define SETTING_OPENGL_FXAA_ENABLED "opengl_fxaa_enabled"
|
||||
#define SETTING_OPENGL_FPS_CAP "opengl_fps_cap"
|
||||
#define SETTING_OPENGL_SHOW_FPS_OVERLAY "opengl_show_fps_overlay"
|
||||
#define SETTING_OPENGL_REALISTIC_MODE "opengl_realistic_mode"
|
||||
#define SETTING_OPENGL_REALISTIC_PHONG "opengl_realistic_phong"
|
||||
#define SETTING_OPENGL_SHADING_MODEL "opengl_shading_model"
|
||||
#define SETTING_OPENGL_PHONG_BASIC_PLATE_SHADOWS "opengl_phong_basic_plate_shadows"
|
||||
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
|
||||
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
|
||||
@@ -85,6 +91,7 @@ public:
|
||||
std::string get_language_code();
|
||||
std::string get_hms_host();
|
||||
bool get_stealth_mode();
|
||||
bool get_hide_login_side_panel();
|
||||
|
||||
// Clear and reset to defaults.
|
||||
void reset();
|
||||
|
||||
@@ -26,7 +26,13 @@ std::string WideningBeadingStrategy::toString() const
|
||||
|
||||
WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (thickness < optimal_width) {
|
||||
// Use getTransitionThickness(1) to determine if this is a thin wall that should produce
|
||||
// a single bead. This ensures consistency with getOptimalBeadCount() which uses the same
|
||||
// threshold (via RedistributeBeadingStrategy) to decide between 1 and 2 beads.
|
||||
// Previously used optimal_width which could differ from the outer wall width used in
|
||||
// bead count calculations, causing inconsistency where bead_count=2 was requested but
|
||||
// only 1 bead was produced.
|
||||
if (thickness < getTransitionThickness(1)) {
|
||||
Beading ret;
|
||||
ret.total_thickness = thickness;
|
||||
if (thickness >= min_input_width) {
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Slic3r::Arachne::LinearAlg2D
|
||||
{
|
||||
|
||||
/*!
|
||||
* Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows.
|
||||
* Returns the determinant of the 2D matrix defined by the vectors ab and ap as rows.
|
||||
*
|
||||
* The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b
|
||||
* The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b.
|
||||
|
||||
+17
-2
@@ -879,7 +879,9 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders)
|
||||
std::vector<unsigned int>& printExtruders,
|
||||
std::map<ObjectID, ExPolygons>* objectBrimAreasOut,
|
||||
std::map<ObjectID, ExPolygons>* supportBrimAreasOut)
|
||||
{
|
||||
std::map<ObjectID, double> brim_width_map;
|
||||
std::map<ObjectID, ExPolygons> brimAreaMap;
|
||||
@@ -928,12 +930,25 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
for (size_t iia = 0; iia < islands_area.size(); ++iia)
|
||||
islands_area[iia].translate(plate_shift);
|
||||
|
||||
// Orca: keep translated brim footprints for skirt grouping.
|
||||
auto translate_area_map = [plate_shift](const std::map<ObjectID, ExPolygons>& src) {
|
||||
std::map<ObjectID, ExPolygons> dst = src;
|
||||
for (auto& [_, areas] : dst)
|
||||
for (ExPolygon& area : areas)
|
||||
area.translate(plate_shift);
|
||||
return dst;
|
||||
};
|
||||
if (objectBrimAreasOut != nullptr)
|
||||
*objectBrimAreasOut = translate_area_map(brimAreaMap);
|
||||
if (supportBrimAreasOut != nullptr)
|
||||
*supportBrimAreasOut = translate_area_map(supportBrimAreaMap);
|
||||
|
||||
const bool combine_brims = print.config().combine_brims.value;
|
||||
const bool is_by_object = (print.config().print_sequence == PrintSequence::ByObject);
|
||||
const bool can_combine_brims = combine_brims && !is_by_object;
|
||||
|
||||
if (!can_combine_brims) {
|
||||
// Orca: Generate brims separately for each object when multiple extruders are used
|
||||
// Orca: Generate brims separately when brims cannot be combined.
|
||||
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
|
||||
if (!iter->second.empty()) {
|
||||
brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef slic3r_Brim_hpp_
|
||||
#define slic3r_Brim_hpp_
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include<map>
|
||||
@@ -19,7 +20,9 @@ void make_brim(const Print& print, PrintTryCancel try_cancel,
|
||||
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders);
|
||||
std::vector<unsigned int>& printExtruders,
|
||||
std::map<ObjectID, ExPolygons>* objectBrimAreasOut = nullptr,
|
||||
std::map<ObjectID, ExPolygons>* supportBrimAreasOut = nullptr);
|
||||
|
||||
// BBS: automatically make brim
|
||||
ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area);
|
||||
|
||||
@@ -520,7 +520,7 @@ ClipperLib::Paths expolygon_offset(const Slic3r::ExPolygon &expolygon, const flo
|
||||
|
||||
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
|
||||
// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
|
||||
// Each ExPolygon is offsetted separately. For outer offset, the the offsetted ExPolygons shall be united outside of this function.
|
||||
// Each ExPolygon is offsetted separately. For outer offset, the offsetted ExPolygons shall be united outside of this function.
|
||||
template<typename ExPolygonVector>
|
||||
static std::pair<ClipperLib::Paths, size_t> expolygons_offset_raw(const ExPolygonVector &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||
{
|
||||
|
||||
@@ -2192,7 +2192,7 @@ private:
|
||||
else
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
}
|
||||
else {
|
||||
else if (this->keys_map != nullptr) {
|
||||
for (const auto& kvp : *this->keys_map)
|
||||
if (kvp.second == v)
|
||||
ss << kvp.first;
|
||||
|
||||
@@ -118,6 +118,9 @@ public:
|
||||
virtual void reverse() = 0;
|
||||
virtual Point first_point() const = 0;
|
||||
virtual Point last_point() const = 0;
|
||||
virtual const Point3& first_point3() const = 0;
|
||||
virtual const Point3& last_point3() const = 0;
|
||||
|
||||
// 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.
|
||||
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
|
||||
@@ -253,9 +256,9 @@ public:
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
|
||||
void reverse() override { this->polyline.reverse(); }
|
||||
Point first_point() const override { return this->polyline.points.front().to_point(); }
|
||||
Point3 first_point3() const { return this->polyline.points.front(); }
|
||||
const Point3& first_point3() const override { return this->polyline.points.front(); }
|
||||
Point last_point() const override { return this->polyline.points.back().to_point(); }
|
||||
Point3 last_point3() const { return this->polyline.points.back(); }
|
||||
const Point3& last_point3() const override { return this->polyline.points.back(); }
|
||||
size_t size() const { return this->polyline.size(); }
|
||||
bool empty() const { return this->polyline.empty(); }
|
||||
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
|
||||
@@ -403,7 +406,9 @@ public:
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
|
||||
void reverse() override;
|
||||
Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
|
||||
const Point3& first_point3() const override { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const override { return this->paths.back().polyline.points.back().to_point(); }
|
||||
const Point3& last_point3() const override { return this->paths.back().polyline.points.back(); }
|
||||
size_t size() const { return this->paths.size(); }
|
||||
bool empty() const { return this->paths.empty(); }
|
||||
double length() const override;
|
||||
@@ -459,7 +464,9 @@ public:
|
||||
bool is_counter_clockwise() { return this->polygon().is_counter_clockwise(); }
|
||||
void reverse() override;
|
||||
Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
|
||||
const Point3& first_point3() const override { return this->paths.front().polyline.points.front(); }
|
||||
Point last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back().to_point()); return this->first_point(); }
|
||||
const Point3& last_point3() const override { assert(this->first_point3() == this->paths.back().polyline.points.back()); return this->first_point3(); }
|
||||
Polygon polygon() const;
|
||||
double length() const override;
|
||||
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
|
||||
@@ -510,7 +517,7 @@ public:
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate() const {
|
||||
assert(this->first_point() == this->paths.back().polyline.points.back());
|
||||
assert(this->first_point3() == this->paths.back().polyline.points.back());
|
||||
for (size_t i = 1; i < paths.size(); ++ i)
|
||||
assert(this->paths[i - 1].polyline.points.back() == this->paths[i].polyline.points.front());
|
||||
return true;
|
||||
|
||||
@@ -119,7 +119,10 @@ public:
|
||||
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
|
||||
void reverse() override;
|
||||
Point first_point() const override { return this->entities.front()->first_point(); }
|
||||
const Point3& first_point3() const override { return this->entities.front()->first_point3(); }
|
||||
Point last_point() const override { return this->entities.back()->last_point(); }
|
||||
const Point3& last_point3() const override { return this->entities.back()->last_point3(); }
|
||||
|
||||
// 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;
|
||||
|
||||
+62
-50
@@ -256,17 +256,15 @@ struct SurfaceFillParams
|
||||
|
||||
// Index of this entry in a linear vector.
|
||||
size_t idx = 0;
|
||||
// infill speed settings
|
||||
float sparse_infill_speed = 0;
|
||||
float top_surface_speed = 0;
|
||||
float solid_infill_speed = 0;
|
||||
// Infill speed setting for the effective extrusion role.
|
||||
float role_speed = 0;
|
||||
|
||||
// Params for lattice infill angles
|
||||
float lateral_lattice_angle_1 = 0.f;
|
||||
float lateral_lattice_angle_2 = 0.f;
|
||||
float infill_lock_depth = 0;
|
||||
float skin_infill_depth = 0;
|
||||
bool symmetric_infill_y_axis = false;
|
||||
float infill_lock_depth = 0;
|
||||
float skin_infill_depth = 0;
|
||||
bool symmetric_infill_y_axis = false;
|
||||
|
||||
// Params for Lateral honeycomb
|
||||
float infill_overhang_angle = 60.f;
|
||||
@@ -298,9 +296,7 @@ struct SurfaceFillParams
|
||||
RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge);
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
|
||||
RETURN_COMPARE_NON_EQUAL(sparse_infill_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(top_surface_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(solid_infill_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(role_speed);
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_1);
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_2);
|
||||
RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis);
|
||||
@@ -312,30 +308,28 @@ struct SurfaceFillParams
|
||||
}
|
||||
|
||||
bool operator==(const SurfaceFillParams &rhs) const {
|
||||
return this->extruder == rhs.extruder &&
|
||||
this->pattern == rhs.pattern &&
|
||||
this->spacing == rhs.spacing &&
|
||||
this->overlap == rhs.overlap &&
|
||||
this->angle == rhs.angle &&
|
||||
this->fixed_angle == rhs.fixed_angle &&
|
||||
this->bridge == rhs.bridge &&
|
||||
this->bridge_angle == rhs.bridge_angle &&
|
||||
this->density == rhs.density &&
|
||||
this->multiline == rhs.multiline &&
|
||||
// this->dont_adjust == rhs.dont_adjust &&
|
||||
this->anchor_length == rhs.anchor_length &&
|
||||
this->anchor_length_max == rhs.anchor_length_max &&
|
||||
this->flow == rhs.flow &&
|
||||
this->extrusion_role == rhs.extrusion_role &&
|
||||
this->sparse_infill_speed == rhs.sparse_infill_speed &&
|
||||
this->top_surface_speed == rhs.top_surface_speed &&
|
||||
this->solid_infill_speed == rhs.solid_infill_speed &&
|
||||
this->lateral_lattice_angle_1 == rhs.lateral_lattice_angle_1 &&
|
||||
this->lateral_lattice_angle_2 == rhs.lateral_lattice_angle_2 &&
|
||||
this->infill_lock_depth == rhs.infill_lock_depth &&
|
||||
this->skin_infill_depth == rhs.skin_infill_depth &&
|
||||
this->infill_overhang_angle == rhs.infill_overhang_angle &&
|
||||
this->gyroid_optimized == rhs.gyroid_optimized;
|
||||
return this->extruder == rhs.extruder &&
|
||||
this->pattern == rhs.pattern &&
|
||||
this->spacing == rhs.spacing &&
|
||||
this->overlap == rhs.overlap &&
|
||||
this->angle == rhs.angle &&
|
||||
this->fixed_angle == rhs.fixed_angle &&
|
||||
this->bridge == rhs.bridge &&
|
||||
this->bridge_angle == rhs.bridge_angle &&
|
||||
this->density == rhs.density &&
|
||||
this->multiline == rhs.multiline &&
|
||||
// this->dont_adjust == rhs.dont_adjust &&
|
||||
this->anchor_length == rhs.anchor_length &&
|
||||
this->anchor_length_max == rhs.anchor_length_max &&
|
||||
this->flow == rhs.flow &&
|
||||
this->extrusion_role == rhs.extrusion_role &&
|
||||
this->role_speed == rhs.role_speed &&
|
||||
this->lateral_lattice_angle_1 == rhs.lateral_lattice_angle_1 &&
|
||||
this->lateral_lattice_angle_2 == rhs.lateral_lattice_angle_2 &&
|
||||
this->infill_lock_depth == rhs.infill_lock_depth &&
|
||||
this->skin_infill_depth == rhs.skin_infill_depth &&
|
||||
this->infill_overhang_angle == rhs.infill_overhang_angle &&
|
||||
this->gyroid_optimized == rhs.gyroid_optimized;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -922,6 +916,12 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.extrusion_role = erSolidInfill;
|
||||
}
|
||||
}
|
||||
if (params.extrusion_role == erTopSolidInfill)
|
||||
params.extruder = region_config.top_surface_filament_id;
|
||||
else if (params.extrusion_role == erBottomSurface)
|
||||
params.extruder = region_config.bottom_surface_filament_id;
|
||||
else if (params.extrusion_role == erSolidInfill)
|
||||
params.extruder = region_config.internal_solid_filament_id;
|
||||
// Orca: apply fill multiline only for sparse infill
|
||||
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
|
||||
|
||||
@@ -941,10 +941,13 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.fixed_angle = !region_config.solid_infill_rotate_template.value.empty();
|
||||
}
|
||||
params.bridge_angle = float(surface.bridge_angle);
|
||||
|
||||
|
||||
// ORCA: Align infill angle to model
|
||||
float align_offset = 0.f;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = layer.object()->trafo().matrix();
|
||||
params.angle += atan2((float) m(1, 0), (float) m(0, 0));
|
||||
align_offset = atan2((float)m(1, 0), (float)m(0, 0));
|
||||
params.angle += align_offset;
|
||||
}
|
||||
|
||||
// Calculate the actual flow we'll be using for this infill.
|
||||
@@ -954,15 +957,18 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
//Orca: enable thick bridge based on config
|
||||
layerm.bridging_flow(extrusion_role, is_thick_bridge) :
|
||||
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
|
||||
// record speed params
|
||||
if (!params.bridge) {
|
||||
if (params.extrusion_role == erInternalInfill)
|
||||
params.sparse_infill_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erTopSolidInfill) {
|
||||
params.top_surface_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
} else if (params.extrusion_role == erSolidInfill)
|
||||
params.solid_infill_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
}
|
||||
|
||||
params.role_speed = 0;
|
||||
if (params.extrusion_role == erBridgeInfill)
|
||||
params.role_speed = region_config.bridge_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erInternalBridgeInfill)
|
||||
params.role_speed = region_config.get_abs_value_at("internal_bridge_speed", layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erInternalInfill)
|
||||
params.role_speed = region_config.sparse_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erTopSolidInfill)
|
||||
params.role_speed = region_config.top_surface_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
else if (params.extrusion_role == erSolidInfill)
|
||||
params.role_speed = region_config.internal_solid_infill_speed.get_at(layer.get_extruder_id(params.extruder));
|
||||
// Calculate flow spacing for infill pattern generation.
|
||||
if (surface.is_solid() || is_bridge) {
|
||||
params.spacing = params.flow.spacing();
|
||||
@@ -1027,6 +1033,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
if (fill.region_id == size_t(-1)) {
|
||||
fill.region_id = region_id;
|
||||
fill.surface = surface;
|
||||
fill.surface.bridge_angle = params->bridge_angle;
|
||||
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
|
||||
//BBS
|
||||
fill.region_id_group.push_back(region_id);
|
||||
@@ -1570,18 +1577,18 @@ void Layer::make_ironing()
|
||||
((config.top_shell_layers > 0 || (this->object()->print()->config().spiral_mode && config.bottom_shell_layers > 1)) &&
|
||||
(config.ironing_type == IroningType::TopSurfaces ||
|
||||
(config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
|
||||
if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) {
|
||||
if (config.outer_wall_filament_id == config.top_surface_filament_id || config.wall_loops == 0) {
|
||||
// Iron the whole face.
|
||||
ironing_params.extruder = config.solid_infill_filament;
|
||||
ironing_params.extruder = config.top_surface_filament_id;
|
||||
} else {
|
||||
// Iron just the infill.
|
||||
ironing_params.extruder = config.solid_infill_filament;
|
||||
ironing_params.extruder = config.top_surface_filament_id;
|
||||
}
|
||||
}
|
||||
if (ironing_params.extruder != -1) {
|
||||
//TODO just_infill is currently not used.
|
||||
ironing_params.just_infill = false;
|
||||
// Get filament-specific overrides if configured, otherwise use default values
|
||||
// ORCA: Get filament-specific overrides if configured, otherwise use process values
|
||||
size_t extruder_idx = ironing_params.extruder - 1;
|
||||
ironing_params.line_spacing = (!config.filament_ironing_spacing.is_nil(extruder_idx)
|
||||
? config.filament_ironing_spacing.get_at(extruder_idx)
|
||||
@@ -1595,7 +1602,12 @@ void Layer::make_ironing()
|
||||
ironing_params.speed = (!config.filament_ironing_speed.is_nil(extruder_idx)
|
||||
? config.filament_ironing_speed.get_at(extruder_idx)
|
||||
: config.ironing_speed);
|
||||
ironing_params.angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.;
|
||||
double ironing_angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.;
|
||||
if (config.align_infill_direction_to_model) {
|
||||
auto m = this->object()->trafo().matrix();
|
||||
ironing_angle += atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
ironing_params.angle = ironing_angle;
|
||||
ironing_params.fixed_angle = config.ironing_angle_fixed || !config.solid_infill_rotate_template.value.empty();
|
||||
ironing_params.pattern = config.ironing_pattern;
|
||||
ironing_params.layerm = layerm;
|
||||
|
||||
@@ -1025,7 +1025,7 @@ void mark_boundary_segments_touching_infill(
|
||||
#endif // INFILL_DEBUG_OUTPUT
|
||||
|
||||
EdgeGrid::Grid grid;
|
||||
// Make sure that the the grid is big enough for queries against the thick segment.
|
||||
// Make sure that the grid is big enough for queries against the thick segment.
|
||||
grid.set_bbox(boundary_bbox.inflated(distance_colliding * 1.43));
|
||||
// Inflate the bounding box by a thick line width.
|
||||
grid.create(boundary, coord_t(std::max(clip_distance, distance_colliding) + scale_(10.)));
|
||||
|
||||
@@ -2318,7 +2318,7 @@ static std::vector<MonotonicRegionLink> chain_monotonic_regions(
|
||||
constexpr float const pheromone_evaporation = 0.1f;
|
||||
// Evaporation rate to diversify paths taken by individual ants.
|
||||
constexpr float const pheromone_diversification = 0.1f;
|
||||
// Probability at which to take the next best path. Otherwise take the the path based on the cost distribution.
|
||||
// Probability at which to take the next best path. Otherwise take the path based on the cost distribution.
|
||||
constexpr float const probability_take_best = 0.9f;
|
||||
// Exponents of the cost function.
|
||||
constexpr float const pheromone_alpha = 1.f; // pheromone exponent
|
||||
|
||||
@@ -89,9 +89,9 @@ Generator::Generator(const PrintObject &print_object, const std::function<void()
|
||||
|
||||
m_supporting_radius = coord_t(m_infill_extrusion_width) * 100 * n_multiline / region_config.sparse_infill_density;
|
||||
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_overhang_angle = region_config.lightning_overhang_angle.value * M_PI / 180.0;
|
||||
const double lightning_infill_prune_angle = region_config.lightning_prune_angle.value * M_PI / 180.0;
|
||||
const double lightning_infill_straightening_angle = region_config.lightning_straightening_angle.value * M_PI / 180.0;
|
||||
m_wall_supporting_radius = coord_t(layer_thickness * std::tan(lightning_infill_overhang_angle));
|
||||
m_prune_length = coord_t(layer_thickness * std::tan(lightning_infill_prune_angle));
|
||||
m_straightening_max_distance = coord_t(layer_thickness * std::tan(lightning_infill_straightening_angle));
|
||||
@@ -128,7 +128,7 @@ Generator::Generator(PrintObject* m_object, std::vector<Polygons>& contours, std
|
||||
//TODO: decide whether enable density controller in advanced options or not
|
||||
density = std::max(0.15f, density);
|
||||
m_supporting_radius = coord_t(m_infill_extrusion_width) / density;
|
||||
|
||||
// Keep support-lightning behavior fixed and independent of user print-region angles.
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||
|
||||
+25
-7
@@ -49,6 +49,8 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
||||
return frInfill;
|
||||
else if (opt_key == "internal_solid_infill_line_width")
|
||||
return frSolidInfill;
|
||||
else if (opt_key == "bridge_line_width")
|
||||
return frSolidInfill;
|
||||
else if (opt_key == "top_surface_line_width")
|
||||
return frTopSolidInfill;
|
||||
else if (opt_key == "support_line_width")
|
||||
@@ -59,7 +61,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
||||
|
||||
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
|
||||
{
|
||||
throw FlowErrorMissingVariable((boost::format(L("Failed to calculate line width of %1%. Cannot get value of \"%2%\" ")) % opt_key % dependent_opt_key).str());
|
||||
throw FlowErrorMissingVariable((boost::format(L("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d ")) % opt_key % dependent_opt_key).str());
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
@@ -67,6 +69,26 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloat
|
||||
{
|
||||
assert(opt != nullptr);
|
||||
|
||||
auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
if (opt_nozzle_diameters == nullptr)
|
||||
throw_on_missing_variable(opt_key, "nozzle_diameter");
|
||||
const float nozzle_diameter = float(opt_nozzle_diameters->get_at(first_printing_extruder));
|
||||
|
||||
if (opt_key == "bridge_line_width") {
|
||||
if (opt->percent) {
|
||||
const double bridge_width = opt->get_abs_value(nozzle_diameter);
|
||||
if (bridge_width > 0.)
|
||||
return bridge_width;
|
||||
} else if (opt->value > 0.) {
|
||||
return opt->value;
|
||||
}
|
||||
|
||||
opt = config.option<ConfigOptionFloatOrPercent>("internal_solid_infill_line_width");
|
||||
if (opt == nullptr)
|
||||
throw_on_missing_variable(opt_key, "internal_solid_infill_line_width");
|
||||
return extrusion_width("internal_solid_infill_line_width", opt, config, first_printing_extruder);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This is the logic used for skit / brim, but not for the rest of the 1st layer.
|
||||
if (opt->value == 0. && first_layer) {
|
||||
@@ -84,17 +106,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloat
|
||||
throw_on_missing_variable(opt_key, "line_width");
|
||||
}
|
||||
|
||||
auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
if (opt_nozzle_diameters == nullptr)
|
||||
throw_on_missing_variable(opt_key, "nozzle_diameter");
|
||||
|
||||
if (opt->percent) {
|
||||
return opt->get_abs_value(float(opt_nozzle_diameters->get_at(first_printing_extruder)));
|
||||
return opt->get_abs_value(nozzle_diameter);
|
||||
}
|
||||
|
||||
if (opt->value == 0.) {
|
||||
// If user left option to 0, calculate a sane default width.
|
||||
return auto_extrusion_width(opt_key_to_flow_role(opt_key), float(opt_nozzle_diameters->get_at(first_printing_extruder)));
|
||||
return auto_extrusion_width(opt_key_to_flow_role(opt_key), nozzle_diameter);
|
||||
}
|
||||
|
||||
return opt->value;
|
||||
|
||||
@@ -107,7 +107,7 @@ public:
|
||||
double linear_defletion = 0.003,
|
||||
double angle_defletion = 0.5);
|
||||
|
||||
std::atomic<bool> m_stop_mesh;
|
||||
std::atomic<bool> m_stop_mesh{false};
|
||||
void update_process(int load_stage, int current, int total, bool& cancel);
|
||||
private:
|
||||
std::string m_path;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
|
||||
#include <boost/assign.hpp>
|
||||
#include <boost/bimap.hpp>
|
||||
@@ -8137,6 +8138,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Orca: replicates GCode's object-label sanitization (sanitize_instance_name in GCode.cpp), used to
|
||||
// build the per-instance object name written into slice_info.config so it matches the EXCLUDE_OBJECT /
|
||||
// M486 object name embedded in the g-code. Keep this in sync with GCode.cpp.
|
||||
static std::string sanitize_object_label(const std::string& name)
|
||||
{
|
||||
// Compiled once: building a std::regex is expensive and this runs per object instance.
|
||||
static const std::regex non_word_re("[ !@#$%^&*()=+\\[\\]{};:\",']+");
|
||||
std::string result = std::regex_replace(name, non_word_re, "_");
|
||||
if (!result.empty() && result.front() == '_')
|
||||
result.erase(result.begin());
|
||||
if (!result.empty() && result.back() == '_')
|
||||
result.erase(result.end() - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config)
|
||||
{
|
||||
std::stringstream stream;
|
||||
@@ -8217,6 +8233,21 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
stream << "\"/>\n";
|
||||
}
|
||||
|
||||
// Orca: for non-BambuLab printers that label objects in the g-code (Klipper/Marlin/RRF via
|
||||
// EXCLUDE_OBJECT / M486), write the per-instance g-code object name into slice_info.config
|
||||
// (e.g. "OrcaCube_v2.drc_id_0_copy_0") so the printer can correlate objects between the 3MF
|
||||
// and the g-code. The g-code names objects per plate as
|
||||
// "<name>_id_<object index>_copy_<instance index>" (see GCode::set_object_info), which we
|
||||
// reconstruct here from the (sorted) objects_and_instances list. identify_id is unchanged.
|
||||
// BambuLab printers keep the raw object name.
|
||||
const GCodeFlavor slice_gcode_flavor = config.opt_enum<GCodeFlavor>("gcode_flavor");
|
||||
const bool use_gcode_object_name = !GCodeProcessor::s_IsBBLPrinter &&
|
||||
(slice_gcode_flavor == gcfKlipper || slice_gcode_flavor == gcfMarlinLegacy ||
|
||||
slice_gcode_flavor == gcfMarlinFirmware || slice_gcode_flavor == gcfRepRapFirmware);
|
||||
int gcode_object_index = -1;
|
||||
int gcode_copy_index = 0;
|
||||
int last_object_id = -1;
|
||||
|
||||
for (auto it = plate_data->objects_and_instances.begin(); it != plate_data->objects_and_instances.end(); it++)
|
||||
{
|
||||
int obj_id = it->first;
|
||||
@@ -8241,7 +8272,24 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
identify_id = inst->id().id;
|
||||
bool skipped = std::find(plate_data->skipped_objects.begin(), plate_data->skipped_objects.end(), identify_id) !=
|
||||
plate_data->skipped_objects.end();
|
||||
stream << " <" << OBJECT_TAG << " " << IDENTIFYID_ATTR << "=\"" << std::to_string(identify_id) << "\" " << NAME_ATTR << "=\"" << xml_escape(obj->name)
|
||||
|
||||
// Advance the per-plate g-code object/copy index. objects_and_instances is sorted,
|
||||
// so all instances of the same object are contiguous.
|
||||
if (obj_id != last_object_id) {
|
||||
++gcode_object_index;
|
||||
gcode_copy_index = 0;
|
||||
last_object_id = obj_id;
|
||||
} else {
|
||||
++gcode_copy_index;
|
||||
}
|
||||
|
||||
std::string object_name = obj->name;
|
||||
if (use_gcode_object_name)
|
||||
// Matches GCode::get_instance_name(): sanitize(sanitize(name) + "_id_<obj>_copy_<inst>").
|
||||
object_name = sanitize_object_label(sanitize_object_label(obj->name) + "_id_" +
|
||||
std::to_string(gcode_object_index) + "_copy_" + std::to_string(gcode_copy_index));
|
||||
|
||||
stream << " <" << OBJECT_TAG << " " << IDENTIFYID_ATTR << "=\"" << std::to_string(identify_id) << "\" " << NAME_ATTR << "=\"" << xml_escape(object_name)
|
||||
<< "\" " << SKIPPED_ATTR << "=\"" << (skipped ? "true" : "false")
|
||||
<< "\" />\n";
|
||||
}
|
||||
|
||||
+205
-109
@@ -1817,10 +1817,10 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
||||
std::string warning;
|
||||
size_t i = 0;
|
||||
for (i = 0; i < std::min(warning_ranges.size(), size_t(5)); ++i)
|
||||
warning += Slic3r::format(_(L("Object can't be printed for empty layer between %1% and %2%.")),
|
||||
warning += Slic3r::format(_(L("The object has empty layers between %1% and %2% and can\u2019t be printed.")),
|
||||
warning_ranges[i].first, warning_ranges[i].second) + "\n";
|
||||
warning += Slic3r::format(_(L("Object: %1%")), object.model_object()->name) + "\n"
|
||||
+ _(L("Maybe parts of the object at these height are too thin, or the object has faulty mesh"));
|
||||
+ _(L("Parts of the object at these heights may be too thin or the object may have a faulty mesh."));
|
||||
|
||||
const_cast<Print*>(object.print())->active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingEmptyGcodeLayers);
|
||||
@@ -2195,7 +2195,9 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
|
||||
print->set_done(psGCodeExport);
|
||||
|
||||
if(is_BBL_Printer())
|
||||
// Orca: label_object_enabled reflects whether objects are labeled in the g-code (EXCLUDE_OBJECT /
|
||||
// M486), which is driven by exclude_object for every printer
|
||||
if(result != nullptr)
|
||||
result->label_object_enabled = m_enable_exclude_object;
|
||||
// Write the profiler measurements to file
|
||||
PROFILE_UPDATE();
|
||||
@@ -2279,17 +2281,45 @@ namespace DoExport {
|
||||
ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
|
||||
}
|
||||
|
||||
// Count tool/filament changes across the print from the tool ordering. Used as a fallback when no
|
||||
// wipe tower populated WipeTowerData::number_of_toolchanges (left at -1). Covers non-sequential
|
||||
// prints without a wipe tower (manual swaps, toolchanger/IDEX). Note: sequential (by-object) prints
|
||||
// leave print.tool_ordering() empty, so total_toolchanges stays 0 there (unchanged from before).
|
||||
static int total_toolchanges_from_ordering(const ToolOrdering &tool_ordering)
|
||||
{
|
||||
int changes = 0;
|
||||
int last = -1;
|
||||
for (const LayerTools < : tool_ordering)
|
||||
for (unsigned int extruder : lt.extruders) {
|
||||
if (last >= 0 && int(extruder) != last)
|
||||
++ changes;
|
||||
last = int(extruder);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
// Total tool changes for the print, preferring the wipe-tower count and falling back to the tool
|
||||
// ordering when no wipe tower populated it (number_of_toolchanges < 0).
|
||||
static int resolve_total_toolchanges(const WipeTowerData &wipe_tower_data, const ToolOrdering &tool_ordering)
|
||||
{
|
||||
int changes = wipe_tower_data.number_of_toolchanges;
|
||||
if (changes < 0)
|
||||
changes = total_toolchanges_from_ordering(tool_ordering);
|
||||
return std::max(0, changes);
|
||||
}
|
||||
|
||||
// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
|
||||
static std::string update_print_stats_and_format_filament_stats(
|
||||
const bool has_wipe_tower,
|
||||
const WipeTowerData &wipe_tower_data,
|
||||
const std::vector<Extruder> &extruders,
|
||||
PrintStatistics &print_statistics)
|
||||
PrintStatistics &print_statistics,
|
||||
const ToolOrdering &tool_ordering)
|
||||
{
|
||||
std::string filament_stats_string_out;
|
||||
|
||||
print_statistics.clear();
|
||||
print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
|
||||
print_statistics.total_toolchanges = resolve_total_toolchanges(wipe_tower_data, tool_ordering);
|
||||
if (! extruders.empty()) {
|
||||
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
|
||||
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
|
||||
@@ -2522,6 +2552,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
std::string top_gcode_template = print.config().file_start_gcode.value;
|
||||
if (!top_gcode_template.empty()) {
|
||||
DynamicConfig top_config;
|
||||
// file_start_gcode runs before the parser copy that normally restores these, so set them here.
|
||||
PlaceholderParser::update_timestamp(top_config);
|
||||
PlaceholderParser::update_user_name(top_config);
|
||||
top_config.set_key_value("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
|
||||
top_config.set_key_value("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
|
||||
std::string top_gcode = print.placeholder_parser().process(top_gcode_template, 0, &top_config);
|
||||
@@ -2720,7 +2753,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
}
|
||||
if (initial_extruder_id == static_cast<unsigned int>(-1))
|
||||
// No object to print was found, cancel the G-code export.
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small")));
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. It may be too small.")));
|
||||
// We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
|
||||
// Use the extruder IDs collected from Regions.
|
||||
this->set_extruders(print.extruders());
|
||||
@@ -2736,7 +2769,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
max_additional_fan = temp_max_additional_fan;
|
||||
if (tool_ordering.all_extruders().empty())
|
||||
// No object to print was found, cancel the G-code export.
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small")));
|
||||
throw Slic3r::SlicingError(_(L("No object can be printed. It may be too small.")));
|
||||
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
|
||||
// Orca: support all extruder priming
|
||||
initial_extruder_id = (wipe_tower_type == WipeTowerType::Type2 && has_wipe_tower && !print.config().single_extruder_multi_material_priming) ?
|
||||
@@ -2859,7 +2892,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
|
||||
this->placeholder_parser().set("has_wipe_tower", has_wipe_tower);
|
||||
this->placeholder_parser().set("has_single_extruder_multi_material_priming", wipe_tower_type == WipeTowerType::Type2 && has_wipe_tower && print.config().single_extruder_multi_material_priming);
|
||||
this->placeholder_parser().set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
|
||||
this->placeholder_parser().set("total_toolchanges", DoExport::resolve_total_toolchanges(print.wipe_tower_data(), print.tool_ordering()));
|
||||
this->placeholder_parser().set("num_extruders", int(print.config().nozzle_diameter.values.size()));
|
||||
this->placeholder_parser().set("retract_length", new ConfigOptionFloats(print.config().retraction_length));
|
||||
|
||||
@@ -3142,18 +3175,24 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (is_bbl_printers) {
|
||||
this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true);
|
||||
}
|
||||
// Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed
|
||||
bool activate_air_filtration_during_print = false;
|
||||
int during_print_exhaust_fan_speed = 0;
|
||||
for (const auto &extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_during_print.get_at(extruder.id())) {
|
||||
activate_air_filtration_during_print = true;
|
||||
during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed,
|
||||
m_config.during_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
|
||||
// Orca: when air filtration is supported, check if it needs to be activated during printing and set the exhaust fan speed accordingly
|
||||
if (m_config.support_air_filtration.value) {
|
||||
bool activate_air_filtration_during_print = false;
|
||||
int during_print_exhaust_fan_speed = 0;
|
||||
|
||||
// Orca: when activate_air_filtration is set on any extruder, find and set the highest during_print_exhaust_fan_speed
|
||||
for (const auto &extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_during_print.get_at(extruder.id())) {
|
||||
activate_air_filtration_during_print = true;
|
||||
during_print_exhaust_fan_speed = std::max(during_print_exhaust_fan_speed,
|
||||
m_config.during_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
}
|
||||
}
|
||||
|
||||
if (activate_air_filtration_during_print)
|
||||
file.write(m_writer.set_exhaust_fan(during_print_exhaust_fan_speed));
|
||||
}
|
||||
if (activate_air_filtration_during_print)
|
||||
file.write(m_writer.set_exhaust_fan(during_print_exhaust_fan_speed, true));
|
||||
|
||||
print.throw_if_canceled();
|
||||
|
||||
@@ -3452,16 +3491,23 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
if (activate_chamber_temp_control && max_chamber_temp > 0)
|
||||
file.write(m_writer.set_chamber_temperature(0, false)); //close chamber_temperature
|
||||
|
||||
bool activate_air_filtration_on_completion = false;
|
||||
int complete_print_exhaust_fan_speed = 0;
|
||||
for (const auto& extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_on_completion.get_at(extruder.id())) {
|
||||
activate_air_filtration_on_completion = true;
|
||||
complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
// Orca: when air filtration is supported, check if it needs to be activated after print completion and set the exhaust fan speed accordingly
|
||||
if (m_config.support_air_filtration.value) {
|
||||
bool activate_air_filtration_on_completion = false;
|
||||
int complete_print_exhaust_fan_speed = 0;
|
||||
|
||||
// Orca: when activate_air_filtration is set on any extruder, find and set the highest complete_print_exhaust_fan_speed
|
||||
for (const auto& extruder : m_writer.extruders()) {
|
||||
if (m_config.activate_air_filtration.get_at(extruder.id()) && m_config.activate_air_filtration_on_completion.get_at(extruder.id())) {
|
||||
activate_air_filtration_on_completion = true;
|
||||
complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id()));
|
||||
}
|
||||
}
|
||||
|
||||
if (activate_air_filtration_on_completion)
|
||||
file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed));
|
||||
}
|
||||
if (activate_air_filtration_on_completion)
|
||||
file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true));
|
||||
|
||||
// adds tags for time estimators
|
||||
file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str());
|
||||
file.write_format("; EXECUTABLE_BLOCK_END\n\n");
|
||||
@@ -3474,7 +3520,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
has_wipe_tower, print.wipe_tower_data(),
|
||||
m_writer.extruders(),
|
||||
// Modifies
|
||||
print.m_print_statistics));
|
||||
print.m_print_statistics,
|
||||
// Const input (tool-change fallback for non-wipe-tower prints)
|
||||
print.tool_ordering()));
|
||||
print.m_print_statistics.initial_tool = initial_extruder_id;
|
||||
if (!is_bbl_printers) {
|
||||
file.write_format("; total filament used [g] = %.2lf\n",
|
||||
@@ -4737,6 +4785,7 @@ LayerResult GCode::process_layer(
|
||||
|
||||
// Group extrusions by an extruder, then by an object, an island and a region.
|
||||
std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
|
||||
std::vector<std::unique_ptr<ExtrusionEntityCollection>> split_perimeter_storage;
|
||||
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
|
||||
for (const LayerToPrint &layer_to_print : layers) {
|
||||
if (layer_to_print.support_layer != nullptr) {
|
||||
@@ -4892,55 +4941,83 @@ LayerResult GCode::process_layer(
|
||||
if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail.
|
||||
continue;
|
||||
|
||||
// This extrusion is part of certain Region, which tells us which extruder should be used for it:
|
||||
int correct_extruder_id = layer_tools.extruder(*extrusions, region);
|
||||
auto process_extrusions = [&](const ExtrusionEntityCollection *current_extrusions,
|
||||
const ExtrusionEntityCollection *overrides_key,
|
||||
bool use_overrides) {
|
||||
// This extrusion is part of certain Region, which tells us which extruder should be used for it.
|
||||
int correct_extruder_id = layer_tools.extruder(*current_extrusions, region);
|
||||
|
||||
// Let's recover vector of extruder overrides:
|
||||
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
|
||||
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
||||
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
||||
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
||||
correct_extruder_id = layer_tools.extruders.back();
|
||||
}
|
||||
printing_extruders.clear();
|
||||
if (is_anything_overridden) {
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size());
|
||||
if (entity_overrides == nullptr) {
|
||||
const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr;
|
||||
if (! layer_tools.has_extruder(correct_extruder_id)) {
|
||||
// this entity is not overridden, but its extruder is not in layer_tools - we'll print it
|
||||
// by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
|
||||
correct_extruder_id = layer_tools.extruders.back();
|
||||
}
|
||||
printing_extruders.clear();
|
||||
if (is_anything_overridden && use_overrides) {
|
||||
entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(overrides_key, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size());
|
||||
if (entity_overrides == nullptr) {
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
} else {
|
||||
printing_extruders.reserve(entity_overrides->size());
|
||||
for (int extruder : *entity_overrides)
|
||||
printing_extruders.emplace_back(extruder >= 0 ?
|
||||
// at least one copy is overridden to use this extruder
|
||||
extruder :
|
||||
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
|
||||
static_cast<unsigned int>(- extruder - 1));
|
||||
Slic3r::sort_remove_duplicates(printing_extruders);
|
||||
}
|
||||
} else {
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
} else {
|
||||
printing_extruders.reserve(entity_overrides->size());
|
||||
for (int extruder : *entity_overrides)
|
||||
printing_extruders.emplace_back(extruder >= 0 ?
|
||||
// at least one copy is overridden to use this extruder
|
||||
extruder :
|
||||
// at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
|
||||
static_cast<unsigned int>(- extruder - 1));
|
||||
Slic3r::sort_remove_duplicates(printing_extruders);
|
||||
}
|
||||
} else
|
||||
printing_extruders.emplace_back(correct_extruder_id);
|
||||
|
||||
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
|
||||
for (unsigned int extruder : printing_extruders)
|
||||
{
|
||||
std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
|
||||
by_extruder,
|
||||
extruder,
|
||||
&layer_to_print - layers.data(),
|
||||
layers.size(), n_slices+1);
|
||||
for (size_t i = 0; i <= n_slices; ++ i) {
|
||||
bool last = i == n_slices;
|
||||
size_t island_idx = last ? n_slices : slices_test_order[i];
|
||||
if (// extrusions->first_point does not fit inside any slice
|
||||
last ||
|
||||
// extrusions->first_point fits inside ith slice
|
||||
point_inside_surface(island_idx, extrusions->first_point())) {
|
||||
if (islands[island_idx].by_region.empty())
|
||||
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
|
||||
islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides);
|
||||
break;
|
||||
// Now we must add this extrusion into the by_extruder map, once for each extruder that will print it.
|
||||
for (unsigned int extruder : printing_extruders) {
|
||||
std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
|
||||
by_extruder,
|
||||
extruder,
|
||||
&layer_to_print - layers.data(),
|
||||
layers.size(), n_slices + 1);
|
||||
for (size_t i = 0; i <= n_slices; ++i) {
|
||||
bool last = i == n_slices;
|
||||
size_t island_idx = last ? n_slices : slices_test_order[i];
|
||||
if (last || point_inside_surface(island_idx, current_extrusions->first_point())) {
|
||||
if (islands[island_idx].by_region.empty())
|
||||
islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region());
|
||||
islands[island_idx].by_region[region.print_region_id()].append(entity_type, current_extrusions, entity_overrides);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool split_mixed_perimeters =
|
||||
entity_type == ObjectByExtruder::Island::Region::PERIMETERS &&
|
||||
region.config().outer_wall_filament_id.value != region.config().inner_wall_filament_id.value &&
|
||||
extrusions->role() == erMixed;
|
||||
|
||||
if (split_mixed_perimeters) {
|
||||
auto outer_perimeters = std::make_unique<ExtrusionEntityCollection>();
|
||||
auto inner_perimeters = std::make_unique<ExtrusionEntityCollection>();
|
||||
for (const ExtrusionEntity *entity : extrusions->entities) {
|
||||
const ExtrusionRole role = entity->role();
|
||||
if (role == erExternalPerimeter || role == erOverhangPerimeter)
|
||||
outer_perimeters->append(*entity);
|
||||
else if (role == erPerimeter)
|
||||
inner_perimeters->append(*entity);
|
||||
}
|
||||
|
||||
if (!outer_perimeters->entities.empty()) {
|
||||
split_perimeter_storage.emplace_back(std::move(outer_perimeters));
|
||||
process_extrusions(split_perimeter_storage.back().get(), nullptr, false);
|
||||
}
|
||||
if (!inner_perimeters->entities.empty()) {
|
||||
split_perimeter_storage.emplace_back(std::move(inner_perimeters));
|
||||
process_extrusions(split_perimeter_storage.back().get(), nullptr, false);
|
||||
}
|
||||
} else {
|
||||
process_extrusions(extrusions, extrusions, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5082,44 +5159,30 @@ LayerResult GCode::process_layer(
|
||||
bool has_insert_wrapping_detection_gcode = false;
|
||||
|
||||
// Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
|
||||
// Orca: Print unified global brim before any object.
|
||||
// Only do this if `combine_brims` is enabled and we are printing by layer.
|
||||
if (first_layer && sequence_by_layer && m_config.combine_brims && !print.m_brimMap.empty()) {
|
||||
const ObjectID unified_object_id = [&]() -> ObjectID {
|
||||
ObjectID id;
|
||||
bool found = false;
|
||||
for (const auto& [obj_id, brim] : print.m_brimMap) {
|
||||
const bool has_printable_entities = std::any_of(brim.entities.begin(), brim.entities.end(),
|
||||
[](const ExtrusionEntity* ee) { return ee != nullptr; });
|
||||
if (!has_printable_entities)
|
||||
continue;
|
||||
if (found)
|
||||
return ObjectID();
|
||||
id = obj_id;
|
||||
found = true;
|
||||
}
|
||||
return found ? id : ObjectID();
|
||||
}();
|
||||
|
||||
if (unified_object_id.valid()) {
|
||||
const auto it = print.m_brimMap.find(unified_object_id);
|
||||
if (it != print.m_brimMap.end()) {
|
||||
this->set_origin(0., 0.);
|
||||
for (const ExtrusionEntity* ee : it->second.entities)
|
||||
if (ee != nullptr)
|
||||
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
|
||||
|
||||
// Mark brim as printed for this object to avoid per-object brim emission later.
|
||||
this->m_objsWithBrim.erase(unified_object_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int extruder_id : layer_tools.extruders)
|
||||
{
|
||||
if (print.config().skirt_type == stCombined && !print.skirt().empty())
|
||||
gcode += generate_skirt(print, print.skirt(), Point(0, 0), layer.object()->config().skirt_start_angle, layer_tools, layer,
|
||||
extruder_id);
|
||||
if ((print.config().skirt_type == stCombined ||
|
||||
(print.config().skirt_type == stPerObject && print.config().print_sequence == PrintSequence::ByLayer)) &&
|
||||
!print.skirt_groups().empty()) {
|
||||
bool skirt_generated_for_current_print_z = false;
|
||||
for (const ExtrusionEntityCollection& skirt_group : print.skirt_groups()) {
|
||||
if (skirt_group.empty())
|
||||
continue;
|
||||
|
||||
// Orca: each grouped skirt is emitted as its own collection so higher skirt layers
|
||||
// follow the same per-group behavior as the first layer.
|
||||
if (first_layer)
|
||||
m_skirt_done.clear();
|
||||
else if (skirt_generated_for_current_print_z && !m_skirt_done.empty())
|
||||
m_skirt_done.pop_back();
|
||||
|
||||
std::string skirt_gcode = generate_skirt(print, skirt_group, Point(0, 0), layer.object()->config().skirt_start_angle,
|
||||
layer_tools, layer, extruder_id);
|
||||
if (!skirt_gcode.empty())
|
||||
skirt_generated_for_current_print_z = true;
|
||||
gcode += std::move(skirt_gcode);
|
||||
}
|
||||
}
|
||||
|
||||
if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) {
|
||||
std::vector<size_t> filament_instances_id;
|
||||
@@ -5220,7 +5283,38 @@ LayerResult GCode::process_layer(
|
||||
}
|
||||
}
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
||||
// Orca: Print unified global brim after the skirt and before any object.
|
||||
// Only do this if `combine_brims` is enabled and we are printing by layer.
|
||||
if (first_layer && sequence_by_layer && m_config.combine_brims && !print.m_brimMap.empty()) {
|
||||
const ObjectID unified_object_id = [&]() -> ObjectID {
|
||||
ObjectID id;
|
||||
for (const auto& [obj_id, brim] : print.m_brimMap) {
|
||||
const bool has_printable_entities = std::any_of(brim.entities.begin(), brim.entities.end(),
|
||||
[](const ExtrusionEntity* ee) { return ee != nullptr; });
|
||||
if (!has_printable_entities)
|
||||
continue;
|
||||
|
||||
if (id.valid())
|
||||
return ObjectID();
|
||||
|
||||
id = obj_id;
|
||||
}
|
||||
return id;
|
||||
}();
|
||||
|
||||
if (unified_object_id.valid() && this->m_objsWithBrim.find(unified_object_id) != this->m_objsWithBrim.end()) {
|
||||
const ExtrusionEntityCollection& unified_brim = print.m_brimMap.at(unified_object_id);
|
||||
this->set_origin(0., 0.);
|
||||
for (const ExtrusionEntity* ee : unified_brim.entities)
|
||||
if (ee != nullptr)
|
||||
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
|
||||
|
||||
// Mark brim as printed for this object to avoid per-object brim emission later.
|
||||
this->m_objsWithBrim.erase(unified_object_id);
|
||||
}
|
||||
}
|
||||
|
||||
// We are almost ready to print. However, we must go through all the objects twice to print the overridden extrusions first (infill/perimeter wiping feature):
|
||||
std::vector<ObjectByExtruder::Island::Region> by_region_per_copy_cache;
|
||||
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
|
||||
if (is_anything_overridden && print_wipe_extrusions == 0)
|
||||
@@ -5258,7 +5352,7 @@ LayerResult GCode::process_layer(
|
||||
gcode += std::move(skirt_gcode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
|
||||
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
|
||||
// To control print speed of the 1st object layer printed over raft interface.
|
||||
@@ -6136,7 +6230,9 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
|
||||
if (extrusions.empty())
|
||||
return gcode;
|
||||
|
||||
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
|
||||
//ORCA: Respect no_sort to preserve support base outline->fill order.
|
||||
if (!support_fills.no_sort)
|
||||
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
|
||||
|
||||
//const double support_speed = m_config.support_speed.value;
|
||||
//const double support_interface_speed = m_config.get_abs_value("support_interface_speed");
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <cctype>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -282,4 +283,81 @@ std::string AdaptivePAProcessor::process_layer(std::string &&gcode) {
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string AdaptivePAProcessor::validate_adaptive_pa_model(const std::string& model_str)
|
||||
{
|
||||
if (model_str.empty())
|
||||
return {}; // Empty model is valid
|
||||
|
||||
std::istringstream model_stream(model_str);
|
||||
std::string line;
|
||||
int line_number = 0;
|
||||
|
||||
while (std::getline(model_stream, line)) {
|
||||
++line_number;
|
||||
|
||||
// Trim whitespace
|
||||
const auto first = line.find_first_not_of(" \t\r\n");
|
||||
if (first == std::string::npos)
|
||||
continue; // Skip empty lines
|
||||
|
||||
const auto last = line.find_last_not_of(" \t\r\n");
|
||||
line = line.substr(first, last - first + 1);
|
||||
|
||||
// Only numbers, commas and dots are allowed (no letters or other characters)
|
||||
for (char c : line) {
|
||||
if (!std::isdigit(static_cast<unsigned char>(c)) && c != ',' && c != '.') {
|
||||
return "Line " + std::to_string(line_number) +
|
||||
": only numbers, commas and dots are allowed";
|
||||
}
|
||||
}
|
||||
|
||||
// Count commas to validate format (should be exactly 2 for 3 values)
|
||||
int comma_count = 0;
|
||||
for (char c : line) {
|
||||
if (c == ',') comma_count++;
|
||||
}
|
||||
|
||||
if (comma_count != 2) {
|
||||
return "Line " + std::to_string(line_number) +
|
||||
": must contain exactly 3 comma-separated values (PA, flow, acceleration)";
|
||||
}
|
||||
|
||||
// Parse and validate the values
|
||||
try {
|
||||
std::istringstream line_stream(line);
|
||||
std::string value;
|
||||
|
||||
// Parse PA
|
||||
if (!std::getline(line_stream, value, ','))
|
||||
return "Line " + std::to_string(line_number) + ": missing PA value";
|
||||
double pa = std::stod(value);
|
||||
|
||||
// Parse flow
|
||||
if (!std::getline(line_stream, value, ','))
|
||||
return "Line " + std::to_string(line_number) + ": missing flow value";
|
||||
double flow = std::stod(value);
|
||||
|
||||
// Parse acceleration
|
||||
if (!std::getline(line_stream, value, ','))
|
||||
return "Line " + std::to_string(line_number) + ": missing acceleration value";
|
||||
double accel = std::stod(value);
|
||||
|
||||
// Validate constraints
|
||||
if (pa >= 2.0) {
|
||||
return "Line " + std::to_string(line_number) + ": PA value must be less than 2";
|
||||
}
|
||||
if (flow <= pa) {
|
||||
return "Line " + std::to_string(line_number) + ": flow value must be greater than PA value";
|
||||
}
|
||||
if (accel <= flow) {
|
||||
return "Line " + std::to_string(line_number) + ": acceleration value must be greater than flow value";
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return "Line " + std::to_string(line_number) + ": invalid numeric value";
|
||||
}
|
||||
}
|
||||
|
||||
return {}; // All validations passed
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -54,6 +54,20 @@ public:
|
||||
*/
|
||||
void resetPreviousPA(double PA){ m_last_predicted_pa = PA; };
|
||||
|
||||
/**
|
||||
* @brief Validates an adaptive pressure advance model string.
|
||||
*
|
||||
* Checks that:
|
||||
* - Each non-empty line has exactly 3 comma-separated values (PA, flow, accel)
|
||||
* - PA value is less than 2
|
||||
* - Flow value is greater than PA value
|
||||
* - Accel value is greater than flow value
|
||||
*
|
||||
* @param model_str The model string to validate (typically from config)
|
||||
* @return Empty string if valid, or an error message describing the first issue found
|
||||
*/
|
||||
static std::string validate_adaptive_pa_model(const std::string& model_str);
|
||||
|
||||
private:
|
||||
GCode &m_gcodegen; ///< Reference to the GCode object.
|
||||
std::unordered_map<unsigned int, std::unique_ptr<AdaptivePAInterpolator>> m_AdaptivePAInterpolators; ///< Map between Interpolator objects and tool ID's
|
||||
|
||||
@@ -3120,7 +3120,7 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
|
||||
// Orca: Integrate filament consumption for purging performed to an external device and controlled via macros
|
||||
// (eg. Happy Hare) in the filament consumption stats.
|
||||
if (boost::starts_with(comment, GCodeProcessor::External_Purge_Tag)) {
|
||||
std::regex numberRegex(R"(\d+\.\d+)");
|
||||
static const std::regex numberRegex(R"(\d+\.\d+)");
|
||||
std::smatch match;
|
||||
std::string line(comment);
|
||||
if (std::regex_search(line, match, numberRegex)) {
|
||||
@@ -4976,7 +4976,7 @@ void GCodeProcessor::process_M572(const GCodeReader::GCodeLine &line)
|
||||
|
||||
void GCodeProcessor::process_SET_PRESSURE_ADVANCE(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
std::regex regex(R"(SET_PRESSURE_ADVANCE\s+(?:.*\s+)?ADVANCE\s*=\s*([\d.]+))");
|
||||
static const std::regex regex(R"(SET_PRESSURE_ADVANCE\s+(?:.*\s+)?ADVANCE\s*=\s*([\d.]+))");
|
||||
std::smatch matches;
|
||||
|
||||
if (std::regex_search(line.raw(), matches, regex) && matches.size() > 1) {
|
||||
@@ -5198,9 +5198,9 @@ void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line)
|
||||
void GCodeProcessor::process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& line)
|
||||
{
|
||||
// handle SQUARE_CORNER_VELOCITY
|
||||
std::regex pattern("\\sSQUARE_CORNER_VELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
static const std::regex square_corner_velocity_pattern("\\sSQUARE_CORNER_VELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
std::smatch matches;
|
||||
if (std::regex_search(line.raw(), matches, pattern) && matches.size() == 2) {
|
||||
if (std::regex_search(line.raw(), matches, square_corner_velocity_pattern) && matches.size() == 2) {
|
||||
float _jerk = 0;
|
||||
try
|
||||
{
|
||||
@@ -5213,8 +5213,8 @@ void GCodeProcessor::process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& li
|
||||
}
|
||||
}
|
||||
|
||||
pattern = std::regex("\\sACCEL\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, pattern) && matches.size() == 2) {
|
||||
static const std::regex accel_pattern("\\sACCEL\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, accel_pattern) && matches.size() == 2) {
|
||||
float _accl = 0;
|
||||
try
|
||||
{
|
||||
@@ -5227,8 +5227,8 @@ void GCodeProcessor::process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& li
|
||||
}
|
||||
}
|
||||
|
||||
pattern = std::regex("\\sVELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, pattern) && matches.size() == 2) {
|
||||
static const std::regex velocity_pattern("\\sVELOCITY\\s*=\\s*([0-9]*\\.*[0-9]*)");
|
||||
if (std::regex_search(line.raw(), matches, velocity_pattern) && matches.size() == 2) {
|
||||
float _speed = 0;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -80,40 +80,54 @@ bool check_filament_printable_after_group(const std::vector<unsigned int> &used_
|
||||
}
|
||||
|
||||
// Return a zero based extruder from the region, or extruder_override if overriden.
|
||||
unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const
|
||||
unsigned int LayerTools::wall_extruder_id(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().wall_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override) - 1;
|
||||
assert(region.config().outer_wall_filament_id.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().outer_wall_filament_id.value : this->extruder_override) - 1;
|
||||
}
|
||||
|
||||
unsigned int LayerTools::sparse_infill_filament(const PrintRegion ®ion) const
|
||||
unsigned int LayerTools::sparse_infill_filament_id(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().sparse_infill_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().sparse_infill_filament.value : this->extruder_override) - 1;
|
||||
assert(region.config().sparse_infill_filament_id.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().sparse_infill_filament_id.value : this->extruder_override) - 1;
|
||||
}
|
||||
|
||||
unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const
|
||||
unsigned int LayerTools::internal_solid_filament_id(const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().solid_infill_filament.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override) - 1;
|
||||
assert(region.config().internal_solid_filament_id.value > 0);
|
||||
return ((this->extruder_override == 0) ? region.config().internal_solid_filament_id.value : this->extruder_override) - 1;
|
||||
}
|
||||
|
||||
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
|
||||
unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const
|
||||
{
|
||||
assert(region.config().wall_filament.value > 0);
|
||||
assert(region.config().sparse_infill_filament.value > 0);
|
||||
assert(region.config().solid_infill_filament.value > 0);
|
||||
assert(region.config().outer_wall_filament_id.value > 0);
|
||||
assert(region.config().sparse_infill_filament_id.value > 0);
|
||||
assert(region.config().internal_solid_filament_id.value > 0);
|
||||
assert(region.config().top_surface_filament_id.value > 0);
|
||||
assert(region.config().bottom_surface_filament_id.value > 0);
|
||||
// 1 based extruder ID.
|
||||
unsigned int extruder = 1;
|
||||
if (this->extruder_override == 0) {
|
||||
if (extrusions.has_infill()) {
|
||||
if (extrusions.has_solid_infill())
|
||||
extruder = region.config().solid_infill_filament;
|
||||
if (extrusions.has_solid_infill()) {
|
||||
ExtrusionRole role = extrusions.role();
|
||||
if (role == erTopSolidInfill || role == erIroning)
|
||||
extruder = region.config().top_surface_filament_id;
|
||||
else if (role == erBottomSurface)
|
||||
extruder = region.config().bottom_surface_filament_id;
|
||||
else
|
||||
extruder = region.config().internal_solid_filament_id;
|
||||
} else {
|
||||
extruder = region.config().sparse_infill_filament_id;
|
||||
}
|
||||
} else {
|
||||
const ExtrusionRole role = extrusions.role();
|
||||
if (role == erPerimeter)
|
||||
extruder = region.config().inner_wall_filament_id.value;
|
||||
else
|
||||
extruder = region.config().sparse_infill_filament;
|
||||
} else
|
||||
extruder = region.config().wall_filament.value;
|
||||
extruder = region.config().outer_wall_filament_id.value;
|
||||
}
|
||||
} else
|
||||
extruder = this->extruder_override;
|
||||
|
||||
@@ -527,7 +541,7 @@ std::vector<unsigned int> ToolOrdering::generate_first_layer_tool_order(const Pr
|
||||
return tool_order;
|
||||
|
||||
for (auto layerm : target_layer->regions()) {
|
||||
int extruder_id = layerm->region().config().option("wall_filament")->getInt();
|
||||
int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt();
|
||||
|
||||
for (auto expoly : layerm->raw_slices) {
|
||||
const double nozzle_diameter = print.config().nozzle_diameter.get_at(0);
|
||||
@@ -591,7 +605,7 @@ std::vector<unsigned int> ToolOrdering::generate_first_layer_tool_order(const Pr
|
||||
return tool_order;
|
||||
|
||||
for (auto layerm : target_layer->regions()) {
|
||||
int extruder_id = layerm->region().config().option("wall_filament")->getInt();
|
||||
int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt();
|
||||
for (auto expoly : layerm->raw_slices) {
|
||||
const double nozzle_diameter = object.print()->config().nozzle_diameter.get_at(0);
|
||||
const coordf_t line_width = object.config().get_abs_value("line_width", nozzle_diameter);
|
||||
@@ -682,24 +696,32 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
}
|
||||
|
||||
if (something_nonoverriddable){
|
||||
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
|
||||
layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override);
|
||||
if (extruder_override == 0 && region.config().wall_loops.value > 1)
|
||||
layer_tools.extruders.emplace_back(region.config().inner_wall_filament_id.value);
|
||||
if (layerCount == 0) {
|
||||
firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().wall_filament.value : extruder_override);
|
||||
firstLayerExtruders.emplace_back((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override);
|
||||
}
|
||||
}
|
||||
|
||||
layer_tools.has_object = true;
|
||||
}
|
||||
|
||||
bool has_infill = false;
|
||||
bool has_solid_infill = false;
|
||||
bool has_infill = false;
|
||||
bool has_internal_solid = false;
|
||||
bool has_top_solid_surface = false;
|
||||
bool has_bottom_surface = false;
|
||||
bool something_nonoverriddable = false;
|
||||
for (const ExtrusionEntity *ee : layerm->fills.entities) {
|
||||
// fill represents infill extrusions of a single island.
|
||||
const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
||||
ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role();
|
||||
if (is_solid_infill(role))
|
||||
has_solid_infill = true;
|
||||
if (role == erTopSolidInfill || role == erIroning)
|
||||
has_top_solid_surface = true;
|
||||
else if (role == erBottomSurface)
|
||||
has_bottom_surface = true;
|
||||
else if (is_solid_infill(role))
|
||||
has_internal_solid = true;
|
||||
else if (role != erNone)
|
||||
has_infill = true;
|
||||
|
||||
@@ -711,14 +733,18 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
|
||||
|
||||
if (something_nonoverriddable || !m_print_config_ptr) {
|
||||
if (extruder_override == 0) {
|
||||
if (has_solid_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().solid_infill_filament);
|
||||
if (has_internal_solid)
|
||||
layer_tools.extruders.emplace_back(region.config().internal_solid_filament_id);
|
||||
if (has_top_solid_surface)
|
||||
layer_tools.extruders.emplace_back(region.config().top_surface_filament_id);
|
||||
if (has_bottom_surface)
|
||||
layer_tools.extruders.emplace_back(region.config().bottom_surface_filament_id);
|
||||
if (has_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().sparse_infill_filament);
|
||||
} else if (has_solid_infill || has_infill)
|
||||
layer_tools.extruders.emplace_back(region.config().sparse_infill_filament_id);
|
||||
} else if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_infill)
|
||||
layer_tools.extruders.emplace_back(extruder_override);
|
||||
}
|
||||
if (has_solid_infill || has_infill)
|
||||
if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_infill)
|
||||
layer_tools.has_object = true;
|
||||
}
|
||||
layerCount++;
|
||||
@@ -1657,7 +1683,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int
|
||||
if (wipe_into_infill_only && ! is_infill_first)
|
||||
// In this case we must check that the original extruder is used on this layer before the one we are overridding
|
||||
// (and the perimeters will be finished before the infill is printed):
|
||||
if (!lt.is_extruder_order(lt.wall_filament(region), new_extruder))
|
||||
if (!lt.is_extruder_order(lt.wall_extruder_id(region), new_extruder))
|
||||
continue;
|
||||
|
||||
if ((!is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume))
|
||||
@@ -1775,8 +1801,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
|
||||
if (is_infill_first
|
||||
//BBS
|
||||
//|| object->config().flush_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|
||||
|| lt.is_extruder_order(lt.wall_filament(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
||||
|| ! lt.has_extruder(lt.sparse_infill_filament(region)))) // we have to force override - this could violate infill_first (FIXME)
|
||||
|| lt.is_extruder_order(lt.wall_extruder_id(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|
||||
|| ! lt.has_extruder(lt.sparse_infill_filament_id(region)))) // we have to force override - this could violate infill_first (FIXME)
|
||||
set_extruder_override(fill, object, copy, (is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
|
||||
else {
|
||||
// In this case we can (and should) leave it to be printed normally.
|
||||
|
||||
@@ -139,9 +139,9 @@ public:
|
||||
bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); }
|
||||
|
||||
// Return a zero based extruder from the region, or extruder_override if overriden.
|
||||
unsigned int wall_filament(const PrintRegion ®ion) const;
|
||||
unsigned int sparse_infill_filament(const PrintRegion ®ion) const;
|
||||
unsigned int solid_infill_filament(const PrintRegion ®ion) const;
|
||||
unsigned int wall_extruder_id(const PrintRegion ®ion) const;
|
||||
unsigned int sparse_infill_filament_id(const PrintRegion ®ion) const;
|
||||
unsigned int internal_solid_filament_id(const PrintRegion ®ion) const;
|
||||
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
|
||||
unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const;
|
||||
|
||||
|
||||
@@ -3881,7 +3881,7 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
for (auto &used : m_used_filament_length) // reset used filament stats
|
||||
used = 0.f;
|
||||
|
||||
int wall_filament = get_wall_filament_for_all_layer();
|
||||
int wall_filament_id = get_wall_filament_for_all_layer();
|
||||
|
||||
std::vector<WipeTower::ToolChangeResult> layer_result;
|
||||
int index = 0;
|
||||
@@ -3909,24 +3909,24 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
|
||||
ToolChangeResult finish_layer_tcr;
|
||||
ToolChangeResult timelapse_wall;
|
||||
|
||||
auto get_wall_filament_for_this_layer = [this, &layer, &wall_filament]() -> int {
|
||||
auto get_wall_filament_for_this_layer = [this, &layer, &wall_filament_id]() -> int {
|
||||
if (layer.tool_changes.size() == 0)
|
||||
return -1;
|
||||
|
||||
int candidate_id = -1;
|
||||
for (size_t idx = 0; idx < layer.tool_changes.size(); ++idx) {
|
||||
if (idx == 0) {
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament)
|
||||
return wall_filament;
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament].category) {
|
||||
if (layer.tool_changes[idx].old_tool == wall_filament_id)
|
||||
return wall_filament_id;
|
||||
else if (m_filpar[layer.tool_changes[idx].old_tool].category == m_filpar[wall_filament_id].category) {
|
||||
candidate_id = layer.tool_changes[idx].old_tool;
|
||||
}
|
||||
}
|
||||
if (layer.tool_changes[idx].new_tool == wall_filament) {
|
||||
return wall_filament;
|
||||
if (layer.tool_changes[idx].new_tool == wall_filament_id) {
|
||||
return wall_filament_id;
|
||||
}
|
||||
|
||||
if ((candidate_id == -1) && (m_filpar[layer.tool_changes[idx].new_tool].category == m_filpar[wall_filament].category))
|
||||
if ((candidate_id == -1) && (m_filpar[layer.tool_changes[idx].new_tool].category == m_filpar[wall_filament_id].category))
|
||||
candidate_id = layer.tool_changes[idx].new_tool;
|
||||
}
|
||||
return candidate_id == -1 ? layer.tool_changes[0].new_tool : candidate_id;
|
||||
|
||||
@@ -462,10 +462,10 @@ std::string GCodeWriter::set_input_shaping(char axis, float damp, float freq, st
|
||||
break;
|
||||
}
|
||||
case gcfMarlinLegacy: {
|
||||
throw std::runtime_error(_u8L("Input shaping is not supported by Marlin < 2.1.2.\nCheck your firmware version and update your G-code flavor to ´Marlin 2´"));
|
||||
throw std::runtime_error(_u8L("Input shaping is not supported by Marlin < 2.1.2.\nCheck your firmware version and update your G-code flavor to ´Marlin 2´."));
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(_u8L("Input shaping is only supported by Klipper, RepRapFirmware and Marlin 2"));
|
||||
throw std::runtime_error(_u8L("Input shaping is only supported by Klipper, RepRapFirmware and Marlin 2."));
|
||||
}
|
||||
if (!gcode.str().empty()) {
|
||||
if (GCodeWriter::full_gcode_comment) {
|
||||
@@ -1154,13 +1154,19 @@ std::string GCodeWriter::set_additional_fan(unsigned int speed)
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_exhaust_fan( int speed,bool add_eol)
|
||||
std::string GCodeWriter::set_exhaust_fan(int speed)
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
gcode << "M106" << " P3" << " S" << (int)(speed / 100.0 * 255);
|
||||
|
||||
if(add_eol)
|
||||
gcode << "\n";
|
||||
if (GCodeWriter::full_gcode_comment) {
|
||||
if (speed == 0)
|
||||
gcode << " ; disable exhaust fan ";
|
||||
else
|
||||
gcode << " ; enable exhaust fan ";
|
||||
}
|
||||
|
||||
gcode << "\n";
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
std::string set_fan(unsigned int speed) const;
|
||||
//BBS: set additional fan speed for BBS machine only
|
||||
static std::string set_additional_fan(unsigned int speed);
|
||||
static std::string set_exhaust_fan(int speed,bool add_eol);
|
||||
static std::string set_exhaust_fan(int speed);
|
||||
//BBS
|
||||
void set_object_start_str(std::string start_string) { m_gcode_label_objects_start = start_string; }
|
||||
bool is_object_start_str_empty() { return m_gcode_label_objects_start.empty(); }
|
||||
|
||||
@@ -1236,7 +1236,7 @@ std::vector<Vec2d> edge_offset_contour_intersections(
|
||||
// 2) offset_distance == dmin_new -> one duplicate point is found.
|
||||
// If 2) is ignored, then two tangentially touching offset curves are created.
|
||||
// If not ignored, then the two offset curves merge at this double point.
|
||||
// We should merge the contours while pushing the the two copies of the tangent point away a bit.
|
||||
// We should merge the contours while pushing the two copies of the tangent point away a bit.
|
||||
dmin = dmin_new;
|
||||
num_intersections = (offset_distance > dmin) + 1;
|
||||
}
|
||||
|
||||
@@ -141,14 +141,15 @@ bool Layer::is_perimeter_compatible(const Print& print, const PrintRegion& a, co
|
||||
const PrintRegionConfig& config = a.config();
|
||||
const PrintRegionConfig& other_config = b.config();
|
||||
|
||||
return config.wall_filament == other_config.wall_filament
|
||||
return config.outer_wall_filament_id == other_config.outer_wall_filament_id
|
||||
&& config.inner_wall_filament_id == other_config.inner_wall_filament_id
|
||||
&& config.wall_loops == other_config.wall_loops
|
||||
&& config.wall_sequence == other_config.wall_sequence
|
||||
&& config.is_infill_first == other_config.is_infill_first
|
||||
&& config.inner_wall_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.inner_wall_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.outer_wall_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.outer_wall_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.small_perimeter_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.small_perimeter_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.gap_infill_speed.get_at(print.get_extruder_id(config.wall_filament)) == other_config.gap_infill_speed.get_at(print.get_extruder_id(config.wall_filament))
|
||||
&& config.inner_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.inner_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.outer_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.outer_wall_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.small_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.small_perimeter_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id)) == other_config.gap_infill_speed.get_at(print.get_extruder_id(config.outer_wall_filament_id))
|
||||
&& config.filter_out_gap_fill.value == other_config.filter_out_gap_fill.value
|
||||
&& config.detect_overhang_wall == other_config.detect_overhang_wall
|
||||
&& config.overhang_reverse == other_config.overhang_reverse
|
||||
|
||||
@@ -34,16 +34,26 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const
|
||||
const PrintRegionConfig ®ion_config = region.config();
|
||||
const PrintObject &print_object = *this->layer()->object();
|
||||
Flow bridge_flow;
|
||||
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will fall back to zero'th element, so everything is all right.
|
||||
auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1));
|
||||
const ConfigOptionFloatOrPercent& bridge_width_opt = region_config.bridge_line_width;
|
||||
const double bridge_width = bridge_width_opt.get_abs_value(nozzle_diameter);
|
||||
const bool has_bridge_width = bridge_width > 0.;
|
||||
const double bridge_flow_ratio = region_config.bridge_flow;
|
||||
|
||||
if (thick_bridge) {
|
||||
// The old Slic3r way (different from all other slicers): Use rounded extrusions.
|
||||
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
|
||||
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
|
||||
// Applies default bridge spacing.
|
||||
bridge_flow = Flow::bridging_flow(float(sqrt(region_config.bridge_flow)) * nozzle_diameter, nozzle_diameter);
|
||||
float thread_diameter = has_bridge_width ? float(bridge_width) : nozzle_diameter;
|
||||
if (bridge_flow_ratio > 0.)
|
||||
thread_diameter *= float(sqrt(bridge_flow_ratio));
|
||||
bridge_flow = Flow::bridging_flow(thread_diameter, nozzle_diameter);
|
||||
} else {
|
||||
// The same way as other slicers: Use normal extrusions. Apply bridge_flow while maintaining the original spacing.
|
||||
bridge_flow = this->flow(role).with_flow_ratio(region_config.bridge_flow);
|
||||
Flow base_flow = this->flow(role);
|
||||
if (has_bridge_width)
|
||||
base_flow = Flow(float(bridge_width), base_flow.height(), nozzle_diameter);
|
||||
bridge_flow = base_flow.with_flow_ratio(bridge_flow_ratio);
|
||||
}
|
||||
return bridge_flow;
|
||||
|
||||
@@ -83,6 +93,12 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
(this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) &&
|
||||
this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON);
|
||||
|
||||
double model_rotation_rad = 0.0;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = this->layer()->object()->trafo().matrix();
|
||||
model_rotation_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
|
||||
PerimeterGenerator g(
|
||||
// input:
|
||||
&slices,
|
||||
@@ -94,6 +110,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
&this->layer()->object()->config(),
|
||||
&print_config,
|
||||
spiral_mode,
|
||||
model_rotation_rad,
|
||||
|
||||
// output:
|
||||
&this->perimeters,
|
||||
@@ -517,10 +534,27 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
SurfaceCollection bridges;
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
|
||||
const double custom_angle = this->region().config().bridge_angle.value;
|
||||
bridges.surfaces = custom_angle > 0 ?
|
||||
expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) :
|
||||
// ORCA: Relative/Align Bridge Angle
|
||||
const auto ®ion_config = this->region().config();
|
||||
const double custom_angle_deg = region_config.bridge_angle.value;
|
||||
const bool relative_angle = region_config.relative_bridge_angle.value;
|
||||
const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg);
|
||||
|
||||
double align_offset_rad = 0.0;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = this->layer()->object()->trafo().matrix();
|
||||
align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
|
||||
bridges.surfaces = (custom_angle_deg > 0.0 && !relative_angle) ?
|
||||
expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, custom_angle_rad + align_offset_rad) :
|
||||
expand_bridges_detect_orientations(this->fill_surfaces.surfaces, expansion_zones, closing_radius);
|
||||
if (custom_angle_deg > 0.0 && relative_angle) {
|
||||
for (Surface &bridge_surface : bridges.surfaces) {
|
||||
if (bridge_surface.bridge_angle >= 0)
|
||||
bridge_surface.bridge_angle += custom_angle_rad;
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
@@ -782,12 +816,25 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
// would get merged into a single one while they need different directions
|
||||
// also, supply the original expolygon instead of the grown one, because in case
|
||||
// of very thin (but still working) anchors, the grown expolygon would go beyond them
|
||||
double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value);
|
||||
if (custom_angle > 0.0) {
|
||||
bridges[idx_last].bridge_angle = custom_angle;
|
||||
// ORCA: Relative/Align Bridge Angle
|
||||
const auto ®ion_config = this->region().config();
|
||||
const double custom_angle_deg = region_config.bridge_angle.value;
|
||||
const bool relative_angle = region_config.relative_bridge_angle.value;
|
||||
const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg);
|
||||
|
||||
double align_offset_rad = 0.0;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = this->layer()->object()->trafo().matrix();
|
||||
align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
|
||||
if (custom_angle_deg > 0.0 && !relative_angle) {
|
||||
bridges[idx_last].bridge_angle = custom_angle_rad + align_offset_rad;
|
||||
} else {
|
||||
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices));
|
||||
bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
||||
if (custom_angle_deg > 0.0 && relative_angle)
|
||||
bridges[idx_last].bridge_angle += custom_angle_rad;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+19
-14
@@ -225,7 +225,7 @@ _finished:
|
||||
}
|
||||
|
||||
if (model.objects.empty())
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn't be read because it's empty"));
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn\'t be read because it\'s empty."));
|
||||
|
||||
for (ModelObject *o : model.objects)
|
||||
o->input_file = input_file;
|
||||
@@ -337,7 +337,7 @@ Model Model::read_from_file(const std::string&
|
||||
}
|
||||
#endif
|
||||
else
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format. Input file must have .stl, .obj, .amf(.xml) extension."));
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format: input file must have .stl, .obj, or .amf(.xml) extension."));
|
||||
|
||||
if (is_cb_cancel) {
|
||||
Model empty_model;
|
||||
@@ -352,7 +352,7 @@ Model Model::read_from_file(const std::string&
|
||||
}
|
||||
|
||||
if (model.objects.empty())
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn't be read because it's empty"));
|
||||
throw Slic3r::RuntimeError(_L("The supplied file couldn\'t be read because it\'s empty."));
|
||||
|
||||
for (ModelObject *o : model.objects)
|
||||
o->input_file = input_file;
|
||||
@@ -399,7 +399,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
||||
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
||||
result = load_amf(input_file.c_str(), config, config_substitutions, &model, &is_bbl_3mf);
|
||||
else
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format. Input file must have .3mf or .zip.amf extension."));
|
||||
throw Slic3r::RuntimeError(_L("Unknown file format: input file must have .3mf or .zip.amf extension."));
|
||||
|
||||
if (out_file_type != En3mfType::From_Prusa) {
|
||||
if (is_orca_3mf)
|
||||
@@ -696,8 +696,7 @@ unsigned int Model::update_print_volume_state(const BuildVolume &build_volume)
|
||||
num_printable += model_object->update_instances_print_volume_state(build_volume);
|
||||
//BBS: add logs for build_volume
|
||||
const BoundingBoxf3& print_volume = build_volume.bounding_volume();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}, got %7% printable istances")\
|
||||
%print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z() %num_printable;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}, got %7% printable istances") %print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z() %num_printable;
|
||||
return num_printable;
|
||||
}
|
||||
|
||||
@@ -2127,10 +2126,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects, const bool remap_paint)
|
||||
|
||||
if (is_multi_volume_object) {
|
||||
// BBS: volume geometry not changed, so we can keep the paint facets
|
||||
#define COPY_FACETS(f) \
|
||||
if (new_vol->f.timestamp() == volume->f.timestamp()) \
|
||||
new_vol->f.reset(); /* BBS: let next assign take effect */ \
|
||||
new_vol->f.assign(volume->f)
|
||||
#define COPY_FACETS(f) if (new_vol->f.timestamp() == volume->f.timestamp()) new_vol->f.reset(); /* BBS: let next assign take effect */ new_vol->f.assign(volume->f)
|
||||
|
||||
COPY_FACETS(supported_facets);
|
||||
COPY_FACETS(seam_facets);
|
||||
@@ -2372,8 +2368,7 @@ unsigned int ModelObject::update_instances_print_volume_state(const BuildVolume
|
||||
unsigned int num_printable = 0;
|
||||
//BBS: add logs for build_volume
|
||||
//const BoundingBoxf3& print_volume = build_volume.bounding_volume();
|
||||
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}")\
|
||||
// %print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z();
|
||||
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", print_volume {%1%, %2%, %3%} to {%4%, %5%, %6%}") // %print_volume.min.x() %print_volume.min.y() %print_volume.min.z()%print_volume.max.x() %print_volume.max.y() %print_volume.max.z();
|
||||
for (ModelInstance* model_instance : this->instances) {
|
||||
if (model_instance->update_print_volume_state(build_volume) == ModelInstancePVS_Inside) {
|
||||
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", object %1%'s instance inside print volum")%this->name;
|
||||
@@ -2573,6 +2568,12 @@ void ModelVolume::update_extruder_count(size_t extruder_count)
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Clear a stale per-volume filament assignment that no longer exists after the extruder count
|
||||
// shrank (e.g. printer switch to one with fewer filaments), so downstream readers never index
|
||||
// per-filament config vectors out of range. Ported from BambuStudio (STUDIO-15763).
|
||||
if (extruder_id() > extruder_count) {
|
||||
this->config.erase("extruder");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_count, size_t filament_id, int replace_filament_id)
|
||||
@@ -2584,6 +2585,11 @@ void ModelVolume::update_extruder_count_when_delete_filament(size_t extruder_cou
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Same stale-assignment cleanup as update_extruder_count, for the filament-delete path.
|
||||
// Ported from BambuStudio (STUDIO-15763).
|
||||
if (extruder_id() > extruder_count) {
|
||||
this->config.erase("extruder");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelVolume::center_geometry_after_creation(bool update_source_offset)
|
||||
@@ -3423,8 +3429,7 @@ ModelInstanceEPrintVolumeState ModelInstance::calc_print_volume_state(const Buil
|
||||
BoundingBoxf3 bb = vol->get_convex_hull().bounding_box();
|
||||
Vec3d size = bb.size();
|
||||
if ((size.x() == 0.f) || (size.y() == 0.f) || (size.z() == 0.f)) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", object %1%'s vol %2% is empty, skip it, box: {%3%, %4%, %5%} to {%6%, %7%, %8%}")%this->object->name %vol->name\
|
||||
%bb.min.x() %bb.min.y() %bb.min.z()%bb.max.x() %bb.max.y() %bb.max.z();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", object %1%'s vol %2% is empty, skip it, box: {%3%, %4%, %5%} to {%6%, %7%, %8%}")%this->object->name %vol->name %bb.min.x() %bb.min.y() %bb.min.z()%bb.max.x() %bb.max.y() %bb.max.z();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstance
|
||||
for (ModelObject *mo : model.objects)
|
||||
for (ModelInstance *minst : mo->instances) {
|
||||
minst->get_arrange_polygon(&ap);
|
||||
// ModelInstance::get_arrange_polygon leaves bed_idx at its UNARRANGED
|
||||
// default; seed it to bed 0 (as get_instance_arrange_poly does) so the
|
||||
// nester treats the item as placeable instead of returning it unplaced.
|
||||
ap.bed_idx = 0;
|
||||
input.emplace_back(ap);
|
||||
instances.emplace_back(minst);
|
||||
}
|
||||
|
||||
@@ -1345,14 +1345,14 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
if (const PrintRegionConfig &config = region->region().config();
|
||||
// color_idx == 0 means "don't know" extruder aka the underlying extruder.
|
||||
// As this region may split existing regions, we collect statistics over all regions for color_idx == 0.
|
||||
color_idx == 0 || config.wall_filament == int(color_idx)) {
|
||||
color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) {
|
||||
//BBS: the extrusion line width is outer wall rather than inner wall
|
||||
const double nozzle_diameter = print_object.print()->config().nozzle_diameter.get_at(0);
|
||||
double outer_wall_line_width = config.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
||||
out.extrusion_width = std::max<float>(out.extrusion_width, outer_wall_line_width);
|
||||
out.top_shell_layers = std::max<int>(out.top_shell_layers, config.top_shell_layers);
|
||||
out.bottom_shell_layers = std::max<int>(out.bottom_shell_layers, config.bottom_shell_layers);
|
||||
out.small_region_threshold = config.gap_infill_speed.get_at(print_object.print()->get_extruder_id(config.wall_filament - 1)) > 0 ?
|
||||
out.small_region_threshold = config.gap_infill_speed.get_at(print_object.print()->get_extruder_id(config.outer_wall_filament_id - 1)) > 0 ?
|
||||
// Gap fill enabled. Enable a single line of 1/2 extrusion width.
|
||||
0.5f * outer_wall_line_width :
|
||||
// Gap fill disabled. Enable two lines slightly overlapping.
|
||||
|
||||
@@ -179,7 +179,7 @@ static bool clip_narrow_corner(
|
||||
bool blocked = forward == Blocked || backward == Blocked;
|
||||
assert(polygon.size() < 3 ||
|
||||
// Remaining triangle is CCW oriented. Both sides must be "blocked", but the other side may have not been
|
||||
// updated after the the p02 / p22 became united into a single point.
|
||||
// updated after the p02 / p22 became united into a single point.
|
||||
blocked ||
|
||||
// Remaining triangle is concave, however both of its arms are long.
|
||||
(forward == Far && backward == Far));
|
||||
|
||||
@@ -581,7 +581,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
|
||||
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
|
||||
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->wall_filament - 1)) > 0;
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
|
||||
|
||||
// split the polygons with top/not_top
|
||||
// get the offset from solid surface anchor
|
||||
@@ -620,7 +620,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
// get the real top surface
|
||||
ExPolygons grown_lower_slices;
|
||||
ExPolygons bridge_checker;
|
||||
auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1);
|
||||
auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
|
||||
// Check whether surface be bridge or not
|
||||
if (this->lower_slices != NULL) {
|
||||
// BBS: get the Polygons below the polygon this layer
|
||||
@@ -1173,7 +1173,7 @@ void PerimeterGenerator::process_classic()
|
||||
// We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
// in the current layer
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1);
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
|
||||
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2)));
|
||||
}
|
||||
|
||||
@@ -1189,7 +1189,7 @@ void PerimeterGenerator::process_classic()
|
||||
// internal flow which is unrelated.
|
||||
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->wall_filament - 1)) > 0;
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
|
||||
|
||||
// BBS: this flow is for smaller external perimeter for small area
|
||||
coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE));
|
||||
@@ -1727,9 +1727,12 @@ void PerimeterGenerator::add_infill_contour_for_arachne( ExPolygons infil
|
||||
// Orca: sacrificial bridge layer algorithm ported from SuperSlicer
|
||||
void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perimeter_spacing, coord_t ext_perimeter_width)
|
||||
{
|
||||
|
||||
if (this->config->counterbore_hole_bridging == chbNone)
|
||||
return; // return if counterbore hole is not enabled
|
||||
|
||||
//store surface for bridge infill to avoid unsupported perimeters (but the first one, this one is always good)
|
||||
if (this->config->counterbore_hole_bridging != chbNone
|
||||
&& this->lower_slices != NULL && !this->lower_slices->empty()) {
|
||||
if (this->lower_slices != NULL && !this->lower_slices->empty()) {
|
||||
const coordf_t bridged_infill_margin = scale_(BRIDGE_INFILL_MARGIN);
|
||||
|
||||
for (size_t surface_idx = 0; surface_idx < all_surfaces.size(); surface_idx++) {
|
||||
@@ -1738,11 +1741,8 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
//compute our unsupported surface
|
||||
ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
|
||||
if (!unsupported.empty()) {
|
||||
// remove small overhangs (when using chbFilled we need to be less aggressive in removing small overhangs,
|
||||
// to avoid affecting bridging detection.)
|
||||
const int outset_divisor = this->config->counterbore_hole_bridging.value == chbFilled ? 2 : 1;
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing),
|
||||
double(perimeter_spacing) / outset_divisor);
|
||||
//remove small overhangs
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
|
||||
|
||||
if (!unsupported_filtered.empty()) {
|
||||
//to_draw.insert(to_draw.end(), last.begin(), last.end());
|
||||
@@ -1759,13 +1759,24 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
for (ExPolygon unsupported : unsupported_filtered) {
|
||||
BridgeDetector detector{ unsupported,
|
||||
lower_island.expolygons,
|
||||
perimeter_spacing };
|
||||
if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value)))
|
||||
perimeter_spacing / 4}; // Use a finer BridgeDetector. This affects coverage resolution, not extrusion spacing.
|
||||
// ORCA: Relative/Align Bridge Angle
|
||||
const double custom_angle_deg = this->config->bridge_angle.value;
|
||||
const bool relative_angle = this->config->relative_bridge_angle.value;
|
||||
const double detect_angle_rad = (custom_angle_deg > 0.0 && !relative_angle)
|
||||
? Geometry::deg2rad(custom_angle_deg) +
|
||||
(this->config->align_infill_direction_to_model ? this->m_model_rotation_rad : 0.0)
|
||||
: 0.0;
|
||||
if (detector.detect_angle(detect_angle_rad))
|
||||
expolygons_append(bridgeable, union_ex(detector.coverage(-1, true)));
|
||||
}
|
||||
if (!bridgeable.empty()) {
|
||||
//check if we get everything or just the bridgeable area
|
||||
if (/*this->config->counterbore_hole_bridging.value == chbNoPeri || */this->config->counterbore_hole_bridging.value == chbFilled) {
|
||||
if (!bridgeable.empty() && !surface->expolygon.holes.empty()) { // keep out if cannot be bridged or no holes to bridge
|
||||
const coordf_t bridge_anchor_offset = std::min({bridged_infill_margin, coordf_t(perimeter_spacing), coordf_t(ext_perimeter_width)});
|
||||
|
||||
// Handle filled vs partial counterbore bridging modes.
|
||||
if (this->config->counterbore_hole_bridging.value == chbFilled) {
|
||||
unsupported_filtered = offset_ex(unsupported_filtered, -perimeter_spacing); // shrink it to survive the strict bridge-candidate filter
|
||||
|
||||
//we bridge everything, even the not-bridgeable bits
|
||||
for (size_t i = 0; i < unsupported_filtered.size();) {
|
||||
ExPolygon& poly_unsupp = *(unsupported_filtered.begin() + i);
|
||||
@@ -1785,139 +1796,96 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
unsupported_filtered.erase(unsupported_filtered.begin() + i);
|
||||
}
|
||||
}
|
||||
unsupported_filtered = intersection_ex(last,
|
||||
offset_ex(unsupported_filtered, 0.5 * double(bridged_infill_margin)));
|
||||
if (this->config->counterbore_hole_bridging.value == chbFilled) {
|
||||
for (ExPolygon& expol : unsupported_filtered) {
|
||||
//check if the holes won't be covered by the upper layer
|
||||
//TODO: if we want to do that, we must modify the geometry before making perimeters.
|
||||
//if (this->upper_slices != nullptr && !this->upper_slices->expolygons.empty()) {
|
||||
// for (Polygon &poly : expol.holes) poly.make_counter_clockwise();
|
||||
// float perimeterwidth = this->config->perimeters == 0 ? 0 : (this->ext_perimeter_flow.scaled_width() + (this->config->perimeters - 1) + this->perimeter_flow.scaled_spacing());
|
||||
// std::cout << "test upper slices with perimeterwidth=" << perimeterwidth << "=>" << offset_ex(this->upper_slices->expolygons, -perimeterwidth).size();
|
||||
// if (intersection(Polygons() = { expol.holes }, to_polygons(offset_ex(this->upper_slices->expolygons, -this->ext_perimeter_flow.scaled_width() / 2))).empty()) {
|
||||
// std::cout << " EMPTY";
|
||||
// expol.holes.clear();
|
||||
// } else {
|
||||
// }
|
||||
// std::cout << "\n";
|
||||
//} else {
|
||||
expol.holes.clear();
|
||||
//}
|
||||
|
||||
//detect inside volume
|
||||
for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) {
|
||||
if (surface_idx == surface_idx_other) continue;
|
||||
if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) {
|
||||
//this means that other_surf was inside an expol holes
|
||||
//as we removed them, we need to add a new one
|
||||
ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing));
|
||||
if (new_poly.size() == 1) {
|
||||
all_surfaces[surface_idx_other].expolygon = new_poly[0];
|
||||
expol.holes.push_back(new_poly[0].contour);
|
||||
unsupported_filtered = offset_ex(unsupported_filtered, perimeter_spacing + bridge_anchor_offset); // restore it back to its original size and add anchor
|
||||
unsupported_filtered = intersection_ex(last, unsupported_filtered); // clamp to the original surface, to avoid creating new unsupported areas
|
||||
|
||||
for (ExPolygon& expol : unsupported_filtered) {
|
||||
// Remove holes that need sacrificial fill, but keep holes
|
||||
// whose wall is already supported by the lower layer.
|
||||
const float hole_wall_width = float(ext_perimeter_width / 2);
|
||||
for (size_t hole_idx = 0; hole_idx < expol.holes.size();) {
|
||||
Polygon hole_area_contour = expol.holes[hole_idx];
|
||||
hole_area_contour.make_counter_clockwise();
|
||||
|
||||
const ExPolygons hole_area = { ExPolygon(hole_area_contour) };
|
||||
ExPolygons hole_wall_area = diff_ex(
|
||||
offset_ex(hole_area_contour, hole_wall_width),
|
||||
hole_area,
|
||||
ApplySafetyOffset::Yes);
|
||||
hole_wall_area = intersection_ex(hole_wall_area, ExPolygons{ expol }, ApplySafetyOffset::Yes);
|
||||
if (!hole_wall_area.empty() &&
|
||||
intersection_ex(hole_wall_area, *this->lower_slices, ApplySafetyOffset::Yes).empty())
|
||||
expol.holes.erase(expol.holes.begin() + hole_idx);
|
||||
// After erase(), the next hole shifts into the same index. So hole_idx
|
||||
// must not be incremented, otherwise the next hole would be skipped.
|
||||
else
|
||||
++hole_idx; // keep this hole, it won't be bridged, so we need to keep it as a hole
|
||||
}
|
||||
|
||||
//detect inside volume
|
||||
for (size_t surface_idx_other = 0; surface_idx_other < all_surfaces.size(); surface_idx_other++) {
|
||||
if (surface_idx == surface_idx_other) continue;
|
||||
if (intersection_ex(ExPolygons() = { expol }, ExPolygons() = { all_surfaces[surface_idx_other].expolygon }).size() > 0) {
|
||||
//this means that other_surf was inside an expol holes
|
||||
//as we removed them, we need to add a new one
|
||||
ExPolygons new_poly = offset2_ex(ExPolygons{ all_surfaces[surface_idx_other].expolygon }, double(-bridged_infill_margin - perimeter_spacing), double(perimeter_spacing));
|
||||
if (new_poly.size() == 1) {
|
||||
all_surfaces[surface_idx_other].expolygon = new_poly[0];
|
||||
expol.holes.push_back(new_poly[0].contour);
|
||||
expol.holes.back().make_clockwise();
|
||||
} else {
|
||||
for (size_t idx = 0; idx < new_poly.size(); idx++) {
|
||||
Surface new_surf = all_surfaces[surface_idx_other];
|
||||
new_surf.expolygon = new_poly[idx];
|
||||
all_surfaces.push_back(new_surf);
|
||||
expol.holes.push_back(new_poly[idx].contour);
|
||||
expol.holes.back().make_clockwise();
|
||||
} else {
|
||||
for (size_t idx = 0; idx < new_poly.size(); idx++) {
|
||||
Surface new_surf = all_surfaces[surface_idx_other];
|
||||
new_surf.expolygon = new_poly[idx];
|
||||
all_surfaces.push_back(new_surf);
|
||||
expol.holes.push_back(new_poly[idx].contour);
|
||||
expol.holes.back().make_clockwise();
|
||||
}
|
||||
all_surfaces.erase(all_surfaces.begin() + surface_idx_other);
|
||||
if (surface_idx_other < surface_idx) {
|
||||
surface_idx--;
|
||||
surface = &all_surfaces[surface_idx];
|
||||
}
|
||||
surface_idx_other--;
|
||||
}
|
||||
all_surfaces.erase(all_surfaces.begin() + surface_idx_other);
|
||||
if (surface_idx_other < surface_idx) {
|
||||
surface_idx--;
|
||||
surface = &all_surfaces[surface_idx];
|
||||
}
|
||||
surface_idx_other--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//TODO: add other polys as holes inside this one (-margin)
|
||||
} else if (/*this->config->counterbore_hole_bridging.value == chbBridgesOverhangs || */this->config->counterbore_hole_bridging.value == chbBridges) {
|
||||
// Partially bridged counterbore handling should not rewrite generic bridge islands
|
||||
// because by doing so regular bridges will lose their overhang-wall perimeters.
|
||||
if (surface->expolygon.holes.empty()) {
|
||||
unsupported_filtered.clear(); // "Partially bridged" only applies to hole-bearing bridge islands.
|
||||
continue;
|
||||
}
|
||||
//simplify to avoid most of artefacts from printing lines.
|
||||
ExPolygons bridgeable_simplified;
|
||||
} else { // if(this->config->counterbore_hole_bridging.value == chbBridges)
|
||||
// Orca: Partial counterbore bridging is mask-based. Preserve the supported
|
||||
// remainder (`last`) and use simplified BridgeDetector coverage to derive the
|
||||
// bridgeable counterbore span. The span is grown from supported material,
|
||||
// shrunk back, stripped from `last`, and expanded back. It is then prevented
|
||||
// from intruding deeper into `last` than the explicit anchor overlap.
|
||||
// Finally, add the allowed anchor band from `last` then remove the
|
||||
// narrow hole-side wall contact, which must remain unbridgeable.
|
||||
|
||||
last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons bridgeable_filtered;
|
||||
for (ExPolygon& poly : bridgeable) {
|
||||
poly.simplify(perimeter_spacing, &bridgeable_simplified);
|
||||
poly.simplify(perimeter_spacing, &bridgeable_filtered);
|
||||
}
|
||||
bridgeable_simplified = offset2_ex(bridgeable_simplified, -ext_perimeter_width, ext_perimeter_width);
|
||||
//bridgeable_simplified = intersection_ex(bridgeable_simplified, unsupported_filtered);
|
||||
//offset by perimeter spacing because the simplify may have reduced it a bit.
|
||||
//it's not dangerous as it will be intersected by 'unsupported' later
|
||||
//FIXME: add overlap in this->fill_surfaces->append
|
||||
//FIXME: it overlap inside unsuppported not-bridgeable area!
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width);
|
||||
|
||||
//bridgeable_simplified = offset2_ex(bridgeable_simplified, (double)-perimeter_spacing, (double)perimeter_spacing * 2);
|
||||
//ExPolygons unbridgeable = offset_ex(diff_ex(unsupported, bridgeable_simplified), perimeter_spacing * 3 / 2);
|
||||
//ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
|
||||
//unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
|
||||
// Get rid of coarseness of the resulted bridgeable area by using the original supported area as reference.
|
||||
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
|
||||
bridgeable_filtered = union_ex(offset_ex(last, perimeter_spacing), bridgeable_filtered);
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, last, ApplySafetyOffset::Yes);
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, perimeter_spacing); // filter noise from the diff_ex
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area
|
||||
// Safety measure: Keep the bridge mask from intruding deeper into the
|
||||
// supported anchor region (`last`) than the explicit anchor overlap.
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(last, -bridge_anchor_offset));
|
||||
|
||||
|
||||
// if (this->config->counterbore_hole_bridging.value == chbBridges) {
|
||||
ExPolygons unbridgeable = unsupported_filtered;
|
||||
for (ExPolygon& expol : unbridgeable)
|
||||
expol.holes.clear();
|
||||
unbridgeable = diff_ex(unbridgeable, bridgeable_simplified);
|
||||
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width * 2, ext_perimeter_width * 2);
|
||||
ExPolygons bridges_temp = offset2_ex(intersection_ex(last, diff_ex(unsupported_filtered, unbridgeable), ApplySafetyOffset::Yes), -ext_perimeter_width / 4, ext_perimeter_width / 4);
|
||||
//remove the overhangs section from the surface polygons
|
||||
ExPolygons reference = last;
|
||||
last = diff_ex(last, unsupported_filtered);
|
||||
//ExPolygons no_bridge = diff_ex(offset_ex(unbridgeable, ext_perimeter_width * 3 / 2), last);
|
||||
//bridges_temp = diff_ex(bridges_temp, no_bridge);
|
||||
coordf_t offset_to_do = bridged_infill_margin;
|
||||
bool first = true;
|
||||
unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width));
|
||||
while (offset_to_do > ext_perimeter_width * 1.5) {
|
||||
unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width / 4, ext_perimeter_width * 2.25, ClipperLib::jtSquare);
|
||||
bridges_temp = diff_ex(bridges_temp, unbridgeable);
|
||||
bridges_temp = offset_ex(bridges_temp, ext_perimeter_width, ClipperLib::jtMiter, 6.);
|
||||
unbridgeable = diff_ex(unbridgeable, offset_ex(bridges_temp, ext_perimeter_width));
|
||||
offset_to_do -= ext_perimeter_width;
|
||||
first = false;
|
||||
}
|
||||
unbridgeable = offset_ex(unbridgeable, ext_perimeter_width + offset_to_do, ClipperLib::jtSquare);
|
||||
bridges_temp = diff_ex(bridges_temp, unbridgeable);
|
||||
unsupported_filtered = offset_ex(bridges_temp, offset_to_do);
|
||||
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
|
||||
|
||||
// Normalize anchor size for partial bridges:
|
||||
// derive the bridge core first, then add a fixed overlap into support.
|
||||
const coordf_t anchor_overlap = bridged_infill_margin;
|
||||
ExPolygons bridge_core = diff_ex(unsupported_filtered, support, ApplySafetyOffset::Yes);
|
||||
if (bridge_core.empty()) {
|
||||
bridge_core = unsupported_filtered;
|
||||
}
|
||||
ExPolygons anchor_overlap_area = intersection_ex(
|
||||
offset_ex(bridge_core, anchor_overlap),
|
||||
support,
|
||||
ApplySafetyOffset::Yes);
|
||||
unsupported_filtered = union_ex(bridge_core, anchor_overlap_area);
|
||||
unsupported_filtered = intersection_ex(unsupported_filtered, reference);
|
||||
// } else {
|
||||
// ExPolygons unbridgeable = intersection_ex(unsupported, diff_ex(unsupported_filtered, offset_ex(bridgeable_simplified, ext_perimeter_width / 2)));
|
||||
// unbridgeable = offset2_ex(unbridgeable, -ext_perimeter_width, ext_perimeter_width);
|
||||
// unsupported_filtered = unbridgeable;
|
||||
|
||||
// ////put the bridge area inside the unsupported_filtered variable
|
||||
// //unsupported_filtered = intersection_ex(last,
|
||||
// // diff_ex(
|
||||
// // offset_ex(bridgeable_simplified, (double)perimeter_spacing / 2),
|
||||
// // unbridgeable
|
||||
// // )
|
||||
// // );
|
||||
// }
|
||||
} else {
|
||||
unsupported_filtered.clear();
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(last, offset_ex(unsupported_filtered, bridge_anchor_offset));
|
||||
unsupported_filtered = union_ex(bridgeable_filtered, bridge_anchor_areas); // add bridge anchor
|
||||
unsupported_filtered = opening_ex(unsupported_filtered, bridge_anchor_offset); // remove anchor area from hole-side walls, it must remain unbridgeable
|
||||
// TODO: Fix the case with thin outer walls around the bridge (1~2 walls) where classic wall
|
||||
// might generate two walls in a tiny space or non at all if "Detect thin walls" is not activated
|
||||
}
|
||||
} else {
|
||||
unsupported_filtered.clear();
|
||||
@@ -2146,7 +2114,7 @@ void PerimeterGenerator::process_arachne()
|
||||
// We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
// in the current layer
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1);
|
||||
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1);
|
||||
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2)));
|
||||
}
|
||||
|
||||
@@ -2579,7 +2547,7 @@ bool PerimeterGeneratorLoop::is_internal_contour() const
|
||||
|
||||
std::vector<Polygons> PerimeterGenerator::generate_lower_polygons_series(float width)
|
||||
{
|
||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1);
|
||||
float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1);
|
||||
float start_offset = -0.5 * width;
|
||||
float end_offset = 0.5 * nozzle_diameter;
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ public:
|
||||
const PrintObjectConfig* object_config,
|
||||
const PrintConfig* print_config,
|
||||
const bool spiral_mode,
|
||||
const double model_rotation_rad,
|
||||
// Output:
|
||||
// Loops with the external thin walls
|
||||
ExtrusionEntityCollection* loops,
|
||||
@@ -132,6 +133,7 @@ public:
|
||||
config(config), object_config(object_config), print_config(print_config),
|
||||
m_spiral_vase(spiral_mode),
|
||||
m_scaled_resolution(scaled<double>(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)),
|
||||
m_model_rotation_rad(model_rotation_rad),
|
||||
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap),
|
||||
m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1)
|
||||
{}
|
||||
@@ -157,6 +159,7 @@ private:
|
||||
private:
|
||||
bool m_spiral_vase;
|
||||
double m_scaled_resolution;
|
||||
double m_model_rotation_rad;
|
||||
double m_ext_mm3_per_mm;
|
||||
double m_mm3_per_mm;
|
||||
double m_mm3_per_mm_overhang;
|
||||
|
||||
@@ -1005,6 +1005,9 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"lateral_lattice_angle_1",
|
||||
"lateral_lattice_angle_2",
|
||||
"infill_overhang_angle",
|
||||
"lightning_overhang_angle",
|
||||
"lightning_prune_angle",
|
||||
"lightning_straightening_angle",
|
||||
"top_surface_pattern",
|
||||
"bottom_surface_pattern",
|
||||
"infill_direction",
|
||||
@@ -1069,10 +1072,13 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"print_order",
|
||||
"support_remove_small_overhang",
|
||||
"filename_format",
|
||||
"wall_filament",
|
||||
"outer_wall_filament_id",
|
||||
"inner_wall_filament_id",
|
||||
"support_bottom_z_distance",
|
||||
"sparse_infill_filament",
|
||||
"solid_infill_filament",
|
||||
"sparse_infill_filament_id",
|
||||
"internal_solid_filament_id",
|
||||
"top_surface_filament_id",
|
||||
"bottom_surface_filament_id",
|
||||
"support_filament",
|
||||
"support_interface_filament",
|
||||
"support_interface_not_for_body",
|
||||
@@ -1094,6 +1100,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"infill_wall_overlap",
|
||||
"top_bottom_infill_wall_overlap",
|
||||
"bridge_flow",
|
||||
"bridge_line_width",
|
||||
"internal_bridge_flow",
|
||||
"elefant_foot_compensation",
|
||||
"elefant_foot_compensation_layers",
|
||||
@@ -1158,6 +1165,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"small_perimeter_threshold",
|
||||
"bridge_angle",
|
||||
"internal_bridge_angle",
|
||||
"relative_bridge_angle",
|
||||
"filter_out_gap_fill",
|
||||
"travel_acceleration",
|
||||
"inner_wall_acceleration",
|
||||
@@ -1318,7 +1326,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
|
||||
static std::vector<std::string> s_Preset_printer_options {
|
||||
"printer_technology",
|
||||
"printable_area", "extruder_printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
|
||||
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
|
||||
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
|
||||
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
|
||||
@@ -1329,7 +1337,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"scan_first_layer", "enable_power_loss_recovery", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_tool_change_time", "time_cost", "machine_pause_gcode", "template_custom_gcode",
|
||||
"nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","printer_structure",
|
||||
"best_object_pos", "head_wrap_detect_zone",
|
||||
"host_type", "print_host", "printhost_apikey", "bbl_use_printhost", "printer_agent",
|
||||
"host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent",
|
||||
"print_host_webui",
|
||||
"printhost_cafile","printhost_port","printhost_authorization_type",
|
||||
"printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format",
|
||||
@@ -1338,7 +1346,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
||||
"cooling_tube_retraction",
|
||||
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower",
|
||||
"z_offset",
|
||||
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
|
||||
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
|
||||
"bed_temperature_formula", "nozzle_flush_dataset"
|
||||
};
|
||||
|
||||
@@ -2354,6 +2362,10 @@ bool PresetCollection::validate_preset(const std::string &preset_name, std::stri
|
||||
const std::string canonical_inherit_name = this->canonical_preset_name(inherit_name);
|
||||
it = this->find_preset_internal(canonical_inherit_name);
|
||||
found = it != m_presets.end() && it->name == canonical_inherit_name && is_trusted(*it);
|
||||
if (!found) {
|
||||
it = this->find_preset_renamed(canonical_inherit_name);
|
||||
found = it != m_presets.end() && is_trusted(*it);
|
||||
}
|
||||
if (found)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list")%preset_name %inherit_name;
|
||||
else
|
||||
@@ -2445,7 +2457,9 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
|
||||
if (!inherits.empty() && (different_settings_list.size() > 0)) {
|
||||
auto iter = this->find_preset_internal(inherits);
|
||||
if (iter != m_presets.end() && iter->name == inherits) {
|
||||
if (iter == m_presets.end() || iter->name != inherits)
|
||||
iter = this->find_preset_renamed(inherits);
|
||||
if (iter != m_presets.end()) {
|
||||
//std::vector<std::string> dirty_options = cfg.diff(iter->config);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": change preset %1% inherit %2% 's value to %3% 's values")%original_name %inherits %path;
|
||||
cfg.update_non_diff_values_to_base_config(iter->config, keys, different_settings_list, extruder_id_name, extruder_variant_name, *key_set1, *key_set2);
|
||||
@@ -2497,7 +2511,9 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
// and override its settings with the loaded ones.
|
||||
assert(it == m_presets.end());
|
||||
it = this->find_preset_internal(inherits);
|
||||
found = it != m_presets.end() && it->name == inherits;
|
||||
if (it == m_presets.end() || it->name != inherits)
|
||||
it = this->find_preset_renamed(inherits);
|
||||
found = it != m_presets.end();
|
||||
if (found && profile_print_params_same(it->config, cfg)) {
|
||||
// The system preset exists and it matches the values stored inside config.
|
||||
if (select == LoadAndSelect::Always)
|
||||
@@ -3839,6 +3855,7 @@ static std::vector<std::string> s_PhysicalPrinter_opts {
|
||||
"print_host",
|
||||
"print_host_webui",
|
||||
"printhost_apikey",
|
||||
"flashforge_serial_number",
|
||||
"printhost_cafile",
|
||||
"printhost_port",
|
||||
"printhost_authorization_type",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "libslic3r.h"
|
||||
#include "I18N.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
@@ -2231,6 +2232,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
[&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
auto bundle = std::make_unique<PresetBundle>();
|
||||
bundle->set_is_validation_mode(validation_mode);
|
||||
try {
|
||||
auto result = bundle->load_vendor_configs_from_json(
|
||||
dir.string(), other_vendors[i], PresetBundle::LoadSystem,
|
||||
@@ -2871,6 +2873,14 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
|
||||
if (use_default_nozzle_volume_type) {
|
||||
project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values = current_printer.config.option<ConfigOptionEnumsGeneric>("default_nozzle_volume_type")->values;
|
||||
} else {
|
||||
// Orca: make sure `nozzle_volume_type` not shorter than `default_nozzle_volume_type`, otherwise we got array out of bound access
|
||||
// later in `Tab::switch_excluder`
|
||||
auto& opt = project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
|
||||
const auto& opt_default = current_printer.config.option<ConfigOptionEnumsGeneric>("default_nozzle_volume_type")->values;
|
||||
while (opt.size() < opt_default.size()) {
|
||||
opt.emplace_back(opt_default[opt.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the initial physical printer name.
|
||||
@@ -4095,13 +4105,16 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
|
||||
opt->value = boost::algorithm::clamp<int>(opt->value, 0, int(num_filaments));
|
||||
}
|
||||
|
||||
static const char* keys_1based[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament"};
|
||||
for (size_t i = 0; i < sizeof(keys_1based) / sizeof(keys_1based[0]); ++ i) {
|
||||
std::string key = std::string(keys_1based[i]);
|
||||
static const char* keys_with_default[] = {
|
||||
"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id",
|
||||
"internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id"
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(keys_with_default) / sizeof(keys_with_default[0]); ++ i) {
|
||||
std::string key = std::string(keys_with_default[i]);
|
||||
auto *opt = dynamic_cast<ConfigOptionInt*>(out.option(key, false));
|
||||
assert(opt != nullptr);
|
||||
if(opt->value < 1 || opt->value > int(num_filaments))
|
||||
opt->value = 1;
|
||||
if(opt->value < 0 || opt->value > int(num_filaments))
|
||||
opt->value = 0;
|
||||
}
|
||||
out.option<ConfigOptionString >("print_settings_id", true)->value = this->prints.get_selected_preset_name();
|
||||
out.option<ConfigOptionStrings>("filament_settings_id", true)->values = this->filament_presets;
|
||||
@@ -4971,6 +4984,39 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
reason = std::string("can not find printer_variant in vendor profile");
|
||||
return reason;
|
||||
}
|
||||
// An instantiation printer profile's nozzle_diameter must match the numeric (diameter)
|
||||
// prefix of its printer_variant: "0.4" -> {0.4}, "0.8HF" -> {0.8} (a trailing
|
||||
// non-numeric suffix such as "HF"/"HS" distinguishes a hardware sub-variant and is
|
||||
// ignored here), and for multi-nozzle printers "0.4+0.6" -> {0.4, 0.6}.
|
||||
// Note: a variant may legitimately repeat across presets of the same model (e.g. speed
|
||||
// modes, IDEX copy/mirror, or different control boards), so only the diameter is
|
||||
// validated, not variant uniqueness. Validation-only so the app keeps loading existing
|
||||
// profiles unchanged.
|
||||
if (validation_mode && instantiation == "true") {
|
||||
const auto *nd = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
std::set<double> nozzles, variant_nozzles;
|
||||
if (nd != nullptr)
|
||||
nozzles.insert(nd->values.begin(), nd->values.end());
|
||||
std::vector<std::string> variant_tokens;
|
||||
boost::algorithm::split(variant_tokens, printer_variant, boost::algorithm::is_any_of("+"));
|
||||
bool variant_ok = true; // printer_variant is already guaranteed non-empty above
|
||||
for (const std::string &tok : variant_tokens) {
|
||||
size_t consumed = 0;
|
||||
double d = string_to_double_decimal_point(tok, &consumed);
|
||||
// Require a leading numeric diameter; a trailing suffix (e.g. "HF") is allowed.
|
||||
if (consumed == 0) { variant_ok = false; break; }
|
||||
variant_nozzles.insert(d);
|
||||
}
|
||||
if (!variant_ok || variant_nozzles != nozzles) {
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
|
||||
preset_name << "\" has printer_variant \"" << printer_variant <<
|
||||
"\" that does not match its nozzle_diameter \"" << (nd ? nd->serialize() : std::string()) << "\". "
|
||||
"printer_variant must begin with the nozzle diameter, optionally followed by a non-numeric suffix "
|
||||
"(e.g. \"0.4\", \"0.8HF\"); for multi-nozzle printers, join the per-nozzle diameters with \"+\" in "
|
||||
"nozzle order (e.g. \"0.4+0.6\").";
|
||||
}
|
||||
}
|
||||
}
|
||||
const Preset *preset_existing = presets_collection->find_preset(preset_name, false);
|
||||
if (preset_existing != nullptr) {
|
||||
|
||||
+352
-200
@@ -23,7 +23,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_set>
|
||||
#include <sstream>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@@ -237,7 +239,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"bed_temperature_formula",
|
||||
"filament_notes",
|
||||
"process_notes",
|
||||
"printer_notes"
|
||||
"printer_notes",
|
||||
"use_3mf"
|
||||
};
|
||||
|
||||
static std::unordered_set<std::string> steps_ignore;
|
||||
@@ -669,13 +672,13 @@ StringObjectException Print::sequential_print_clearance_valid(const Print &print
|
||||
//juedge the exclude area
|
||||
if (!intersection(exclude_polys, convex_hull_no_offset).empty()) {
|
||||
if (single_object_exception.string.empty()) {
|
||||
single_object_exception.string = (boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.string = (boost::format(L("%1% is too close to exclusion area. There may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
// single_object_exception.object = instance.model_instance->get_object();
|
||||
//ORCA: Pass ModelInstance instead of ModelObject
|
||||
single_object_exception.object = instance.model_instance;
|
||||
}
|
||||
else {
|
||||
single_object_exception.string += "\n"+(boost::format(L("%1% is too close to exclusion area, there may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.string += "\n"+(boost::format(L("%1% is too close to exclusion area. There may be collisions when printing.")) %instance.model_instance->get_object()->name).str();
|
||||
single_object_exception.object = nullptr;
|
||||
}
|
||||
//if (polygons) {
|
||||
@@ -1038,7 +1041,7 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
|
||||
/*if (warning) {
|
||||
warning->string += L("Prime Tower is too close to exclusion area, there may be collisions when printing.\n");
|
||||
}*/
|
||||
return {L("Prime Tower") + L(" is too close to exclusion area, and collisions will be caused.\n")};
|
||||
return {L("Prime Tower") + L(" is too close to an exclusion area, and collisions will be caused.\n")};
|
||||
}
|
||||
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) {
|
||||
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
|
||||
@@ -1254,14 +1257,23 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Orca: this g92e0 regex is used copied from PrusaSlicer
|
||||
// Matches "G92 E0" with various forms of writing the zero and with an optional comment.
|
||||
boost::regex regex_g92e0 { "^[ \\t]*[gG]92[ \\t]*[eE](0(\\.0*)?|\\.0+)[ \\t]*(;.*)?$" };
|
||||
|
||||
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
|
||||
//BBS: refine seq-print validation logic.....FIXME:StringObjectException *warning can only contain one warning, but there might be many warnings, need a vector<StringObjectException>
|
||||
StringObjectException Print::validate(StringObjectException *warning, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
|
||||
//BBS: refine seq-print validation logic
|
||||
StringObjectException Print::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
|
||||
{
|
||||
auto add_warning = [warnings](StringObjectException w) {
|
||||
w.is_warning = true;
|
||||
if (warnings != nullptr)
|
||||
warnings->push_back(std::move(w));
|
||||
};
|
||||
auto warn = [&](std::string msg, std::string opt_key = "", const ObjectBase* object = nullptr) {
|
||||
StringObjectException w;
|
||||
w.string = std::move(msg);
|
||||
w.opt_key = std::move(opt_key);
|
||||
w.object = object;
|
||||
add_warning(std::move(w));
|
||||
};
|
||||
|
||||
std::vector<unsigned int> extruders = this->extruders();
|
||||
unsigned int nozzles = m_config.nozzle_diameter.size();
|
||||
|
||||
@@ -1276,9 +1288,8 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (!ret.string.empty())
|
||||
{
|
||||
ret.type = STRING_EXCEPT_FILAMENTS_DIFFERENT_TEMP;
|
||||
if (ret.is_warning && warning != nullptr) {
|
||||
*warning = ret;
|
||||
//return {};
|
||||
if (ret.is_warning) {
|
||||
add_warning(ret);
|
||||
}else
|
||||
return ret;
|
||||
}
|
||||
@@ -1304,32 +1315,26 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
}
|
||||
else {
|
||||
//BBS
|
||||
auto ret = layered_print_cleareance_valid(*this, warning);
|
||||
StringObjectException layer_warning;
|
||||
auto ret = layered_print_cleareance_valid(*this, &layer_warning);
|
||||
if (!ret.string.empty()) {
|
||||
ret.type = STRING_EXCEPT_OBJECT_COLLISION_IN_LAYER_PRINT;
|
||||
return ret;
|
||||
}
|
||||
if (!layer_warning.string.empty())
|
||||
add_warning(layer_warning);
|
||||
}
|
||||
|
||||
if (m_config.enable_prime_tower) {
|
||||
for (const PrintObject* object : m_objects) {
|
||||
if (object->config().precise_z_height.value && warning != nullptr) {
|
||||
StringObjectException warningtemp;
|
||||
warningtemp.string = L("Enabling both precise Z height and the prime tower may cause slicing errors.");
|
||||
warningtemp.opt_key = "precise_z_height";
|
||||
warningtemp.is_warning = true;
|
||||
*warning = warningtemp;
|
||||
if (object->config().precise_z_height.value) {
|
||||
warn(L("Enabling both precise Z height and the prime tower may cause slicing errors."), "precise_z_height");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (m_config.enable_wrapping_detection && warning!=nullptr) {
|
||||
StringObjectException warningtemp;
|
||||
warningtemp.string = L("A prime tower is required for clumping detection; otherwise, there may be flaws on the model.");
|
||||
warningtemp.opt_key = "enable_prime_tower";
|
||||
warningtemp.is_warning = true;
|
||||
*warning = warningtemp;
|
||||
}
|
||||
if (m_config.enable_wrapping_detection)
|
||||
warn(L("A prime tower is required for clumping detection; otherwise, there may be flaws on the model."), "enable_prime_tower");
|
||||
}
|
||||
|
||||
if (m_config.spiral_mode) {
|
||||
@@ -1346,7 +1351,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (std::any_of(all_regions.begin() + 1, all_regions.end(), [this, ra = all_regions.front()](const auto rb) {
|
||||
return !Layer::is_perimeter_compatible(*this, ra, rb);
|
||||
})) {
|
||||
return {L("The spiral vase mode does not work when an object contains more than one materials."), nullptr, "spiral_mode"};
|
||||
return {L("Spiral (vase) mode does not work when an object contains more than one material."), nullptr, "spiral_mode"};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1425,8 +1430,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam
|
||||
|| std::abs((filament_diam - first_filament_diam) / first_filament_diam) > 0.1) {
|
||||
// return { L("Different nozzle diameters and different filament diameters may not work well when prime tower is enabled. It's very experimental, please proceed with caucious.") };
|
||||
warning->string = L("Different nozzle diameters and different filament diameters may not work well when the prime tower is enabled. It's very experimental, so please proceed with caution.");
|
||||
warning->opt_key = "nozzle_diameter";
|
||||
warn(L("Different nozzle diameters and different filament diameters may not work well when the prime tower is enabled. It's very experimental, so please proceed with caution."), "nozzle_diameter");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1443,14 +1447,14 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
return { L("The prime tower is currently only supported for the Marlin, RepRap/Sprinter, RepRapFirmware and Repetier G-code flavors.")};
|
||||
|
||||
if ((m_config.print_sequence == PrintSequence::ByObject) && extruders.size() > 1)
|
||||
return { L("The prime tower is not supported in \"By object\" print."), nullptr, "enable_prime_tower" };
|
||||
return { L("A prime tower is not supported in \u201cBy object\u201d print."), nullptr, "enable_prime_tower" };
|
||||
|
||||
// BBS: When prime tower is on, object layer and support layer must be aligned. So support gap should be multiple of object layer height.
|
||||
for (size_t i = 0; i < m_objects.size(); i++) {
|
||||
const PrintObject* object = m_objects[i];
|
||||
const SlicingParameters& slicing_params = object->slicing_parameters();
|
||||
if (object->config().adaptive_layer_height) {
|
||||
return { L("The prime tower is not supported when adaptive layer height is on. It requires that all objects have the same layer height."), object, "adaptive_layer_height" };
|
||||
return { L("A prime tower is not supported when adaptive layer height is on. It requires that all objects have the same layer height."), object, "adaptive_layer_height" };
|
||||
}
|
||||
|
||||
if (!object->config().enable_support)
|
||||
@@ -1458,7 +1462,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
|
||||
double gap_layers = slicing_params.gap_object_support / slicing_params.layer_height;
|
||||
if (gap_layers - (int)gap_layers > EPSILON) {
|
||||
return {L("The prime tower requires \"support gap\" to be multiple of layer height."), object};
|
||||
return {L("A prime tower requires any \u201csupport gap\u201d to be a multiple of layer height."), object};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1471,9 +1475,9 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
const SlicingParameters &slicing_params = object->slicing_parameters();
|
||||
if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON ||
|
||||
std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON)
|
||||
return {L("The prime tower requires that all objects have the same layer heights."), object, "initial_layer_print_height"};
|
||||
return {L("A prime tower requires that all objects have the same layer height."), object, "initial_layer_print_height"};
|
||||
if (slicing_params.raft_layers() != slicing_params0.raft_layers())
|
||||
return {L("The prime tower requires that all objects are printed over the same number of raft layers."), object, "raft_layers"};
|
||||
return {L("A prime tower requires that all objects are printed over the same number of raft layers."), object, "raft_layers"};
|
||||
// BBS: support gap can be multiple of object layer height, remove _L()
|
||||
#if 0
|
||||
if (slicing_params0.gap_object_support != slicing_params.gap_object_support ||
|
||||
@@ -1481,7 +1485,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
return {L("The prime tower is only supported for multiple objects if they are printed with the same support_top_z_distance."), object};
|
||||
#endif
|
||||
if (!equal_layering(slicing_params, slicing_params0))
|
||||
return { L("The prime tower requires that all objects are sliced with the same layer heights."), object };
|
||||
return { L("A prime tower requires that all objects are sliced with the same layer height."), object };
|
||||
if (has_custom_layering) {
|
||||
auto &lh = layer_height_profile(i);
|
||||
auto &lh_tallest = layer_height_profile(tallest_object_idx);
|
||||
@@ -1544,13 +1548,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
auto validate_extrusion_width = [min_nozzle_diameter, max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool {
|
||||
double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter);
|
||||
double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter);
|
||||
if (extrusion_width_min == 0) {
|
||||
// Default "auto-generated" extrusion width is always valid.
|
||||
} else if (extrusion_width_min <= layer_height) {
|
||||
err_msg = L("Too small line width");
|
||||
return false;
|
||||
} else if (extrusion_width_max > max_nozzle_diameter * MAX_LINE_WIDTH_MULTIPLIER) {
|
||||
err_msg = L("Too large line width");
|
||||
if (extrusion_width_min == 0) {
|
||||
// Default "auto-generated" extrusion width is always valid.
|
||||
} else if (extrusion_width_min <= layer_height) {
|
||||
err_msg = L("Line width too small");
|
||||
return false;
|
||||
} else if (extrusion_width_max > max_nozzle_diameter * MAX_LINE_WIDTH_MULTIPLIER) {
|
||||
err_msg = L("Line width too large");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -1572,7 +1576,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// BBS
|
||||
#if 0
|
||||
if (this->has_wipe_tower() && object->config().independent_support_layer_height) {
|
||||
return {L("The prime tower requires that support has the same layer height with object."), object, "support_filament"};
|
||||
return {L("A prime tower requires that support has the same layer height as the object."), object, "support_filament"};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1583,22 +1587,15 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// Orca: use organic as default
|
||||
object->config().support_style == smsDefault) {
|
||||
|
||||
if (warning) {
|
||||
// Orca: check the support wall count and the base pattern
|
||||
if (object->config().tree_support_wall_count > 1 &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpNone &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpDefault) {
|
||||
warning->string = L("For Organic supports, two walls are supported only with the Hollow/Default base pattern.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
}
|
||||
// Orca: check the support wall count and the base pattern
|
||||
if (object->config().tree_support_wall_count > 1 &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpNone &&
|
||||
object->config().support_base_pattern != SupportMaterialPattern::smpDefault)
|
||||
warn(L("For Organic supports, two walls are supported only with the Hollow/Default base pattern."), "support_base_pattern");
|
||||
|
||||
// Orca: check if the Lightning base pattern selected
|
||||
if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning) {
|
||||
warning->string = L(
|
||||
"The Lightning base pattern is not supported by this support type; Rectilinear will be used instead.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
}
|
||||
}
|
||||
// Orca: check if the Lightning base pattern selected
|
||||
if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning)
|
||||
warn(L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
|
||||
|
||||
float extrusion_width = std::min(
|
||||
support_material_flow(object).width(),
|
||||
@@ -1610,28 +1607,23 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (object->config().tree_support_branch_diameter_organic < object->config().tree_support_tip_diameter)
|
||||
return { L("Organic support branch diameter must not be smaller than support tree tip diameter."), object, "tree_support_branch_diameter_organic" };
|
||||
}
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning && warning) {
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpLightning) {
|
||||
// Orca: check if the Lightning base pattern selected
|
||||
warning->string = L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpNone && warning) {
|
||||
warn(L("The Lightning base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
|
||||
} else if (object->config().support_base_pattern == SupportMaterialPattern::smpNone) {
|
||||
// Orca: check if the Hollow base pattern selected
|
||||
warning->string = L("The Hollow base pattern is not supported by this support type; Rectilinear will be used instead.");
|
||||
warning->opt_key = "support_base_pattern";
|
||||
warn(L("The Hollow base pattern is not supported by this support type; Rectilinear will be used instead."), "support_base_pattern");
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have custom support data that would not be used?
|
||||
// Notify the user in that case.
|
||||
if (! object->has_support() && warning) {
|
||||
if (! object->has_support()) {
|
||||
for (const ModelVolume* mv : object->model_object()->volumes) {
|
||||
bool has_enforcers = mv->is_support_enforcer() ||
|
||||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
|
||||
if (has_enforcers) {
|
||||
StringObjectException warningtemp;
|
||||
warningtemp.string = L("Support enforcers are used but support is not enabled. Please enable support.");
|
||||
warningtemp.object = object;
|
||||
*warning = warningtemp;
|
||||
warn(L("Support enforcers are used but support is not enabled. Please enable support."), "", object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1671,30 +1663,83 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
for (const PrintRegion ®ion : object->all_regions())
|
||||
if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg))
|
||||
return {err_msg, object, opt_key};
|
||||
|
||||
const bool allow_thin_bridge_width = object->config().thick_bridges && object->config().thick_internal_bridges;
|
||||
for (const PrintRegion ®ion : object->all_regions()) {
|
||||
const auto &bridge_width_opt = region.config().bridge_line_width;
|
||||
for (FlowRole bridge_role : { frPerimeter, frInfill, frSolidInfill, frTopSolidInfill }) {
|
||||
const double nozzle_diameter = m_config.nozzle_diameter.get_at(region.extruder(bridge_role) - 1);
|
||||
const double bridge_width = bridge_width_opt.get_abs_value(nozzle_diameter);
|
||||
if (bridge_width <= 0.)
|
||||
continue;
|
||||
if (bridge_width > nozzle_diameter) {
|
||||
err_msg = L("Bridge line width must not exceed nozzle diameter");
|
||||
return { err_msg, object, "bridge_line_width" };
|
||||
}
|
||||
if (!allow_thin_bridge_width && bridge_width <= layer_height) {
|
||||
err_msg = L("Line width too small");
|
||||
return { err_msg, object, "bridge_line_width" };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: G92 E0 is not supported when using absolute extruder addressing
|
||||
// This check is copied from PrusaSlicer, the original author is Vojtech Bubnik
|
||||
if(!is_BBL_printer()) {
|
||||
bool before_layer_gcode_resets_extruder =
|
||||
boost::regex_search(m_config.before_layer_change_gcode.value, regex_g92e0);
|
||||
bool layer_gcode_resets_extruder = boost::regex_search(m_config.layer_change_gcode.value, regex_g92e0);
|
||||
if (m_config.use_relative_e_distances) {
|
||||
// See GH issues #6336 #5073
|
||||
if ((m_config.gcode_flavor == gcfMarlinLegacy || m_config.gcode_flavor == gcfMarlinFirmware) &&
|
||||
!before_layer_gcode_resets_extruder && !layer_gcode_resets_extruder)
|
||||
return {L("Relative extruder addressing requires resetting the extruder position at each layer to "
|
||||
"prevent loss of floating point accuracy. Add \"G92 E0\" to layer_gcode."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
} else if (before_layer_gcode_resets_extruder)
|
||||
return {L("\"G92 E0\" was found in before_layer_gcode, which is incompatible with absolute extruder "
|
||||
// This check is modified from PrusaSlicer, the original author is Vojtech Bubnik
|
||||
// Orca: case‑sensitive match for exactly "G92 E0" (uppercase G and E only)
|
||||
// because gcode is case sensitive and G92 e0 satisfies the regex but causes a slicing error
|
||||
// https://github.com/OrcaSlicer/OrcaSlicer/issues/13927
|
||||
|
||||
// Matches any case of "G92 E0" (original pattern)
|
||||
static const boost::regex regex_g92e0 {
|
||||
"^[ \\t]*[gG]92[ \\t]*[eE](0(\\.0*)?|\\.0+)[ \\t]*(;.*)?$"
|
||||
};
|
||||
// Matches only the exact uppercase "G92 E0"
|
||||
static const boost::regex regex_g92e0_correct {
|
||||
"^[ \\t]*G92[ \\t]*E(0(\\.0*)?|\\.0+)[ \\t]*(;.*)?$"
|
||||
};
|
||||
|
||||
const bool before_has_g92_any = boost::regex_search(
|
||||
m_config.before_layer_change_gcode.value, regex_g92e0);
|
||||
const bool layer_has_g92_any = boost::regex_search(
|
||||
m_config.layer_change_gcode.value, regex_g92e0);
|
||||
|
||||
if (m_config.use_relative_e_distances) {
|
||||
// Relative mode: "G92 E0" is required to reset extruder position.
|
||||
const bool before_has_g92_exact = boost::regex_search(
|
||||
m_config.before_layer_change_gcode.value, regex_g92e0_correct);
|
||||
const bool layer_has_g92_exact = boost::regex_search(
|
||||
m_config.layer_change_gcode.value, regex_g92e0_correct);
|
||||
|
||||
// Wrong case found?
|
||||
if (before_has_g92_any && !before_has_g92_exact)
|
||||
return {L("\"G92 E0\" was found in before_layer_change_gcode, but the G or E are not uppercase. "
|
||||
"Please change them to the exact uppercase \"G92 E0\"."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
if (layer_has_g92_any && !layer_has_g92_exact)
|
||||
return {L("\"G92 E0\" was found in layer_change_gcode, but the G or E are not uppercase. "
|
||||
"Please change them to the exact uppercase \"G92 E0\"."),
|
||||
nullptr, "layer_change_gcode"};
|
||||
|
||||
// Only Marlin flavours need the reset; BBL printers do not.
|
||||
if ((m_config.gcode_flavor == gcfMarlinLegacy || m_config.gcode_flavor == gcfMarlinFirmware) &&
|
||||
!is_BBL_printer() &&
|
||||
!before_has_g92_exact && !layer_has_g92_exact)
|
||||
return {L("Relative extruder addressing requires resetting the extruder position at each layer to "
|
||||
"prevent loss of floating point accuracy. Add \"G92 E0\" to layer_gcode."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
} else {
|
||||
// Absolute mode: any occurrence of "G92 E0" is incompatible.
|
||||
if (before_has_g92_any)
|
||||
return {L("\"G92 E0\" was found in before_layer_change_gcode, which is incompatible with absolute extruder "
|
||||
"addressing."),
|
||||
nullptr, "before_layer_change_gcode"};
|
||||
else if (layer_gcode_resets_extruder)
|
||||
return {L("\"G92 E0\" was found in layer_gcode, which is incompatible with absolute extruder addressing."),
|
||||
if (layer_has_g92_any)
|
||||
return {L("\"G92 E0\" was found in layer_change_gcode, which is incompatible with absolute extruder "
|
||||
"addressing."),
|
||||
nullptr, "layer_change_gcode"};
|
||||
}
|
||||
}
|
||||
|
||||
const ConfigOptionDef* bed_type_def = print_config_def.get("curr_bed_type");
|
||||
assert(bed_type_def != nullptr);
|
||||
@@ -1730,7 +1775,11 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
}
|
||||
|
||||
// check if print speed/accel/jerk is higher than the maximum speed of the printer
|
||||
if (warning) {
|
||||
if (warnings) {
|
||||
// The motion-ability checks are mutually exclusive (gated on warning_key), so collect the
|
||||
// single one that fires into a local and push it once - separate from the precise-wall and
|
||||
// shrinkage warnings below.
|
||||
StringObjectException motion_warning;
|
||||
try {
|
||||
auto check_extruder = [&](const int extruder_id) {
|
||||
auto check_motion_ability_object_setting = [&](const std::vector<std::string>& keys_to_check, double limit) -> std::string {
|
||||
@@ -1762,7 +1811,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
if (!ignore_jerk_validation) {
|
||||
if (m_default_object_config.default_jerk.get_at(extruder_id) == 1 || m_default_object_config.outer_wall_jerk.get_at(extruder_id) == 1 ||
|
||||
m_default_object_config.inner_wall_jerk.get_at(extruder_id) == 1) {
|
||||
warning->string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
|
||||
motion_warning.string = L("Setting the jerk speed too low could lead to artifacts on curved surfaces");
|
||||
if (m_default_object_config.outer_wall_jerk.get_at(extruder_id) == 1)
|
||||
warning_key = "outer_wall_jerk";
|
||||
else if (m_default_object_config.inner_wall_jerk.get_at(extruder_id) == 1)
|
||||
@@ -1770,7 +1819,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
else
|
||||
warning_key = "default_jerk";
|
||||
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
|
||||
if (warning_key.empty() && m_default_object_config.default_jerk.get_at(extruder_id) > 0) {
|
||||
@@ -1780,11 +1829,11 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
warning_key.clear();
|
||||
warning_key = check_motion_ability_object_setting(jerk_to_check, max_jerk);
|
||||
if (!warning_key.empty()) {
|
||||
warning->string = L(
|
||||
motion_warning.string = L(
|
||||
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n"
|
||||
"Orca will automatically cap the jerk speed to ensure it doesn't surpass the printer's capabilities.\n"
|
||||
"You can adjust the maximum jerk setting in your printer's configuration to get higher speeds.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1793,10 +1842,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// Orca: Only marlin FW supports max junction deviation. Dont display warning if firmware is not supporting it.
|
||||
const bool support_max_junction_deviation = ( m_config.gcode_flavor == gcfMarlinFirmware);
|
||||
if (warning_key.empty() && m_default_object_config.default_junction_deviation.get_at(extruder_id) > max_junction_deviation && support_max_junction_deviation) {
|
||||
warning->string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n"
|
||||
motion_warning.string = L( "Junction deviation setting exceeds the printer's maximum value (machine_max_junction_deviation).\n"
|
||||
"Orca will automatically cap the junction deviation to ensure it doesn't surpass the printer's capabilities.\n"
|
||||
"You can adjust the machine_max_junction_deviation value in your printer's configuration to get higher limits.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = "default_junction_deviation";
|
||||
}
|
||||
|
||||
// check acceleration
|
||||
@@ -1831,12 +1880,12 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
};
|
||||
warning_key = check_motion_ability_object_setting(accel_to_check, max_accel);
|
||||
if (!warning_key.empty()) {
|
||||
warning->string = L("The acceleration setting exceeds the printer's maximum acceleration "
|
||||
motion_warning.string = L("The acceleration setting exceeds the printer's maximum acceleration "
|
||||
"(machine_max_acceleration_extruding).\nOrca will "
|
||||
"automatically cap the acceleration speed to ensure it doesn't surpass the printer's "
|
||||
"capabilities.\nYou can adjust the "
|
||||
"machine_max_acceleration_extruding value in your printer's configuration to get higher speeds.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
if (support_travel_acc) {
|
||||
const auto max_travel = m_config.machine_max_acceleration_travel.values[0];
|
||||
@@ -1846,13 +1895,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
};
|
||||
warning_key = check_motion_ability_object_setting(accel_to_check, max_travel);
|
||||
if (!warning_key.empty()) {
|
||||
warning->string = L(
|
||||
motion_warning.string = L(
|
||||
"The travel acceleration setting exceeds the printer's maximum travel acceleration "
|
||||
"(machine_max_acceleration_travel).\nOrca will "
|
||||
"automatically cap the travel acceleration speed to ensure it doesn't surpass the printer's "
|
||||
"capabilities.\nYou can adjust the "
|
||||
"machine_max_acceleration_travel value in your printer's configuration to get higher speeds.");
|
||||
warning->opt_key = warning_key;
|
||||
motion_warning.opt_key = warning_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1869,30 +1918,45 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons*
|
||||
// if (warning_key.empty() && m_config.travel_speed > max_speed)
|
||||
// warning_key = "travel_speed";
|
||||
// if (!warning_key.empty()) {
|
||||
// warning->string = L(
|
||||
// motion_warning.string = L(
|
||||
// "The speed setting exceeds the printer's maximum speed (machine_max_speed_x/machine_max_speed_y).\nOrca will "
|
||||
// "automatically cap the print speed to ensure it doesn't surpass the printer's capabilities.\nYou can adjust the "
|
||||
// "maximum speed setting in your printer's configuration to get higher speeds.");
|
||||
// warning->opt_key = warning_key;
|
||||
// motion_warning.opt_key = warning_key;
|
||||
// }
|
||||
// }
|
||||
};
|
||||
check_extruder(0); // TODO: check used extruder variants
|
||||
|
||||
// check wall sequence and precise outer wall
|
||||
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter) {
|
||||
warning->string = L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences.");
|
||||
warning->opt_key = "precise_outer_wall";
|
||||
if (m_default_region_config.precise_outer_wall && m_default_region_config.wall_sequence != WallSequence::InnerOuter)
|
||||
warn(L("The precise wall option will be ignored for outer-inner or inner-outer-inner wall sequences."), "precise_outer_wall");
|
||||
|
||||
// check adaptive pressure advance model
|
||||
for (unsigned int extruder_id : extruders) {
|
||||
if (m_config.adaptive_pressure_advance.get_at(extruder_id) &&
|
||||
m_config.enable_pressure_advance.get_at(extruder_id)) {
|
||||
|
||||
const std::string pa_model = m_config.adaptive_pressure_advance_model.get_at(extruder_id);
|
||||
if (!pa_model.empty()) {
|
||||
std::string validation_error = AdaptivePAProcessor::validate_adaptive_pa_model(pa_model);
|
||||
if (!validation_error.empty()) {
|
||||
warn(L("The Adaptive Pressure Advance model for one or more extruders may contain invalid values."),
|
||||
"adaptive_pressure_advance_model");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Orca: validate motion ability failed: " << e.what() << std::endl;
|
||||
}
|
||||
if (!motion_warning.string.empty())
|
||||
add_warning(motion_warning);
|
||||
}
|
||||
if (!this->has_same_shrinkage_compensations()){
|
||||
warning->string = L("Filament shrinkage will not be used because filament shrinkage for the used filaments does not match.");
|
||||
warning->opt_key = "";
|
||||
}
|
||||
if (!this->has_same_shrinkage_compensations())
|
||||
warn(L("Filament shrinkage will not be used because filament shrinkage for the used filaments does not match."));
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1975,7 +2039,7 @@ Flow Print::brim_flow() const
|
||||
frPerimeter,
|
||||
// Flow::new_from_config_width takes care of the percent to value substitution
|
||||
width,
|
||||
(float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1),
|
||||
(float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().outer_wall_filament_id-1),
|
||||
(float)this->skirt_first_layer_height());
|
||||
}
|
||||
|
||||
@@ -2398,7 +2462,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
start_time = (long long)Slic3r::Utils::get_current_time_utc();
|
||||
|
||||
m_skirt.clear();
|
||||
m_skirt_groups.clear();
|
||||
m_skirt_convex_hull.clear();
|
||||
m_objectBrimAreas.clear();
|
||||
m_supportBrimAreas.clear();
|
||||
m_first_layer_convex_hull.points.clear();
|
||||
for (PrintObject *object : m_objects) object->m_skirt.clear();
|
||||
|
||||
@@ -2492,7 +2559,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
|
||||
if (this->has_brim()) {
|
||||
Polygons islands_area;
|
||||
make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap,
|
||||
m_supportBrimMap, objPrintVec, printExtruders);
|
||||
m_supportBrimMap, objPrintVec, printExtruders, &m_objectBrimAreas, &m_supportBrimAreas);
|
||||
for (Polygon& poly_ex : islands_area)
|
||||
poly_ex.douglas_peucker(SCALED_RESOLUTION);
|
||||
for (Polygon &poly : union_(this->first_layer_islands(), islands_area))
|
||||
@@ -2589,7 +2656,8 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
gcode.export_layer_filaments(result);
|
||||
//BBS
|
||||
result->conflict_result = m_conflict_result;
|
||||
if (result != nullptr)
|
||||
result->conflict_result = m_conflict_result;
|
||||
return path.c_str();
|
||||
}
|
||||
|
||||
@@ -2613,11 +2681,13 @@ void Print::_make_skirt()
|
||||
skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z);
|
||||
}
|
||||
|
||||
// Collect points from all layers contained in skirt height.
|
||||
Points points;
|
||||
struct ObjectSkirtHull {
|
||||
PrintObject* object;
|
||||
Polygon hull;
|
||||
};
|
||||
|
||||
// BBS
|
||||
std::map<PrintObject*, Polygon> object_convex_hulls;
|
||||
// Orca: build one local occupied hull per object from object and support geometry up to skirt height.
|
||||
std::vector<ObjectSkirtHull> object_convex_hulls;
|
||||
for (PrintObject *object : m_objects) {
|
||||
Points object_points;
|
||||
// Get object layers up to skirt_height_z.
|
||||
@@ -2635,30 +2705,13 @@ void Print::_make_skirt()
|
||||
layer->support_fills.collect_points(object_points);
|
||||
}
|
||||
|
||||
object_convex_hulls.insert({ object, Slic3r::Geometry::convex_hull(object_points) });
|
||||
|
||||
// Repeat points for each object copy.
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
Points copy_points = object_points;
|
||||
for (Point &pt : copy_points)
|
||||
pt += instance.shift;
|
||||
append(points, copy_points);
|
||||
}
|
||||
object_convex_hulls.push_back({ object, Slic3r::Geometry::convex_hull(object_points) });
|
||||
}
|
||||
|
||||
// Include the wipe tower.
|
||||
append(points, this->first_layer_wipe_tower_corners());
|
||||
|
||||
// Unless draft shield is enabled, include all brims as well.
|
||||
if (config().draft_shield == dsDisabled)
|
||||
append(points, m_first_layer_convex_hull.points);
|
||||
|
||||
if (points.size() < 3)
|
||||
// At least three points required for a convex hull.
|
||||
if (object_convex_hulls.empty())
|
||||
return;
|
||||
|
||||
this->throw_if_canceled();
|
||||
Polygon convex_hull = Slic3r::Geometry::convex_hull(points);
|
||||
|
||||
// Skirt may be printed on several layers, having distinct layer heights,
|
||||
// but loops must be aligned so can't vary width/spacing
|
||||
@@ -2680,13 +2733,13 @@ void Print::_make_skirt()
|
||||
}
|
||||
}
|
||||
|
||||
// Initial offset of the brim inner edge from the object (possible with a support & raft).
|
||||
// The skirt will touch the brim if the brim is extruded.
|
||||
auto distance = float(scale_(m_config.skirt_distance.value - spacing/2.));
|
||||
// Draw outlines from outside to inside.
|
||||
// Initial skirt centerline offset from the occupied outline.
|
||||
// The skirt will touch the occupied outline if skirt_distance is zero.
|
||||
// Generate loops inward to outward; callers reverse them before G-code export.
|
||||
// Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
|
||||
std::vector<coordf_t> extruded_length(extruders.size(), 0.);
|
||||
if (m_config.skirt_type == stCombined) {
|
||||
auto append_skirt_loops_for_hull = [&](const Polygon& hull, ExtrusionEntityCollection& dst, bool collect_skirt_hull) {
|
||||
float distance = float(scale_(m_config.skirt_distance.value - spacing/2.));
|
||||
std::vector<coordf_t> extruded_length(extruders.size(), 0.);
|
||||
for (size_t i = m_config.skirt_loops, extruder_idx = 0; i > 0; -- i) {
|
||||
this->throw_if_canceled();
|
||||
// Offset the skirt outside.
|
||||
@@ -2694,8 +2747,8 @@ void Print::_make_skirt()
|
||||
// Generate the skirt centerline.
|
||||
Polygon loop;
|
||||
{
|
||||
// BBS. skirt_distance is defined as the gap between skirt and outer most brim, so no need to add max_brim_width
|
||||
Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
// Orca: the hull already represents the occupied outline used for this skirt.
|
||||
Polygons loops = offset(hull, distance, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
Geometry::simplify_polygons(loops, scale_(0.05), &loops);
|
||||
if (loops.empty())
|
||||
break;
|
||||
@@ -2711,7 +2764,7 @@ void Print::_make_skirt()
|
||||
(float)initial_layer_print_height // this will be overridden at G-code export time
|
||||
)));
|
||||
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
|
||||
m_skirt.append(eloop);
|
||||
dst.append(eloop);
|
||||
if (m_config.min_skirt_length.value > 0) {
|
||||
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
|
||||
extruded_length[extruder_idx] += unscale<double>(loop.length()) * extruders_e_per_mm[extruder_idx];
|
||||
@@ -2727,69 +2780,147 @@ void Print::_make_skirt()
|
||||
++ extruder_idx;
|
||||
}
|
||||
} else {
|
||||
// The skirt lenght is not limited, extrude the skirt with the 1st extruder only.
|
||||
// The skirt length is not limited, extrude the skirt with the 1st extruder only.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_skirt.clear();
|
||||
}
|
||||
// Brims were generated inside out, reverse to print the outmost contour first.
|
||||
m_skirt.reverse();
|
||||
|
||||
// Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull.
|
||||
for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1))))
|
||||
append(m_skirt_convex_hull, std::move(poly.points));
|
||||
if (collect_skirt_hull)
|
||||
for (Polygon &poly : offset(hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1))))
|
||||
append(m_skirt_convex_hull, std::move(poly.points));
|
||||
};
|
||||
|
||||
if (m_config.skirt_type == stPerObject) {
|
||||
// BBS
|
||||
for (auto obj_cvx_hull : object_convex_hulls) {
|
||||
double object_skirt_distance = float(scale_(m_config.skirt_distance.value - spacing/2.));
|
||||
PrintObject* object = obj_cvx_hull.first;
|
||||
object->m_skirt.clear();
|
||||
extruded_length.assign(extruded_length.size(), 0.);
|
||||
for (size_t i = m_config.skirt_loops.value, extruder_idx = 0; i > 0; -- i) {
|
||||
object_skirt_distance += float(scale_(spacing));
|
||||
Polygon loop;
|
||||
{
|
||||
// BBS. skirt_distance is defined as the gap between skirt and outer most brim, so no need to add max_brim_width
|
||||
Polygons loops = offset(obj_cvx_hull.second, object_skirt_distance, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
Geometry::simplify_polygons(loops, scale_(0.05), &loops);
|
||||
if (loops.empty())
|
||||
break;
|
||||
loop = loops.front();
|
||||
}
|
||||
m_skirt.clear();
|
||||
m_skirt_groups.clear();
|
||||
|
||||
// Extrude the skirt loop.
|
||||
ExtrusionLoop eloop(elrSkirt);
|
||||
eloop.paths.emplace_back(ExtrusionPath(
|
||||
ExtrusionPath(
|
||||
erSkirt,
|
||||
(float)mm3_per_mm, // this will be overridden at G-code export time
|
||||
flow.width(),
|
||||
(float)initial_layer_print_height // this will be overridden at G-code export time
|
||||
)));
|
||||
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
|
||||
object->m_skirt.append(std::move(eloop));
|
||||
if (m_config.min_skirt_length.value > 0) {
|
||||
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
|
||||
extruded_length[extruder_idx] += unscale<double>(loop.length()) * extruders_e_per_mm[extruder_idx];
|
||||
if (extruded_length[extruder_idx] < m_config.min_skirt_length.value) {
|
||||
// Not extruded enough yet with the current extruder. Add another loop.
|
||||
if (i == 1)
|
||||
++ i;
|
||||
} else {
|
||||
assert(extruded_length[extruder_idx] >= m_config.min_skirt_length.value);
|
||||
// Enough extruded with the current extruder. Extrude with the next one,
|
||||
// until the prescribed number of skirt loops is extruded.
|
||||
if (extruder_idx + 1 < extruders.size())
|
||||
++ extruder_idx;
|
||||
}
|
||||
} else {
|
||||
// The skirt lenght is not limited, extrude the skirt with the 1st extruder only.
|
||||
}
|
||||
if (m_config.skirt_type == stPerObject && m_config.print_sequence == PrintSequence::ByObject) {
|
||||
for (const ObjectSkirtHull& object_hull : object_convex_hulls) {
|
||||
object_hull.object->m_skirt.clear();
|
||||
append_skirt_loops_for_hull(object_hull.hull, object_hull.object->m_skirt, false);
|
||||
object_hull.object->m_skirt.reverse();
|
||||
}
|
||||
} else if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) {
|
||||
struct SkirtGroupItem {
|
||||
Points occupied_points;
|
||||
bool emits_skirt;
|
||||
};
|
||||
|
||||
// Orca: group items represent occupied first-layer areas. Object items emit skirts;
|
||||
// obstacle-only items, such as wipe tower, only force nearby object groups to merge.
|
||||
std::vector<SkirtGroupItem> group_items;
|
||||
const coord_t grouping_offset = scale_(m_config.skirt_distance.value + m_config.skirt_loops.value * spacing);
|
||||
for (const ObjectSkirtHull& object_hull : object_convex_hulls) {
|
||||
PrintObject* object = object_hull.object;
|
||||
Points occupied_points;
|
||||
for (const PrintInstance &instance : object->instances()) {
|
||||
Points copy_points = object_hull.hull.points;
|
||||
for (Point &pt : copy_points)
|
||||
pt += instance.shift;
|
||||
append(occupied_points, copy_points);
|
||||
}
|
||||
|
||||
auto append_brim_points = [&occupied_points](const ExPolygons& areas) {
|
||||
for (const ExPolygon& area : areas)
|
||||
append(occupied_points, area.contour.points);
|
||||
};
|
||||
if (auto it = m_objectBrimAreas.find(object->id()); it != m_objectBrimAreas.end())
|
||||
append_brim_points(it->second);
|
||||
if (auto it = m_supportBrimAreas.find(object->id()); it != m_supportBrimAreas.end())
|
||||
append_brim_points(it->second);
|
||||
if (occupied_points.size() < 3)
|
||||
continue;
|
||||
|
||||
// Orca: include the object's brim/support-brim footprint before checking skirt collisions.
|
||||
group_items.push_back({ std::move(occupied_points), true });
|
||||
}
|
||||
|
||||
// Orca: the wipe tower contributes occupied area, but does not emit a skirt by itself.
|
||||
Points wipe_tower_points = this->first_layer_wipe_tower_corners();
|
||||
if (wipe_tower_points.size() >= 3)
|
||||
group_items.push_back({ std::move(wipe_tower_points), false });
|
||||
|
||||
std::vector<size_t> parent(group_items.size());
|
||||
std::iota(parent.begin(), parent.end(), 0);
|
||||
// Orca: union-find keeps collision merging local without repeatedly rebuilding item lists.
|
||||
auto find_parent = [&parent](size_t idx) {
|
||||
while (parent[idx] != idx) {
|
||||
parent[idx] = parent[parent[idx]];
|
||||
idx = parent[idx];
|
||||
}
|
||||
return idx;
|
||||
};
|
||||
auto unite = [&parent, &find_parent](size_t a, size_t b) {
|
||||
a = find_parent(a);
|
||||
b = find_parent(b);
|
||||
if (a != b)
|
||||
parent[b] = a;
|
||||
};
|
||||
|
||||
// Orca: combined skirt is the same grouping model with all items forced into one group.
|
||||
if (m_config.skirt_type == stCombined && !group_items.empty())
|
||||
for (size_t i = 1; i < group_items.size(); ++i)
|
||||
unite(0, i);
|
||||
|
||||
auto build_grouped_points = [&]() {
|
||||
struct GroupData {
|
||||
Points points;
|
||||
bool emits_skirt = false;
|
||||
};
|
||||
|
||||
std::map<size_t, GroupData> grouped;
|
||||
for (size_t i = 0; i < group_items.size(); ++i) {
|
||||
GroupData& group = grouped[find_parent(i)];
|
||||
append(group.points, group_items[i].occupied_points);
|
||||
group.emits_skirt = group.emits_skirt || group_items[i].emits_skirt;
|
||||
}
|
||||
return grouped;
|
||||
};
|
||||
|
||||
bool groups_changed = m_config.skirt_type == stPerObject;
|
||||
while (groups_changed) {
|
||||
groups_changed = false;
|
||||
auto grouped_points = build_grouped_points();
|
||||
std::vector<std::pair<size_t, Polygon>> group_envelopes;
|
||||
for (const auto& [root, group] : grouped_points) {
|
||||
if (group.points.size() < 3)
|
||||
continue;
|
||||
|
||||
// Orca: emitting groups are expanded to their final skirt reach; obstacle groups are not.
|
||||
Polygon envelope = Geometry::convex_hull(group.points);
|
||||
if (group.emits_skirt) {
|
||||
// Orca: merge groups when a skirt envelope intersects another group or obstacle.
|
||||
Polygons envelopes = offset(envelope, grouping_offset, ClipperLib::jtRound, float(scale_(0.1)));
|
||||
if (envelopes.empty())
|
||||
continue;
|
||||
envelope = std::move(envelopes.front());
|
||||
}
|
||||
group_envelopes.emplace_back(root, std::move(envelope));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < group_envelopes.size(); ++i) {
|
||||
for (size_t j = i + 1; j < group_envelopes.size(); ++j) {
|
||||
const size_t root_i = find_parent(group_envelopes[i].first);
|
||||
const size_t root_j = find_parent(group_envelopes[j].first);
|
||||
if (root_i != root_j && !intersection(group_envelopes[i].second, group_envelopes[j].second).empty()) {
|
||||
unite(root_i, root_j);
|
||||
groups_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto grouped_points = build_grouped_points();
|
||||
for (auto& [_, group] : grouped_points) {
|
||||
if (!group.emits_skirt || group.points.size() < 3)
|
||||
continue;
|
||||
// Orca: after merging, use the occupied outline directly; do not add skirt distance twice.
|
||||
ExtrusionEntityCollection group_skirt;
|
||||
append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true);
|
||||
if (!group_skirt.empty()) {
|
||||
group_skirt.reverse();
|
||||
// Orca: keep m_skirt as a flattened compatibility mirror for preview/extents.
|
||||
m_skirt.append(group_skirt.entities);
|
||||
m_skirt_groups.push_back(std::move(group_skirt));
|
||||
}
|
||||
object->m_skirt.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3502,6 +3633,24 @@ std::string Print::output_filename(const std::string &filename_base) const
|
||||
config.set_key_value("plate_number", new ConfigOptionString(get_plate_number_formatted()));
|
||||
config.set_key_value("model_name", new ConfigOptionString(get_model_name()));
|
||||
|
||||
// the same type of filament contains multiple names, support exporting according to the filament name
|
||||
auto full_print_config = this->full_print_config();
|
||||
const ConfigOptionStrings* filament_settings_id = full_print_config.option<ConfigOptionStrings>("filament_settings_id");
|
||||
std::string filament_name = "";
|
||||
auto extruders = this->extruders(true);
|
||||
if(!extruders.empty()) {
|
||||
// first extruder is the default extruder
|
||||
int extruder_id = extruders.front();
|
||||
if(filament_settings_id->values.size() > extruder_id) {
|
||||
filament_name = filament_settings_id->values[extruder_id];
|
||||
}
|
||||
}
|
||||
size_t end_pos = filament_name.find_first_of("@");
|
||||
if (end_pos != std::string::npos) {
|
||||
filament_name = filament_name.substr(0, end_pos);
|
||||
}
|
||||
config.set_key_value("filament_name", new ConfigOptionString(filament_name));
|
||||
|
||||
return this->PrintBase::output_filename(m_config.filename_format.value, ".gcode", filename_base, &config);
|
||||
}
|
||||
|
||||
@@ -3594,9 +3743,12 @@ DynamicConfig PrintStatistics::config() const
|
||||
config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost));
|
||||
config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges));
|
||||
config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight));
|
||||
config.set_key_value("extruded_weight_total", new ConfigOptionFloat(this->total_weight));
|
||||
config.set_key_value("extruded_volume_total", new ConfigOptionFloat(this->total_extruded_volume));
|
||||
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost));
|
||||
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament));
|
||||
config.set_key_value("initial_tool", new ConfigOptionInt(static_cast<int>(this->initial_tool)));
|
||||
config.set_key_value("initial_extruder", new ConfigOptionInt(static_cast<int>(this->initial_tool)));
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -3605,8 +3757,8 @@ DynamicConfig PrintStatistics::placeholders()
|
||||
DynamicConfig config;
|
||||
for (const std::string key : {
|
||||
"print_time", "normal_print_time", "silent_print_time",
|
||||
"used_filament", "extruded_volume", "total_cost", "total_weight",
|
||||
"initial_tool", "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"})
|
||||
"used_filament", "extruded_volume", "extruded_volume_total", "total_cost", "total_weight", "extruded_weight_total",
|
||||
"initial_tool", "initial_extruder", "total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"})
|
||||
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -932,7 +932,7 @@ public:
|
||||
}
|
||||
|
||||
// Returns an empty string if valid, otherwise returns an error message.
|
||||
StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
|
||||
StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
|
||||
double skirt_first_layer_height() const;
|
||||
Flow brim_flow() const;
|
||||
Flow skirt_flow() const;
|
||||
@@ -971,6 +971,7 @@ public:
|
||||
PrintRegionPtrs& print_regions_mutable() { return m_print_regions; }
|
||||
std::vector<size_t> layers_sorted_for_object(float start, float end, std::vector<LayerPtrs> &layers_of_objects, std::vector<BoundingBox> &boundingBox_for_objects, VecOfPoints& objects_instances_shift);
|
||||
const ExtrusionEntityCollection& skirt() const { return m_skirt; }
|
||||
const std::vector<ExtrusionEntityCollection>& skirt_groups() const { return m_skirt_groups; }
|
||||
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
// It does NOT encompass user extrusions generated by custom G-code,
|
||||
@@ -1144,9 +1145,13 @@ private:
|
||||
|
||||
// Ordered collections of extrusion paths to build skirt loops and brim.
|
||||
ExtrusionEntityCollection m_skirt;
|
||||
std::vector<ExtrusionEntityCollection> m_skirt_groups;
|
||||
// BBS: collecting extrusion paths to build brim by objs
|
||||
std::map<ObjectID, ExtrusionEntityCollection> m_brimMap;
|
||||
std::map<ObjectID, ExtrusionEntityCollection> m_supportBrimMap;
|
||||
// Orca: cached occupied brim footprints used when grouping per-object skirts.
|
||||
std::map<ObjectID, ExPolygons> m_objectBrimAreas;
|
||||
std::map<ObjectID, ExPolygons> m_supportBrimAreas;
|
||||
// Convex hull of the 1st layer extrusions.
|
||||
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
|
||||
// It does NOT encompass user extrusions generated by custom G-code,
|
||||
|
||||
@@ -815,9 +815,12 @@ bool verify_update_print_object_regions(
|
||||
for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) {
|
||||
const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent];
|
||||
PrintRegionConfig cfg = parent_region.region->config();
|
||||
cfg.wall_filament.value = region.extruder_id;
|
||||
cfg.solid_infill_filament.value = region.extruder_id;
|
||||
cfg.sparse_infill_filament.value = region.extruder_id;
|
||||
cfg.outer_wall_filament_id.value = region.extruder_id;
|
||||
cfg.inner_wall_filament_id.value = region.extruder_id;
|
||||
cfg.internal_solid_filament_id.value = region.extruder_id;
|
||||
cfg.top_surface_filament_id.value = region.extruder_id;
|
||||
cfg.bottom_surface_filament_id.value = region.extruder_id;
|
||||
cfg.sparse_infill_filament_id.value = region.extruder_id;
|
||||
if (cfg != region.region->config()) {
|
||||
// Region configuration changed.
|
||||
if (print_region_ref_cnt(*region.region) == 0) {
|
||||
@@ -1060,9 +1063,12 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
|
||||
parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
|
||||
PrintRegionConfig cfg = parent_region.region->config();
|
||||
cfg.wall_filament.value = painted_extruder_id;
|
||||
cfg.solid_infill_filament.value = painted_extruder_id;
|
||||
cfg.sparse_infill_filament.value = painted_extruder_id;
|
||||
cfg.outer_wall_filament_id.value = painted_extruder_id;
|
||||
cfg.inner_wall_filament_id.value = painted_extruder_id;
|
||||
cfg.internal_solid_filament_id.value = painted_extruder_id;
|
||||
cfg.top_surface_filament_id.value = painted_extruder_id;
|
||||
cfg.bottom_surface_filament_id.value = painted_extruder_id;
|
||||
cfg.sparse_infill_filament_id.value = painted_extruder_id;
|
||||
layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))});
|
||||
}
|
||||
// Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MM segmentation.
|
||||
@@ -1254,6 +1260,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: found full_config_diff changed.")%__LINE__;
|
||||
update_apply_status(this->invalidate_step(psGCodeExport));
|
||||
m_placeholder_parser.clear_config();
|
||||
// clear_config() wiped the constructor-set "version"; restore it for custom G-code.
|
||||
m_placeholder_parser.set("version", std::string(SoftFever_VERSION));
|
||||
// Set the profile aliases for the PrintBase::output_filename()
|
||||
m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
|
||||
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
|
||||
@@ -1630,6 +1638,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: full_config_diff previous empty, need to apply now.")%__LINE__;
|
||||
|
||||
m_placeholder_parser.clear_config();
|
||||
// clear_config() wiped the constructor-set "version"; restore it for custom G-code.
|
||||
m_placeholder_parser.set("version", std::string(SoftFever_VERSION));
|
||||
// Set the profile aliases for the PrintBase::output_filename()
|
||||
m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone());
|
||||
m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone());
|
||||
|
||||
@@ -83,7 +83,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str
|
||||
filename.replace_extension(default_ext);
|
||||
return filename.string();
|
||||
} catch (std::runtime_error &err) {
|
||||
throw Slic3r::PlaceholderParserError(L("Failed processing of the filename_format template.") + "\n" + err.what());
|
||||
throw Slic3r::PlaceholderParserError(L("Processing of the filename_format template failed.") + "\n" + err.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ struct StringObjectException
|
||||
std::string string;
|
||||
ObjectBase const *object = nullptr;
|
||||
std::string opt_key;
|
||||
StringExceptionType type; // warning type for tips
|
||||
StringExceptionType type = STRING_EXCEPT_NOT_DEFINED; // warning type for tips
|
||||
bool is_warning = false;
|
||||
std::vector<std::string> params; // warning params for tips
|
||||
};
|
||||
@@ -396,7 +396,7 @@ public:
|
||||
|
||||
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
|
||||
//BBS: add more paremeters to validate
|
||||
virtual StringObjectException validate(StringObjectException *warning = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const { return {}; }
|
||||
virtual StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const { return {}; }
|
||||
|
||||
enum ApplyStatus {
|
||||
// No change after the Print::apply() call.
|
||||
|
||||
+487
-336
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,7 @@ enum class WipeTowerType {
|
||||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint, htElegooLink
|
||||
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htESP3D, htCrealityPrint, htObico, htFlashforge, htSimplyPrint, htElegooLink, ht3DPrinterOS, htMoonraker
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
@@ -1091,7 +1091,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, bottom_shell_thickness))
|
||||
((ConfigOptionFloat, bridge_angle))
|
||||
((ConfigOptionFloat, internal_bridge_angle)) // ORCA: Internal bridge angle override
|
||||
((ConfigOptionBool, relative_bridge_angle)) // ORCA: Relative bridge angle flag
|
||||
((ConfigOptionFloat, bridge_flow))
|
||||
((ConfigOptionFloatOrPercent, bridge_line_width))
|
||||
((ConfigOptionFloat, internal_bridge_flow))
|
||||
((ConfigOptionFloatsNullable, bridge_speed))
|
||||
((ConfigOptionFloatsOrPercentsNullable, internal_bridge_speed))
|
||||
@@ -1114,6 +1116,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, lateral_lattice_angle_1))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_2))
|
||||
((ConfigOptionFloat, infill_overhang_angle))
|
||||
((ConfigOptionFloat, lightning_overhang_angle))
|
||||
((ConfigOptionFloat, lightning_prune_angle))
|
||||
((ConfigOptionFloat, lightning_straightening_angle))
|
||||
((ConfigOptionBool, align_infill_direction_to_model))
|
||||
((ConfigOptionString, extra_solid_infills))
|
||||
((ConfigOptionEnum<FuzzySkinType>, fuzzy_skin))
|
||||
@@ -1129,7 +1134,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionPercent, fuzzy_skin_ripple_offset))
|
||||
((ConfigOptionInt, fuzzy_skin_layers_between_ripple_offset))
|
||||
((ConfigOptionFloatsNullable, gap_infill_speed))
|
||||
((ConfigOptionInt, sparse_infill_filament))
|
||||
((ConfigOptionInt, sparse_infill_filament_id))
|
||||
((ConfigOptionFloatOrPercent, sparse_infill_line_width))
|
||||
((ConfigOptionPercent, infill_wall_overlap))
|
||||
((ConfigOptionPercent, top_bottom_infill_wall_overlap))
|
||||
@@ -1162,14 +1167,17 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloatsNullable, filament_ironing_speed))
|
||||
// Detect bridging perimeters
|
||||
((ConfigOptionBool, detect_overhang_wall))
|
||||
((ConfigOptionInt, wall_filament))
|
||||
((ConfigOptionInt, outer_wall_filament_id))
|
||||
((ConfigOptionInt, inner_wall_filament_id))
|
||||
((ConfigOptionFloatOrPercent, inner_wall_line_width))
|
||||
((ConfigOptionFloatsNullable, inner_wall_speed))
|
||||
// Total number of perimeters.
|
||||
((ConfigOptionInt, wall_loops))
|
||||
((ConfigOptionBool, alternate_extra_wall))
|
||||
((ConfigOptionFloat, minimum_sparse_infill_area))
|
||||
((ConfigOptionInt, solid_infill_filament))
|
||||
((ConfigOptionInt, internal_solid_filament_id))
|
||||
((ConfigOptionInt, top_surface_filament_id))
|
||||
((ConfigOptionInt, bottom_surface_filament_id))
|
||||
((ConfigOptionFloatOrPercent, internal_solid_infill_line_width))
|
||||
((ConfigOptionFloatsNullable, internal_solid_infill_speed))
|
||||
// Detect thin walls.
|
||||
@@ -1470,6 +1478,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, enable_filament_ramming))
|
||||
((ConfigOptionBool, tool_change_on_wipe_tower))
|
||||
((ConfigOptionBool, support_multi_bed_types))
|
||||
((ConfigOptionBool, use_3mf))
|
||||
|
||||
// Small Area Infill Flow Compensation
|
||||
((ConfigOptionStrings, small_area_infill_flow_compensation_model))
|
||||
@@ -1491,6 +1500,9 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionFloatOrPercent, max_travel_detour_distance))
|
||||
((ConfigOptionPoints, printable_area))
|
||||
((ConfigOptionPointsGroups, extruder_printable_area))
|
||||
((ConfigOptionBool, support_parallel_printheads))
|
||||
((ConfigOptionInt, parallel_printheads_count))
|
||||
((ConfigOptionStrings, parallel_printheads_bed_exclude_areas))
|
||||
//BBS: add bed_exclude_area
|
||||
((ConfigOptionPoints, bed_exclude_area))
|
||||
((ConfigOptionPoints, head_wrap_detect_zone))
|
||||
|
||||
+368
-116
@@ -232,7 +232,7 @@ void PrintObject::_transform_hole_to_polyholes()
|
||||
bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.value;
|
||||
if (diameter_max - diameter_min < max_variation * 2 && diameter_line_max - diameter_line_min < max_variation * 2) {
|
||||
layerid2center[layer_idx].emplace_back(
|
||||
std::tuple<Point, float, int, coord_t, bool>{center, diameter_max, layer->m_regions[region_idx]->region().config().wall_filament.value, max_variation, twist}, & hole);
|
||||
std::tuple<Point, float, int, coord_t, bool>{center, diameter_max, layer->m_regions[region_idx]->region().config().outer_wall_filament_id.value, max_variation, twist}, & hole);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,18 +316,29 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
continue;
|
||||
for (auto layerm : layer->regions()) {
|
||||
auto region = layerm->region();
|
||||
int wall_filament = region.config().wall_filament;
|
||||
int solid_infill_filament = region.config().solid_infill_filament;
|
||||
int sparse_infill_filament = region.config().sparse_infill_filament;
|
||||
int outer_wall_filament_id = region.config().outer_wall_filament_id;
|
||||
int inner_wall_filament_id = region.config().inner_wall_filament_id;
|
||||
int internal_solid_filament_id = region.config().internal_solid_filament_id;
|
||||
int top_surface_filament_id = region.config().top_surface_filament_id;
|
||||
int bottom_surface_filament_id = region.config().bottom_surface_filament_id;
|
||||
int sparse_infill_filament_id = region.config().sparse_infill_filament_id;
|
||||
|
||||
if (!layerm->fills.entities.empty()) {
|
||||
if (solid_infill_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(solid_infill_filament - 1);
|
||||
if (sparse_infill_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(sparse_infill_filament - 1);
|
||||
if (internal_solid_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(internal_solid_filament_id - 1);
|
||||
if (top_surface_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(top_surface_filament_id - 1);
|
||||
if (bottom_surface_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(bottom_surface_filament_id - 1);
|
||||
if (sparse_infill_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(sparse_infill_filament_id - 1);
|
||||
}
|
||||
if (!layerm->perimeters.entities.empty()) {
|
||||
if (outer_wall_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
geometric_unprintables[extruder_id].insert(inner_wall_filament_id - 1);
|
||||
}
|
||||
if (!layerm->perimeters.entities.empty() && wall_filament > 0)
|
||||
geometric_unprintables[extruder_id].insert(wall_filament - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,21 +363,28 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
auto layer = m_layers[j];
|
||||
for (auto layerm : layer->regions()) {
|
||||
const auto& region = layerm->region();
|
||||
int wall_filament = region.config().wall_filament;
|
||||
int solid_infill_filament = region.config().solid_infill_filament;
|
||||
int sparse_infill_filament = region.config().sparse_infill_filament;
|
||||
int outer_wall_filament_id = region.config().outer_wall_filament_id;
|
||||
int inner_wall_filament_id = region.config().inner_wall_filament_id;
|
||||
int internal_solid_filament_id = region.config().internal_solid_filament_id;
|
||||
int top_surface_filament_id = region.config().top_surface_filament_id;
|
||||
int bottom_surface_filament_id = region.config().bottom_surface_filament_id;
|
||||
int sparse_infill_filament_id = region.config().sparse_infill_filament_id;
|
||||
std::optional<ExPolygons> fill_expolys;
|
||||
BoundingBox fill_bbox;
|
||||
std::optional<ExPolygons> wall_expolys;
|
||||
BoundingBox wall_bbox;
|
||||
|
||||
for (size_t idx = 0; idx < unprintable_area_in_obj_coord.size(); ++idx) {
|
||||
bool do_infill_filament_detect = (solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) == 0) ||
|
||||
(sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament-1) == 0);
|
||||
bool do_infill_filament_detect = (internal_solid_filament_id > 0 && tbb_geometric_unprintables[idx].count(internal_solid_filament_id - 1) == 0) ||
|
||||
(top_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(top_surface_filament_id - 1) == 0) ||
|
||||
(bottom_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(bottom_surface_filament_id - 1) == 0) ||
|
||||
(sparse_infill_filament_id > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament_id-1) == 0);
|
||||
|
||||
bool infill_unprintable = !layerm->fills.entities.empty() &&
|
||||
((solid_infill_filament > 0 && tbb_geometric_unprintables[idx].count(solid_infill_filament - 1) > 0) ||
|
||||
(sparse_infill_filament > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament - 1) > 0));
|
||||
((internal_solid_filament_id > 0 && tbb_geometric_unprintables[idx].count(internal_solid_filament_id - 1) > 0) ||
|
||||
(top_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(top_surface_filament_id - 1) > 0) ||
|
||||
(bottom_surface_filament_id > 0 && tbb_geometric_unprintables[idx].count(bottom_surface_filament_id - 1) > 0) ||
|
||||
(sparse_infill_filament_id > 0 && tbb_geometric_unprintables[idx].count(sparse_infill_filament_id - 1) > 0));
|
||||
|
||||
if (!layerm->fills.entities.empty() && do_infill_filament_detect) {
|
||||
if (!fill_expolys) {
|
||||
@@ -375,19 +393,27 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
}
|
||||
if (fill_bbox.overlap(unprintable_area_bbox[idx]) &&
|
||||
!intersection(*fill_expolys, unprintable_area_in_obj_coord[idx]).empty()) {
|
||||
if (solid_infill_filament > 0)
|
||||
tbb_geometric_unprintables[idx].insert(solid_infill_filament - 1);
|
||||
if (sparse_infill_filament > 0)
|
||||
tbb_geometric_unprintables[idx].insert(sparse_infill_filament - 1);
|
||||
if (internal_solid_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(internal_solid_filament_id - 1);
|
||||
if (top_surface_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(top_surface_filament_id - 1);
|
||||
if (bottom_surface_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(bottom_surface_filament_id - 1);
|
||||
if (sparse_infill_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(sparse_infill_filament_id - 1);
|
||||
infill_unprintable = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool do_wall_filament_detect = wall_filament > 0 && tbb_geometric_unprintables[idx].count(wall_filament - 1) == 0;
|
||||
if (!layerm->perimeters.entities.empty() && do_wall_filament_detect) {
|
||||
bool do_outer_wall_filament_detect = outer_wall_filament_id > 0 && tbb_geometric_unprintables[idx].count(outer_wall_filament_id - 1) == 0;
|
||||
bool do_inner_wall_filament_detect = inner_wall_filament_id > 0 && tbb_geometric_unprintables[idx].count(inner_wall_filament_id - 1) == 0;
|
||||
if (!layerm->perimeters.entities.empty() && (do_outer_wall_filament_detect || do_inner_wall_filament_detect)) {
|
||||
// if infill is unprintable, no need to check wall since wall contour surrounds infill contour
|
||||
if (infill_unprintable) {
|
||||
tbb_geometric_unprintables[idx].insert(wall_filament - 1);
|
||||
if (outer_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(inner_wall_filament_id - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -402,7 +428,10 @@ std::vector<std::set<int>> PrintObject::detect_extruder_geometric_unprintables()
|
||||
|
||||
if (wall_bbox.overlap(unprintable_area_bbox[idx]) &&
|
||||
!intersection(*wall_expolys, unprintable_area_in_obj_coord[idx]).empty()) {
|
||||
tbb_geometric_unprintables[idx].insert(wall_filament - 1);
|
||||
if (outer_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(outer_wall_filament_id - 1);
|
||||
if (inner_wall_filament_id > 0)
|
||||
tbb_geometric_unprintables[idx].insert(inner_wall_filament_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1265,8 +1294,10 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
|| opt_key == "sparse_infill_filament"
|
||||
|| opt_key == "solid_infill_filament"
|
||||
|| opt_key == "sparse_infill_filament_id"
|
||||
|| opt_key == "internal_solid_filament_id"
|
||||
|| opt_key == "top_surface_filament_id"
|
||||
|| opt_key == "bottom_surface_filament_id"
|
||||
|| opt_key == "sparse_infill_line_width"
|
||||
|| opt_key == "skin_infill_line_width"
|
||||
|| opt_key == "skeleton_infill_line_width"
|
||||
@@ -1277,7 +1308,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "ensure_vertical_shell_thickness"
|
||||
|| opt_key == "bridge_angle"
|
||||
|| opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override
|
||||
|| opt_key == "relative_bridge_angle" // ORCA: Relative bridge angle
|
||||
//BBS
|
||||
|| opt_key == "bridge_line_width"
|
||||
|| opt_key == "bridge_density"
|
||||
|| opt_key == "internal_bridge_density") {
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
@@ -1302,6 +1335,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_shift_step"
|
||||
|| opt_key == "sparse_infill_rotate_template"
|
||||
|| opt_key == "solid_infill_rotate_template"
|
||||
|| opt_key == "lightning_overhang_angle"
|
||||
|| opt_key == "lightning_prune_angle"
|
||||
|| opt_key == "lightning_straightening_angle"
|
||||
|| opt_key == "skeleton_infill_density"
|
||||
|| opt_key == "skin_infill_density"
|
||||
|| opt_key == "infill_lock_depth"
|
||||
@@ -1324,7 +1360,8 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (
|
||||
opt_key == "outer_wall_line_width"
|
||||
|| opt_key == "wall_filament"
|
||||
|| opt_key == "outer_wall_filament_id"
|
||||
|| opt_key == "inner_wall_filament_id"
|
||||
|| opt_key == "fuzzy_skin"
|
||||
|| opt_key == "fuzzy_skin_thickness"
|
||||
|| opt_key == "fuzzy_skin_point_distance"
|
||||
@@ -1705,30 +1742,46 @@ void PrintObject::detect_surfaces_type()
|
||||
// Only iterate to the second-to-last layer, since we look at layer i+1.
|
||||
if( (this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)){
|
||||
const size_t last = (m_layers.empty() ? 0 : m_layers.size() - 1);
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, region_id](const tbb::blocked_range<size_t> &range) {
|
||||
|
||||
// ORCA: Two-phase split (collect-then-apply) to eliminate a data race in the
|
||||
// original single-phase parallel_for, where iteration `i` rewrote
|
||||
// m_layers[i+1]->slices.surfaces via std::move while iteration `i+1` (running
|
||||
// on an adjacent TBB block on another worker thread) was iterating that same
|
||||
// Surfaces vector as its bot_surfs. Splitting into a read-only collect pass
|
||||
// followed by a write-only apply pass removes the cross-iteration aliasing.
|
||||
//
|
||||
// Phase 1: read-only pass — collect each layer's stBottomBridge polygons into a
|
||||
// per-layer cache. No surfaces are mutated, so concurrent reads are safe.
|
||||
std::vector<Polygons> bridge_polys_per_layer(last);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, last), [this, region_id, &bridge_polys_per_layer](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
// Step 1: Find bridge polygons
|
||||
// Current layer (i): Search for stBottomBridge polygons.
|
||||
const Surfaces &bot_surfs = m_layers[i]->m_regions[region_id]->slices.surfaces;
|
||||
// Next layer (i+1): The layer where stInternal polygons may be re-classified.
|
||||
Surfaces &top_surfs = m_layers[i + 1]->m_regions[region_id]->slices.surfaces;
|
||||
|
||||
// Step 2: Collect the bridge polygons in the current layer region
|
||||
Polygons polygons_bridge;
|
||||
for (const Surface &sbot : bot_surfs) {
|
||||
if (sbot.surface_type == stBottomBridge) {
|
||||
polygons_append(polygons_bridge, to_polygons(sbot));
|
||||
polygons_append(bridge_polys_per_layer[i], to_polygons(sbot));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 2: write pass — each iteration mutates only m_layers[i+1]->slices.surfaces
|
||||
// and reads its bridge polygons from the precomputed cache. Different iterations
|
||||
// never share a write target, so there is no aliasing between worker threads.
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, region_id, &bridge_polys_per_layer](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
// Step 1 + 2: pull the precomputed bridge polygons for the current source layer.
|
||||
const Polygons &polygons_bridge = bridge_polys_per_layer[i];
|
||||
|
||||
// Step 3: Early termination of loop if no meaningfull bridge found
|
||||
// No bridge polygons found, continue to the next layer
|
||||
if (polygons_bridge.empty())
|
||||
continue;
|
||||
|
||||
// Step 4: Bottom bridge polygons found - scan and create layer+1 bridge polygon
|
||||
Surfaces &top_surfs = m_layers[i + 1]->m_regions[region_id]->slices.surfaces;
|
||||
Surfaces new_surfaces;
|
||||
new_surfaces.reserve(top_surfs.size());
|
||||
|
||||
@@ -1742,7 +1795,50 @@ void PrintObject::detect_surfaces_type()
|
||||
// This would also skip generation of very short dual bridge layers (that are shorter than N perimeters), but these are unecessary as the bridge distance is
|
||||
// We could reduce this slightly to account for innacurcies in the clipping operation.
|
||||
// TODO: Monitor GitHub issues to check whether second bridge layers are ommited where they should be generated. If yes, reduce the filtering distance
|
||||
|
||||
|
||||
// ORCA: Same-layer-top guard.
|
||||
//
|
||||
// Collect every stTop polygon present at layer i+1 (this region) and
|
||||
// expand it by the same `offset_distance` used by the bridge filter
|
||||
// above. Note that `offset_distance` here is the full wall-band
|
||||
// distance for the region (external wall width + all configured
|
||||
// internal wall widths, i.e. external + (wall_loops - 1) × internal),
|
||||
// not a single perimeter width. Any candidate second-bridge area that
|
||||
// falls under this expanded mask will be subtracted out below.
|
||||
//
|
||||
// Why this exists: detect_surfaces_type() classifies a layer's "top"
|
||||
// surfaces as the geometry that is not covered by the layer above. Those
|
||||
// stTop regions often have small stInternal islands embedded in them.
|
||||
// The pre-existing wall-band filter (shrink_ex/offset_ex by
|
||||
// offset_distance) is supposed to throw those tiny islands away, but
|
||||
// its result is sensitive to Clipper's floating-point order of
|
||||
// operations: on macOS ARM the filter eats them, on Windows/Intel it
|
||||
// doesn't. Visible bridges then show up scattered across the printed
|
||||
// top surface.
|
||||
//
|
||||
// Expanding stTop by offset_distance and subtracting it from the
|
||||
// overlap makes the decision platform-independent: an island fully
|
||||
// surrounded by stTop disappears regardless of which Clipper happens
|
||||
// to be doing the math, while large stInternal regions away from the
|
||||
// top survive intact (the expansion only nibbles the wall-band depth
|
||||
// inward).
|
||||
//
|
||||
// Keep ExPolygons throughout so that any holes inside an stTop surface
|
||||
// are offset with the correct sign (positive offset shrinks holes /
|
||||
// grows the solid region). Using Polygons + expand() would treat the
|
||||
// contour and each hole as independent polygons and could distort the
|
||||
// mask.
|
||||
ExPolygons same_layer_top_expanded;
|
||||
{
|
||||
ExPolygons same_layer_top;
|
||||
for (const Surface &s : top_surfs) {
|
||||
if (s.surface_type == stTop)
|
||||
same_layer_top.push_back(s.expolygon);
|
||||
}
|
||||
if (! same_layer_top.empty())
|
||||
same_layer_top_expanded = offset_ex(same_layer_top, offset_distance);
|
||||
}
|
||||
|
||||
// For each surface in the layer above
|
||||
for (Surface &s_up : top_surfs) {
|
||||
// Only reclassify stInternal polygons (i.e. what will become later solid and sparse infill)
|
||||
@@ -1757,7 +1853,13 @@ void PrintObject::detect_surfaces_type()
|
||||
// Filter out the resulting candidate bridges based on size. First perform a shrink operation...
|
||||
// ...followed by an expand operation to bring them back to the original size (positive offset)
|
||||
overlap = offset_ex(shrink_ex(overlap, offset_distance), offset_distance);
|
||||
|
||||
|
||||
// ORCA: subtract the expanded same-layer stTop mask (see comment above
|
||||
// the mask construction). Drops stInternal islands fully surrounded by
|
||||
// stTop at i+1 without affecting bridges that lie away from the top.
|
||||
if (! same_layer_top_expanded.empty() && ! overlap.empty())
|
||||
overlap = diff_ex(overlap, same_layer_top_expanded, ApplySafetyOffset::Yes);
|
||||
|
||||
// Now subtract the filtered new bridge layer from the remaining internal surfaces to create the new internal surface
|
||||
ExPolygons remainder = diff_ex(p_up, overlap, ApplySafetyOffset::Yes);
|
||||
|
||||
@@ -2502,29 +2604,76 @@ void PrintObject::bridge_over_infill()
|
||||
backup_surfaces[lidx] = {};
|
||||
}
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
|
||||
&surfaces_by_layer](tbb::blocked_range<size_t> r) {
|
||||
// ORCA: Two-phase split (collect-then-apply) to eliminate a data race in
|
||||
// the original single-phase parallel_for, where iteration `lidx` read
|
||||
// m_layers[lidx-1]->regions()->fill_surfaces (its lower_layer) to compute
|
||||
// `lightning_fill`, while iteration `lidx-1`, on an adjacent TBB block,
|
||||
// was concurrently std::move-ing / emplace_back-ing into that same
|
||||
// SurfaceCollection.
|
||||
//
|
||||
// Semantic choice — read ORIGINAL surfaces in Phase 1:
|
||||
// The lower_layer that iteration `lidx` looks at is the *current* layer
|
||||
// for iteration `lidx-1`, which Phase 2 will modify. We therefore have to
|
||||
// pick whether Phase 1 sees that layer's pre-modification or
|
||||
// post-modification state. We deliberately use the original (pre-modification)
|
||||
// state, for two reasons:
|
||||
// 1. The gate is asking "does the layer below use lightning sparse
|
||||
// infill?" — that's a property of the layer's configuration plus its
|
||||
// original sparse-infill classification. Phase 2's edits only carve
|
||||
// a small overhang-aligned slice of sparse into solid; they do not
|
||||
// change whether the layer is using lightning. The realistic gate
|
||||
// answer is the same either way.
|
||||
// 2. Each layer's solid expansion is meant to give its OWN lower_layer
|
||||
// something to anchor lightning lines onto. Cascading the gate
|
||||
// across layers ("skip mine because the layer below already did
|
||||
// some") would invert that intent and force a serial Phase 2.
|
||||
// The original racy code didn't actually implement either choice
|
||||
// consistently — it returned whichever bytes happened to be in the
|
||||
// vector when the thread arrived. This split makes the behaviour
|
||||
// defined, deterministic across runs and platforms, and equivalent to
|
||||
// a clean sequential implementation that gathered all gates first and
|
||||
// then applied modifications.
|
||||
//
|
||||
// Phase 1: read-only — for each layer, determine whether its lower_layer
|
||||
// has any stInternal area inside a lightning-infill region. That's the
|
||||
// sole purpose of `lightning_fill` in the original code: a gate. Capture
|
||||
// it once into a per-layer bool, derived from the original (unmodified)
|
||||
// surfaces, so the gate is platform-independent and order-independent.
|
||||
std::vector<char> needs_lightning_expansion(this->layers().size(), 0);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &surfaces_by_layer,
|
||||
&needs_lightning_expansion](tbb::blocked_range<size_t> r) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
if (surfaces_by_layer.find(lidx) == surfaces_by_layer.end())
|
||||
continue;
|
||||
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
const Layer *layer = po->get_layer(lidx);
|
||||
const Layer *lower_layer = layer->lower_layer;
|
||||
if (lower_layer == nullptr)
|
||||
continue;
|
||||
|
||||
Polygons lightning_fill;
|
||||
for (const LayerRegion *region : lower_layer->regions()) {
|
||||
if (region->region().config().sparse_infill_pattern == ipLightning) {
|
||||
Polygons lf = to_polygons(region->fill_surfaces.filter_by_type(stInternal));
|
||||
lightning_fill.insert(lightning_fill.end(), lf.begin(), lf.end());
|
||||
if (region->region().config().sparse_infill_pattern == ipLightning
|
||||
&& ! region->fill_surfaces.filter_by_type(stInternal).empty()) {
|
||||
needs_lightning_expansion[lidx] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (lightning_fill.empty())
|
||||
// Phase 2: write-only — each iteration mutates only m_layers[lidx]'s
|
||||
// fill_surfaces and never reads any other layer's surfaces. Different
|
||||
// iterations write to disjoint LayerRegion::fill_surfaces vectors, so
|
||||
// there is no aliasing between worker threads.
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, this->layers().size()), [po = this, &backup_surfaces,
|
||||
&surfaces_by_layer,
|
||||
&needs_lightning_expansion](tbb::blocked_range<size_t> r) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); lidx++) {
|
||||
if (! needs_lightning_expansion[lidx])
|
||||
continue;
|
||||
|
||||
Layer *layer = po->get_layer(lidx);
|
||||
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
backup_surfaces[lidx][region] = std::move(
|
||||
region->fill_surfaces); // Make backup copy by move!! so that pointers in candidate surfaces stay valid
|
||||
@@ -3104,8 +3253,19 @@ void PrintObject::bridge_over_infill()
|
||||
}
|
||||
|
||||
// ORCA: Internal bridge angle override
|
||||
if (candidate.region->region().config().internal_bridge_angle > 0)
|
||||
bridging_angle = candidate.region->region().config().internal_bridge_angle.value * PI / 180.0; // Convert degrees to radians
|
||||
if (candidate.region->region().config().internal_bridge_angle.value > 0) {
|
||||
const auto ®ion_config = candidate.region->region().config();
|
||||
const double custom_angle_rad = Geometry::deg2rad(region_config.internal_bridge_angle.value);
|
||||
if (region_config.relative_bridge_angle.value)
|
||||
bridging_angle += custom_angle_rad;
|
||||
else {
|
||||
bridging_angle = custom_angle_rad;
|
||||
if (region_config.align_infill_direction_to_model) {
|
||||
auto m = po->trafo().matrix();
|
||||
bridging_angle += std::atan2((double)m(1, 0), (double)m(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
|
||||
if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) {
|
||||
@@ -3234,53 +3394,75 @@ void PrintObject::bridge_over_infill()
|
||||
// === ORCA: Create a second internal bridge layer above the first bridge layer. ========================================================
|
||||
// ======================================================================================================================================
|
||||
if ( this->m_config.enable_extra_bridge_layer == eblApplyToAll || this->m_config.enable_extra_bridge_layer == eblInternalBridgeOnly) {
|
||||
// Process layers in parallel up to second-to-last
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, this->layers().size() - 1), [this](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx)
|
||||
{
|
||||
// ORCA: Two-phase to eliminate the same data race as the external-bridge
|
||||
// pass in detect_surfaces_type().
|
||||
//
|
||||
// Phase 1: read-only — for each layer, collect its stInternalBridge polygons and
|
||||
// the matching bridge angle into a per-layer cache.
|
||||
struct LayerBridgeCache {
|
||||
ExPolygons polys;
|
||||
double angle = 0.0;
|
||||
float offset_distance = 0.0f;
|
||||
bool has_bridge = false;
|
||||
};
|
||||
// Guard against size_t underflow when the object has 0 or 1 layers — there is
|
||||
// no "layer above" to receive an extra bridge, so the whole pass is a no-op.
|
||||
const size_t last = (this->layers().size() < 2) ? 0 : this->layers().size() - 1;
|
||||
std::vector<LayerBridgeCache> caches(this->layers().size());
|
||||
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, &caches](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx) {
|
||||
Layer* layer = this->get_layer(lidx);
|
||||
|
||||
// (A) Gather internal bridging surfaces in the current layer
|
||||
ExPolygons bridging_current_layer;
|
||||
double bridging_angle_current = 0.0;
|
||||
|
||||
bool found_any_bridge = false;
|
||||
float offset_distance = 0.0f;
|
||||
|
||||
// Pick a region from which to retrieve the flow width
|
||||
LayerBridgeCache &cache = caches[lidx];
|
||||
|
||||
if (!layer->regions().empty())
|
||||
offset_distance = layer->regions().front()->flow(frSolidInfill).scaled_width();
|
||||
|
||||
cache.offset_distance = layer->regions().front()->flow(frSolidInfill).scaled_width();
|
||||
for (LayerRegion *region : layer->regions()) {
|
||||
for (const Surface &surf : region->fill_surfaces.surfaces) {
|
||||
if (surf.surface_type == stInternalBridge) {
|
||||
bridging_current_layer.push_back(surf.expolygon);
|
||||
bridging_angle_current = surf.bridge_angle; // Store the last bridging angle of the current print object
|
||||
found_any_bridge = true;
|
||||
cache.polys.push_back(surf.expolygon);
|
||||
cache.angle = surf.bridge_angle; // last bridge angle on this layer wins, matching prior behaviour
|
||||
cache.has_bridge = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no bridging in this layer, continue with the next
|
||||
if (!found_any_bridge || bridging_current_layer.empty())
|
||||
|
||||
if (!cache.has_bridge || cache.polys.empty()) {
|
||||
cache.has_bridge = false;
|
||||
continue;
|
||||
|
||||
// (B) Shrink-expand to remove trivial bridging areas
|
||||
bridging_current_layer = offset_ex( shrink_ex(bridging_current_layer, offset_distance), offset_distance );
|
||||
|
||||
if (bridging_current_layer.empty())
|
||||
continue; // all bridging was trivial, continue with the next layer
|
||||
|
||||
}
|
||||
|
||||
// Shrink-expand to remove trivial bridging areas
|
||||
cache.polys = offset_ex(shrink_ex(cache.polys, cache.offset_distance), cache.offset_distance);
|
||||
if (cache.polys.empty())
|
||||
cache.has_bridge = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 2: write — each iteration mutates only m_layers[lidx+1]->fill_surfaces and
|
||||
// pulls its bridge polygons from the precomputed cache. Different iterations never
|
||||
// touch the same fill_surfaces vector, so there is no aliasing between workers.
|
||||
tbb::parallel_for( tbb::blocked_range<size_t>(0, last), [this, &caches](const tbb::blocked_range<size_t>& r) {
|
||||
for (size_t lidx = r.begin(); lidx < r.end(); ++lidx)
|
||||
{
|
||||
const LayerBridgeCache &cache = caches[lidx];
|
||||
|
||||
// If no bridging in this layer, continue with the next
|
||||
if (!cache.has_bridge || cache.polys.empty())
|
||||
continue;
|
||||
|
||||
// (C) If there is a next layer, identify overlapping stInternal & stInternalSolid areas and convert the overlap to stSecondInternalBridge
|
||||
if (lidx + 1 < this->layers().size()) {
|
||||
Layer* next_layer = this->get_layer(lidx + 1);
|
||||
|
||||
// second bridging angle is 90 degrees offset
|
||||
double bridging_angle_second = bridging_angle_current + M_PI / 2.0;
|
||||
|
||||
double bridging_angle_second = cache.angle + M_PI / 2.0;
|
||||
// Union the bridging polygons
|
||||
ExPolygons bridging_union = union_safety_offset_ex(bridging_current_layer);
|
||||
|
||||
ExPolygons bridging_union = union_safety_offset_ex(cache.polys);
|
||||
const float offset_distance = cache.offset_distance;
|
||||
|
||||
for (LayerRegion *next_region : next_layer->regions()) {
|
||||
Surfaces next_new_surfaces;
|
||||
Surfaces keep_surfaces;
|
||||
@@ -3379,6 +3561,12 @@ static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_extruders
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
static void clamp_feature_filament_to_valid(ConfigOptionInt &opt, size_t num_extruders)
|
||||
{
|
||||
if (opt.value <= 0 || opt.value > (int)num_extruders)
|
||||
opt.value = 1;
|
||||
}
|
||||
|
||||
PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders)
|
||||
{
|
||||
PrintObjectConfig config = default_object_config;
|
||||
@@ -3394,63 +3582,124 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject
|
||||
}
|
||||
|
||||
const std::string key_extruder { "extruder" };
|
||||
static constexpr const std::initializer_list<const std::string_view> keys_extruders { "sparse_infill_filament"sv, "solid_infill_filament"sv, "wall_filament"sv };
|
||||
static constexpr const std::initializer_list<const std::string_view> keys_extruders {
|
||||
"sparse_infill_filament_id"sv,
|
||||
"internal_solid_filament_id"sv,
|
||||
"top_surface_filament_id"sv,
|
||||
"bottom_surface_filament_id"sv,
|
||||
"outer_wall_filament_id"sv,
|
||||
"inner_wall_filament_id"sv
|
||||
};
|
||||
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in)
|
||||
struct FeatureFilamentOverrideMask
|
||||
{
|
||||
// 1) Map legacy "extruder" to feature filament keys as a fallback only.
|
||||
// If any feature-specific filament is explicitly set, keep those values.
|
||||
bool sparse_infill_filament_id = false;
|
||||
bool internal_solid_filament_id = false;
|
||||
bool top_surface_filament_id = false;
|
||||
bool bottom_surface_filament_id = false;
|
||||
bool outer_wall_filament_id = false;
|
||||
bool inner_wall_filament_id = false;
|
||||
};
|
||||
|
||||
static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides)
|
||||
{
|
||||
// 1) Explicit feature filament values take precedence over base extruder fallback.
|
||||
auto *opt_extruder = in.opt<ConfigOptionInt>(key_extruder);
|
||||
auto *opt_sparse_infill_filament = in.opt<ConfigOptionInt>("sparse_infill_filament");
|
||||
auto *opt_solid_infill_filament = in.opt<ConfigOptionInt>("solid_infill_filament");
|
||||
auto *opt_wall_filament = in.opt<ConfigOptionInt>("wall_filament");
|
||||
const bool has_feature_filament_override =
|
||||
(opt_sparse_infill_filament != nullptr && opt_sparse_infill_filament->value > 0) ||
|
||||
(opt_solid_infill_filament != nullptr && opt_solid_infill_filament->value > 0) ||
|
||||
(opt_wall_filament != nullptr && opt_wall_filament->value > 0);
|
||||
if (opt_extruder)
|
||||
if (int extruder = opt_extruder->value; extruder > 1 && ! has_feature_filament_override) {
|
||||
// Not a default extruder.
|
||||
out.sparse_infill_filament.value = extruder;
|
||||
out.solid_infill_filament.value = extruder;
|
||||
out.wall_filament.value = extruder;
|
||||
}
|
||||
int base_extruder = (opt_extruder != nullptr) ? opt_extruder->value : 0;
|
||||
|
||||
// 2) Copy the rest of the values.
|
||||
for (auto it = in.cbegin(); it != in.cend(); ++ it)
|
||||
if (it->first != key_extruder)
|
||||
if (ConfigOption* my_opt = out.option(it->first, false); my_opt != nullptr) {
|
||||
if (one_of(it->first, keys_extruders)) {
|
||||
// Ignore "default" extruders.
|
||||
// "Default" (0) clears explicit override for this scope and lets fallback apply.
|
||||
int extruder = static_cast<const ConfigOptionInt*>(it->second.get())->value;
|
||||
if (extruder > 0)
|
||||
if (extruder > 0) {
|
||||
my_opt->setInt(extruder);
|
||||
if (it->first == "sparse_infill_filament_id")
|
||||
feature_overrides.sparse_infill_filament_id = true;
|
||||
else if (it->first == "internal_solid_filament_id")
|
||||
feature_overrides.internal_solid_filament_id = true;
|
||||
else if (it->first == "top_surface_filament_id")
|
||||
feature_overrides.top_surface_filament_id = true;
|
||||
else if (it->first == "bottom_surface_filament_id")
|
||||
feature_overrides.bottom_surface_filament_id = true;
|
||||
else if (it->first == "outer_wall_filament_id")
|
||||
feature_overrides.outer_wall_filament_id = true;
|
||||
else if (it->first == "inner_wall_filament_id")
|
||||
feature_overrides.inner_wall_filament_id = true;
|
||||
} else {
|
||||
if (it->first == "sparse_infill_filament_id")
|
||||
feature_overrides.sparse_infill_filament_id = false;
|
||||
else if (it->first == "internal_solid_filament_id")
|
||||
feature_overrides.internal_solid_filament_id = false;
|
||||
else if (it->first == "top_surface_filament_id")
|
||||
feature_overrides.top_surface_filament_id = false;
|
||||
else if (it->first == "bottom_surface_filament_id")
|
||||
feature_overrides.bottom_surface_filament_id = false;
|
||||
else if (it->first == "outer_wall_filament_id")
|
||||
feature_overrides.outer_wall_filament_id = false;
|
||||
else if (it->first == "inner_wall_filament_id")
|
||||
feature_overrides.inner_wall_filament_id = false;
|
||||
}
|
||||
} else
|
||||
my_opt->set(it->second.get());
|
||||
}
|
||||
|
||||
// 3) Apply base extruder only to features that were not explicitly overridden.
|
||||
if (base_extruder > 0) {
|
||||
if (!feature_overrides.sparse_infill_filament_id)
|
||||
out.sparse_infill_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.internal_solid_filament_id)
|
||||
out.internal_solid_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.top_surface_filament_id)
|
||||
out.top_surface_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.bottom_surface_filament_id)
|
||||
out.bottom_surface_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.outer_wall_filament_id)
|
||||
out.outer_wall_filament_id.value = base_extruder;
|
||||
if (!feature_overrides.inner_wall_filament_id)
|
||||
out.inner_wall_filament_id.value = base_extruder;
|
||||
}
|
||||
}
|
||||
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders)
|
||||
{
|
||||
PrintRegionConfig config = default_or_parent_region_config;
|
||||
FeatureFilamentOverrideMask feature_overrides;
|
||||
|
||||
// For model parts, non-zero values coming from the print defaults should stay explicit.
|
||||
if (volume.is_model_part()) {
|
||||
feature_overrides.sparse_infill_filament_id = (config.sparse_infill_filament_id.value > 0);
|
||||
feature_overrides.internal_solid_filament_id = (config.internal_solid_filament_id.value > 0);
|
||||
feature_overrides.top_surface_filament_id = (config.top_surface_filament_id.value > 0);
|
||||
feature_overrides.bottom_surface_filament_id = (config.bottom_surface_filament_id.value > 0);
|
||||
feature_overrides.outer_wall_filament_id = (config.outer_wall_filament_id.value > 0);
|
||||
feature_overrides.inner_wall_filament_id = (config.inner_wall_filament_id.value > 0);
|
||||
}
|
||||
|
||||
if (volume.is_model_part()) {
|
||||
// default_or_parent_region_config contains the Print's PrintRegionConfig.
|
||||
// Override with ModelObject's PrintRegionConfig values.
|
||||
apply_to_print_region_config(config, volume.get_object()->config.get());
|
||||
apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides);
|
||||
} else {
|
||||
// default_or_parent_region_config contains parent PrintRegion config, which already contains ModelVolume's config.
|
||||
}
|
||||
apply_to_print_region_config(config, volume.config.get());
|
||||
apply_to_print_region_config(config, volume.config.get(), feature_overrides);
|
||||
if (! volume.material_id().empty())
|
||||
apply_to_print_region_config(config, volume.material()->config.get());
|
||||
apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides);
|
||||
if (layer_range_config != nullptr) {
|
||||
// Not applicable to modifiers.
|
||||
assert(volume.is_model_part());
|
||||
apply_to_print_region_config(config, *layer_range_config);
|
||||
apply_to_print_region_config(config, *layer_range_config, feature_overrides);
|
||||
}
|
||||
// Clamp invalid extruders to the default extruder (with index 1).
|
||||
clamp_exturder_to_default(config.sparse_infill_filament, num_extruders);
|
||||
clamp_exturder_to_default(config.wall_filament, num_extruders);
|
||||
clamp_exturder_to_default(config.solid_infill_filament, num_extruders);
|
||||
// Resolve feature defaults and clamp invalid extruders to index 1.
|
||||
clamp_feature_filament_to_valid(config.sparse_infill_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.outer_wall_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.inner_wall_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.internal_solid_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.top_surface_filament_id, num_extruders);
|
||||
clamp_feature_filament_to_valid(config.bottom_surface_filament_id, num_extruders);
|
||||
if (config.sparse_infill_density.value < 0.00011f)
|
||||
// Switch of infill for very low infill rates, also avoid division by zero in infill generator for these very low rates.
|
||||
// See GH issue #5910.
|
||||
@@ -3513,9 +3762,12 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full
|
||||
object_config.brim_type != btNoBrim && object_config.brim_width > 0.,
|
||||
object_extruders);
|
||||
for (const std::pair<const t_layer_height_range, ModelConfig> &range_and_config : model_object.layer_config_ranges)
|
||||
if (range_and_config.second.has("wall_filament") ||
|
||||
range_and_config.second.has("sparse_infill_filament") ||
|
||||
range_and_config.second.has("solid_infill_filament"))
|
||||
if (range_and_config.second.has("outer_wall_filament_id") ||
|
||||
range_and_config.second.has("inner_wall_filament_id") ||
|
||||
range_and_config.second.has("sparse_infill_filament_id") ||
|
||||
range_and_config.second.has("internal_solid_filament_id") ||
|
||||
range_and_config.second.has("top_surface_filament_id") ||
|
||||
range_and_config.second.has("bottom_surface_filament_id"))
|
||||
PrintRegion::collect_object_printing_extruders(
|
||||
print_config,
|
||||
region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders),
|
||||
@@ -3932,8 +4184,8 @@ void PrintObject::combine_infill()
|
||||
// Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
|
||||
//FIXME limit the layer height to max_layer_height
|
||||
double nozzle_diameter = std::min(
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament.value - 1),
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_filament.value - 1));
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1),
|
||||
this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1));
|
||||
|
||||
//Orca: Limit combination of infill to up to infill_combination_max_layer_height
|
||||
const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter);
|
||||
|
||||
@@ -359,7 +359,7 @@ static std::vector<std::vector<ExPolygons>> slices_to_regions(
|
||||
bool rhs_empty = rhs.region_id < 0 || rhs.expolygons.empty();
|
||||
// Sort the empty items to the end of the list.
|
||||
// Sort by region_id & volume_id lexicographically.
|
||||
return ! this_empty && (rhs_empty || (this->region_id < rhs.region_id || (this->region_id == rhs.region_id && volume_id < volume_id)));
|
||||
return ! this_empty && (rhs_empty || (this->region_id < rhs.region_id || (this->region_id == rhs.region_id && volume_id < rhs.volume_id)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,12 +7,16 @@ namespace Slic3r {
|
||||
unsigned int PrintRegion::extruder(FlowRole role) const
|
||||
{
|
||||
size_t extruder = 0;
|
||||
if (role == frPerimeter || role == frExternalPerimeter)
|
||||
extruder = m_config.wall_filament;
|
||||
if (role == frPerimeter)
|
||||
extruder = m_config.inner_wall_filament_id;
|
||||
else if (role == frExternalPerimeter)
|
||||
extruder = m_config.outer_wall_filament_id;
|
||||
else if (role == frInfill)
|
||||
extruder = m_config.sparse_infill_filament;
|
||||
else if (role == frSolidInfill || role == frTopSolidInfill)
|
||||
extruder = m_config.solid_infill_filament;
|
||||
extruder = m_config.sparse_infill_filament_id;
|
||||
else if (role == frSolidInfill)
|
||||
extruder = m_config.internal_solid_filament_id;
|
||||
else if (role == frTopSolidInfill)
|
||||
extruder = m_config.top_surface_filament_id;
|
||||
else
|
||||
throw Slic3r::InvalidArgument("Unknown role");
|
||||
return extruder;
|
||||
@@ -51,9 +55,12 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he
|
||||
|
||||
coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
|
||||
{
|
||||
return (print_config.nozzle_diameter.get_at(m_config.wall_filament.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.solid_infill_filament.value - 1)) / 3.;
|
||||
return (print_config.nozzle_diameter.get_at(m_config.outer_wall_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.inner_wall_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.internal_solid_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.top_surface_filament_id.value - 1) +
|
||||
print_config.nozzle_diameter.get_at(m_config.bottom_surface_filament_id.value - 1)) / 6.;
|
||||
}
|
||||
|
||||
coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const
|
||||
@@ -70,12 +77,19 @@ void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_con
|
||||
int i = std::max(0, extruder_id - 1);
|
||||
object_extruders.emplace_back((i >= num_extruders) ? 0 : i);
|
||||
};
|
||||
if (region_config.wall_loops.value > 0 || has_brim)
|
||||
emplace_extruder(region_config.wall_filament);
|
||||
if (region_config.wall_loops.value > 0 || has_brim) {
|
||||
emplace_extruder(region_config.outer_wall_filament_id);
|
||||
if (region_config.wall_loops.value > 1)
|
||||
emplace_extruder(region_config.inner_wall_filament_id);
|
||||
}
|
||||
if (region_config.sparse_infill_density.value > 0)
|
||||
emplace_extruder(region_config.sparse_infill_filament);
|
||||
if (region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.solid_infill_filament);
|
||||
emplace_extruder(region_config.sparse_infill_filament_id);
|
||||
if (region_config.sparse_infill_density.value > 0 || region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.internal_solid_filament_id);
|
||||
if (region_config.top_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.top_surface_filament_id);
|
||||
if (region_config.bottom_shell_layers.value > 0)
|
||||
emplace_extruder(region_config.bottom_surface_filament_id);
|
||||
}
|
||||
|
||||
void PrintRegion::collect_object_printing_extruders(const Print &print, std::vector<unsigned int> &object_extruders) const
|
||||
@@ -85,9 +99,12 @@ void PrintRegion::collect_object_printing_extruders(const Print &print, std::vec
|
||||
#ifndef NDEBUG
|
||||
// BBS
|
||||
auto num_extruders = int(print.config().filament_diameter.size());
|
||||
assert(this->config().wall_filament <= num_extruders);
|
||||
assert(this->config().sparse_infill_filament <= num_extruders);
|
||||
assert(this->config().solid_infill_filament <= num_extruders);
|
||||
assert(this->config().outer_wall_filament_id <= num_extruders);
|
||||
assert(this->config().inner_wall_filament_id <= num_extruders);
|
||||
assert(this->config().sparse_infill_filament_id <= num_extruders);
|
||||
assert(this->config().internal_solid_filament_id <= num_extruders);
|
||||
assert(this->config().top_surface_filament_id <= num_extruders);
|
||||
assert(this->config().bottom_surface_filament_id <= num_extruders);
|
||||
#endif
|
||||
collect_object_printing_extruders(print.config(), this->config(), print.has_brim(), object_extruders);
|
||||
}
|
||||
|
||||
@@ -613,7 +613,7 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const
|
||||
return this->PrintBase::output_filename(m_print_config.filename_format.value, ".sl1", filename_base, &config);
|
||||
}
|
||||
|
||||
StringObjectException SLAPrint::validate(StringObjectException *exception, Polygons *collison_polygons, std::vector<std::pair<Polygon, float>> *height_polygons) const
|
||||
StringObjectException SLAPrint::validate(std::vector<StringObjectException> *warnings, Polygons *collison_polygons, std::vector<std::pair<Polygon, float>> *height_polygons) const
|
||||
{
|
||||
for(SLAPrintObject * po : m_objects) {
|
||||
|
||||
|
||||
@@ -488,7 +488,7 @@ public:
|
||||
|
||||
const SLAPrintStatistics& print_statistics() const { return m_print_statistics; }
|
||||
|
||||
StringObjectException validate(StringObjectException * warning = nullptr,
|
||||
StringObjectException validate(std::vector<StringObjectException> * warnings = nullptr,
|
||||
Polygons * collison_polygons = nullptr,
|
||||
std::vector<std::pair<Polygon, float>> *height_polygons = nullptr) const override;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
#include <libslic3r/TriangleMeshSlicer.hpp>
|
||||
|
||||
// Need the cylinder method for the the drainholes in hollowing step
|
||||
// Need the cylinder method for the drainholes in hollowing step
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "clipper.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "Print.hpp"
|
||||
@@ -15,6 +16,30 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Orca: Some support entities may contain empty nested paths, which cannot be reordered safely.
|
||||
static bool extrusion_entity_has_endpoints(const ExtrusionEntity *entity)
|
||||
{
|
||||
auto paths_have_endpoints = [](const ExtrusionPaths &paths) {
|
||||
return !paths.empty() &&
|
||||
!paths.front().polyline.points.empty() &&
|
||||
!paths.back().polyline.points.empty();
|
||||
};
|
||||
|
||||
if (entity == nullptr)
|
||||
return false;
|
||||
if (const auto *collection = dynamic_cast<const ExtrusionEntityCollection *>(entity))
|
||||
return !collection->entities.empty() &&
|
||||
extrusion_entity_has_endpoints(collection->entities.front()) &&
|
||||
extrusion_entity_has_endpoints(collection->entities.back());
|
||||
if (const auto *path = dynamic_cast<const ExtrusionPath *>(entity))
|
||||
return !path->polyline.points.empty();
|
||||
if (const auto *multipath = dynamic_cast<const ExtrusionMultiPath *>(entity))
|
||||
return paths_have_endpoints(multipath->paths);
|
||||
if (const auto *loop = dynamic_cast<const ExtrusionLoop *>(entity))
|
||||
return paths_have_endpoints(loop->paths);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Naive implementation of the Traveling Salesman Problem, it works by always taking the next closest neighbor.
|
||||
// This implementation will always produce valid result even if some segments cannot reverse.
|
||||
template<typename EndPointType, typename KDTreeType, typename CouldReverseFunc>
|
||||
@@ -1035,8 +1060,10 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
|
||||
|
||||
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
|
||||
{
|
||||
// this function crashes if there are empty elements in entities
|
||||
entities.erase(std::remove_if(entities.begin(), entities.end(), [](ExtrusionEntity *entity) { return static_cast<ExtrusionEntityCollection *>(entity)->empty(); }),
|
||||
// Orca: Reordering queries first_point() / last_point(); drop entities that cannot provide valid endpoints.
|
||||
entities.erase(std::remove_if(entities.begin(), entities.end(), [](ExtrusionEntity *entity) {
|
||||
return !extrusion_entity_has_endpoints(entity);
|
||||
}),
|
||||
entities.end());
|
||||
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
|
||||
}
|
||||
|
||||
@@ -1733,8 +1733,8 @@ void generate_support_toolpaths(
|
||||
interface_as_base ? ExtrusionRole::erSupportMaterial : ExtrusionRole::erSupportMaterialInterface, interface_flow);
|
||||
}
|
||||
};
|
||||
const bool top_interfaces = config.support_interface_top_layers.value != 0;
|
||||
const bool bottom_interfaces = top_interfaces && config.support_interface_bottom_layers != 0;
|
||||
const bool top_interfaces = support_params.num_top_interface_layers > 0;
|
||||
const bool bottom_interfaces = top_interfaces && support_params.num_bottom_interface_layers > 0;
|
||||
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
|
||||
if (!organic_tree)
|
||||
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
|
||||
|
||||
@@ -1226,7 +1226,7 @@ namespace SupportMaterialInternal {
|
||||
// Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
|
||||
Polygons lower_grown_slices = expand(lower_layer_polygons,
|
||||
//FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
|
||||
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament-1))),
|
||||
0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().outer_wall_filament_id-1))),
|
||||
SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
// Collect perimeters of this layer.
|
||||
//FIXME split_at_first_point() could split a bridge mid-way
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
#include "../Flow.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
inline int number_of_support_interface_bottom_layers(const PrintObjectConfig& object_config)
|
||||
{
|
||||
return object_config.support_interface_bottom_layers.value < 0 ?
|
||||
object_config.support_interface_top_layers.value :
|
||||
object_config.support_interface_bottom_layers.value;
|
||||
}
|
||||
|
||||
struct SupportParameters {
|
||||
SupportParameters() = delete;
|
||||
SupportParameters(const PrintObject& object)
|
||||
@@ -26,8 +34,7 @@ struct SupportParameters {
|
||||
|
||||
{
|
||||
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
|
||||
this->num_bottom_interface_layers = object_config.support_interface_bottom_layers < 0 ?
|
||||
num_top_interface_layers : object_config.support_interface_bottom_layers;
|
||||
this->num_bottom_interface_layers = number_of_support_interface_bottom_layers(object_config);
|
||||
this->has_top_contacts = num_top_interface_layers > 0;
|
||||
this->has_bottom_contacts = num_bottom_interface_layers > 0;
|
||||
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
|
||||
|
||||
@@ -1332,7 +1332,12 @@ static void make_perimeter_and_infill(ExtrusionEntitiesPtr& dst, const ExPolygon
|
||||
dst = std::move(loops_entities);
|
||||
}
|
||||
}
|
||||
dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) { return static_cast<ExtrusionEntityCollection *>(entity)->empty(); }), dst.end());
|
||||
|
||||
// Orca: Some entities are direct paths, so check the type before testing for an empty collection.
|
||||
dst.erase(std::remove_if(dst.begin(), dst.end(), [](ExtrusionEntity *entity) {
|
||||
return entity != nullptr && entity->is_collection() && static_cast<ExtrusionEntityCollection *>(entity)->empty();
|
||||
}), dst.end());
|
||||
|
||||
if (infill_first) {
|
||||
// sort regions to reduce travel
|
||||
Points ordering_points;
|
||||
@@ -1610,80 +1615,71 @@ void TreeSupport::generate_toolpaths()
|
||||
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);
|
||||
|
||||
Polygons loops = to_polygons(poly);
|
||||
//ORCA: Group base per area as no_sort to keep outline->fill together.
|
||||
std::unique_ptr<ExtrusionEntityCollection> base_eec = std::make_unique<ExtrusionEntityCollection>();
|
||||
base_eec->no_sort = true;
|
||||
ExtrusionEntitiesPtr &base_dst = base_eec->entities;
|
||||
if (layer_id == 0) {
|
||||
float density = float(m_object_config->raft_first_layer_density.value * 0.01);
|
||||
fill_expolygons_with_sheath_generate_paths(ts_layer->support_fills.entities, loops, filler_support.get(), density, erSupportMaterial, flow,
|
||||
fill_expolygons_with_sheath_generate_paths(base_dst, loops, filler_support.get(), density, erSupportMaterial, flow,
|
||||
m_support_params, true, false);
|
||||
}
|
||||
else {
|
||||
//ORCA: Force base walls before infill to keep outline->fill order.
|
||||
if (need_infill && m_support_params.base_fill_pattern != ipLightning) {
|
||||
// allow infill-only mode if support is thick enough (so min_wall_count is 0);
|
||||
// otherwise must draw 1 wall
|
||||
// Don't need extra walls if we have infill. Extra walls may overlap with the infills.
|
||||
size_t min_wall_count = offset(poly, -scale_(support_spacing * 1.5)).empty() ? 1 : 0;
|
||||
make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::max(min_wall_count, wall_count), flow,
|
||||
erSupportMaterial, filler_support.get(), support_density);
|
||||
make_perimeter_and_infill(base_dst, poly, std::max(min_wall_count, wall_count), flow,
|
||||
erSupportMaterial, filler_support.get(), support_density, false);
|
||||
}
|
||||
else {
|
||||
SupportParameters support_params = m_support_params;
|
||||
if (area_group.need_extra_wall && object_config.tree_support_wall_count.value == 0)
|
||||
support_params.tree_branch_diameter_double_wall_area_scaled = 0.1;
|
||||
tree_supports_generate_paths(ts_layer->support_fills.entities, loops, flow, support_params);
|
||||
tree_supports_generate_paths(base_dst, loops, flow, support_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_support_params.base_fill_pattern == ipLightning)
|
||||
{
|
||||
double print_z = ts_layer->print_z;
|
||||
if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end())
|
||||
continue;
|
||||
//TODO:
|
||||
//1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour.
|
||||
// whether current value works correctly remained to be tested
|
||||
//2.related to previous one, that lightning roots need to be trimed more when support has multiple walls
|
||||
//3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots,
|
||||
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
|
||||
auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]);
|
||||
|
||||
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_support_params.first_layer_flow : support_flow;
|
||||
ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing());
|
||||
|
||||
for (auto& area : areas)
|
||||
{
|
||||
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
|
||||
for (auto itr = polylines.begin(); itr != polylines.end();)
|
||||
{
|
||||
if (itr->length() < scale_(1.0))
|
||||
itr = polylines.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
Polylines opt_polylines;
|
||||
#if 1
|
||||
//this wont create connection patterns along contours
|
||||
append(opt_polylines, chain_polylines(std::move(polylines)));
|
||||
#else
|
||||
//this will create connection patterns along contours
|
||||
FillParams params;
|
||||
params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing());
|
||||
params.anchor_length_max = Fill::infill_anchor_max;
|
||||
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
|
||||
Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params);
|
||||
#endif
|
||||
extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial,
|
||||
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
||||
|
||||
//ORCA: Emit lightning infill per base area to avoid interleaving across islands.
|
||||
if (m_support_params.base_fill_pattern == ipLightning) {
|
||||
double print_z = ts_layer->print_z;
|
||||
auto lightning_layer_mapping = printZ_to_lightninglayer.find(print_z);
|
||||
if (lightning_layer_mapping != printZ_to_lightninglayer.end()) {
|
||||
auto &lightning_layer = generator->getTreesForLayer(lightning_layer_mapping->second);
|
||||
ExPolygons areas;
|
||||
areas.emplace_back(poly);
|
||||
areas = offset_ex(areas, -flow.scaled_spacing());
|
||||
for (auto &area : areas) {
|
||||
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
|
||||
for (auto itr = polylines.begin(); itr != polylines.end();) {
|
||||
if (itr->length() < scale_(1.0))
|
||||
itr = polylines.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
Polylines opt_polylines;
|
||||
append(opt_polylines, chain_polylines(std::move(polylines)));
|
||||
extrusion_entities_append_paths(base_dst, opt_polylines, erSupportMaterial,
|
||||
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
std::string name = debug_out_path("trees_polyline_%.2f.svg", ts_layer->print_z);
|
||||
BoundingBox bbox = get_extents(ts_layer->base_areas);
|
||||
SVG svg(name, bbox);
|
||||
if (svg.is_opened()) {
|
||||
svg.draw(ts_layer->base_areas, "blue");
|
||||
svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red");
|
||||
for (auto &line : opt_polylines) svg.draw(line, "yellow");
|
||||
}
|
||||
std::string name = debug_out_path("trees_polyline_%.2f.svg", ts_layer->print_z);
|
||||
BoundingBox bbox = get_extents(ts_layer->base_areas);
|
||||
SVG svg(name, bbox);
|
||||
if (svg.is_opened()) {
|
||||
svg.draw(ts_layer->base_areas, "blue");
|
||||
svg.draw(generator->Overhangs()[lightning_layer_mapping->second], "red");
|
||||
for (auto &line : opt_polylines) svg.draw(line, "yellow");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ORCA: Keep per-area base paths grouped for outline->fill preservation.
|
||||
if (!base_eec->empty())
|
||||
ts_layer->support_fills.entities.push_back(base_eec.release());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,13 +1692,6 @@ void TreeSupport::generate_toolpaths()
|
||||
);
|
||||
}
|
||||
|
||||
void deleteDirectoryContents(const std::filesystem::path& dir)
|
||||
{
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir))
|
||||
std::filesystem::remove_all(entry.path());
|
||||
}
|
||||
|
||||
|
||||
void TreeSupport::move_bounds_to_contact_nodes(std::vector<TreeSupport3D::SupportElements> &move_bounds,
|
||||
PrintObject &print_object,
|
||||
const TreeSupport3D::TreeSupportSettings &config)
|
||||
@@ -2024,8 +2013,8 @@ void TreeSupport::draw_circles()
|
||||
|
||||
// generate areas
|
||||
const coordf_t layer_height = config.layer_height.value;
|
||||
const size_t top_interface_layers = config.support_interface_top_layers.value;
|
||||
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value < 0 ? top_interface_layers : config.support_interface_bottom_layers.value;
|
||||
const size_t top_interface_layers = m_support_params.num_top_interface_layers;
|
||||
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
const double nozzle_diameter = m_object->print()->config().nozzle_diameter.get_at(0);
|
||||
const coordf_t line_width = config.get_abs_value("support_line_width", nozzle_diameter);
|
||||
const coordf_t line_width_scaled = scale_(line_width);
|
||||
@@ -2155,13 +2144,9 @@ void TreeSupport::draw_circles()
|
||||
if (!area.empty()) has_circle_node = true;
|
||||
if (node.need_extra_wall) need_extra_wall = true;
|
||||
|
||||
// Merge the overhang into the roof area so tree tips can still produce
|
||||
// a continuous support interface. Suppressing this for build-plate-only
|
||||
// support drops the roof polygons entirely in valid tree branches.
|
||||
// ORCA: Only keep top interface polygons that fully fit in the mm height cap.
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 &&
|
||||
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON &&
|
||||
!node.is_sharp_tail) {
|
||||
// merge overhang to get a smoother interface surface
|
||||
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only && !node.is_sharp_tail) {
|
||||
ExPolygons overhang_expanded;
|
||||
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
|
||||
overhang_expanded.emplace_back(node.overhang);
|
||||
@@ -2207,16 +2192,6 @@ void TreeSupport::draw_circles()
|
||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||
roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border);
|
||||
|
||||
// Build-plate-only pruning can collapse the roof stack down to a single
|
||||
// printable layer. In that case we still need to emit an interface layer
|
||||
// instead of downgrading the last roof-adjacent layer to base support.
|
||||
if (on_buildplate_only && top_interface_layers > 0 && roof_areas.empty() && !roof_1st_layer.empty()) {
|
||||
append(roof_areas, roof_1st_layer);
|
||||
roof_1st_layer.clear();
|
||||
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
|
||||
max_layers_above_roof1 = 0;
|
||||
}
|
||||
|
||||
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
|
||||
base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas)));
|
||||
base_areas = intersection_ex(base_areas, m_machine_border);
|
||||
@@ -2374,6 +2349,15 @@ void TreeSupport::draw_circles()
|
||||
ts_layer->base_areas = std::move(expanded_base_areas);
|
||||
}
|
||||
|
||||
// Orca: Final tree base polygons may be too close above model surfaces.
|
||||
// Enforce bottom Z clearance for non-contact support layers as well.
|
||||
if (!ts_layer->base_areas.empty()) {
|
||||
const Polygons trimming = get_trim_support_regions(
|
||||
*m_object, ts_layer, 0., m_slicing_params.gap_object_support, 0);
|
||||
if (!trimming.empty())
|
||||
ts_layer->base_areas = diff_ex(ts_layer->base_areas, trimming);
|
||||
}
|
||||
|
||||
auto &area_groups = ts_layer->area_groups;
|
||||
|
||||
for (auto& expoly : ts_layer->base_areas) {
|
||||
@@ -2678,8 +2662,7 @@ void TreeSupport::drop_nodes()
|
||||
const size_t tip_layers = base_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle.
|
||||
const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution;
|
||||
const bool support_on_buildplate_only = config.support_on_build_plate_only.value;
|
||||
const size_t top_interface_layers = config.support_interface_top_layers.value;
|
||||
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value < 0 ? top_interface_layers : config.support_interface_bottom_layers.value;
|
||||
const size_t bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
SupportNode::diameter_angle_scale_factor = diameter_angle_scale_factor;
|
||||
float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5; // do not move contact points under 5mm
|
||||
|
||||
@@ -3576,7 +3559,14 @@ void TreeSupport::generate_contact_points()
|
||||
}
|
||||
|
||||
// add supports along contours
|
||||
libnest2d::placers::EdgeCache<ExPolygon> edge_cache(overhang);
|
||||
ExPolygon closed_overhang = overhang; // make a copy to add closing point for edge cache
|
||||
if (closed_overhang.contour.points.size() > 1)
|
||||
closed_overhang.contour.points.emplace_back(closed_overhang.contour.points.front());
|
||||
for (Polygon &hole : closed_overhang.holes)
|
||||
if (hole.points.size() > 1)
|
||||
hole.points.emplace_back(hole.points.front());
|
||||
|
||||
libnest2d::placers::EdgeCache<ExPolygon> edge_cache(closed_overhang);
|
||||
for (size_t i = 0; i < edge_cache.holeCount() + 1; i++) {
|
||||
double step = point_spread / (i == 0 ? edge_cache.circumference() : edge_cache.circumference(i - 1));
|
||||
double distance = 0;
|
||||
|
||||
@@ -124,6 +124,9 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
|
||||
{
|
||||
std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> grouped_meshes;
|
||||
|
||||
// Orca: Recompute static mesh-group state for this support generation pass.
|
||||
TreeSupportSettings::zero_top_z_gap = false;
|
||||
|
||||
//FIXME this is ugly, it does not belong here.
|
||||
for (size_t object_id : print_object_ids) {
|
||||
const PrintObject &print_object = *print.get_object(object_id);
|
||||
@@ -1194,7 +1197,7 @@ void sample_overhang_area(
|
||||
point_count += poly.size();
|
||||
const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(3), coord_t(total_length(overhang_area) / connect_length)));
|
||||
if (point_count <= min_support_points) {
|
||||
// add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the the support line width.
|
||||
// add the outer wall (of the overhang) to ensure it is correct supported instead. Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the support line width.
|
||||
// I assume that even small overhangs are over one line width wide, so lets try to place the support points in a way that the full support area generated from them
|
||||
// will support the overhang (if this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60 degrees so there is a fallback,
|
||||
// as some support is better than none.
|
||||
@@ -1375,7 +1378,7 @@ static void generate_initial_areas(
|
||||
}
|
||||
#if 0
|
||||
// If the xy distance overrides the z distance, some support needs to be inserted further down.
|
||||
//=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the the setting description, it would not produce reasonable results. )
|
||||
//=> Analyze which support points do not fit on this layer and check if they will fit a few layers down (while adding them an infinite amount of layers down would technically be closer the setting description, it would not produce reasonable results. )
|
||||
if (! min_xy_dist) {
|
||||
LineInformations overhang_lines;
|
||||
{
|
||||
@@ -1600,13 +1603,19 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
|
||||
if (settings.increase_radius)
|
||||
current_elem.effective_radius_height += 1;
|
||||
coord_t radius = support_element_collision_radius(config, current_elem);
|
||||
|
||||
const auto _tiny_area_threshold = tiny_area_threshold();
|
||||
if (settings.move) {
|
||||
increased = relevant_offset;
|
||||
if (overspeed > 0) {
|
||||
const coord_t safe_movement_distance =
|
||||
coord_t safe_movement_distance =
|
||||
(current_elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) +
|
||||
(std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0);
|
||||
// Orca:
|
||||
// safe_movement_distance is used as the safe_offset_inc() step, so keep it non-zero
|
||||
// to preserve branch movement with zero-clearance support settings.
|
||||
if (safe_movement_distance == 0)
|
||||
safe_movement_distance = scaled<coord_t>(0.1);
|
||||
// The difference to ensure that the result not only conforms to wall_restriction, but collision/avoidance is done later.
|
||||
// The higher last_safe_step_movement_distance comes exactly from the fact that the collision will be subtracted later.
|
||||
increased = safe_offset_inc(increased, overspeed, volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist),
|
||||
@@ -1817,9 +1826,15 @@ static void increase_areas_one_layer(
|
||||
* layer z-1:dddddxxxxxxxxxx
|
||||
* For more detailed visualisation see calculateWallRestrictions
|
||||
*/
|
||||
const coord_t safe_movement_distance =
|
||||
coord_t safe_movement_distance =
|
||||
(elem.use_min_xy_dist ? config.xy_min_distance : config.xy_distance) +
|
||||
(std::min(config.z_distance_top_layers, config.z_distance_bottom_layers) > 0 ? config.min_feature_size : 0);
|
||||
|
||||
// safe_movement_distance is used as a divisor and as the safe_offset_inc() step,
|
||||
// so keep it non-zero to avoid division by zero and preserve branch movement.
|
||||
if (safe_movement_distance == 0)
|
||||
safe_movement_distance = scaled<coord_t>(0.1);
|
||||
|
||||
if (ceiled_parent_radius == volumes.ceilRadius(projected_radius_increased, parent.state.use_min_xy_dist) ||
|
||||
projected_radius_increased < config.increase_radius_until_radius)
|
||||
// If it is guaranteed possible to increase the radius, the maximum movement speed can be increased, as it is assumed that the maximum movement speed is the one of the slower moving wall
|
||||
@@ -2902,6 +2917,7 @@ static std::pair<float, float> extrude_branch(
|
||||
const TreeSupportSettings &config,
|
||||
const SlicingParameters &slicing_params,
|
||||
const std::vector<SupportElements> &move_bounds,
|
||||
bool has_root,
|
||||
indexed_triangle_set &result)
|
||||
{
|
||||
Vec3d p1, p2, p3;
|
||||
@@ -2923,24 +2939,38 @@ static std::pair<float, float> extrude_branch(
|
||||
v1 = (p2 - p1).normalized();
|
||||
if (ipath == 1) {
|
||||
nprev = v1;
|
||||
// Extrude the bottom half sphere.
|
||||
float radius = unscaled<float>(support_element_radius(config, prev));
|
||||
float angle_step = 2. * acos(1. - eps / radius);
|
||||
auto nsteps = int(ceil(M_PI / (2. * angle_step)));
|
||||
angle_step = M_PI / (2. * nsteps);
|
||||
int ifan = int(result.vertices.size());
|
||||
result.vertices.emplace_back((p1 - nprev * radius).cast<float>());
|
||||
zmin = result.vertices.back().z();
|
||||
float angle = angle_step;
|
||||
for (int i = 1; i < nsteps; ++ i, angle += angle_step) {
|
||||
std::pair<int, int> strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast<float>(), nprev.cast<float>(), radius * sin(angle), eps, result.vertices);
|
||||
if (i == 1)
|
||||
triangulate_fan<false>(result, ifan, strip.first, strip.second);
|
||||
else
|
||||
triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second);
|
||||
// sprintf(fname, "d:\\temp\\meshes\\tree-partial-%d.obj", ++ irun);
|
||||
// its_write_obj(result, fname);
|
||||
prev_strip = strip;
|
||||
if (has_root && prev.state.layer_idx == 0) {
|
||||
// Orca: Buildplate roots need a flat foot. A rounded cap can extend far
|
||||
// below the bed and make the first layer slice cut unrelated trunk geometry.
|
||||
const Vec3f normal(0.f, 0.f, 1.f);
|
||||
const Vec3f bottom_center(float(p1.x()), float(p1.y()), 0.f);
|
||||
const Vec3f top_center(float(p1.x()), float(p1.y()), float(p1.z()));
|
||||
int ifan = int(result.vertices.size());
|
||||
result.vertices.emplace_back(bottom_center);
|
||||
std::pair<int, int> bottom_strip = discretize_circle(bottom_center, normal, radius, eps, result.vertices);
|
||||
triangulate_fan<false>(result, ifan, bottom_strip.first, bottom_strip.second);
|
||||
prev_strip = discretize_circle(top_center, normal, radius, eps, result.vertices);
|
||||
triangulate_strip(result, bottom_strip.first, bottom_strip.second, prev_strip.first, prev_strip.second);
|
||||
zmin = 0.f;
|
||||
} else {
|
||||
// Extrude the bottom half sphere.
|
||||
float angle_step = 2. * acos(1. - eps / radius);
|
||||
auto nsteps = int(ceil(M_PI / (2. * angle_step)));
|
||||
angle_step = M_PI / (2. * nsteps);
|
||||
int ifan = int(result.vertices.size());
|
||||
result.vertices.emplace_back((p1 - nprev * radius).cast<float>());
|
||||
zmin = result.vertices.back().z();
|
||||
float angle = angle_step;
|
||||
for (int i = 1; i < nsteps; ++ i, angle += angle_step) {
|
||||
std::pair<int, int> strip = discretize_circle((p1 - nprev * radius * cos(angle)).cast<float>(), nprev.cast<float>(), radius * sin(angle), eps, result.vertices);
|
||||
if (i == 1)
|
||||
triangulate_fan<false>(result, ifan, strip.first, strip.second);
|
||||
else
|
||||
triangulate_strip(result, prev_strip.first, prev_strip.second, strip.first, strip.second);
|
||||
|
||||
prev_strip = strip;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ipath + 1 == path.size()) {
|
||||
@@ -3123,13 +3153,60 @@ static void organic_smooth_branches_avoid_collisions(
|
||||
static constexpr const double max_nudge_collision_avoidance = 0.5;
|
||||
static constexpr const double max_nudge_smoothing = 0.2;
|
||||
static constexpr const size_t num_iter = 100; // 1000;
|
||||
|
||||
// Orca:
|
||||
// Collision and Laplacian smoothing run iteratively; keep each candidate reachable from linked upper/lower layers to avoid accumulated drift.
|
||||
auto limit_candidate_to_linked_layers = [&collision_spheres, &linear_data_layers, &config](const size_t collision_sphere_id, Vec2d candidate) {
|
||||
auto constrain_to_anchor = [](Vec2d candidate, const Vec2d ¤t_pos, const Vec2d &anchor, double allowed_shift) {
|
||||
const Vec2d delta = candidate - anchor;
|
||||
const double candidate_dist = delta.norm();
|
||||
const double current_dist = (current_pos - anchor).norm();
|
||||
allowed_shift = std::max(allowed_shift, current_dist);
|
||||
return candidate_dist > allowed_shift && candidate_dist > EPSILON ?
|
||||
anchor + delta * (allowed_shift / candidate_dist) :
|
||||
candidate;
|
||||
};
|
||||
|
||||
const CollisionSphere &sphere = collision_spheres[collision_sphere_id];
|
||||
const LayerIndex layer_idx = sphere.element.state.layer_idx;
|
||||
const Vec2d current_pos = to_2d(sphere.position).cast<double>();
|
||||
const double current_radius = double(support_element_radius(config, sphere.element));
|
||||
const double maximum_move_distance_slow = double(config.maximum_move_distance_slow);
|
||||
|
||||
if (sphere.element_below_id != -1 && layer_idx > 0) {
|
||||
const size_t lower_id = linear_data_layers[layer_idx - 1] + size_t(sphere.element_below_id);
|
||||
if (lower_id < collision_spheres.size()) {
|
||||
const CollisionSphere &lower = collision_spheres[lower_id];
|
||||
const double lower_radius = double(support_element_radius(config, lower.element));
|
||||
const double allowed_shift = unscaled<double>(std::max(0., lower_radius - current_radius) + maximum_move_distance_slow);
|
||||
candidate = constrain_to_anchor(candidate, current_pos, to_2d(lower.prev_position).cast<double>(), allowed_shift);
|
||||
}
|
||||
}
|
||||
|
||||
const LayerIndex upper_layer_idx = layer_idx + 1;
|
||||
if (!sphere.element.parents.empty() && upper_layer_idx < LayerIndex(linear_data_layers.size())) {
|
||||
const size_t upper_offset = linear_data_layers[upper_layer_idx];
|
||||
for (int32_t parent_idx : sphere.element.parents) {
|
||||
const size_t upper_id = upper_offset + size_t(parent_idx);
|
||||
if (upper_id >= collision_spheres.size())
|
||||
continue;
|
||||
const CollisionSphere &upper = collision_spheres[upper_id];
|
||||
const double upper_radius = double(support_element_radius(config, upper.element));
|
||||
const double allowed_shift = unscaled<double>(std::max(0., current_radius - upper_radius) + maximum_move_distance_slow);
|
||||
candidate = constrain_to_anchor(candidate, current_pos, to_2d(upper.prev_position).cast<double>(), allowed_shift);
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
};
|
||||
|
||||
for (size_t iter = 0; iter < num_iter; ++ iter) {
|
||||
// Back up prev position before Laplacian smoothing.
|
||||
for (CollisionSphere &collision_sphere : collision_spheres)
|
||||
collision_sphere.prev_position = collision_sphere.position;
|
||||
std::atomic<size_t> num_moved{ 0 };
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, collision_spheres.size()),
|
||||
[&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel](const tbb::blocked_range<size_t> range) {
|
||||
[&collision_spheres, &layer_collision_cache, &slicing_params, &config, &linear_data_layers, &num_moved, &throw_on_cancel, &limit_candidate_to_linked_layers](const tbb::blocked_range<size_t> range) {
|
||||
for (size_t collision_sphere_id = range.begin(); collision_sphere_id < range.end(); ++ collision_sphere_id)
|
||||
if (CollisionSphere &collision_sphere = collision_spheres[collision_sphere_id]; ! collision_sphere.locked) {
|
||||
// Calculate collision of multiple 2D layers against a collision sphere.
|
||||
@@ -3158,10 +3235,12 @@ static void organic_smooth_branches_avoid_collisions(
|
||||
if (collision_sphere.last_collision_depth > EPSILON)
|
||||
// a little bit of hysteresis to detect end of
|
||||
++ num_moved;
|
||||
// Shift by maximum 2mm.
|
||||
// Limit collision-avoidance nudge per iteration.
|
||||
double nudge_dist = std::min(std::max(0., collision_sphere.last_collision_depth + collision_extra_gap), max_nudge_collision_avoidance);
|
||||
Vec2d nudge_vector = (to_2d(collision_sphere.position) - to_2d(collision_sphere.last_collision)).cast<double>().normalized() * nudge_dist;
|
||||
collision_sphere.position.head<2>() += (nudge_vector * nudge_dist).cast<float>();
|
||||
Vec2d candidate = to_2d(collision_sphere.position).cast<double>() + nudge_vector * nudge_dist;
|
||||
candidate = limit_candidate_to_linked_layers(collision_sphere_id, candidate);
|
||||
collision_sphere.position.head<2>() = candidate.cast<float>();
|
||||
}
|
||||
// Laplacian smoothing
|
||||
Vec2d avg{ 0, 0 };
|
||||
@@ -3185,9 +3264,13 @@ static void organic_smooth_branches_avoid_collisions(
|
||||
Vec2d new_pos = (1. - smoothing_factor) * old_pos + smoothing_factor * avg;
|
||||
Vec2d shift = new_pos - old_pos;
|
||||
double nudge_dist_max = shift.norm();
|
||||
// Shift by maximum 1mm, less than the collision avoidance factor.
|
||||
// Limit Laplacian smoothing nudge per iteration.
|
||||
double nudge_dist = std::min(std::max(0., nudge_dist_max), max_nudge_smoothing);
|
||||
collision_sphere.position.head<2>() += (shift.normalized() * nudge_dist).cast<float>();
|
||||
if (nudge_dist > 0.) {
|
||||
Vec2d candidate = old_pos + shift * (nudge_dist / nudge_dist_max);
|
||||
candidate = limit_candidate_to_linked_layers(collision_sphere_id, candidate);
|
||||
collision_sphere.position.head<2>() = candidate.cast<float>();
|
||||
}
|
||||
|
||||
throw_on_cancel();
|
||||
}
|
||||
@@ -3454,6 +3537,7 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
|
||||
|
||||
// value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas
|
||||
std::vector<SupportElements> move_bounds(num_support_layers);
|
||||
|
||||
// ### Place tips of the support tree
|
||||
for (size_t mesh_idx : processing.second)
|
||||
generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs,
|
||||
@@ -3764,6 +3848,7 @@ void organic_draw_branches(
|
||||
// ++ ielement;
|
||||
}
|
||||
}
|
||||
|
||||
const SlicingParameters &slicing_params = print_object.slicing_parameters();
|
||||
MeshSlicingParams mesh_slicing_params;
|
||||
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
|
||||
@@ -3778,7 +3863,7 @@ void organic_draw_branches(
|
||||
for (const Branch &branch : tree.branches) {
|
||||
// Triangulate the tube.
|
||||
partial_mesh.clear();
|
||||
std::pair<float, float> zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, partial_mesh);
|
||||
std::pair<float, float> zspan = extrude_branch(branch.path, config, slicing_params, move_bounds, branch.has_root, partial_mesh);
|
||||
LayerIndex layer_begin = branch.has_root ?
|
||||
branch.path.front()->state.layer_idx :
|
||||
std::min(branch.path.front()->state.layer_idx, layer_idx_ceil(slicing_params, config, zspan.first));
|
||||
@@ -3945,19 +4030,7 @@ void organic_draw_branches(
|
||||
}
|
||||
// ORCA: bottom contacts provide the footprint; interface layers are built later.
|
||||
|
||||
#if 0
|
||||
//FIXME branch.has_tip seems to not be reliable.
|
||||
if (branch.has_tip && interface_placer.support_parameters.has_top_contacts)
|
||||
// Add top slices to top contacts / interfaces / base interfaces.
|
||||
for (int i = int(branch.path.size()) - 1; i >= 0; -- i) {
|
||||
const SupportElement &el = *branch.path[i];
|
||||
if (el.state.missing_roof_layers == 0)
|
||||
break;
|
||||
//FIXME Move or not?
|
||||
interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx,
|
||||
interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers);
|
||||
}
|
||||
#endif
|
||||
recover_pending_branch_roofs(interface_placer, branch.path, layer_begin, slices);
|
||||
|
||||
while (! slices.empty() && slices.back().empty()) {
|
||||
slices.pop_back();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "../Polygon.hpp"
|
||||
#include "SupportCommon.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r
|
||||
@@ -60,12 +61,10 @@ struct TreeSupportMeshGroupSettings {
|
||||
this->support_angle = 0.5 * M_PI - std::clamp<double>((config.support_threshold_angle + 1) * M_PI / 180., 0., 0.5 * M_PI);
|
||||
this->support_line_width = support_material_flow(&print_object, config.layer_height).scaled_width();
|
||||
this->support_roof_line_width = support_material_interface_flow(&print_object, config.layer_height).scaled_width();
|
||||
//FIXME add it to SlicingParameters and reuse in both tree and normal supports?
|
||||
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && config.support_interface_bottom_layers.value != 0;
|
||||
const int bottom_interface_layers = number_of_support_interface_bottom_layers(config);
|
||||
this->support_bottom_enable = config.support_interface_top_layers.value > 0 && bottom_interface_layers > 0;
|
||||
this->support_bottom_height = this->support_bottom_enable ?
|
||||
(config.support_interface_bottom_layers.value > 0 ?
|
||||
config.support_interface_bottom_layers.value :
|
||||
config.support_interface_top_layers.value) * this->layer_height :
|
||||
bottom_interface_layers * this->layer_height :
|
||||
0;
|
||||
this->support_material_buildplate_only = config.support_on_build_plate_only;
|
||||
this->support_xy_distance = scaled<coord_t>(config.support_object_xy_distance.value);
|
||||
@@ -76,8 +75,8 @@ struct TreeSupportMeshGroupSettings {
|
||||
this->support_bottom_distance = scaled<coord_t>(slicing_params.gap_object_support);
|
||||
this->support_roof_enable = config.support_interface_top_layers.value > 0;
|
||||
this->support_roof_layers = config.support_interface_top_layers.value;
|
||||
this->support_floor_enable = config.support_interface_bottom_layers.value > 0;
|
||||
this->support_floor_layers = config.support_interface_bottom_layers.value;
|
||||
this->support_floor_enable = bottom_interface_layers > 0;
|
||||
this->support_floor_layers = bottom_interface_layers;
|
||||
this->support_roof_pattern = config.support_interface_pattern;
|
||||
this->support_pattern = config.support_base_pattern;
|
||||
this->support_line_spacing = scaled<coord_t>(config.support_base_pattern_spacing.value);
|
||||
@@ -614,19 +613,35 @@ inline double layer_z(const SlicingParameters &slicing_params, const TreeSupport
|
||||
slicing_params.object_print_z_min + slicing_params.first_object_layer_height + (layer_idx - config.raft_layers.size()) * slicing_params.layer_height :
|
||||
config.raft_layers[layer_idx];
|
||||
}
|
||||
|
||||
inline double first_object_support_layer_z(const SlicingParameters &slicing_params)
|
||||
{
|
||||
return slicing_params.object_print_z_min + slicing_params.first_object_layer_height;
|
||||
}
|
||||
|
||||
// Orca: Reverse layer_z() for support layers below the first object layer.
|
||||
// config.raft_layers may include raft/contact/intermediate support Zs, so do not collapse them to the first object support layer.
|
||||
// Lowest collision layer
|
||||
inline LayerIndex layer_idx_ceil(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
|
||||
{
|
||||
return
|
||||
LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, ceil((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
|
||||
const double first_object_z = first_object_support_layer_z(slicing_params);
|
||||
if (!config.raft_layers.empty() && z < first_object_z - EPSILON) {
|
||||
auto it = std::lower_bound(config.raft_layers.begin(), config.raft_layers.end(), z - EPSILON);
|
||||
return LayerIndex(it == config.raft_layers.end() ? config.raft_layers.size() : std::distance(config.raft_layers.begin(), it));
|
||||
}
|
||||
return LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, LayerIndex(std::ceil((z - first_object_z) / slicing_params.layer_height)));
|
||||
}
|
||||
// Highest collision layer
|
||||
inline LayerIndex layer_idx_floor(const SlicingParameters &slicing_params, const TreeSupportSettings &config, const double z)
|
||||
{
|
||||
return
|
||||
LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, floor((z - slicing_params.object_print_z_min - slicing_params.first_object_layer_height) / slicing_params.layer_height));
|
||||
const double first_object_z = first_object_support_layer_z(slicing_params);
|
||||
if (!config.raft_layers.empty() && z < first_object_z - EPSILON) {
|
||||
auto it = std::upper_bound(config.raft_layers.begin(), config.raft_layers.end(), z + EPSILON);
|
||||
return LayerIndex(it == config.raft_layers.begin() ? 0 : std::distance(config.raft_layers.begin(), it) - 1);
|
||||
}
|
||||
return LayerIndex(config.raft_layers.size()) +
|
||||
std::max<LayerIndex>(0, LayerIndex(std::floor((z - first_object_z) / slicing_params.layer_height)));
|
||||
}
|
||||
|
||||
inline SupportGeneratorLayer& layer_initialize(
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
|
||||
// Enable rendering of objects using environment map
|
||||
#define ENABLE_ENVIRONMENT_MAP 0
|
||||
// Enable smoothing of objects normals
|
||||
#define ENABLE_SMOOTH_NORMALS 0
|
||||
|
||||
// Enable rendering markers for options in preview as fixed screen size points
|
||||
#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS 1
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace boost { namespace filesystem { class directory_entry; }}
|
||||
namespace Slic3r {
|
||||
|
||||
extern void set_logging_level(unsigned int level);
|
||||
extern void set_logging_file(const std::string &file);
|
||||
extern unsigned int level_string_to_boost(std::string level);
|
||||
extern std::string get_string_logging_level(unsigned level);
|
||||
extern unsigned get_logging_level();
|
||||
@@ -193,6 +194,7 @@ std::string debug_out_path(const char *name, ...);
|
||||
// smaller level means less log. level=5 means saving all logs.
|
||||
void set_log_path_and_level(const std::string& file, unsigned int level);
|
||||
void flush_logs();
|
||||
boost::filesystem::path get_log_file_name();
|
||||
|
||||
// A special type for strings encoded in the local Windows 8-bit code page.
|
||||
// This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded.
|
||||
|
||||
@@ -92,7 +92,7 @@ std::string MZ_Archive::get_errorstr(mz_zip_error mz_err)
|
||||
case MZ_ZIP_TOO_MANY_FILES:
|
||||
return L("too many files");
|
||||
case MZ_ZIP_FILE_TOO_LARGE:
|
||||
return L("file too large");
|
||||
return L("File too large");
|
||||
case MZ_ZIP_UNSUPPORTED_METHOD:
|
||||
return L("unsupported method");
|
||||
case MZ_ZIP_UNSUPPORTED_ENCRYPTION:
|
||||
@@ -106,7 +106,7 @@ std::string MZ_Archive::get_errorstr(mz_zip_error mz_err)
|
||||
case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED:
|
||||
return L("invalid header or corrupted");
|
||||
case MZ_ZIP_UNSUPPORTED_MULTIDISK:
|
||||
return L("unsupported multidisk");
|
||||
return L("Saving to RAID is not supported.");
|
||||
case MZ_ZIP_DECOMPRESSION_FAILED:
|
||||
return L("decompression failed");
|
||||
case MZ_ZIP_COMPRESSION_FAILED:
|
||||
@@ -138,13 +138,13 @@ std::string MZ_Archive::get_errorstr(mz_zip_error mz_err)
|
||||
case MZ_ZIP_INVALID_FILENAME:
|
||||
return L("invalid filename");
|
||||
case MZ_ZIP_BUF_TOO_SMALL:
|
||||
return L("buffer too small");
|
||||
return L("Buffer too small");
|
||||
case MZ_ZIP_INTERNAL_ERROR:
|
||||
return L("internal error");
|
||||
case MZ_ZIP_FILE_NOT_FOUND:
|
||||
return L("file not found");
|
||||
case MZ_ZIP_ARCHIVE_TOO_LARGE:
|
||||
return L("archive too large");
|
||||
return L("Archive too large");
|
||||
case MZ_ZIP_VALIDATION_FAILED:
|
||||
return L("validation failed");
|
||||
case MZ_ZIP_WRITE_CALLBACK_FAILED:
|
||||
|
||||
@@ -129,6 +129,11 @@ void set_logging_level(unsigned int level)
|
||||
);
|
||||
}
|
||||
|
||||
void set_logging_file(const std::string &file)
|
||||
{
|
||||
boost::log::add_file_log(file);
|
||||
}
|
||||
|
||||
unsigned int level_string_to_boost(std::string level)
|
||||
{
|
||||
std::map<std::string, int> Control_Param;
|
||||
@@ -387,6 +392,14 @@ void flush_logs()
|
||||
return;
|
||||
}
|
||||
|
||||
// ORCA
|
||||
boost::filesystem::path get_log_file_name()
|
||||
{
|
||||
if (g_log_sink)
|
||||
return g_log_sink->locked_backend()->get_current_file_name();
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// The following helpers are borrowed from the LLVM project https://github.com/llvm
|
||||
namespace WindowsSupport
|
||||
|
||||
Reference in New Issue
Block a user