mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-30 20:31:09 +00:00
Merge branch 'main' into feature/h2c_support_clean
This commit is contained in:
@@ -188,6 +188,18 @@ ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta)
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType)
|
||||
{
|
||||
Clipper2Lib::Paths64 subject = Slic3rExPolygons_to_Paths64(expolygons);
|
||||
Clipper2Lib::ClipperOffset offsetter;
|
||||
offsetter.AddPaths(subject, joinType, Clipper2Lib::EndType::Polygon);
|
||||
Clipper2Lib::PolyPath64 polytree;
|
||||
offsetter.Execute(delta, polytree);
|
||||
ExPolygons results = PolyTreeToExPolygons(std::move(polytree));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
ExPolygons offset2_ex_2(const ExPolygons& expolygons, double delta1, double delta2)
|
||||
{
|
||||
// 1st offset
|
||||
|
||||
@@ -15,6 +15,7 @@ Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Pol
|
||||
ExPolygons union_ex_2(const Polygons &expolygons);
|
||||
ExPolygons union_ex_2(const ExPolygons &expolygons);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta);
|
||||
ExPolygons offset_ex_2(const ExPolygons &expolygons, double delta, Clipper2Lib::JoinType joinType);
|
||||
ExPolygons offset2_ex_2(const ExPolygons &expolygons, double delta1, double delta2);
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,10 @@ struct SurfaceFillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" wave.
|
||||
bool gyroid_optimized = false;
|
||||
|
||||
bool anisotropic_surfaces{false};
|
||||
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
|
||||
bool separated_infills{false};
|
||||
|
||||
bool operator<(const SurfaceFillParams &rhs) const {
|
||||
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
|
||||
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
|
||||
@@ -301,8 +305,12 @@ struct SurfaceFillParams
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_2);
|
||||
RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_lock_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
|
||||
RETURN_COMPARE_NON_EQUAL(anisotropic_surfaces);
|
||||
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
|
||||
RETURN_COMPARE_NON_EQUAL(separated_infills);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -329,6 +337,9 @@ struct SurfaceFillParams
|
||||
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->anisotropic_surfaces == rhs.anisotropic_surfaces &&
|
||||
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
|
||||
this->separated_infills == rhs.separated_infills &&
|
||||
this->gyroid_optimized == rhs.gyroid_optimized;
|
||||
}
|
||||
};
|
||||
@@ -868,6 +879,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.lateral_lattice_angle_1 = region_config.lateral_lattice_angle_1;
|
||||
params.lateral_lattice_angle_2 = region_config.lateral_lattice_angle_2;
|
||||
params.infill_overhang_angle = region_config.infill_overhang_angle;
|
||||
params.anisotropic_surfaces = region_config.anisotropic_surfaces;
|
||||
params.center_of_surface_pattern = region_config.center_of_surface_pattern;
|
||||
params.separated_infills = region_config.separated_infills;
|
||||
if (params.pattern == ipLockedZag) {
|
||||
params.infill_lock_depth = scale_(region_config.infill_lock_depth);
|
||||
params.skin_infill_depth = scale_(region_config.skin_infill_depth);
|
||||
@@ -1309,6 +1323,22 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.config = ®ion_config;
|
||||
params.pattern = surface_fill.params.pattern;
|
||||
|
||||
// Orca: Checking the filling of a centered surface by drawing for each model parts
|
||||
bool is_top_or_bottom = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
|
||||
bool is_centered_infill = surface_fill.params.pattern == ipArchimedeanChords || surface_fill.params.pattern == ipOctagramSpiral;
|
||||
if (is_top_or_bottom) {
|
||||
params.is_anisotropic = surface_fill.params.anisotropic_surfaces; // Orca: anisotropic surfaces
|
||||
params.center_of_surface_pattern = surface_fill.params.center_of_surface_pattern; // Orca: center of surface pattern
|
||||
}
|
||||
// Orca: Each_Model centers the pattern on each model part's bbox; Each_Surface / Each_Assembly
|
||||
// fall through to the default (whole-object) bounding box below.
|
||||
bool is_per_model_center = is_top_or_bottom && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model && is_centered_infill;
|
||||
bool is_separate_infill = !is_top_or_bottom && surface_fill.params.separated_infills &&
|
||||
(
|
||||
is_centered_infill ||
|
||||
params.config->solid_infill_rotate_template != "" ||
|
||||
params.config->sparse_infill_rotate_template != "" );
|
||||
|
||||
if( surface_fill.params.pattern == ipLockedZag ) {
|
||||
params.locked_zag = true;
|
||||
params.infill_lock_depth = surface_fill.params.infill_lock_depth;
|
||||
@@ -1332,7 +1362,65 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.can_reverse = false;
|
||||
for (ExPolygon& expoly : surface_fill.expolygons) {
|
||||
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
// Orca: separate infill / per-model pattern centering.
|
||||
//
|
||||
// First assign this fill region to the model part whose slice at this layer overlaps it
|
||||
// the most. A strict "contains" test is ambiguous for assemblies whose parts overlap (a
|
||||
// region may sit inside several parts, or straddle a boundary and be inside none), so we
|
||||
// pick by intersection area instead.
|
||||
//
|
||||
// The center must belong to an *overlap group*, not a single part: parts that
|
||||
// touch/overlap form one connected physical body that shares a single center, while a
|
||||
// part detached from the rest of the assembly gets its own. This holds for both
|
||||
// separated infills and Each_Model surface centering (Each_Model == per connected body).
|
||||
// firstLayerObjGroups() already holds these connected components, so we widen the chosen
|
||||
// part's bbox to the whole group it belongs to.
|
||||
if (is_per_model_center || is_separate_infill) {
|
||||
double best_overlap = 0.;
|
||||
ObjectID best_vol_id;
|
||||
const PrintInstance* best_instance = nullptr;
|
||||
for (const auto& instance : this->object()->instances()) {
|
||||
for (const auto& volume : instance.print_object->firstLayerObjSlice()) {
|
||||
if (f->layer_id >= volume.slices.size())
|
||||
continue;
|
||||
const double overlap = area(intersection_ex(volume.slices[f->layer_id], ExPolygons{expoly}));
|
||||
if (overlap > best_overlap) {
|
||||
best_overlap = overlap;
|
||||
best_vol_id = volume.volume_id;
|
||||
best_instance = &instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_instance) {
|
||||
const Transform3d matrix = best_instance->model_instance->get_matrix();
|
||||
Point shift = best_instance->shift; // get_volume_bbox takes a non-const ref
|
||||
auto& volumes = best_instance->model_instance->get_object()->volumes;
|
||||
|
||||
// Volume ids to center on: the whole overlap group the winning part belongs to,
|
||||
// falling back to just that part if it isn't part of any group.
|
||||
std::vector<ObjectID> center_ids;
|
||||
for (const auto& group : best_instance->print_object->firstLayerObjGroups()) {
|
||||
bool in_group = false;
|
||||
for (const ObjectID& vid : group.volume_ids)
|
||||
if (vid == best_vol_id) { in_group = true; break; }
|
||||
if (in_group) { center_ids = group.volume_ids; break; }
|
||||
}
|
||||
if (center_ids.empty())
|
||||
center_ids.push_back(best_vol_id);
|
||||
|
||||
BoundingBox bbox;
|
||||
for (const ObjectID& vid : center_ids)
|
||||
for (auto model_volume : volumes)
|
||||
if (vid.id == model_volume->id().id) {
|
||||
bbox.merge(model_volume->get_volume_bbox(matrix, shift, true));
|
||||
break;
|
||||
}
|
||||
if (bbox.defined)
|
||||
f->set_bounding_box(bbox);
|
||||
}
|
||||
} // - End: separate infill / per-model pattern centering
|
||||
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
if (params.symmetric_infill_y_axis) {
|
||||
params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
|
||||
expoly.symmetric_y(params.symmetric_y_axis);
|
||||
|
||||
@@ -165,7 +165,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
|
||||
// ORCA: special flag for flow rate calibration
|
||||
auto is_flow_calib = params.extrusion_role == erTopSolidInfill && this->print_object_config->has("calib_flowrate_topinfill_special_order") &&
|
||||
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool();
|
||||
if (is_flow_calib) {
|
||||
if (is_flow_calib || params.is_anisotropic) { // Orca: disable sorting while anisotropic surfaces
|
||||
eec->no_sort = true;
|
||||
}
|
||||
size_t idx = eec->entities.size();
|
||||
@@ -186,7 +186,8 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
|
||||
}
|
||||
|
||||
// Orca: run gap fill
|
||||
this->_create_gap_fill(surface, params, eec);
|
||||
if (!(params.is_anisotropic)) // Orca: Disable gap filling while anisotropic
|
||||
this->_create_gap_fill(surface, params, eec);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ struct FillParams
|
||||
bool locked_zag{false};
|
||||
float infill_lock_depth{0.0};
|
||||
float skin_infill_depth{0.0};
|
||||
bool is_anisotropic{false};
|
||||
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
|
||||
};
|
||||
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
|
||||
|
||||
|
||||
@@ -77,20 +77,24 @@ void FillPlanePath::_fill_surface_single(
|
||||
|
||||
//FIXME Vojtech: We are not sure whether the user expects the fill patterns on visible surfaces to be aligned across all the islands of a single layer.
|
||||
// One may align for this->centered() to align the patterns for Archimedean Chords and Octagram Spiral patterns.
|
||||
const bool align = params.density < 0.995;
|
||||
|
||||
// Orca: the old implementation became obsolete when it became possible to change the density of the top and bottom surfaces
|
||||
bool align = params.extrusion_role == ExtrusionRole::erInternalInfill;
|
||||
BoundingBox bounding_box;
|
||||
BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON);
|
||||
|
||||
// Expand the bounding box to avoid artifacts at the edges
|
||||
snug_bounding_box.offset(scale_(this->spacing)*params.multiline);
|
||||
|
||||
// Rotated bounding box of the area to fill in with the pattern.
|
||||
BoundingBox bounding_box = align ?
|
||||
// Sparse infill needs to be aligned across layers. Align infill across layers using the object's bounding box.
|
||||
this->bounding_box.rotated(-direction.first) :
|
||||
// Solid infill does not need to be aligned across layers, generate the infill pattern
|
||||
// around the clipping expolygon only.
|
||||
snug_bounding_box;
|
||||
// Sparse infill (or Internal where align == true) needs to be aligned across layers. Align infill across layers using the object's bounding box.
|
||||
// Solid infill does not need to be aligned across layers, generate the infill pattern around the clipping expolygon only.
|
||||
if (align)
|
||||
bounding_box = this->bounding_box.rotated(-direction.first);
|
||||
else if (params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Surface)
|
||||
bounding_box = snug_bounding_box;
|
||||
else if (params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model)
|
||||
bounding_box = this->bounding_box.rotated(-direction.first);
|
||||
else
|
||||
bounding_box = extended_object_bounding_box();
|
||||
|
||||
Point shift = this->centered() ?
|
||||
bounding_box.center() :
|
||||
@@ -129,35 +133,49 @@ void FillPlanePath::_fill_surface_single(
|
||||
polylines = intersection_pl(std::move(polylines), expolygon);
|
||||
if (!polylines.empty()) {
|
||||
Polylines chained;
|
||||
if (params.dont_connect() || params.density > 0.5) {
|
||||
// ORCA: special flag for flow rate calibration
|
||||
auto is_flow_calib = params.extrusion_role == erTopSolidInfill &&
|
||||
this->print_object_config->has("calib_flowrate_topinfill_special_order") &&
|
||||
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool() &&
|
||||
dynamic_cast<FillArchimedeanChords*>(this);
|
||||
if (is_flow_calib) {
|
||||
// We want the spiral part to be printed inside-out
|
||||
// Find the center spiral line first, by looking for the longest one
|
||||
auto it = std::max_element(polylines.begin(), polylines.end(),
|
||||
[](const Polyline& a, const Polyline& b) { return a.length() < b.length(); });
|
||||
Polyline center_spiral = std::move(*it);
|
||||
if (!params.is_anisotropic) { // Orca: not anisotropic surface
|
||||
if ((params.dont_connect() || params.density > 0.5)) {
|
||||
// ORCA: special flag for flow rate calibration
|
||||
auto is_flow_calib = params.extrusion_role == erTopSolidInfill &&
|
||||
this->print_object_config->has("calib_flowrate_topinfill_special_order") &&
|
||||
this->print_object_config->option("calib_flowrate_topinfill_special_order")->getBool() &&
|
||||
dynamic_cast<FillArchimedeanChords*>(this);
|
||||
if (is_flow_calib) {
|
||||
// We want the spiral part to be printed inside-out
|
||||
// Find the center spiral line first, by looking for the longest one
|
||||
auto it = std::max_element(polylines.begin(), polylines.end(),
|
||||
[](const Polyline& a, const Polyline& b) { return a.length() < b.length(); });
|
||||
Polyline center_spiral = std::move(*it);
|
||||
|
||||
// Ensure the spiral is printed from inside to out
|
||||
if (center_spiral.first_point().squaredNorm() > center_spiral.last_point().squaredNorm()) {
|
||||
center_spiral.reverse();
|
||||
// Ensure the spiral is printed from inside to out
|
||||
if ((center_spiral.first_point().squaredNorm() > center_spiral.last_point().squaredNorm())) {
|
||||
center_spiral.reverse();
|
||||
}
|
||||
|
||||
// Chain the other polylines
|
||||
polylines.erase(it);
|
||||
chained = chain_polylines(std::move(polylines), nullptr);
|
||||
|
||||
// Then add the center spiral back
|
||||
chained.push_back(std::move(center_spiral));
|
||||
} else {
|
||||
chained = chain_polylines(std::move(polylines), nullptr);
|
||||
}
|
||||
|
||||
// Chain the other polylines
|
||||
polylines.erase(it);
|
||||
chained = chain_polylines(std::move(polylines));
|
||||
|
||||
// Then add the center spiral back
|
||||
chained.push_back(std::move(center_spiral));
|
||||
} else {
|
||||
chained = chain_polylines(std::move(polylines));
|
||||
} else
|
||||
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
|
||||
} else { // Orca: anisotropic surface
|
||||
const Point _center(0., 0.);
|
||||
for (Polyline& segment : polylines) { // sort paths by its direction
|
||||
if (segment.size() > 1) { // need at least two points to evaluate direction
|
||||
if (segment.first_point().ccw(segment.points[1], _center) < 0)
|
||||
segment.reverse();
|
||||
}
|
||||
chained.emplace_back(std::move(segment));
|
||||
}
|
||||
} else
|
||||
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
|
||||
std::sort(chained.begin(), chained.end(), [&_center](const Polyline& a, const Polyline& b) { // just sort polylines from center to outside
|
||||
return a.distance_to(_center) < b.distance_to(_center);
|
||||
});
|
||||
}
|
||||
// paths must be repositioned and rotated back
|
||||
for (Polyline& pl : chained) {
|
||||
pl.translate(shift.x(), shift.y());
|
||||
|
||||
@@ -6861,7 +6861,8 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
|
||||
static constexpr const char* support_transition_label = "support transition";
|
||||
static constexpr const char* support_ironing_label = "support ironing";
|
||||
|
||||
static const auto speed_for_path = [&](double length, ExtrusionRole role, double default_speed = -1.0) {
|
||||
// Not static: it captures `this` by reference.
|
||||
const auto speed_for_path = [&](double length, ExtrusionRole role, double default_speed = -1.0) {
|
||||
if (!is_support(role) || length > SMALL_PERIMETER_LENGTH(NOZZLE_CONFIG(small_support_perimeter_threshold)))
|
||||
return default_speed;
|
||||
|
||||
|
||||
@@ -142,7 +142,8 @@ static double calc_max_layer_height(const PrintConfig &config, double max_object
|
||||
{
|
||||
double max_layer_height = std::numeric_limits<double>::max();
|
||||
for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) {
|
||||
double mlh = config.max_layer_height.values[i];
|
||||
// max_layer_height may be shorter than the extruder count; get_at() clamps.
|
||||
double mlh = config.max_layer_height.get_at(i);
|
||||
if (mlh == 0.)
|
||||
mlh = 0.75 * config.nozzle_diameter.values[i];
|
||||
max_layer_height = std::min(max_layer_height, mlh);
|
||||
@@ -213,6 +214,10 @@ static void apply_first_layer_order(const DynamicPrintConfig* config, std::vecto
|
||||
|
||||
void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& tool_order_layer0)
|
||||
{
|
||||
const PrintConfig* print_config = m_print_config_ptr;
|
||||
if (!print_config && m_print_object_ptr)
|
||||
print_config = &m_print_object_ptr->print()->config();
|
||||
|
||||
if(m_layer_tools.empty() || tool_order_layer0.empty())
|
||||
return;
|
||||
|
||||
@@ -247,6 +252,8 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& too
|
||||
for (int i = 1; i < m_layer_tools.size(); i++) {
|
||||
LayerTools& lt = m_layer_tools[i];
|
||||
|
||||
// Extruders in lt.extruders are already sorted.
|
||||
|
||||
if (lt.extruders.empty())
|
||||
continue;
|
||||
if (lt.extruders.size() == 1 && lt.extruders.front() == 0)
|
||||
@@ -255,14 +262,23 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& too
|
||||
if (lt.extruders.front() == 0)
|
||||
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
||||
lt.extruders.erase(lt.extruders.begin());
|
||||
// Reorder the extruders to start with the last one.
|
||||
for (size_t i = 1; i < lt.extruders.size(); ++i)
|
||||
if (lt.extruders[i] == last_extruder_id) {
|
||||
// Move the last extruder to the front.
|
||||
memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int));
|
||||
lt.extruders.front() = last_extruder_id;
|
||||
break;
|
||||
|
||||
if (print_config == nullptr
|
||||
|| print_config->toolchange_ordering == ToolChangeOrderingType::Default)
|
||||
{
|
||||
// Reorder the extruders to start with the last one.
|
||||
for (size_t i = 1; i < lt.extruders.size(); ++i) {
|
||||
if (lt.extruders[i] == last_extruder_id) {
|
||||
// Move the last extruder to the front.
|
||||
std::rotate(
|
||||
lt.extruders.begin(),
|
||||
lt.extruders.begin() + i,
|
||||
lt.extruders.begin() + i + 1
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
last_extruder_id = lt.extruders.back();
|
||||
}
|
||||
@@ -278,6 +294,10 @@ void ToolOrdering::handle_dontcare_extruder(const std::vector<unsigned int>& too
|
||||
|
||||
void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id)
|
||||
{
|
||||
const PrintConfig* print_config = m_print_config_ptr;
|
||||
if (!print_config && m_print_object_ptr)
|
||||
print_config = &m_print_object_ptr->print()->config();
|
||||
|
||||
if(m_layer_tools.empty())
|
||||
return;
|
||||
if(last_extruder_id == (unsigned int)-1){
|
||||
@@ -301,6 +321,8 @@ void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id)
|
||||
}
|
||||
|
||||
for (LayerTools < : m_layer_tools) {
|
||||
// Extruders in lt.extruders are already sorted.
|
||||
|
||||
if (lt.extruders.empty())
|
||||
continue;
|
||||
if (lt.extruders.size() == 1 && lt.extruders.front() == 0)
|
||||
@@ -309,21 +331,30 @@ void ToolOrdering::handle_dontcare_extruder(unsigned int last_extruder_id)
|
||||
if (lt.extruders.front() == 0)
|
||||
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
|
||||
lt.extruders.erase(lt.extruders.begin());
|
||||
// Reorder the extruders to start with the last one.
|
||||
for (size_t i = 1; i < lt.extruders.size(); ++ i)
|
||||
if (lt.extruders[i] == last_extruder_id) {
|
||||
// Move the last extruder to the front.
|
||||
memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int));
|
||||
lt.extruders.front() = last_extruder_id;
|
||||
break;
|
||||
|
||||
if (print_config == nullptr
|
||||
|| print_config->toolchange_ordering == ToolChangeOrderingType::Default)
|
||||
{
|
||||
// Reorder the extruders to start with the last one.
|
||||
for (size_t i = 1; i < lt.extruders.size(); ++i) {
|
||||
if (lt.extruders[i] == last_extruder_id) {
|
||||
// Move the last extruder to the front.
|
||||
std::rotate(
|
||||
lt.extruders.begin(),
|
||||
lt.extruders.begin() + i,
|
||||
lt.extruders.begin() + i + 1
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lt == m_layer_tools[0]) {
|
||||
// On first layer with wipe tower, prefer a soluble extruder
|
||||
// at the beginning, so it is not wiped on the first layer.
|
||||
if (m_print_config_ptr && m_print_config_ptr->enable_prime_tower) {
|
||||
if (print_config && print_config->enable_prime_tower) {
|
||||
for (size_t i = 0; i<lt.extruders.size(); ++i)
|
||||
if (m_print_config_ptr->filament_soluble.get_at(lt.extruders[i]-1)) { // 1-based...
|
||||
if (print_config->filament_soluble.get_at(lt.extruders[i]-1)) { // 1-based...
|
||||
std::swap(lt.extruders[i], lt.extruders.front());
|
||||
break;
|
||||
}
|
||||
@@ -422,6 +453,7 @@ void ToolOrdering::sort_and_build_data(const PrintObject& object , unsigned int
|
||||
ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material)
|
||||
{
|
||||
m_print_full_config = &object.print()->full_print_config();
|
||||
m_print_config_ptr = &object.print()->config();
|
||||
m_print_object_ptr = &object;
|
||||
m_print = const_cast<Print*>(object.print());
|
||||
if (object.layers().empty())
|
||||
@@ -1940,8 +1972,11 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
if (!m_layer_tools.empty())
|
||||
first_layer_filaments = m_layer_tools[0].extruders;
|
||||
|
||||
const bool use_cyclic_ordering =
|
||||
(print_config->toolchange_ordering == ToolChangeOrderingType::Cyclic);
|
||||
|
||||
// other_layers_seq: the layer_idx and extruder_idx are base on 1
|
||||
auto get_custom_seq = [&other_layers_seqs, &reorder_first_layer, &first_layer_filaments](int layer_idx, std::vector<int>& out_seq) -> bool {
|
||||
auto get_custom_seq = [&other_layers_seqs, &reorder_first_layer, &first_layer_filaments, &layer_filaments, use_cyclic_ordering](int layer_idx, std::vector<int>& out_seq) -> bool {
|
||||
if (!reorder_first_layer && layer_idx == 0) {
|
||||
out_seq.resize(first_layer_filaments.size());
|
||||
std::transform(first_layer_filaments.begin(), first_layer_filaments.end(), out_seq.begin(), [](auto item) {return item + 1; });
|
||||
@@ -1954,6 +1989,15 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_cyclic_ordering && layer_idx >= 0 && size_t(layer_idx) < layer_filaments.size()) {
|
||||
std::vector<unsigned int> ordered = layer_filaments[size_t(layer_idx)];
|
||||
std::sort(ordered.begin(), ordered.end());
|
||||
out_seq.resize(ordered.size());
|
||||
std::transform(ordered.begin(), ordered.end(), out_seq.begin(), [](auto item) { return int(item) + 1; });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -2708,6 +2708,23 @@ const TriangleMesh& ModelVolume::get_convex_hull() const
|
||||
return *m_convex_hull.get();
|
||||
}
|
||||
|
||||
// Orca: get volume bbox for separate infill
|
||||
static std::mutex mtx_model;
|
||||
BoundingBox ModelVolume::get_volume_bbox(const Transform3d &matrix, Point &shift, bool apply_cache = false) {
|
||||
std::unique_lock l(mtx_model); // locks function here
|
||||
// Orca: the cache is keyed by the instance transform/shift; a ModelVolume is shared
|
||||
// across instances, so returning the cache blindly would hand back another instance's bbox.
|
||||
if (m_cached_volume_bbox.defined && apply_cache
|
||||
&& matrix.isApprox(m_cached_volume_bbox_matrix)
|
||||
&& shift == m_cached_volume_bbox_shift)
|
||||
return m_cached_volume_bbox;
|
||||
auto hull = get_convex_hull_2d(matrix);
|
||||
hull.translate(-shift);
|
||||
m_cached_volume_bbox_matrix = matrix;
|
||||
m_cached_volume_bbox_shift = shift;
|
||||
return m_cached_volume_bbox = hull.bounding_box().polygon().bounding_box();
|
||||
}
|
||||
|
||||
//BBS: refine the model part names
|
||||
ModelVolumeType ModelVolume::type_from_string(const std::string &s)
|
||||
{
|
||||
|
||||
+24
-10
@@ -920,6 +920,14 @@ public:
|
||||
// Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes).
|
||||
int extruder_id() const;
|
||||
|
||||
//Orca: cache clearing procedure to ensure that the shape is positioned accurately when manipulating it
|
||||
void clear_cache() {
|
||||
m_cached_trans_matrix = Transform3d::Identity().inverse(); // get unvelivable matrix
|
||||
m_cached_volume_bbox.reset();
|
||||
m_convex_hull_2d.clear();
|
||||
m_cached_2d_polygon.clear();
|
||||
};
|
||||
|
||||
bool is_splittable() const;
|
||||
|
||||
// BBS
|
||||
@@ -961,39 +969,42 @@ public:
|
||||
// Get count of errors in the mesh
|
||||
int get_repaired_errors_count() const;
|
||||
|
||||
BoundingBox get_volume_bbox(const Transform3d &matrix, Point &shift, bool apply_cache);
|
||||
void reset_volume_bbox() { m_cached_volume_bbox.reset(); };
|
||||
|
||||
// Helpers for loading / storing into AMF / 3MF files.
|
||||
static ModelVolumeType type_from_string(const std::string &s);
|
||||
static std::string type_to_string(const ModelVolumeType t);
|
||||
|
||||
const Geometry::Transformation& get_transformation() const { return m_transformation; }
|
||||
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
|
||||
void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); }
|
||||
void set_transformation(const Geometry::Transformation& transformation) { clear_cache(); m_transformation = transformation; }
|
||||
void set_transformation(const Transform3d& trafo) { clear_cache(); m_transformation.set_matrix(trafo); }
|
||||
|
||||
Vec3d get_offset() const { return m_transformation.get_offset(); }
|
||||
|
||||
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
|
||||
|
||||
void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
|
||||
void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
|
||||
void set_offset(const Vec3d& offset) { clear_cache(); m_transformation.set_offset(offset); }
|
||||
void set_offset(Axis axis, double offset) { clear_cache(); m_transformation.set_offset(axis, offset); }
|
||||
|
||||
Vec3d get_rotation() const { return m_transformation.get_rotation(); }
|
||||
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
|
||||
|
||||
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
|
||||
void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
|
||||
void set_rotation(const Vec3d& rotation) { clear_cache(); m_transformation.set_rotation(rotation); }
|
||||
void set_rotation(Axis axis, double rotation) { clear_cache(); m_transformation.set_rotation(axis, rotation); }
|
||||
|
||||
Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
|
||||
double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
|
||||
|
||||
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
|
||||
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
|
||||
void set_scaling_factor(const Vec3d& scaling_factor) { clear_cache(); m_transformation.set_scaling_factor(scaling_factor); }
|
||||
void set_scaling_factor(Axis axis, double scaling_factor) {clear_cache(); m_transformation.set_scaling_factor(axis, scaling_factor); }
|
||||
|
||||
Vec3d get_mirror() const { return m_transformation.get_mirror(); }
|
||||
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
|
||||
bool is_left_handed() const { return m_transformation.is_left_handed(); }
|
||||
|
||||
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
|
||||
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
|
||||
void set_mirror(const Vec3d& mirror) { clear_cache(); m_transformation.set_mirror(mirror); }
|
||||
void set_mirror(Axis axis, double mirror) { clear_cache(); m_transformation.set_mirror(axis, mirror); }
|
||||
void convert_from_imperial_units();
|
||||
void convert_from_meters();
|
||||
|
||||
@@ -1048,6 +1059,9 @@ private:
|
||||
mutable Transform3d m_cached_trans_matrix; //BBS, used for convex_hell_2d acceleration
|
||||
mutable Polygon m_cached_2d_polygon; //BBS, used for convex_hell_2d acceleration
|
||||
Geometry::Transformation m_transformation;
|
||||
mutable BoundingBox m_cached_volume_bbox; //Orca: used for separated infills
|
||||
mutable Transform3d m_cached_volume_bbox_matrix{Transform3d::Identity()}; //Orca: cache key for m_cached_volume_bbox
|
||||
mutable Point m_cached_volume_bbox_shift{Point(0, 0)}; //Orca: cache key for m_cached_volume_bbox
|
||||
|
||||
//BBS: add convex_hell_2d related logic
|
||||
void calculate_convex_hull_2d(const Geometry::Transformation &transformation) const;
|
||||
|
||||
@@ -571,6 +571,19 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
return extrusion_coll;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top detects the top as "slice − upper", so a feature rising from the middle of a
|
||||
// top surface becomes an enclosed hole that gets ringed with extra inner walls. Fill those holes back
|
||||
// into the top. Only holes that are both covered by the upper layer (excludes bridges) and backed by
|
||||
// solid material (excludes voids) are filled.
|
||||
static ExPolygons fill_enclosed_top_feature_holes(const ExPolygons &top, const Polygons &covered_by_upper, const ExPolygons &solid)
|
||||
{
|
||||
ExPolygons filled = top;
|
||||
for (ExPolygon &ex : filled)
|
||||
ex.holes.clear();
|
||||
const ExPolygons feature_holes = intersection_ex(intersection_ex(diff_ex(filled, top), covered_by_upper), solid);
|
||||
return feature_holes.empty() ? top : union_ex(top, feature_holes);
|
||||
}
|
||||
|
||||
void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills,
|
||||
ExPolygons &non_top_polygons, ExPolygons &fill_clip) const {
|
||||
// other perimeters
|
||||
@@ -636,6 +649,8 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
|
||||
top_polygons = fill_enclosed_top_feature_holes(top_polygons, upper_polygons_series_clipped, orig_polygons);
|
||||
|
||||
// get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the
|
||||
// min_width_top_surface we removed a bit before)
|
||||
ExPolygons temp_gap = diff_ex(top_polygons, fill_clip);
|
||||
@@ -2194,6 +2209,7 @@ void PerimeterGenerator::process_arachne()
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox);
|
||||
|
||||
top_expolygons = diff_ex(infill_contour, upper_slices_clipped);
|
||||
top_expolygons = fill_enclosed_top_feature_holes(top_expolygons, upper_slices_clipped, infill_contour);
|
||||
|
||||
if (!top_expolygons.empty()) {
|
||||
if (lower_slices != nullptr) {
|
||||
|
||||
@@ -1044,6 +1044,9 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"lightning_prune_angle",
|
||||
"lightning_straightening_angle",
|
||||
"top_surface_pattern",
|
||||
"top_surface_expansion",
|
||||
"top_surface_expansion_margin",
|
||||
"top_surface_expansion_direction",
|
||||
"bottom_surface_pattern",
|
||||
"infill_direction",
|
||||
"solid_infill_direction",
|
||||
@@ -1060,6 +1063,9 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"skin_infill_density",
|
||||
"align_infill_direction_to_model",
|
||||
"extra_solid_infills",
|
||||
"anisotropic_surfaces",
|
||||
"center_of_surface_pattern",
|
||||
"separated_infills",
|
||||
"minimum_sparse_infill_area",
|
||||
"reduce_infill_retraction",
|
||||
"internal_solid_infill_pattern",
|
||||
@@ -1272,6 +1278,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"wipe_tower_bridging",
|
||||
"wipe_tower_extra_flow",
|
||||
"single_extruder_multi_material_priming",
|
||||
"toolchange_ordering",
|
||||
"wipe_tower_rotation_angle",
|
||||
"tree_support_branch_distance_organic",
|
||||
"tree_support_branch_diameter_organic",
|
||||
|
||||
@@ -209,6 +209,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"chamber_minimal_temperature",
|
||||
"thumbnails",
|
||||
"thumbnails_format",
|
||||
"anisotropic_surfaces", "center_of_surface_pattern", "separated_infills",
|
||||
"seam_gap",
|
||||
"role_based_wipe_speed",
|
||||
"wipe_speed",
|
||||
@@ -330,6 +331,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "first_layer_print_sequence"
|
||||
|| opt_key == "other_layers_print_sequence"
|
||||
|| opt_key == "other_layers_print_sequence_nums"
|
||||
|| opt_key == "toolchange_ordering"
|
||||
|| opt_key == "extruder_ams_count"
|
||||
|| opt_key == "filament_map_mode"
|
||||
|| opt_key == "filament_map"
|
||||
|
||||
@@ -197,6 +197,12 @@ static t_config_enum_values s_keys_map_PowerLossRecoveryMode {
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PowerLossRecoveryMode)
|
||||
|
||||
static t_config_enum_values s_keys_map_CenterOfSurfacePattern{
|
||||
{"each_surface", int(CenterOfSurfacePattern::Each_Surface)},
|
||||
{"each_model", int(CenterOfSurfacePattern::Each_Model)},
|
||||
{"each_assembly", int(CenterOfSurfacePattern::Each_Assembly)}};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(CenterOfSurfacePattern)
|
||||
|
||||
static t_config_enum_values s_keys_map_FuzzySkinType {
|
||||
{ "none", int(FuzzySkinType::None) },
|
||||
{ "external", int(FuzzySkinType::External) },
|
||||
@@ -230,6 +236,13 @@ static t_config_enum_values s_keys_map_FuzzySkinMode {
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinMode)
|
||||
|
||||
static t_config_enum_values s_keys_map_TopSurfaceExpansionDirection {
|
||||
{ "inward_and_outward", int(TopSurfaceExpansionDirection::InwardAndOutward) },
|
||||
{ "inward", int(TopSurfaceExpansionDirection::Inward) },
|
||||
{ "outward", int(TopSurfaceExpansionDirection::Outward) }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TopSurfaceExpansionDirection)
|
||||
|
||||
static t_config_enum_values s_keys_map_InfillPattern {
|
||||
{ "monotonic", ipMonotonic },
|
||||
{ "monotonicline", ipMonotonicLine },
|
||||
@@ -531,6 +544,12 @@ static t_config_enum_values s_keys_map_PerimeterGeneratorType{
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PerimeterGeneratorType)
|
||||
|
||||
static t_config_enum_values s_keys_map_ToolChangeOrderingType {
|
||||
{ "default", int(ToolChangeOrderingType::Default) },
|
||||
{ "cyclic", int(ToolChangeOrderingType::Cyclic) }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ToolChangeOrderingType)
|
||||
|
||||
static const t_config_enum_values s_keys_map_ZHopType = {
|
||||
{ "Auto Lift", zhtAuto },
|
||||
{ "Normal Lift", zhtNormal },
|
||||
@@ -2191,6 +2210,47 @@ void PrintConfigDef::init_fff_params()
|
||||
def->max = 100;
|
||||
def->set_default_value(new ConfigOptionPercent(100));
|
||||
|
||||
def = this->add("top_surface_expansion", coFloat);
|
||||
def->label = L("Top surface expansion");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
|
||||
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane."
|
||||
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top."
|
||||
"The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
|
||||
def = this->add("top_surface_expansion_margin", coFloat);
|
||||
def->label = L("Top expansion wall margin");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Using “Top surface expansion” may cause a surface that did not previously touch the model's outer walls to now do so.\n"
|
||||
"This can cause contraction marks (such as the hull line) on the outer walls.\n"
|
||||
"By adding a small margin, this contraction will not occur directly on the walls, thereby preventing a visible mark.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 10;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
|
||||
def = this->add("top_surface_expansion_direction", coEnum);
|
||||
def->label = L("Top expansion direction");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Direction in which the top surface expansion grows.\n"
|
||||
" - Inward grows into the holes and gaps left by features rising from the middle of a top surface.\n"
|
||||
" - Outward grows the outer edge of the surface, connecting surfaces separated by features that can divide a surface, such as a lattice pattern.\n"
|
||||
" - Inward and Outward does both.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<TopSurfaceExpansionDirection>::get_enum_values();
|
||||
def->enum_values.push_back("inward_and_outward");
|
||||
def->enum_values.push_back("inward");
|
||||
def->enum_values.push_back("outward");
|
||||
def->enum_labels.push_back(L("Inward and Outward"));
|
||||
def->enum_labels.push_back(L("Inward"));
|
||||
def->enum_labels.push_back(L("Outward"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<TopSurfaceExpansionDirection>(TopSurfaceExpansionDirection::InwardAndOutward));
|
||||
|
||||
def = this->add("bottom_surface_pattern", coEnum);
|
||||
def->label = L("Bottom surface pattern");
|
||||
def->category = L("Strength");
|
||||
@@ -6314,6 +6374,22 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("toolchange_ordering", coEnum);
|
||||
def->label = L("Toolchange ordering");
|
||||
def->category = L("Advanced");
|
||||
def->tooltip = L(
|
||||
"Determines the order of tool changes on each layer.\n"
|
||||
"- Default: Starts with the last used extruder to minimize tool changes.\n"
|
||||
"- Cyclic: Uses a fixed tool sequence each layer. This sacrifices speed for better surface quality, as the extra toolchanges allow layers more time to cool."
|
||||
);
|
||||
def->mode = comAdvanced;
|
||||
def->enum_keys_map = &ConfigOptionEnum<ToolChangeOrderingType>::get_enum_values();
|
||||
def->enum_values.emplace_back("default");
|
||||
def->enum_values.emplace_back("cyclic");
|
||||
def->enum_labels.emplace_back(L("Default"));
|
||||
def->enum_labels.emplace_back(L("Cyclic"));
|
||||
def->set_default_value(new ConfigOptionEnum<ToolChangeOrderingType>(ToolChangeOrderingType::Default));
|
||||
|
||||
def = this->add("slice_closing_radius", coFloat);
|
||||
def->label = L("Slice gap closing radius");
|
||||
def->category = L("Quality");
|
||||
@@ -7037,6 +7113,48 @@ void PrintConfigDef::init_fff_params()
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(0.6));
|
||||
|
||||
def = this->add("anisotropic_surfaces", coBool);
|
||||
def->label = L("Anisotropic surfaces");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Anisotropic patterns on the top and bottom surfaces.\n"
|
||||
"Co-directional printing mode will be applied. For certain patterns, omni-directional filling provides color "
|
||||
"dispersion when using multi-colored or silk plastics.\n"
|
||||
"This option disable the gap fill.\n"
|
||||
"This option can increase a printing time.");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("separated_infills", coBool);
|
||||
def->label = L("Separated infills");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Aligns the internal infill pattern of each part independently instead of across the whole object or assembly.\n"
|
||||
"By default, aligned infill patterns share a single origin for the entire object, so the pattern of every "
|
||||
"part is referenced to the same point. When enabled, each connected body is aligned on its own: parts that "
|
||||
"touch or overlap are treated as one body and share an origin, while parts detached from the rest each get "
|
||||
"their own.\n Useful when an assembly groups several distinct objects that should each keep a self-centered infill.\n"
|
||||
"Only affects centered infill patterns (Archimedean Chords, Octagram Spiral) and patterns driven by an "
|
||||
"infill rotation template.");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("center_of_surface_pattern", coEnum);
|
||||
def->label = L("Center surface pattern on");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Chooses where the centering point of centered top/bottom surface patterns (Archimedean Chords, "
|
||||
"Octagram Spiral) is placed.\n"
|
||||
" - Each Surface: centers the pattern on every individual surface region, so each island is symmetric on its own.\n"
|
||||
" - Each Model: centers the pattern on each connected body. Parts that touch or overlap share one center; "
|
||||
"parts detached from the rest each get their own.\n"
|
||||
" - Each Assembly: uses a single shared center for the whole object or assembly.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<CenterOfSurfacePattern>::get_enum_values();
|
||||
def->enum_values.push_back("each_surface");
|
||||
def->enum_values.push_back("each_model");
|
||||
def->enum_values.push_back("each_assembly");
|
||||
def->enum_labels.push_back(L("Each Surface"));
|
||||
def->enum_labels.push_back(L("Each Model"));
|
||||
def->enum_labels.push_back(L("Each Assembly"));
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionEnum<CenterOfSurfacePattern>(CenterOfSurfacePattern::Each_Surface));
|
||||
|
||||
def = this->add("travel_speed", coFloats);
|
||||
def->label = L("Travel");
|
||||
|
||||
@@ -70,6 +70,19 @@ enum class FuzzySkinMode {
|
||||
Combined,
|
||||
};
|
||||
|
||||
// ORCA: direction in which top_surface_expansion grows the top surfaces.
|
||||
enum class TopSurfaceExpansionDirection {
|
||||
InwardAndOutward,
|
||||
Inward,
|
||||
Outward,
|
||||
};
|
||||
|
||||
enum class CenterOfSurfacePattern {
|
||||
Each_Surface,
|
||||
Each_Model,
|
||||
Each_Assembly,
|
||||
};
|
||||
|
||||
enum class NoiseType {
|
||||
Classic,
|
||||
Perlin,
|
||||
@@ -308,6 +321,12 @@ enum class PerimeterGeneratorType
|
||||
Arachne
|
||||
};
|
||||
|
||||
enum class ToolChangeOrderingType
|
||||
{
|
||||
Default,
|
||||
Cyclic,
|
||||
};
|
||||
|
||||
// BBS
|
||||
enum OverhangFanThreshold {
|
||||
Overhang_threshold_none = 0,
|
||||
@@ -561,6 +580,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopSurfaceExpansionDirection)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WipeTowerType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NoiseType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InfillPattern)
|
||||
@@ -588,6 +608,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrintHostType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(AuthorizationType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WipeTowerWallType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
|
||||
|
||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||
@@ -1149,6 +1170,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, lightning_prune_angle))
|
||||
((ConfigOptionFloat, lightning_straightening_angle))
|
||||
((ConfigOptionBool, align_infill_direction_to_model))
|
||||
((ConfigOptionBool, anisotropic_surfaces))
|
||||
((ConfigOptionEnum<CenterOfSurfacePattern>, center_of_surface_pattern))
|
||||
((ConfigOptionBool, separated_infills))
|
||||
((ConfigOptionString, extra_solid_infills))
|
||||
((ConfigOptionEnum<FuzzySkinType>, fuzzy_skin))
|
||||
((ConfigOptionFloat, fuzzy_skin_thickness))
|
||||
@@ -1214,6 +1238,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloatOrPercent, top_surface_line_width))
|
||||
((ConfigOptionInt, top_shell_layers))
|
||||
((ConfigOptionFloat, top_shell_thickness))
|
||||
((ConfigOptionFloat, top_surface_expansion))
|
||||
((ConfigOptionFloat, top_surface_expansion_margin))
|
||||
((ConfigOptionEnum<TopSurfaceExpansionDirection>, top_surface_expansion_direction))
|
||||
((ConfigOptionFloatsNullable, top_surface_speed))
|
||||
//BBS
|
||||
((ConfigOptionBoolsNullable, enable_overhang_speed))
|
||||
@@ -1449,6 +1476,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, single_extruder_multi_material))
|
||||
((ConfigOptionBool, manual_filament_change))
|
||||
((ConfigOptionBool, single_extruder_multi_material_priming))
|
||||
((ConfigOptionEnum<ToolChangeOrderingType>, toolchange_ordering))
|
||||
((ConfigOptionBool, wipe_tower_no_sparse_layers))
|
||||
((ConfigOptionString, change_filament_gcode))
|
||||
((ConfigOptionString, change_extrusion_role_gcode))
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Print.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Clipper2Utils.hpp"
|
||||
#include "ElephantFootCompensation.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "I18N.hpp"
|
||||
@@ -562,6 +563,11 @@ void PrintObject::prepare_infill()
|
||||
{
|
||||
if (! this->set_started(posPrepareInfill))
|
||||
return;
|
||||
|
||||
// Orca: clear all volume bbox caches
|
||||
for (auto volume : this->model_object()->volumes)
|
||||
volume->reset_volume_bbox();
|
||||
|
||||
m_print->set_status(25, L("Generating infill regions"));
|
||||
if (m_typed_slices) {
|
||||
// To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442.
|
||||
@@ -1297,6 +1303,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_combination_max_layer_height"
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "top_surface_expansion"
|
||||
|| opt_key == "top_surface_expansion_margin"
|
||||
|| opt_key == "top_surface_expansion_direction"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
|| opt_key == "sparse_infill_filament_id"
|
||||
|| opt_key == "internal_solid_filament_id"
|
||||
@@ -1330,6 +1339,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "top_surface_line_width"
|
||||
|| opt_key == "top_surface_density"
|
||||
|| opt_key == "bottom_surface_density"
|
||||
|| opt_key == "anisotropic_surfaces"
|
||||
|| opt_key == "center_of_surface_pattern"
|
||||
|| opt_key == "separated_infills"
|
||||
|| opt_key == "initial_layer_line_width"
|
||||
|| opt_key == "small_area_infill_flow_compensation"
|
||||
|| opt_key == "lateral_lattice_angle_1"
|
||||
@@ -1686,6 +1698,69 @@ void PrintObject::detect_surfaces_type()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Expand the top surfaces outward by top_surface_expansion in every direction. This
|
||||
// enlarges the top solid infill and, in particular, grows it over the covered material left
|
||||
// by features rising from the middle of a top surface (filling holes and joining tops so the
|
||||
// features rest on it). The expansion stays inside the section it belongs to: each connected
|
||||
// solid island has its own outer wall, so the top is grown within each island separately and
|
||||
// clipped to it - growing one island's top across the gap into another island (which may have
|
||||
// no top surface, leaving a partially filled layer) is never allowed. The top infill sits
|
||||
// inside the perimeters, so the margin is measured from the walls: the island is inset by the
|
||||
// band the walls consume (outer wall + inner walls) plus the configured margin, making that
|
||||
// value the real clearance between the expanded top and the walls (avoiding a hull line). The
|
||||
// original top is unioned back in, so where it already sits within that band it is kept as-is.
|
||||
// Never claims a bottom surface.
|
||||
const double top_expansion = layerm->region().config().top_surface_expansion.value;
|
||||
if (top_expansion > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const auto jt = Clipper2Lib::JoinType::Miter;
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
const int wall_loops = layerm->region().config().wall_loops.value;
|
||||
const double wall_band = wall_loops <= 0 ? 0. :
|
||||
double(layerm->flow(frExternalPerimeter).scaled_width()) +
|
||||
double(layerm->flow(frPerimeter).scaled_width()) * double(wall_loops - 1);
|
||||
const double margin = scale_(layerm->region().config().top_surface_expansion_margin.value);
|
||||
// minimum real top to act on: ignore anything thinner than ~2 top-infill lines
|
||||
const float min_top = float(layerm->flow(frTopSolidInfill).scaled_width());
|
||||
const auto direction = layerm->region().config().top_surface_expansion_direction.value;
|
||||
|
||||
ExPolygons grown;
|
||||
for (const ExPolygon &island : union_ex(layerm_slices_surfaces)) {
|
||||
// The top infill only exists inside the perimeters, so seed and measure from the infill
|
||||
// region (the island minus the wall band), not the raw slice. A section whose only
|
||||
// exposed top lies in the wall band - i.e. a layer where the top is just the walls
|
||||
// themselves - has no infill here and is skipped, instead of being flooded inward by
|
||||
// the expansion. Thin slivers inside the infill region are dropped by the opening too.
|
||||
const ExPolygons infill_region = wall_band > 0. ? offset_ex(island, -float(wall_band)) : ExPolygons{ island };
|
||||
const ExPolygons island_top = intersection_ex(T, infill_region);
|
||||
if (opening_ex(island_top, min_top).empty())
|
||||
continue; // no real top infill in this section - never expand into it
|
||||
|
||||
// grow by d, then keep only the part allowed by the configured direction: inward fills
|
||||
// the holes/gaps left by features (clip the growth back to the top's own filled outline,
|
||||
// which leaves the outer edge fixed), outward grows the outer edge toward the walls (drop
|
||||
// the growth that fell into the original holes), and inward+outward keeps both.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, jt);
|
||||
if (direction != TopSurfaceExpansionDirection::InwardAndOutward) {
|
||||
ExPolygons outline; // the top with its holes filled (same outer edge)
|
||||
outline.reserve(island_top.size());
|
||||
for (const ExPolygon &ex : island_top)
|
||||
outline.emplace_back(ex.contour);
|
||||
outline = union_ex(outline);
|
||||
expanded = direction == TopSurfaceExpansionDirection::Inward ?
|
||||
intersection_ex(expanded, outline) : // only growth into the holes
|
||||
diff_ex(expanded, diff_ex(outline, island_top)); // only growth past the outer edge
|
||||
}
|
||||
// hold the expansion clear of the walls by the configured margin
|
||||
const ExPolygons allowed = margin > 0. ? offset_ex(infill_region, -float(margin)) : infill_region;
|
||||
append(grown, intersection_ex(expanded, allowed));
|
||||
}
|
||||
|
||||
ExPolygons new_top = diff_ex(union_ex(T, grown), to_expolygons(bottom));
|
||||
top.clear();
|
||||
surfaces_append(top, std::move(new_top), stTop);
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
|
||||
@@ -713,6 +713,36 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("top_layer_direction", has_top_shell);
|
||||
toggle_field("bottom_layer_direction", has_bottom_shell);
|
||||
|
||||
toggle_line("top_surface_expansion", has_top_shell);
|
||||
toggle_line("top_surface_expansion_margin", has_top_shell);
|
||||
bool has_top_surface_expansion = config->opt_float("top_surface_expansion") > 0;
|
||||
toggle_field("top_surface_expansion_margin", has_top_surface_expansion);
|
||||
toggle_line("top_surface_expansion_direction", has_top_shell);
|
||||
toggle_field("top_surface_expansion_direction", has_top_surface_expansion);
|
||||
|
||||
// Orca: Archimedean Chords and Octagram Spiral are the centered surface patterns that the
|
||||
// pattern-centering, anisotropic-surface and separated-infill features act on.
|
||||
auto is_centered_pattern = [](InfillPattern p) {
|
||||
return p == InfillPattern::ipArchimedeanChords || p == InfillPattern::ipOctagramSpiral;
|
||||
};
|
||||
bool is_top_centered = is_centered_pattern(config->option<ConfigOptionEnum<InfillPattern>>("top_surface_pattern")->value);
|
||||
bool is_bottom_centered = is_centered_pattern(config->option<ConfigOptionEnum<InfillPattern>>("bottom_surface_pattern")->value);
|
||||
bool has_centered_surface = (has_top_shell && is_top_centered) || (has_bottom_shell && is_bottom_centered);
|
||||
|
||||
// Orca: center of surface pattern / anisotropic surfaces
|
||||
toggle_line("center_of_surface_pattern", has_centered_surface);
|
||||
toggle_line("anisotropic_surfaces", has_centered_surface);
|
||||
|
||||
// Orca: separate infills
|
||||
bool is_internal_infill_centered = is_centered_pattern(config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value) ||
|
||||
config->opt_string("sparse_infill_rotate_template") != "" ||
|
||||
config->opt_string("solid_infill_rotate_template") != "";
|
||||
toggle_line("separated_infills", is_internal_infill_centered);
|
||||
|
||||
// Orca: no need gaps
|
||||
for (auto el : {"gap_fill_target", "filter_out_gap_fill"})
|
||||
toggle_field(el, !config->opt_bool("anisotropic_surfaces"));
|
||||
|
||||
for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap",
|
||||
"bridge_angle", "internal_bridge_angle", "relative_bridge_angle",
|
||||
"solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id",
|
||||
|
||||
@@ -6965,6 +6965,10 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
// finishFn tears down the progress dialog (and clears the re-entrancy guard), so it
|
||||
// must run on every exit path — otherwise an early bail-out would leak the modal
|
||||
// dialog and leave the guard stuck, blocking all later manual syncs.
|
||||
// Guard the whole thread body: an uncaught exception here (e.g. a transient
|
||||
// boost::filesystem error while scanning the preset folder) would otherwise
|
||||
// propagate out of the thread and terminate the entire application.
|
||||
try {
|
||||
if (!m_agent) { finishFn(false); return; }
|
||||
|
||||
// One-time scan for orphaned .info files left over from offline deletions; queues HTTP DELETEs.
|
||||
@@ -7206,6 +7210,11 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by exception: " << e.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by unknown exception";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8526,8 +8535,13 @@ void GUI_App::scan_orphaned_info_files()
|
||||
if (!fs::exists(type_dir))
|
||||
continue;
|
||||
|
||||
// Iterate through all .info files
|
||||
for (auto& entry : boost::filesystem::directory_iterator(type_dir)) {
|
||||
// Iterate through all .info files. Use the error_code-based iterator so a transient
|
||||
// directory-read failure (e.g. macOS readdir returning ENOTSUP) is logged and skipped
|
||||
// instead of throwing an uncaught exception that would terminate the app from the
|
||||
// background sync thread this runs on.
|
||||
boost::system::error_code ec;
|
||||
for (boost::filesystem::directory_iterator it(type_dir, ec), end; !ec && it != end; it.increment(ec)) {
|
||||
const auto& entry = *it;
|
||||
if (entry.path().extension() != ".info")
|
||||
continue;
|
||||
|
||||
@@ -8546,6 +8560,8 @@ void GUI_App::scan_orphaned_info_files()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(warning) << "scan_orphaned_info_files: failed to scan " << type_dir.string() << ": " << ec.message();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,9 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
||||
{"infill_anchor", "", 1},
|
||||
{"infill_anchor_max", "", 1},
|
||||
{"top_surface_pattern", "", 1},
|
||||
{"top_surface_expansion", "", 1},
|
||||
{"top_surface_expansion_margin", "", 1},
|
||||
{"top_surface_expansion_direction", "", 1},
|
||||
{"bottom_surface_pattern", "", 1},
|
||||
{"internal_solid_infill_pattern", "", 1},
|
||||
{"align_infill_direction_to_model", "", 1},
|
||||
@@ -187,7 +190,7 @@ std::vector<SimpleSettingData> SettingsFactory::get_visible_options(const std::s
|
||||
//Quality
|
||||
"wall_infill_order", "ironing_type", "inner_wall_line_width", "outer_wall_line_width", "top_surface_line_width",
|
||||
//Shell
|
||||
"wall_loops", "top_shell_layers", "bottom_shell_layers", "top_shell_thickness", "bottom_shell_thickness",
|
||||
"wall_loops", "top_shell_layers", "bottom_shell_layers", "top_shell_thickness", "bottom_shell_thickness", "top_surface_expansion", "top_surface_expansion_margin", "top_surface_expansion_direction",
|
||||
//Infill
|
||||
"sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern", "infill_combination", "infill_direction", "infill_wall_overlap",
|
||||
//speed
|
||||
|
||||
@@ -13699,6 +13699,9 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i
|
||||
_obj->config.set_key_value("top_solid_infill_flow_ratio", new ConfigOptionFloat(1.0f));
|
||||
_obj->config.set_key_value("infill_direction", new ConfigOptionFloat(45));
|
||||
_obj->config.set_key_value("solid_infill_direction", new ConfigOptionFloat(135));
|
||||
_obj->config.set_key_value("anisotropic_surfaces", new ConfigOptionBool(false));
|
||||
_obj->config.set_key_value("center_of_surface_pattern", new ConfigOptionEnum<CenterOfSurfacePattern>(CenterOfSurfacePattern::Each_Surface));
|
||||
_obj->config.set_key_value("separated_infills", new ConfigOptionBool(false));
|
||||
_obj->config.set_key_value("align_infill_direction_to_model", new ConfigOptionBool(true));
|
||||
_obj->config.set_key_value("ironing_type", new ConfigOptionEnum<IroningType>(IroningType::NoIroning));
|
||||
_obj->config.set_key_value("internal_solid_infill_speed", new ConfigOptionFloatsNullable(internal_solid_speeds));
|
||||
|
||||
@@ -2764,11 +2764,16 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("top_surface_density", "strength_settings_top_bottom_shells#surface-density");
|
||||
optgroup->append_single_option_line("top_surface_pattern", "strength_settings_top_bottom_shells#surface-pattern");
|
||||
optgroup->append_single_option_line("top_layer_direction", "strength_settings_infill#top-direction");
|
||||
optgroup->append_single_option_line("top_surface_expansion", "strength_settings_top_bottom_shells#surface-expansion");
|
||||
optgroup->append_single_option_line("top_surface_expansion_margin", "strength_settings_top_bottom_shells#surface-expansion-margin");
|
||||
optgroup->append_single_option_line("top_surface_expansion_direction", "strength_settings_top_bottom_shells#surface-expansion-direction");
|
||||
optgroup->append_single_option_line("bottom_shell_layers", "strength_settings_top_bottom_shells#shell-layers");
|
||||
optgroup->append_single_option_line("bottom_shell_thickness", "strength_settings_top_bottom_shells#shell-thickness");
|
||||
optgroup->append_single_option_line("bottom_surface_density", "strength_settings_top_bottom_shells#surface-density");
|
||||
optgroup->append_single_option_line("bottom_surface_pattern", "strength_settings_top_bottom_shells#surface-pattern");
|
||||
optgroup->append_single_option_line("bottom_layer_direction", "strength_settings_infill#direction");
|
||||
optgroup->append_single_option_line("center_of_surface_pattern", "strength_settings_top_bottom_shells#center-surface-pattern-on");
|
||||
optgroup->append_single_option_line("anisotropic_surfaces", "strength_settings_top_bottom_shells#anisotropic-surfaces");
|
||||
optgroup->append_single_option_line("top_bottom_infill_wall_overlap", "strength_settings_top_bottom_shells#infillwall-overlap");
|
||||
|
||||
optgroup = page->new_optgroup(L("Infill"), L"param_infill");
|
||||
@@ -2799,6 +2804,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("solid_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
|
||||
optgroup->append_single_option_line("gap_fill_target", "strength_settings_infill#apply-gap-fill");
|
||||
optgroup->append_single_option_line("filter_out_gap_fill", "strength_settings_infill#filter-out-tiny-gaps");
|
||||
optgroup->append_single_option_line("separated_infills", "strength_settings_infill#separated-infills");
|
||||
optgroup->append_single_option_line("infill_wall_overlap", "strength_settings_infill#infill-wall-overlap");
|
||||
|
||||
optgroup = page->new_optgroup(L("Advanced"), L"param_advanced");
|
||||
@@ -2997,6 +3003,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("flush_into_support", "multimaterial_settings_flush_options#flush-into-objects-support");
|
||||
optgroup = page->new_optgroup(L("Advanced"), L"advanced");
|
||||
optgroup->append_single_option_line("interlocking_beam", "multimaterial_settings_advanced#interlocking-beam");
|
||||
optgroup->append_single_option_line("toolchange_ordering", "multimaterial_settings_advanced#toolchange-ordering");
|
||||
optgroup->append_single_option_line("interface_shells", "multimaterial_settings_advanced#interface-shells");
|
||||
optgroup->append_single_option_line("mmu_segmented_region_max_width", "multimaterial_settings_advanced#maximum-width-of-segmented-region");
|
||||
optgroup->append_single_option_line("mmu_segmented_region_interlocking_depth", "multimaterial_settings_advanced#interlocking-depth-of-segmented-region");
|
||||
|
||||
@@ -85,3 +85,22 @@ TEST_CASE("Per-object wall filament override is honored", "[MultiFilament]")
|
||||
CHECK(tools_for_role(gcode, "perimeter") == std::set<int>{ 0, 1 });
|
||||
CHECK(tools_for_role(gcode, "infill") == std::set<int>{ 0 }); // infill not overridden: stays on F1
|
||||
}
|
||||
|
||||
// max_layer_height can be shorter than the extruder count (normalization sizes it to the
|
||||
// filament count under single_extruder_multi_material). calc_max_layer_height() in ToolOrdering
|
||||
// indexed it per-nozzle and read past the end. Shortened directly here to isolate that read;
|
||||
// the other per-extruder keys stay extruder-length so slicing reaches the code under test.
|
||||
TEST_CASE("Multi-extruder slice stays in bounds with a short max_layer_height", "[MultiFilament]")
|
||||
{
|
||||
DynamicPrintConfig config = multifilament_config(2);
|
||||
config.set_deserialize_strict({
|
||||
{ "nozzle_diameter", "0.4,0.4" },
|
||||
{ "printer_extruder_id", "1,2" },
|
||||
{ "printer_extruder_variant", "Direct Drive Standard,Direct Drive Standard" },
|
||||
{ "extruder_printable_height", "0,0" },
|
||||
{ "max_layer_height", "0.3" }, // deliberately one entry short
|
||||
});
|
||||
Print print;
|
||||
init_and_process_print({ cube(20) }, print, config);
|
||||
REQUIRE_FALSE(print.objects().front()->layers().empty());
|
||||
}
|
||||
|
||||
@@ -93,3 +93,14 @@ SCENARIO("Support layer Z honors contact distance", "[SupportMaterial]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extrude_support once held a `static` lambda capturing `this`, so a second export in the
|
||||
// same process dereferenced a returned stack frame (ASan: stack-use-after-return).
|
||||
TEST_CASE("Support G-code emission survives a second slice in the same process", "[SupportMaterial][Regression]")
|
||||
{
|
||||
const std::string first = slice({ TestMesh::overhang }, { { "enable_support", 1 } });
|
||||
REQUIRE(! layers_with_role(first, "support").empty());
|
||||
|
||||
const std::string second = slice({ TestMesh::overhang }, { { "enable_support", 1 } });
|
||||
REQUIRE(! layers_with_role(second, "support").empty());
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
|
||||
SECTION("math: round(-13.4)") { REQUIRE(parser.process("{round(-13.4)}") == "-13"); }
|
||||
SECTION("math: round(13.6)") { REQUIRE(parser.process("{round(13.6)}") == "14"); }
|
||||
SECTION("math: round(-13.6)") { REQUIRE(parser.process("{round(-13.6)}") == "-14"); }
|
||||
SECTION("math: round(13.5)") { REQUIRE(parser.process("{round(13.5)}") == "14"); }
|
||||
SECTION("math: floor(13.9)") { REQUIRE(parser.process("{floor(13.9)}") == "13"); }
|
||||
SECTION("math: floor(-13.1)") { REQUIRE(parser.process("{floor(-13.1)}") == "-14"); }
|
||||
SECTION("math: ceil(13.1)") { REQUIRE(parser.process("{ceil(13.1)}") == "14"); }
|
||||
SECTION("math: ceil(-13.9)") { REQUIRE(parser.process("{ceil(-13.9)}") == "-13"); }
|
||||
SECTION("math: digits(5, 15)") { REQUIRE(parser.process("{digits(5, 15)}") == " 5"); }
|
||||
SECTION("math: digits(5., 15)") { REQUIRE(parser.process("{digits(5., 15)}") == " 5"); }
|
||||
SECTION("math: zdigits(5, 15)") { REQUIRE(parser.process("{zdigits(5, 15)}") == "000000000000005"); }
|
||||
@@ -65,6 +70,8 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
|
||||
SECTION("math: interpolate_table(13.84375892476, (0, 0), (20, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13.84375892476, (0, 0), (20, 20))}")) == Catch::Approx(13.84375892476)); }
|
||||
SECTION("math: interpolate_table(13, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13, (0, 0), (20, 20), (30, 20))}")) == Catch::Approx(13.)); }
|
||||
SECTION("math: interpolate_table(25, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(25, (0, 0), (20, 20), (30, 20))}")) == Catch::Approx(20.)); }
|
||||
// Only the grammar's built-in functions are callable; any other name is an undefined variable and throws.
|
||||
SECTION("math: a non-built-in function name throws") { REQUIRE_THROWS(parser.process("{sqrt(16)}")); }
|
||||
|
||||
// regex_replace(subject, /pattern/, replacement): the string-transform primitive.
|
||||
SECTION("regex_replace: strips a file extension") { REQUIRE(parser.process("{regex_replace(\"part.stl\", /\\.[^.]*$/, \"\")}") == "part"); }
|
||||
|
||||
Reference in New Issue
Block a user