Merge branch 'main' into pr/Noisyfox/13712

This commit is contained in:
SoftFever
2026-06-22 10:56:11 +08:00
2782 changed files with 331936 additions and 215943 deletions
@@ -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)) {
+29
View File
@@ -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);
}
+7
View File
@@ -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) {
+1 -1
View File
@@ -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
View File
@@ -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)));
+4 -1
View File
@@ -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);
+1 -1
View File
@@ -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)
{
+1 -1
View File
@@ -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;
+10 -3
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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.)));
+1 -1
View File
@@ -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
+4 -4
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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;
+49 -1
View File
@@ -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
View File
@@ -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 &lt : 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
+8 -8
View File
@@ -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
{
+59 -33
View File
@@ -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 &region) const
unsigned int LayerTools::wall_extruder_id(const PrintRegion &region) 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 &region) const
unsigned int LayerTools::sparse_infill_filament_id(const PrintRegion &region) 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 &region) const
unsigned int LayerTools::internal_solid_filament_id(const PrintRegion &region) 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 &region) 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.
+3 -3
View File
@@ -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 &region) const;
unsigned int sparse_infill_filament(const PrintRegion &region) const;
unsigned int solid_infill_filament(const PrintRegion &region) const;
unsigned int wall_extruder_id(const PrintRegion &region) const;
unsigned int sparse_infill_filament_id(const PrintRegion &region) const;
unsigned int internal_solid_filament_id(const PrintRegion &region) 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 &region) const;
+8 -8
View File
@@ -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;
+11 -5
View File
@@ -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();
}
+1 -1
View File
@@ -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(); }
+1 -1
View File
@@ -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;
}
+6 -5
View File
@@ -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
+57 -10
View File
@@ -34,16 +34,26 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const
const PrintRegionConfig &region_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 &region_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 &region_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
View File
@@ -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;
}
+4
View File
@@ -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);
}
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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));
+107 -139
View File
@@ -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;
+3
View File
@@ -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;
+25 -8
View File
@@ -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",
+51 -5
View File
@@ -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
View File
@@ -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 &region : 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 &region : 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;
}
+6 -1
View File
@@ -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,
+16 -6
View File
@@ -815,9 +815,12 @@ bool verify_update_print_object_regions(
for (const PrintObjectRegions::PaintedRegion &region : 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());
+1 -1
View File
@@ -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());
}
}
+2 -2
View File
@@ -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.
File diff suppressed because it is too large Load Diff
+16 -4
View File
@@ -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
View File
@@ -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 &region_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);
+1 -1
View File
@@ -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)));
}
};
+33 -16
View File
@@ -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);
}
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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>
+29 -2
View File
@@ -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));
}
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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
+9 -2
View File
@@ -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
+74 -84
View File
@@ -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;
+113 -40
View File
@@ -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 &current_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();
+28 -13
View File
@@ -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(
+1 -2
View File
@@ -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
+2
View File
@@ -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.
+4 -4
View File
@@ -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:
+13
View File
@@ -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