Merge remote-tracking branch 'upstream/main' into dev/cut-keep-paint

This commit is contained in:
Noisyfox
2026-05-10 16:41:59 +08:00
342 changed files with 56043 additions and 827 deletions
+1 -1
View File
@@ -299,7 +299,7 @@ void AppConfig::set_defaults()
set_bool("enable_ssl_for_ftp", true);
if (get("log_severity_level").empty())
set("log_severity_level", "warning");
set("log_severity_level", "info");
if (get("internal_developer_mode").empty())
set_bool("internal_developer_mode", false);
+20 -11
View File
@@ -37,7 +37,7 @@ static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &
}
Layer *layer = region->layer();
coordf_t mesh_z = layer->print_z + mesh.ground_level();
coordf_t mesh_slice_z = layer->slice_z + mesh.ground_level();
coordf_t min_z = region->region().config().zaa_min_z;
const Points3 &points = path.polyline.points;
@@ -50,7 +50,12 @@ static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &
Pointf3s contoured_points;
bool was_contoured = false;
for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) {
if (points.size() < 2) {
// Safety check. The loop below does not handle paths with less than two points correctly.
return false;
}
for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) {
Vec2d p1d(unscale_(it->x()), unscale_(it->y()));
Vec2d p2d(unscale_((it+1)->x()), unscale_((it+1)->y()));
Linef line(p1d, p2d);
@@ -69,14 +74,10 @@ static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &
coordf_t x = p.x();
coordf_t y = p.y();
sla::IndexedMesh::hit_result hit_up = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, 1.0});
sla::IndexedMesh::hit_result hit_down = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, -1.0});
sla::IndexedMesh::hit_result hit_up = mesh.query_ray_hit({x, y, mesh_slice_z}, {0.0, 0.0, 1.0});
double d = hit_up.distance() - (layer->print_z - layer->slice_z);
double up = hit_up.distance();
double down = hit_down.distance();
double d = up < down ? up : -down;
const Vec3d &normal = (up < down ? hit_up : hit_down).normal();
double max_up = min_z;
double min_down = -(height - min_z);
double half_width = path.width / 2.0;
@@ -85,7 +86,8 @@ static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &
min_down = -(height + 0.1);
}
if (is_perimeter(path.role())) {
if (is_perimeter(path.role()) && hit_up.is_hit()) {
const Vec3d &normal = hit_up.normal();
double slope_rad = slope_from_normal(normal);
double slope_degrees = slope_rad * 180.0 / M_PI;
@@ -123,7 +125,14 @@ static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &
Vec3d new_point = {p.x(), p.y(), d};
if (contoured_points.size() >= 2) {
if (contoured_points.size() >= 2 && i != 0) {
// Normally, if the new point is collinear with the last two points, we do not add
// it to the list of contoured points. Instead we update the last point to be the
// new point. This is to avoid creating a large number of very short segments.
//
// However, if the new point corresponds to a point in the original path (i == 0),
// even if it is collinear, we add it anyway. This is to avoid creating a degenerate
// polygon with only two points, which may cause issues in downstream code.
double dist = Linef3::distance_to_infinite_squared(new_point, contoured_points[contoured_points.size() - 2],
contoured_points[contoured_points.size() - 1]);
if (dist < EPSILON * EPSILON) {
+155 -45
View File
@@ -79,7 +79,7 @@ static std::unique_ptr<noise::module::Module> get_noise_module(const FuzzySkinCo
//
// Per-layer-group phase shifting works as follows:
// period_index = floor(layer_id / layers_between_ripple_offset)
// phase_shift = period_index * ripple_offset * 2π [radians]
// phase_shift = period_index * (ripple_offset / 100) * 2π [radians]
//
// Setting layers_between_ripple_offset = 1 shifts the phase on every layer;
// setting it to N makes N consecutive layers share the same pattern.
@@ -93,7 +93,7 @@ static double ripple_phase_shift_rad(const FuzzySkinConfig& cfg)
const int effective_layer = std::max(cfg.layer_id, 0);
const int period_index = effective_layer / std::max(cfg.layers_between_ripple_offset, 1);
const double raw_shift = period_index * cfg.ripple_offset * (2.0 * M_PI);
const double raw_shift = period_index * (cfg.ripple_offset/100) * (2.0 * M_PI);
return fmod(raw_shift, 2.0 * M_PI);
}
@@ -418,7 +418,13 @@ void group_region_by_fuzzify(PerimeterGenerator& g)
g.has_fuzzy_skin = false;
g.has_fuzzy_hole = false;
std::unordered_map<FuzzySkinConfig, SurfacesPtr> regions;
struct ConfigSurfaces {
FuzzySkinConfig config;
SurfacesPtr surfaces;
};
std::vector<ConfigSurfaces> regions;
regions.reserve(g.compatible_regions->size());
for (auto region : *g.compatible_regions) {
const auto& region_config = region->region().config();
const FuzzySkinConfig cfg{region_config.fuzzy_skin,
@@ -434,26 +440,36 @@ void group_region_by_fuzzify(PerimeterGenerator& g)
region_config.fuzzy_skin_ripple_offset,
region_config.fuzzy_skin_layers_between_ripple_offset,
g.layer_id};
auto& surfaces = regions[cfg];
auto it = std::find_if(regions.begin(), regions.end(), [&cfg](const ConfigSurfaces& item) {
return item.config == cfg;
});
if (it == regions.end()) {
regions.push_back({cfg, {}});
it = regions.end() - 1;
}
auto& surfaces = it->surfaces;
for (const auto& surface : region->slices.surfaces) {
surfaces.push_back(&surface);
}
if (cfg.type != FuzzySkinType::None && cfg.type != FuzzySkinType::Disabled_fuzzy) {
if (should_fuzzify(cfg, g.layer_id, 0, true)) {
g.has_fuzzy_skin = true;
if (cfg.type != FuzzySkinType::External) {
g.has_fuzzy_hole = true;
}
}
if (should_fuzzify(cfg, g.layer_id, 0, false)) {
g.has_fuzzy_hole = true;
}
}
if (regions.size() == 1) { // optimization
g.regions_by_fuzzify[regions.begin()->first] = {};
g.regions_by_fuzzify.push_back({regions.front().config, {}});
return;
}
for (auto& it : regions) {
g.regions_by_fuzzify[it.first] = offset_ex(it.second, ClipperSafetyOffset);
g.regions_by_fuzzify.reserve(regions.size());
for (const auto& region : regions) {
g.regions_by_fuzzify.push_back({region.config, offset_ex(region.surfaces, ClipperSafetyOffset)});
}
}
@@ -469,12 +485,79 @@ bool should_fuzzify(const FuzzySkinConfig& config, const int layer_id, const siz
return false;
}
const bool fuzzify_contours = loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls;
const bool fuzzify_holes = fuzzify_contours && (fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls);
const bool fuzzify_contours = (loop_idx == 0 && fuzziy_type != FuzzySkinType::Hole) || fuzziy_type == FuzzySkinType::AllWalls;
const bool fuzzify_holes = (fuzziy_type == FuzzySkinType::Hole || fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls)
&& (loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls);
return is_contour ? fuzzify_contours : fuzzify_holes;
}
struct MergedFuzzyRegion {
const FuzzySkinConfig *config;
ExPolygons expolygons;
};
// Compare whether two configs produce the same fuzzy effect (ignoring type/first_layer
// which only control which loops get fuzzified, not the noise itself).
static bool same_fuzzy_effect(const FuzzySkinConfig& a, const FuzzySkinConfig& b)
{
return a.thickness == b.thickness
&& a.point_distance == b.point_distance
&& a.noise_type == b.noise_type
&& a.noise_scale == b.noise_scale
&& a.noise_octaves == b.noise_octaves
&& a.noise_persistence == b.noise_persistence
&& a.mode == b.mode
&& a.ripples_per_layer == b.ripples_per_layer
&& a.ripple_offset == b.ripple_offset
&& a.layers_between_ripple_offset == b.layers_between_ripple_offset;
}
static std::vector<MergedFuzzyRegion> collect_merged_fuzzy_regions(const std::vector<std::pair<FuzzySkinConfig, ExPolygons>>& regions,
const int layer_id,
const size_t loop_idx,
const bool is_contour)
{
// Merge regions that produce identical fuzzy effects (differ only in type).
// When the style (e.g. External) and a painted region (All) both fuzzify this loop
// with the same noise parameters, merging their ExPolygons avoids splitting the
// perimeter at the painted boundary — eliminating discontinuity artifacts.
std::vector<MergedFuzzyRegion> merged_regions;
merged_regions.reserve(regions.size());
for (const auto& region : regions) {
if (!should_fuzzify(region.first, layer_id, loop_idx, is_contour)) {
continue;
}
bool merged = false;
for (auto& merged_region : merged_regions) {
if (same_fuzzy_effect(*merged_region.config, region.first)) {
if (merged_region.expolygons.empty()) {
// Already full coverage, nothing to add.
} else if (region.second.empty()) {
merged_region.expolygons.clear();
} else {
append(merged_region.expolygons, region.second);
}
merged = true;
break;
}
}
if (!merged) {
merged_regions.push_back({&region.first, region.second});
}
}
for (auto& merged_region : merged_regions) {
if (!merged_region.expolygons.empty()) {
merged_region.expolygons = union_ex(merged_region.expolygons);
}
}
return merged_regions;
}
Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perimeter_generator, const size_t loop_idx, const bool is_contour)
{
Polygon fuzzified;
@@ -493,22 +576,30 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
return fuzzified;
}
// Find all affective regions
std::vector<std::pair<const FuzzySkinConfig&, const ExPolygons&>> fuzzified_regions;
fuzzified_regions.reserve(regions.size());
for (const auto& region : regions) {
if (should_fuzzify(region.first, perimeter_generator.layer_id, loop_idx, is_contour)) {
fuzzified_regions.emplace_back(region.first, region.second);
}
}
if (fuzzified_regions.empty()) {
// Merge regions that produce identical fuzzy effects (differ only in type).
// When the style (e.g. External) and a painted region (All) both fuzzify this loop
// with the same noise parameters, merging their ExPolygons avoids splitting the
// perimeter at the painted boundary — eliminating discontinuity artifacts.
auto merged_regions = collect_merged_fuzzy_regions(regions, perimeter_generator.layer_id, loop_idx, is_contour);
if (merged_regions.empty()) {
return polygon;
}
// Fast path: single merged region — apply directly without splitting
if (merged_regions.size() == 1) {
const auto& mr = merged_regions.front();
if (mr.expolygons.empty()) {
fuzzified = polygon;
fuzzy_polyline(fuzzified.points, true, slice_z, *mr.config);
return fuzzified;
}
// Fall through to split_line with a single region below
}
#ifdef DEBUG_FUZZY
{
int i = 0;
for (const auto& r : fuzzified_regions) {
for (const auto& r : merged_regions) {
BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id,
@@ -517,18 +608,26 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
bbox);
svg.draw_outline(perimeter_generator.slices->surfaces);
svg.draw_outline(polygon, "green");
svg.draw(r.second, "red", 0.5);
svg.draw_outline(r.second, "red");
svg.draw(r.expolygons, "red", 0.5);
svg.draw_outline(r.expolygons, "red");
svg.Close();
i++;
}
}
#endif
// Make each region's ExPolygons exclusive so overlapping regions don't double-fuzz
// the same perimeter section. Later regions in the list take priority over earlier ones
// in overlapping areas (matching modifier precedence order).
for (size_t i = 0; i < merged_regions.size(); ++i)
for (size_t j = i + 1; j < merged_regions.size(); ++j)
if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty())
merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons);
// Split the loops into lines with different config, and fuzzy them separately
fuzzified = polygon;
for (const auto& r : fuzzified_regions) {
auto splitted = Algorithm::split_line(fuzzified, r.second, true);
for (const auto& r : merged_regions) {
auto splitted = Algorithm::split_line(fuzzified, r.expolygons, true);
if (splitted.empty()) {
// No intersection, skip
continue;
@@ -537,7 +636,7 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
// Fuzzy splitted polygon
if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
// The entire polygon is fuzzified
fuzzy_polyline(fuzzified.points, true, slice_z, r.first);
fuzzy_polyline(fuzzified.points, true, slice_z, *r.config);
} else {
// Start from a non-clipped junction so wrapped clipped segments do
// not need an artificial reconnection across the seam.
@@ -554,7 +653,7 @@ Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perim
const auto fuzzy_current_segment = [&segment, &fuzzified, &r, slice_z]() {
fuzzified.points.push_back(segment.front());
const auto back = segment.back();
fuzzy_polyline(segment, false, slice_z, r.first);
fuzzy_polyline(segment, false, slice_z, *r.config);
fuzzified.points.insert(fuzzified.points.end(), segment.begin(), segment.end());
fuzzified.points.push_back(back);
segment.clear();
@@ -593,20 +692,23 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
if (fuzzify)
fuzzy_extrusion_line(extrusion->junctions, slice_z, config);
} else {
// Find all affective regions
std::vector<std::pair<const FuzzySkinConfig&, const ExPolygons&>> fuzzified_regions;
fuzzified_regions.reserve(regions.size());
for (const auto& region : regions) {
if (should_fuzzify(region.first, perimeter_generator.layer_id, extrusion->inset_idx, is_contour)) {
fuzzified_regions.emplace_back(region.first, region.second);
// Merge regions that produce identical fuzzy effects (differ only in type).
// When the style (e.g. External) and a painted region (All) both fuzzify this loop
// with the same noise parameters, merging avoids splitting the perimeter at the
// painted boundary — eliminating discontinuity artifacts.
auto merged_regions = collect_merged_fuzzy_regions(regions, perimeter_generator.layer_id, extrusion->inset_idx, is_contour);
if (!merged_regions.empty()) {
// Fast path: single merged region — apply directly without splitting
if (merged_regions.size() == 1 && merged_regions.front().expolygons.empty()) {
fuzzy_extrusion_line(extrusion->junctions, slice_z, *merged_regions.front().config);
return;
}
}
if (!fuzzified_regions.empty()) {
#ifdef DEBUG_FUZZY
{
int i = 0;
for (const auto& r : fuzzified_regions) {
for (const auto& r : merged_regions) {
BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id,
@@ -623,17 +725,25 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
svg.draw_outline(perimeter_generator.slices->surfaces);
svg.draw_outline(extrusion_polygon, "green");
svg.draw(r.second, "red", 0.5);
svg.draw_outline(r.second, "red");
svg.draw(r.expolygons, "red", 0.5);
svg.draw_outline(r.expolygons, "red");
svg.Close();
i++;
}
}
#endif
// Make each region's ExPolygons exclusive so overlapping regions don't double-fuzz
// the same perimeter section. Later regions in the list take priority over earlier ones
// in overlapping areas.
for (size_t i = 0; i < merged_regions.size(); ++i)
for (size_t j = i + 1; j < merged_regions.size(); ++j)
if (!merged_regions[i].expolygons.empty() && !merged_regions[j].expolygons.empty())
merged_regions[i].expolygons = diff_ex(merged_regions[i].expolygons, merged_regions[j].expolygons);
// Split the loops into lines with different config, and fuzzy them separately
for (const auto& r : fuzzified_regions) {
const auto splitted = Algorithm::split_line(*extrusion, r.second, false);
for (const auto& r : merged_regions) {
const auto splitted = Algorithm::split_line(*extrusion, r.expolygons, false);
if (splitted.empty()) {
// No intersection, skip
continue;
@@ -642,7 +752,7 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
// Fuzzy splitted extrusion
if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
// The entire polygon is fuzzified
fuzzy_extrusion_line(extrusion->junctions, slice_z, r.first);
fuzzy_extrusion_line(extrusion->junctions, slice_z, *r.config);
continue;
} else {
const auto current_ext = extrusion->junctions;
@@ -655,7 +765,7 @@ void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerato
const auto front = segment.front();
const auto back = segment.back();
fuzzy_extrusion_line(segment, slice_z, r.first, false);
fuzzy_extrusion_line(segment, slice_z, *r.config, false);
// Orca: only add non fuzzy point if it's not in the extrusion closing point.
if (!extrusion->junctions.empty() && extrusion->junctions.front().p != front.p) {
extrusion->junctions.push_back(front);
+2 -2
View File
@@ -1324,12 +1324,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.density = f->print_object_config->internal_bridge_density.get_abs_value(1.0);
params.dont_adjust = true;
}
// Orca: Elefant foot compensation for solid layers above bottommost by infill density manipulation.
// Orca: Elephant foot compensation for solid layers above bottommost by infill density manipulation.
float elefant_density = f->print_object_config->elefant_foot_layers_density.get_abs_value(1.0);
if (!is_approx(elefant_density, 1.0f) && surface_fill.surface.is_solid_infill()) {
size_t elefant_layers = f->print_object_config->elefant_foot_compensation_layers.value;
if (f->layer_id > 0 && f->layer_id <= elefant_layers)
params.density = elefant_density * (elefant_layers - (f->layer_id - 1)) / elefant_layers;
params.density = 1.0f - (1.0f - elefant_density) * (elefant_layers - (f->layer_id - 1)) / elefant_layers; // Reverse calculation - The higher layer number means the higher density. Counting starts from the second layer.
}
// make fill
f->fill_surface_extrusion(&surface_fill.surface,
+96 -9
View File
@@ -3050,7 +3050,7 @@ bool FillRectilinear::fill_surface_trapezoidal(
FillParams params,
const std::initializer_list<SweepParams>& sweep_params,
Polylines& polylines_out,
int Pattern_type) // 0=grid, 1=triangular
int Pattern_type) // 0=grid, 1=triangular, 2=stars
{
assert(params.multiline > 1);
@@ -3069,9 +3069,9 @@ bool FillRectilinear::fill_surface_trapezoidal(
period = coord_t((2.0 * d1 / params.density) * std::sqrt(2.0));
base_angle = rotate_vector.first + M_PI_4; // 45
} else {
// Triangular pattern parameters
period = coord_t(( 2.0 * d1 / params.density) * std::sqrt(3.0));
base_angle = rotate_vector.first + M_PI_2; //90
// Triangular-family pattern parameters (triangles / stars)
period = coord_t((2.0 * d1 / params.density) * std::sqrt(3.0));
base_angle = rotate_vector.first + M_PI_2; // 90
}
// Obtain the expolygon and rotate to align with pattern base angle
@@ -3257,6 +3257,85 @@ bool FillRectilinear::fill_surface_trapezoidal(
break;
}
case 2: // Tri-hexagon / FillStars
{
// Pattern parameters
const coord_t hex_height = coord_t(0.5 * std::sqrt(3.0) * period);
const coord_t tri_height = hex_height / 2;
const coord_t d1_half = d1 / 2;
const coord_t chamfer_height = std::sqrt(3.0) * d1_half;
const coord_t d1_half_base = d1_half / std::sqrt(3.0);
const coord_t half_period = period / 2;
const coord_t quarter_period = period / 4;
bb.merge(align_to_grid(bb.center(), Point(period, tri_height)));
const size_t layer_mod = infill_layer_id % 3;
const double angle = layer_mod * 2.0 * M_PI / 3.0;
const coord_t half_w = bb.size().x() / 2;
const coord_t half_h = bb.size().y() / 2;
const coord_t num_periods_x = coord_t(std::ceil(half_w / double(period)));
coord_t num_periods_y = coord_t(std::ceil(half_h / double(hex_height)));
if ((num_periods_y % 2) != 0)
++num_periods_y;
const coord_t x_alignment_shift = half_period;
const coord_t y_alignment_shift = (2 * tri_height) / 3;
const coord_t x_min_aligned = -num_periods_x * period - x_alignment_shift;
const coord_t x_max_aligned = num_periods_x * period - x_alignment_shift;
const coord_t y_min_aligned = -num_periods_y * hex_height - y_alignment_shift;
const coord_t y_max_aligned = num_periods_y * hex_height - y_alignment_shift;
const size_t estimated_rows = (y_max_aligned - y_min_aligned) / hex_height + 2;
const size_t estimated_polylines = (estimated_rows + 1) * 2;
polylines.reserve(estimated_polylines);
Polyline star_row_normal;
star_row_normal.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 7);
Polyline star_row_mirrored;
star_row_mirrored.points.reserve(((x_max_aligned - x_min_aligned) / period + 1) * 7);
for (coord_t x = x_min_aligned; x < x_max_aligned; x += period) {
star_row_normal.points.emplace_back(Point(x, hex_height)); // P0
star_row_normal.points.emplace_back(Point(x + quarter_period - d1, hex_height)); // P1
star_row_normal.points.emplace_back(Point(x + quarter_period + d1_half, hex_height - chamfer_height)); // P2
star_row_normal.points.emplace_back(Point(x + half_period - d1_half_base, tri_height + d1_half)); // P3
star_row_normal.points.emplace_back(Point(x + half_period + d1_half_base, tri_height + d1_half)); // P4
star_row_normal.points.emplace_back(Point(x + (period * 3) / 4 - d1_half, hex_height - chamfer_height)); // P5
star_row_normal.points.emplace_back(Point(x + (period * 3) / 4 + d1, hex_height)); // P6
}
star_row_mirrored.points = star_row_normal.points;
for (auto& p : star_row_mirrored.points)
p.y() = hex_height - p.y();
size_t pair_idx = 0;
const coord_t global_x_shift = half_period;
const coord_t global_y_shift = tri_height;
auto append_row_with_shift = [&polylines](const Polyline& row_template, coord_t x_shift, coord_t y_shift) {
Polyline row = row_template;
for (Point& p : row.points) {
p.x() += x_shift;
p.y() += y_shift;
}
if (!row.points.empty())
polylines.emplace_back(std::move(row));
};
for (coord_t y = y_min_aligned; y < y_max_aligned; y += hex_height, ++pair_idx) {
const coord_t x_shift = (pair_idx % 2 == 0) ? 0 : half_period;
append_row_with_shift(star_row_normal, x_shift + global_x_shift, y + global_y_shift);
append_row_with_shift(star_row_mirrored, x_shift + global_x_shift, y + global_y_shift);
}
if (layer_mod)
for (auto& pl : polylines)
pl.rotate(angle, Point(0, 0));
break;
}
default:
// Handle unknown pattern type
break;
@@ -3408,11 +3487,19 @@ Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &
Polylines FillStars::fill_surface(const Surface *surface, const FillParams &params)
{
Polylines polylines_out;
if (! this->fill_surface_by_multilines(
surface, params,
{ { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), float((3./2.) * this->spacing * params.multiline / params.density) } },
polylines_out))
BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface() failed to fill a region.";
if (params.multiline > 1) {
if (!this->fill_surface_trapezoidal(
surface, params,
{{0.f, 0.f}, {float(M_PI / 3.), 0.f}, {float(2. * M_PI / 3.), float((3. / 2.) * this->spacing * params.multiline / params.density)}},
polylines_out, 2))
BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface_trapezoidal() failed.";
} else {
if (! this->fill_surface_by_multilines(
surface, params,
{ { 0.f, 0.f }, { float(M_PI / 3.), 0.f }, { float(2. * M_PI / 3.), float((3./2.) * this->spacing * params.multiline / params.density) } },
polylines_out))
BOOST_LOG_TRIVIAL(error) << "FillStars::fill_surface() failed to fill a region.";
}
return polylines_out;
}
+244 -2
View File
@@ -210,6 +210,7 @@ const std::string BBS_PROJECT_CONFIG_FILE = "Metadata/project_settings.config";
const std::string BBS_MODEL_CONFIG_FILE = "Metadata/model_settings.config";
const std::string BBS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.config.rels";
const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config";
const std::string FILAMENT_SEQUENCE_FILE = "Metadata/filament_sequence.json";
const std::string BBS_LAYER_HEIGHTS_PROFILE_FILE = "Metadata/layer_heights_profile.txt";
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/layer_config_ranges.xml";
const std::string BRIM_EAR_POINTS_FILE = "Metadata/brim_ear_points.txt";
@@ -251,6 +252,12 @@ static constexpr const char* FILAMENT_TYPE_TAG = "type";
static constexpr const char *FILAMENT_COLOR_TAG = "color";
static constexpr const char *FILAMENT_USED_M_TAG = "used_m";
static constexpr const char *FILAMENT_USED_G_TAG = "used_g";
static constexpr const char *FILAMENT_USED_FOR_SUPPORT = "used_for_support";
static constexpr const char *FILAMENT_USED_FOR_OBJECT = "used_for_object";
static constexpr const char *FILAMENT_NOZZLE_GROUP_ID_TAG = "group_id";
static constexpr const char *FILAMENT_NOZZLE_DIAMETER_TAG = "nozzle_diameter";
static constexpr const char *FILAMENT_NOZZLE_VOLUME_TYPE_TAG = "volume_type";
static constexpr const char *NOZZLE_TAG = "nozzle";
static constexpr const char *FILAMENT_TRAY_INFO_ID_TAG = "tray_info_idx";
static constexpr const char *LAYER_FILAMENT_LISTS_TAG = "layer_filament_lists";
static constexpr const char *LAYER_FILAMENT_LIST_TAG = "layer_filament_list";
@@ -365,6 +372,8 @@ static constexpr const char* TIMELAPSE_TYPE_ATTR = "timelapse_type";
static constexpr const char* OUTSIDE_ATTR = "outside";
static constexpr const char* SUPPORT_USED_ATTR = "support_used";
static constexpr const char* LABEL_OBJECT_ENABLED_ATTR = "label_object_enabled";
static constexpr const char* ENABLE_FILAMENT_DYNAMIC_MAP_ATTR = "enable_filament_dynamic_map";
static constexpr const char* HAS_FILAMENT_SWITCHER_ATTR = "has_filament_switcher";
static constexpr const char* SKIPPED_ATTR = "skipped";
static constexpr const char* OBJECT_TYPE = "object";
@@ -528,6 +537,40 @@ void add_vector(std::stringstream &stream, const std::vector<T> &values)
}
}
std::vector<int> parse_int_list(const std::string& value)
{
std::vector<int> out;
if (value.empty())
return out;
std::vector<std::string> tokens;
boost::split(tokens, value, boost::is_any_of(" ,"), boost::token_compress_on);
out.reserve(tokens.size());
for (const std::string& token : tokens) {
if (token.empty())
continue;
try {
out.emplace_back(boost::lexical_cast<int>(token));
} catch (...) {
}
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::string join_int_list_comma(const std::vector<int>& values)
{
std::stringstream stream;
for (size_t i = 0; i < values.size(); ++i) {
stream << values[i];
if (i + 1 < values.size())
stream << ",";
}
return stream.str();
}
Slic3r::Vec3f get_vec3_from_string(const std::string &pos_str)
{
Slic3r::Vec3f pos(0, 0, 0);
@@ -655,6 +698,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
info.id = it->first;
info.used_g = used_filament_g;
info.used_m = used_filament_m;
auto model_volume_it = ps.model_volumes_per_extruder.find(it->first);
auto support_volume_it = ps.support_volumes_per_extruder.find(it->first);
info.used_for_object = model_volume_it != ps.model_volumes_per_extruder.end() && model_volume_it->second > EPSILON;
info.used_for_support = support_volume_it != ps.support_volumes_per_extruder.end() && support_volume_it->second > EPSILON;
slice_filaments_info.push_back(info);
}
@@ -1142,6 +1189,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
void _extract_brim_ear_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_filament_sequence_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
//BBS: add project config file logic
@@ -1535,6 +1583,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element);
m_parsing_slice_info = false;
}
else if (boost::algorithm::iequals(name, FILAMENT_SEQUENCE_FILE)) {
_extract_filament_sequence_from_archive(archive, stat);
}
}
}
@@ -1568,6 +1619,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
plate->slice_filaments_info = it->second->slice_filaments_info;
plate->printer_model_id = it->second->printer_model_id;
plate->nozzle_diameters = it->second->nozzle_diameters;
plate->filament_maps = it->second->filament_maps;
plate->filament_change_sequence = it->second->filament_change_sequence;
plate->nozzle_change_sequence = it->second->nozzle_change_sequence;
plate->optimal_assignment = it->second->optimal_assignment;
plate->warnings = it->second->warnings;
plate->thumbnail_file = it->second->thumbnail_file;
if (plate->thumbnail_file.empty()) {
@@ -1911,6 +1966,9 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element);
m_parsing_slice_info = false;
}
else if (!dont_load_config && boost::algorithm::iequals(name, FILAMENT_SEQUENCE_FILE)) {
_extract_filament_sequence_from_archive(archive, stat);
}
else if (boost::algorithm::istarts_with(name, AUXILIARY_DIR)) {
// extract auxiliary directory to temp directory, do nothing for restore
if (m_load_aux && !m_load_restore)
@@ -2231,6 +2289,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
plate_data_list[it->first-1]->is_label_object_enabled = it->second->is_label_object_enabled;
plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info;
plate_data_list[it->first-1]->skipped_objects = it->second->skipped_objects;
plate_data_list[it->first-1]->printer_model_id = it->second->printer_model_id;
plate_data_list[it->first-1]->nozzle_diameters = it->second->nozzle_diameters;
plate_data_list[it->first-1]->filament_maps = it->second->filament_maps;
plate_data_list[it->first-1]->filament_change_sequence = it->second->filament_change_sequence;
plate_data_list[it->first-1]->nozzle_change_sequence = it->second->nozzle_change_sequence;
plate_data_list[it->first-1]->optimal_assignment = it->second->optimal_assignment;
plate_data_list[it->first-1]->warnings = it->second->warnings;
plate_data_list[it->first-1]->thumbnail_file = (m_load_restore || it->second->thumbnail_file.empty()) ? it->second->thumbnail_file : m_backup_path + "/" + it->second->thumbnail_file;
//plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file;
@@ -3226,6 +3290,62 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
}
}
void _BBS_3MF_Importer::_extract_filament_sequence_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
if (stat.m_uncomp_size == 0) {
add_error("Error while reading filament sequence data to buffer");
return;
}
std::string buffer((size_t) stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*) buffer.data(), (size_t) stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading filament sequence data to buffer");
return;
}
try {
const nlohmann::json sequence_json = nlohmann::json::parse(buffer);
for (auto& elem : m_plater_data) {
const std::string plate_key = "plate_" + std::to_string(elem.first);
auto plate_it = sequence_json.find(plate_key);
if (plate_it == sequence_json.end() || !plate_it->is_object())
continue;
auto filament_it = plate_it->find("filament_sequence");
if (filament_it == plate_it->end())
filament_it = plate_it->find("sequence");
auto nozzle_it = plate_it->find("nozzle_sequence");
if (filament_it == plate_it->end() || !filament_it->is_array() || nozzle_it == plate_it->end() || !nozzle_it->is_array())
continue;
std::vector<unsigned int> filament_sequence;
std::vector<unsigned int> nozzle_sequence;
std::vector<int> optimal_assignment;
for (const auto& item : *filament_it) {
const unsigned int filament_id = item.get<unsigned int>();
filament_sequence.push_back(filament_id > 0 ? filament_id - 1 : 0);
}
for (const auto& item : *nozzle_it)
nozzle_sequence.push_back(item.get<unsigned int>());
auto optimal_assignment_it = plate_it->find("optimal_assignment");
if (optimal_assignment_it != plate_it->end() && optimal_assignment_it->is_array()) {
for (const auto& item : *optimal_assignment_it)
optimal_assignment.emplace_back(item.get<int>());
}
elem.second->filament_change_sequence = std::move(filament_sequence);
elem.second->nozzle_change_sequence = std::move(nozzle_sequence);
if (!optimal_assignment.empty())
elem.second->optimal_assignment = std::move(optimal_assignment);
}
} catch (const std::exception& e) {
add_error(std::string("Error while parsing filament sequence JSON: ") + e.what());
}
}
void _BBS_3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
{
if (m_xml_parser == nullptr)
@@ -4294,7 +4414,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
else if (key == BED_TYPE_ATTR)
{
BedType bed_type = BedType::btPC;
ConfigOptionEnum<BedType>::from_string(value, bed_type);
const std::string bed_type_value = value == "SuperTack Plate" ? "Supertack Plate" : value;
ConfigOptionEnum<BedType>::from_string(bed_type_value, bed_type);
m_curr_plater->config.set_key_value("curr_bed_type", new ConfigOptionEnum<BedType>(bed_type));
}
else if (key == PRINT_SEQUENCE_ATTR)
@@ -4334,6 +4455,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
filament_map[idx] = 1;
}
}
m_curr_plater->filament_maps = filament_map;
m_curr_plater->config.set_key_value("filament_map", new ConfigOptionInts(filament_map));
}
}
@@ -4425,6 +4547,22 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (m_curr_plater)
std::istringstream(value) >> std::boolalpha >> m_curr_plater->is_label_object_enabled;
}
else if (key == ENABLE_FILAMENT_DYNAMIC_MAP_ATTR)
{
if (m_curr_plater) {
bool enable_filament_dynamic_map = false;
std::istringstream(value) >> std::boolalpha >> enable_filament_dynamic_map;
m_curr_plater->config.set_key_value("enable_filament_dynamic_map", new ConfigOptionBool(enable_filament_dynamic_map));
}
}
else if (key == HAS_FILAMENT_SWITCHER_ATTR)
{
if (m_curr_plater) {
bool has_filament_switcher = false;
std::istringstream(value) >> std::boolalpha >> has_filament_switcher;
m_curr_plater->config.set_key_value("has_filament_switcher", new ConfigOptionBool(has_filament_switcher));
}
}
else if (key == PRINTER_MODEL_ID_ATTR)
{
if (m_curr_plater)
@@ -4455,6 +4593,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
std::string used_m = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_M_TAG);
std::string used_g = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_G_TAG);
std::string filament_id = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_TRAY_INFO_ID_TAG);
std::string used_for_object = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_FOR_OBJECT);
std::string used_for_support = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_FOR_SUPPORT);
std::string group_id = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_NOZZLE_GROUP_ID_TAG);
std::string nozzle_diameter = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_NOZZLE_DIAMETER_TAG);
std::string volume_type = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_NOZZLE_VOLUME_TYPE_TAG);
FilamentInfo filament_info;
filament_info.id = atoi(id.c_str()) - 1;
filament_info.type = type;
@@ -4462,6 +4605,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
filament_info.used_m = atof(used_m.c_str());
filament_info.used_g = atof(used_g.c_str());
filament_info.filament_id = filament_id;
std::istringstream(used_for_object) >> std::boolalpha >> filament_info.used_for_object;
std::istringstream(used_for_support) >> std::boolalpha >> filament_info.used_for_support;
filament_info.group_id = parse_int_list(group_id);
filament_info.nozzle_diameter = atof(nozzle_diameter.c_str());
filament_info.nozzle_volume_type = volume_type;
m_curr_plater->slice_filaments_info.push_back(filament_info);
}
return true;
@@ -5756,6 +5904,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false);
bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model);
bool _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);
bool _add_filament_sequence_file_to_archive(mz_zip_archive& archive, const PlateDataPtrs& plate_data_list);
bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr);
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
bool _add_auxiliary_dir_to_archive(mz_zip_archive &archive, const std::string &aux_dir, PackingTemporaryData &data);
@@ -6294,6 +6443,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return false;
}
if (!_add_filament_sequence_file_to_archive(archive, plate_data_list)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_filament_sequence_file_to_archive failed\n");
return false;
}
//BBS progress point
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add auxiliary dir to 3mf\n");
if (proFn) {
@@ -7950,6 +8104,39 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
bool _BBS_3MF_Exporter::_add_filament_sequence_file_to_archive(mz_zip_archive& archive, const PlateDataPtrs& plate_data_list)
{
nlohmann::json sequence_json;
for (size_t idx = 0; idx < plate_data_list.size(); ++idx) {
const PlateData* plate_data = plate_data_list[idx];
if (!plate_data)
continue;
std::vector<unsigned int> filament_sequence = plate_data->filament_change_sequence;
std::transform(filament_sequence.begin(), filament_sequence.end(), filament_sequence.begin(),
[](unsigned int filament_id) { return filament_id + 1; });
const std::string plate_key = "plate_" + std::to_string(idx + 1);
sequence_json[plate_key]["sequence"] = filament_sequence;
sequence_json[plate_key]["nozzle_sequence"] = plate_data->nozzle_change_sequence;
sequence_json[plate_key]["optimal_assignment"] = plate_data->optimal_assignment;
}
if (sequence_json.empty())
return true;
const std::string out = sequence_json.dump();
if (!mz_zip_writer_add_mem(&archive, FILAMENT_SEQUENCE_FILE.c_str(), out.c_str(), out.size(), MZ_DEFAULT_COMPRESSION)) {
add_error("Unable to add filament sequence file to archive");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__
<< boost::format(", store filament sequence to 3mf, length %1%, failed\n") % out.length();
return false;
}
return true;
}
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;
@@ -7987,6 +8174,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
std::vector<int> extruder_types = config.option<ConfigOptionEnumsGeneric>("extruder_type")->values;
std::vector<int> nozzle_volume_types = config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type")->values;
auto* nozzle_volume_type_option = dynamic_cast<const ConfigOptionEnumsGeneric*>(config.option("nozzle_volume_type"));
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << EXTRUDER_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"";
add_vector(stream, extruder_types);
@@ -8010,6 +8198,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OUTSIDE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->toolpath_outside << "\"/>\n";
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SUPPORT_USED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_support_used << "\"/>\n";
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LABEL_OBJECT_ENABLED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_label_object_enabled << "\"/>\n";
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << ENABLE_FILAMENT_DYNAMIC_MAP_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << false << "\"/>\n";
{
bool has_filament_switcher = config.has("has_filament_switcher") ? config.opt_bool("has_filament_switcher") : false;
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << HAS_FILAMENT_SWITCHER_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << has_filament_switcher << "\"/>\n";
}
std::vector<int> filament_maps = plate_data->filament_maps;
if (filament_maps.empty())
@@ -8053,20 +8246,69 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
<< "\" />\n";
}
auto get_nozzle_group_id = [&filament_maps](int filament_id) {
if (filament_id >= 0 && filament_id < static_cast<int>(filament_maps.size()) && filament_maps[filament_id] > 0)
return filament_maps[filament_id] - 1;
return 0;
};
auto get_nozzle_diameter = [nozzle_diameter_option](int nozzle_group_id) {
if (!nozzle_diameter_option || nozzle_diameter_option->values.empty())
return 0.0;
if (nozzle_group_id >= 0 && nozzle_group_id < static_cast<int>(nozzle_diameter_option->values.size()))
return nozzle_diameter_option->values[nozzle_group_id];
return nozzle_diameter_option->values.front();
};
auto get_nozzle_diameter_str = [&get_nozzle_diameter](int nozzle_group_id) {
std::ostringstream diameter_stream;
diameter_stream << std::defaultfloat << get_nozzle_diameter(nozzle_group_id);
return diameter_stream.str();
};
auto get_nozzle_volume_type = [nozzle_volume_type_option](int nozzle_group_id) {
if (!nozzle_volume_type_option || nozzle_volume_type_option->values.empty())
return std::string();
int nozzle_volume_type = nozzle_volume_type_option->values.front();
if (nozzle_group_id >= 0 && nozzle_group_id < static_cast<int>(nozzle_volume_type_option->values.size()))
nozzle_volume_type = nozzle_volume_type_option->values[nozzle_group_id];
if (nozzle_volume_type < 0 || nozzle_volume_type > nvtMaxNozzleVolumeType)
nozzle_volume_type = nvtStandard;
return get_nozzle_volume_type_string(static_cast<NozzleVolumeType>(nozzle_volume_type));
};
std::vector<int> used_nozzle_groups;
for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++)
{
int nozzle_group_id = get_nozzle_group_id(it->id);
if (std::find(used_nozzle_groups.begin(), used_nozzle_groups.end(), nozzle_group_id) == used_nozzle_groups.end())
used_nozzle_groups.push_back(nozzle_group_id);
const std::string filament_nozzle_group_id = it->group_id.empty() ? std::to_string(nozzle_group_id) : join_int_list_comma(it->group_id);
const double filament_nozzle_diameter = it->nozzle_diameter > 0.0 ? it->nozzle_diameter : get_nozzle_diameter(nozzle_group_id);
const std::string filament_nozzle_volume_type = it->nozzle_volume_type.empty() ? get_nozzle_volume_type(nozzle_group_id) : it->nozzle_volume_type;
stream << " <" << FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id + 1) << "\" "
<< FILAMENT_TRAY_INFO_ID_TAG <<"=\""<< it->filament_id <<"\" "
<< FILAMENT_TYPE_TAG << "=\"" << it->type << "\" "
<< FILAMENT_COLOR_TAG << "=\"" << it->color << "\" "
<< FILAMENT_USED_M_TAG << "=\"" << it->used_m << "\" "
<< FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" />\n";
<< FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" "
<< FILAMENT_NOZZLE_GROUP_ID_TAG << "=\"" << filament_nozzle_group_id << "\" "
<< FILAMENT_NOZZLE_DIAMETER_TAG << "=\"" << filament_nozzle_diameter << "\" "
<< FILAMENT_NOZZLE_VOLUME_TYPE_TAG << "=\"" << filament_nozzle_volume_type << "\" "
<< FILAMENT_USED_FOR_OBJECT << "=\"" << std::boolalpha << it->used_for_object << "\" "
<< FILAMENT_USED_FOR_SUPPORT << "=\"" << std::boolalpha << it->used_for_support << "\"/>\n";
}
for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) {
stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n";
}
for (int nozzle_group_id : used_nozzle_groups) {
stream << " <" << NOZZLE_TAG << " "
<< "id=\"" << nozzle_group_id << "\" "
<< "extruder_id=\"" << nozzle_group_id + 1 << "\" "
<< "nozzle_diameter=\"" << get_nozzle_diameter_str(nozzle_group_id) << "\" "
<< "volume_type=\"" << get_nozzle_volume_type(nozzle_group_id) << "\"/>\n";
}
if (!plate_data->layer_filaments.empty()) {
stream << " <" << LAYER_FILAMENT_LISTS_TAG << ">\n";
for (auto iter = plate_data->layer_filaments.begin(); iter != plate_data->layer_filaments.end(); ++iter) {
+3
View File
@@ -98,6 +98,9 @@ struct PlateData
std::vector<int> filament_maps; // 1 base
using LayerFilaments = std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>, GCodeProcessorResult::FilamentSequenceHash>;
LayerFilaments layer_filaments;
std::vector<unsigned int> filament_change_sequence;
std::vector<unsigned int> nozzle_change_sequence;
std::vector<int> optimal_assignment;
// Hexadecimal number,
// the 0th digit corresponds to extruder 1
+65 -5
View File
@@ -94,6 +94,16 @@ static const float g_purge_volume_one_time = 135.f;
static const int g_max_flush_count = 4;
static const size_t g_max_label_object = 64;
static bool is_bambu_x2d_printer(const FullPrintConfig &config)
{
return config.printer_model.value == "Bambu Lab X2D";
}
static int hotend_id_for_gcode_placeholder(const FullPrintConfig &config, int hotend_id)
{
return is_bambu_x2d_printer(config) ? -1 : hotend_id;
}
Vec2d travel_point_1;
Vec2d travel_point_2;
Vec2d travel_point_3;
@@ -835,6 +845,10 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id));
config.set_key_value("next_extruder", new ConfigOptionInt(new_filament_id));
config.set_key_value("current_hotend", new ConfigOptionInt(old_extruder_id >= 0 ?
hotend_id_for_gcode_placeholder(gcodegen.m_config, old_extruder_id) : -1));
config.set_key_value("next_hotend",
new ConfigOptionInt(hotend_id_for_gcode_placeholder(gcodegen.m_config, (int) gcodegen.get_extruder_id(new_filament_id))));
config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
@@ -916,6 +930,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
auto flush_v_speed = m_print_config->filament_flush_volumetric_speed.values;
auto flush_temps = m_print_config->filament_flush_temp.values;
auto filament_cooling_before_tower = m_print_config->filament_cooling_before_tower.values;
for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) {
if (flush_v_speed[idx] == 0)
flush_v_speed[idx] = m_print_config->filament_max_volumetric_speed.get_at(idx);
@@ -924,8 +939,13 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(idx);
}
if (filament_cooling_before_tower.size() < m_print_config->filament_type.values.size())
filament_cooling_before_tower.resize(m_print_config->filament_type.values.size(), m_print_config->filament_cooling_before_tower.get_at(0));
if (tcr.is_contact || gcodegen.m_layer_index == 0)
std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps));
config.set_key_value("filament_cooling_before_tower", new ConfigOptionFloats(filament_cooling_before_tower));
config.set_key_value("flush_length", new ConfigOptionFloat(purge_length));
config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter));
config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x));
@@ -2791,7 +2811,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->placeholder_parser().set("first_non_support_filaments", new ConfigOptionInts(first_non_support_filaments));
this->placeholder_parser().set("initial_no_support_tool", initial_non_support_extruder_id);
this->placeholder_parser().set("initial_no_support_extruder", initial_non_support_extruder_id);
this->placeholder_parser().set("initial_no_support_hotend",
hotend_id_for_gcode_placeholder(m_config, (int) get_extruder_id(initial_non_support_extruder_id)));
this->placeholder_parser().set("current_extruder", initial_extruder_id);
this->placeholder_parser().set("current_hotend", hotend_id_for_gcode_placeholder(m_config, extruder_id));
//Orca: set the key for compatibilty
this->placeholder_parser().set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id));
this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id));
@@ -2806,7 +2829,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->placeholder_parser().set("long_retractions_when_ec",new ConfigOptionBoolsNullable(m_config.long_retractions_when_ec));
this->placeholder_parser().set("max_additional_fan", max_additional_fan);
this->placeholder_parser().set("first_x_layer_fan_speed", 0); // TODO: Orca hack to support BBL profiles
this->placeholder_parser().set("first_x_layer_fan_speed", new ConfigOptionFloats(m_config.first_x_layer_fan_speed));
this->placeholder_parser().set("close_additional_fan_first_x_layers", new ConfigOptionInts(m_config.close_additional_fan_first_x_layers));
this->placeholder_parser().set("additional_fan_full_speed_layer", new ConfigOptionInts(m_config.additional_fan_full_speed_layer));
auto flush_v_speed = m_config.filament_flush_volumetric_speed.values;
auto flush_temps = m_config.filament_flush_temp.values;
@@ -2820,6 +2845,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
this->placeholder_parser().set("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
this->placeholder_parser().set("flush_temperatures", new ConfigOptionInts(flush_temps));
this->placeholder_parser().set("filament_cooling_before_tower", new ConfigOptionFloatsNullable(m_config.filament_cooling_before_tower));
//Set variable for total layer count so it can be used in custom gcode.
this->placeholder_parser().set("total_layer_count", m_layer_count);
// Useful for sequential prints.
@@ -3520,6 +3546,30 @@ void GCode::export_layer_filaments(GCodeProcessorResult* result)
iter->second.emplace_back(idx, idx);
}
}
result->filament_change_sequence.clear();
result->nozzle_change_sequence.clear();
int prev_sequence_filament = -1;
int prev_sequence_nozzle = -1;
for (size_t layer_idx = 0; layer_idx < m_sorted_layer_filaments.size(); ++layer_idx) {
for (unsigned int filament_id : m_sorted_layer_filaments[layer_idx]) {
int nozzle_id = 0;
if (filament_id < filament_map.size() && filament_map[filament_id] > 0)
nozzle_id = filament_map[filament_id] - 1;
if (prev_sequence_nozzle != nozzle_id || prev_sequence_filament != static_cast<int>(filament_id)) {
result->nozzle_change_sequence.emplace_back(static_cast<unsigned int>(nozzle_id));
result->filament_change_sequence.emplace_back(filament_id);
prev_sequence_nozzle = nozzle_id;
prev_sequence_filament = static_cast<int>(filament_id);
}
}
}
result->optimal_assignment.clear();
result->optimal_assignment.reserve(filament_map.size());
for (int nozzle_id : filament_map)
result->optimal_assignment.emplace_back(nozzle_id > 0 ? nozzle_id - 1 : 0);
}
//BBS
@@ -6320,7 +6370,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
// calculate effective extrusion length per distance unit (e_per_mm)
double filament_flow_ratio = m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0);
double filament_flow_ratio = FILAMENT_CONFIG(filament_flow_ratio);
// We set _mm3_per_mm to effectove flow = Geometric volume * print flow ratio * filament flow ratio * role-based-flow-ratios
auto _mm3_per_mm = path.mm3_per_mm * this->config().print_flow_ratio;
_mm3_per_mm *= filament_flow_ratio;
@@ -6516,7 +6566,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (ref_speed == 0)
ref_speed = FILAMENT_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm;
if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) {
if (FILAMENT_CONFIG(filament_max_volumetric_speed) > 0) {
ref_speed = std::min(ref_speed, FILAMENT_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm);
}
if (sloped) {
@@ -7660,6 +7710,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(new_filament_id), 2));
//BBS: add handling for filament change in start gcode
int old_filament_id = -1;
int old_extruder_id = -1;
if (m_writer.filament() != nullptr || m_start_gcode_filament != -1) {
std::vector<float> flush_matrix(cast<float>(get_flush_volumes_matrix(m_config.flush_volumes_matrix.values, new_extruder_id, m_config.nozzle_diameter.values.size())));
const unsigned int number_of_extruders = (unsigned int) (m_config.filament_colour.values.size()); // if is multi_extruder only use the fist extruder matrix
@@ -7669,7 +7720,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
assert(m_start_gcode_filament < number_of_extruders);
old_filament_id = m_writer.filament() != nullptr ? m_writer.filament()->id() : m_start_gcode_filament;
int old_extruder_id = m_writer.filament() != nullptr ? m_writer.filament()->extruder_id() : get_extruder_id(m_start_gcode_filament);
old_extruder_id = m_writer.filament() != nullptr ? m_writer.filament()->extruder_id() : get_extruder_id(m_start_gcode_filament);
old_retract_length = m_config.retraction_length.get_at(old_filament_id);
old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(old_filament_id);
@@ -7715,6 +7766,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
dyn_config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed));
dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id));
dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)new_filament_id));
dyn_config.set_key_value("current_hotend",
new ConfigOptionInt(old_filament_id >= 0 ? hotend_id_for_gcode_placeholder(m_config, old_extruder_id) : -1));
dyn_config.set_key_value("next_hotend", new ConfigOptionInt(hotend_id_for_gcode_placeholder(m_config, new_extruder_id)));
dyn_config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
dyn_config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
dyn_config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
@@ -7767,7 +7821,8 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
}
auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values;
auto flush_temps =m_print->config().filament_flush_temp.values;
auto flush_temps = m_print->config().filament_flush_temp.values;
auto filament_cooling_before_tower = m_print->config().filament_cooling_before_tower.values;
for (size_t idx = 0; idx < flush_v_speed.size(); ++idx) {
if (flush_v_speed[idx] == 0)
flush_v_speed[idx] = m_print->config().filament_max_volumetric_speed.get_at(idx);
@@ -7776,8 +7831,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (flush_temps[idx] == 0)
flush_temps[idx] = m_print->config().nozzle_temperature_range_high.get_at(idx);
}
if (filament_cooling_before_tower.size() < m_print->config().filament_type.values.size())
filament_cooling_before_tower.resize(m_print->config().filament_type.values.size(), m_print->config().filament_cooling_before_tower.get_at(0));
std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
dyn_config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
dyn_config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps));
dyn_config.set_key_value("filament_cooling_before_tower", new ConfigOptionFloats(filament_cooling_before_tower));
dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length));
int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time));
@@ -7850,6 +7909,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
}
this->placeholder_parser().set("current_extruder", new_filament_id);
this->placeholder_parser().set("current_hotend", hotend_id_for_gcode_placeholder(m_config, new_extruder_id));
this->placeholder_parser().set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(new_filament_id));
this->placeholder_parser().set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(new_filament_id));
this->placeholder_parser().set("retraction_distance_when_ec", m_config.retraction_distances_when_ec.get_at(new_filament_id));
+3
View File
@@ -1582,6 +1582,9 @@ void GCodeProcessorResult::reset() {
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
spiral_vase_mode = false;
layer_filaments.clear();
filament_change_sequence.clear();
nozzle_change_sequence.clear();
optimal_assignment.clear();
filament_change_count_map.clear();
warnings.clear();
+6
View File
@@ -251,6 +251,9 @@ class Print;
std::vector<NozzleType> nozzle_type;
// first key stores filaments, second keys stores the layer ranges(enclosed) that use the filaments
std::unordered_map<std::vector<unsigned int>, std::vector<std::pair<int, int>>,FilamentSequenceHash> layer_filaments;
std::vector<unsigned int> nozzle_change_sequence;
std::vector<unsigned int> filament_change_sequence;
std::vector<int> optimal_assignment;
// first key stores `from` filament, second keys stores the `to` filament
std::map<std::pair<int,int>, int > filament_change_count_map;
@@ -288,6 +291,9 @@ class Print;
limit_filament_maps = other.limit_filament_maps;
filament_printable_reuslt = other.filament_printable_reuslt;
layer_filaments = other.layer_filaments;
filament_change_sequence = other.filament_change_sequence;
nozzle_change_sequence = other.nozzle_change_sequence;
optimal_assignment = other.optimal_assignment;
filament_change_count_map = other.filament_change_count_map;
initial_layer_time = other.initial_layer_time;
#if ENABLE_GCODE_VIEWER_STATISTICS
+4 -3
View File
@@ -124,14 +124,15 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
float starting_flowrate = float(m_config.spiral_starting_flow_ratio.value);
float finishing_flowrate = float(m_config.spiral_finishing_flow_ratio.value);
const float min_segment_length = std::max(float(EPSILON), 2 * float(m_config.resolution.value));
float len = 0.f;
SpiralVase::SpiralPoint last_point = previous_layer != NULL && previous_layer->size() >0? previous_layer->at(previous_layer->size()-1): SpiralVase::SpiralPoint(0,0);
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, &current_layer, &previous_layer, &transition_gcode, transition_out, smooth_spiral, &max_xy_dist_for_smoothing, &last_point, starting_flowrate, finishing_flowrate]
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, &current_layer, &previous_layer, &transition_gcode, transition_out, smooth_spiral, &max_xy_dist_for_smoothing, &last_point, starting_flowrate, finishing_flowrate, min_segment_length]
(GCodeReader &reader, GCodeReader::GCodeLine line) {
if (line.cmd_is("G1")) {
// Orca: Filter out retractions at layer change
if (line.retracting(reader) || (line.extruding(reader) && line.dist_XY(reader) < EPSILON)) return;
if (line.retracting(reader) || (line.extruding(reader) && line.dist_XY(reader) < min_segment_length)) return;
if (line.has_z() && !(line.has_x() || line.has_y())) {
// If this is the initial Z move of the layer, replace it with a
// (redundant) move to the last Z of previous layer.
@@ -175,7 +176,7 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
// Remove tiny movement
// We need to figure out the distance of this new line!
float modified_dist_XY = SpiralVaseHelpers::distance(last_point, target);
if (modified_dist_XY < 0.001)
if (modified_dist_XY < min_segment_length)
line.clear();
else {
line.set(X, target.x);
+16 -9
View File
@@ -506,9 +506,20 @@ std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, boo
std::string GCodeWriter::toolchange_prefix() const
{
return config.manual_filament_change ? ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T":
FLAVOR_IS(gcfMakerWare) ? "M135 T" :
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
std::string gcode = "T";
if (config.manual_filament_change)
gcode = ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T";
else {
if (m_is_bbl_printers)
gcode = "M1020 S";
else {
if (FLAVOR_IS(gcfMakerWare))
gcode = "M135 T";
else if (FLAVOR_IS(gcfSailfish))
gcode = "M108 T";
}
}
return gcode;
}
std::string GCodeWriter::toolchange(unsigned int filament_id)
@@ -523,12 +534,8 @@ std::string GCodeWriter::toolchange(unsigned int filament_id)
// if we are running a single-extruder setup, just set the extruder and return nothing
std::ostringstream gcode;
if (this->multiple_extruders || (this->config.filament_diameter.values.size() > 1 && !is_bbl_printers())) {
// BBS
if (this->m_is_bbl_printers)
gcode << "M1020 S" << filament_id;
else
gcode << this->toolchange_prefix() << filament_id;
//BBS
// Orca: call toolchange_prefix() to get the correct command prefix based on the configuration and flavor.
gcode << this->toolchange_prefix() << filament_id;
if (GCodeWriter::full_gcode_comment)
gcode << " ; change extruder";
gcode << "\n";
+17
View File
@@ -250,6 +250,23 @@ void Layer::make_perimeters()
//BBS: Separate fill_no_overlap
(*l)->fill_no_overlap_expolygons = intersection_ex((*l)->slices.surfaces, fill_no_overlap);
}
// When counterbore hole bridging (chbFilled) is active, process_no_bridge may
// create fill surfaces that extend beyond all region slices (e.g. by clearing
// holes in the bridge expolygon). These "extra" fills are lost during the
// intersection-based splitting above. Recover them and assign to the first
// merged region so the sacrificial bridge layer is not broken.
if (layerm_config->region().config().counterbore_hole_bridging.value != chbNone) {
Polygons all_region_slices_p;
for (LayerRegion *l : layerms)
polygons_append(all_region_slices_p, to_polygons(l->slices.surfaces));
ExPolygons extra_fill = diff_ex(fill_surfaces.surfaces, all_region_slices_p, ApplySafetyOffset::Yes);
if (!extra_fill.empty()) {
append(layerms.front()->fill_expolygons, extra_fill);
layerms.front()->fill_expolygons = union_ex(layerms.front()->fill_expolygons);
layerms.front()->fill_surfaces.append(std::move(extra_fill), fill_surfaces.surfaces.front());
}
}
}
}
}
+1
View File
@@ -322,6 +322,7 @@ protected:
ExPolygon *area;
int type;
int interface_id = 0;
bool interface_as_base = false;
coordf_t dist_to_top; // mm dist to top
bool need_infill = false;
bool need_extra_wall = false;
+2 -1
View File
@@ -103,7 +103,8 @@ public:
bool has_fuzzy_skin = false;
bool has_fuzzy_hole = false;
std::unordered_map<FuzzySkinConfig, ExPolygons> regions_by_fuzzify;
// Preserve construction order so overlap precedence remains deterministic.
std::vector<std::pair<FuzzySkinConfig, ExPolygons>> regions_by_fuzzify;
PerimeterGenerator(
// Input:
+2 -2
View File
@@ -1281,7 +1281,7 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
// "bed_type",
//BBS:temperature_vitrification
"temperature_vitrification", "reduce_fan_stop_start_freq","dont_slow_down_outer_wall", "slow_down_for_layer_cooling", "fan_min_speed",
"fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "full_fan_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed",
"fan_max_speed", "enable_overhang_bridge_fan", "overhang_fan_speed", "overhang_fan_threshold", "close_fan_the_first_x_layers", "close_additional_fan_first_x_layers", "first_x_layer_fan_speed", "full_fan_speed_layer", "additional_fan_full_speed_layer", "fan_cooling_layer_time", "slow_down_layer_time", "slow_down_min_speed",
"filament_start_gcode", "filament_end_gcode", "filament_change_extrusion_role_gcode",
//exhaust fan control
"activate_air_filtration","activate_air_filtration_during_print","activate_air_filtration_on_completion","during_print_exhaust_fan_speed","complete_print_exhaust_fan_speed",
@@ -1305,7 +1305,7 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
"filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control",
"filament_long_retractions_when_cut","filament_retraction_distances_when_cut", "idle_temperature",
//BBS filament change length while the extruder color
"filament_change_length","filament_flush_volumetric_speed","filament_flush_temp",
"filament_change_length","filament_flush_volumetric_speed","filament_flush_temp", "filament_cooling_before_tower",
"long_retractions_when_ec", "retraction_distances_when_ec"
};
+112 -87
View File
@@ -1044,29 +1044,61 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
return {};
}
FilamentCompatibilityType Print::check_multi_filaments_compatibility(const std::vector<std::string>& filament_types)
FilamentCompatibilityType Print::check_multi_filaments_compatibility(
const std::vector<std::string>& filament_types,
const std::vector<int>& nozzle_temperatures,
const std::vector<int>& nozzle_temperature_range_lows,
const std::vector<int>& nozzle_temperature_range_highs)
{
bool has_high_temperature_filament = false;
bool has_low_temperature_filament = false;
bool has_mid_temperature_filament = false;
const size_t filament_count = filament_types.size();
if (filament_count < 2)
return FilamentCompatibilityType::Compatible;
for (const auto& type : filament_types) {
if (get_filament_temp_type(type) ==FilamentTempType::HighTemp)
has_high_temperature_filament = true;
else if (get_filament_temp_type(type) == FilamentTempType::LowTemp)
has_low_temperature_filament = true;
else if (get_filament_temp_type(type) == FilamentTempType::HighLowCompatible)
has_mid_temperature_filament = true;
std::vector<int> resolved_temperatures(filament_count, 0);
std::vector<int> resolved_range_lows(filament_count, 0);
std::vector<int> resolved_range_highs(filament_count, 0);
for (size_t i = 0; i < filament_count; ++i) {
int range_low = (i < nozzle_temperature_range_lows.size()) ? nozzle_temperature_range_lows[i] : 0;
int range_high = (i < nozzle_temperature_range_highs.size()) ? nozzle_temperature_range_highs[i] : 0;
if (range_low == 0 || range_high == 0) {
int default_low = range_low;
int default_high = range_high;
MaterialType::get_temperature_range(filament_types[i], default_low, default_high);
if (range_low == 0)
range_low = default_low;
if (range_high == 0)
range_high = default_high;
}
if (range_low >= range_high)
return FilamentCompatibilityType::InvalidTemperatureRange;
int print_temperature = (i < nozzle_temperatures.size()) ? nozzle_temperatures[i] : 0;
resolved_temperatures[i] = print_temperature;
resolved_range_lows[i] = range_low;
resolved_range_highs[i] = range_high;
}
if (has_high_temperature_filament && has_low_temperature_filament)
return FilamentCompatibilityType::HighLowMixed;
else if (has_high_temperature_filament && has_mid_temperature_filament)
return FilamentCompatibilityType::HighMidMixed;
else if (has_low_temperature_filament && has_mid_temperature_filament)
return FilamentCompatibilityType::LowMidMixed;
else
return FilamentCompatibilityType::Compatible;
for (size_t i = 0; i < filament_count; ++i) {
for (size_t j = i + 1; j < filament_count; ++j) {
const bool i_temp_is_compatible_with_j =
resolved_temperatures[i] >= resolved_range_lows[j] &&
resolved_temperatures[i] <= resolved_range_highs[j];
const bool j_temp_is_compatible_with_i =
resolved_temperatures[j] >= resolved_range_lows[i] &&
resolved_temperatures[j] <= resolved_range_highs[i];
if (i_temp_is_compatible_with_j && j_temp_is_compatible_with_i)
continue;
// Range-only rule: any pair outside mutual recommended ranges is incompatible.
return FilamentCompatibilityType::HighLowMixed;
}
}
return FilamentCompatibilityType::Compatible;
}
bool Print::is_filaments_compatible(const std::vector<int>& filament_types)
@@ -1111,18 +1143,21 @@ int Print::get_compatible_filament_type(const std::set<int>& filament_types)
StringObjectException Print::check_multi_filament_valid(const Print& print)
{
auto print_config = print.config();
const std::string incompatible_temp_msg = L("Selected nozzle temperatures are incompatible. Each filament's nozzle temperature must fall within the recommended nozzle temperature range of the other filaments. Otherwise, nozzle clogging or printer damage may occur.");
const std::string invalid_temp_range_msg = L("Invalid recommended nozzle temperature range. The lower bound must be lower than the upper bound.");
const std::string incompatible_temp_msg_preferences_enable = L("If you still want to print, you can enable the option in Preferences / Control / Slicing / Remove mixed temperature restriction.");
if(print_config.print_sequence == PrintSequence::ByObject) {// use ByObject valid under ByObject print sequence
std::set<FilamentCompatibilityType> Compatibility_each_obj;
bool has_incompatible_object = false;
bool enable_mix_printing = !print.need_check_multi_filaments_compatibility();
StringObjectException ret;
for (const auto &objectID_t : print.print_object_ids()) {
std::set<int> obj_used_extruder_ids;
auto print_object = print.get_object(objectID_t);// current object
if (print_object){
auto object_extruders_t = print_object->object_extruders(); // object used extruder
for (int extruder : object_extruders_t) {
assert(extruder > 0);
obj_used_extruder_ids.insert(extruder);
for (unsigned int extruder : object_extruders_t) {
obj_used_extruder_ids.insert(static_cast<int>(extruder));
}
}
@@ -1136,57 +1171,83 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
obj_used_extruder_ids.insert((unsigned int) print_object->config().support_interface_filament - 1);
}
std::vector<std::string> filament_types;
std::vector<int> nozzle_temperatures;
std::vector<int> nozzle_temperature_range_lows;
std::vector<int> nozzle_temperature_range_highs;
filament_types.reserve(obj_used_extruder_ids.size());
for (const auto &extruder_idx : obj_used_extruder_ids) filament_types.push_back(print_config.filament_type.get_at(extruder_idx));
nozzle_temperatures.reserve(obj_used_extruder_ids.size());
nozzle_temperature_range_lows.reserve(obj_used_extruder_ids.size());
nozzle_temperature_range_highs.reserve(obj_used_extruder_ids.size());
auto compatibility = check_multi_filaments_compatibility(filament_types);// check for each object
Compatibility_each_obj.insert(compatibility);
for (const auto &extruder_idx : obj_used_extruder_ids) {
filament_types.push_back(print_config.filament_type.get_at(extruder_idx));
nozzle_temperatures.push_back(print_config.nozzle_temperature.get_at(extruder_idx));
nozzle_temperature_range_lows.push_back(print_config.nozzle_temperature_range_low.get_at(extruder_idx));
nozzle_temperature_range_highs.push_back(print_config.nozzle_temperature_range_high.get_at(extruder_idx));
}
auto compatibility = check_multi_filaments_compatibility(
filament_types,
nozzle_temperatures,
nozzle_temperature_range_lows,
nozzle_temperature_range_highs); // check for each object
if (compatibility == FilamentCompatibilityType::InvalidTemperatureRange) {
ret.string = invalid_temp_range_msg;
return ret;
}
if (compatibility != FilamentCompatibilityType::Compatible) {
has_incompatible_object = true;
break;
}
}
StringObjectException ret;
std::string hypertext = "filament_mix_print";
if (Compatibility_each_obj.count(FilamentCompatibilityType::HighLowMixed)){// at least one object has HighLowMixed
if (has_incompatible_object){
if (enable_mix_printing) {
ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage.");
ret.string = incompatible_temp_msg;
ret.is_warning = true;
// ret.hypetext = hypertext;
} else
ret.string = L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage. If you still want to print, you can enable the option in Preferences.");
}else if (Compatibility_each_obj.count(FilamentCompatibilityType::LowMidMixed) || Compatibility_each_obj.count(FilamentCompatibilityType::HighMidMixed)){// at least one object has other Mixed
ret.is_warning = true;
// ret.hypetext = hypertext;
ret.string = L("Printing different-temp filaments together may cause nozzle clogging or printer damage.");
ret.string = incompatible_temp_msg + " " + incompatible_temp_msg_preferences_enable;
}
return ret;
}
std::vector<unsigned int> extruders = print.extruders();
std::vector<std::string> filament_types;
std::vector<int> nozzle_temperatures;
std::vector<int> nozzle_temperature_range_lows;
std::vector<int> nozzle_temperature_range_highs;
filament_types.reserve(extruders.size());
for (const auto& extruder_idx : extruders)
nozzle_temperatures.reserve(extruders.size());
nozzle_temperature_range_lows.reserve(extruders.size());
nozzle_temperature_range_highs.reserve(extruders.size());
for (const auto& extruder_idx : extruders) {
filament_types.push_back(print_config.filament_type.get_at(extruder_idx));
nozzle_temperatures.push_back(print_config.nozzle_temperature.get_at(extruder_idx));
nozzle_temperature_range_lows.push_back(print_config.nozzle_temperature_range_low.get_at(extruder_idx));
nozzle_temperature_range_highs.push_back(print_config.nozzle_temperature_range_high.get_at(extruder_idx));
}
auto compatibility = check_multi_filaments_compatibility(filament_types);
auto compatibility = check_multi_filaments_compatibility(
filament_types,
nozzle_temperatures,
nozzle_temperature_range_lows,
nozzle_temperature_range_highs);
bool enable_mix_printing = !print.need_check_multi_filaments_compatibility();
StringObjectException ret;
if(compatibility == FilamentCompatibilityType::HighLowMixed){
if (compatibility == FilamentCompatibilityType::InvalidTemperatureRange) {
ret.string = invalid_temp_range_msg;
return ret;
}
if(compatibility != FilamentCompatibilityType::Compatible){
if(enable_mix_printing){
ret.string =L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage.");
ret.string = incompatible_temp_msg;
ret.is_warning = true;
}
else{
ret.string =L("Printing high-temp and low-temp filaments together may cause nozzle clogging or printer damage. If you still want to print, you can enable the option in Preferences.");
ret.string = incompatible_temp_msg + " " + incompatible_temp_msg_preferences_enable;
}
}
else if (compatibility == FilamentCompatibilityType::HighMidMixed) {
ret.is_warning = true;
ret.string =L("Printing high-temp and mid-temp filaments together may cause nozzle clogging or printer damage.");
}
else if (compatibility == FilamentCompatibilityType::LowMidMixed) {
ret.is_warning = true;
ret.string = L("Printing mid-temp and low-temp filaments together may cause nozzle clogging or printer damage.");
}
return ret;
}
@@ -2798,43 +2859,7 @@ Vec2d Print::translate_to_print_space(const Point &point) const {
FilamentTempType Print::get_filament_temp_type(const std::string& filament_type)
{
const static std::string HighTempFilamentStr = "high_temp_filament";
const static std::string LowTempFilamentStr = "low_temp_filament";
const static std::string HighLowCompatibleFilamentStr = "high_low_compatible_filament";
static std::unordered_map<std::string, std::unordered_set<std::string>>filament_temp_type_map;
if (filament_temp_type_map.empty()) {
fs::path file_path = fs::path(resources_dir()) / "info" / "filament_info.json";
std::ifstream in(file_path.string());
json j;
try{
j = json::parse(in);
in.close();
auto&&high_temp_filament_arr =j[HighTempFilamentStr].get < std::vector<std::string>>();
filament_temp_type_map[HighTempFilamentStr] = std::unordered_set<std::string>(high_temp_filament_arr.begin(), high_temp_filament_arr.end());
auto&& low_temp_filament_arr = j[LowTempFilamentStr].get < std::vector<std::string>>();
filament_temp_type_map[LowTempFilamentStr] = std::unordered_set<std::string>(low_temp_filament_arr.begin(), low_temp_filament_arr.end());
auto&& high_low_compatible_filament_arr = j[HighLowCompatibleFilamentStr].get < std::vector<std::string>>();
filament_temp_type_map[HighLowCompatibleFilamentStr] = std::unordered_set<std::string>(high_low_compatible_filament_arr.begin(), high_low_compatible_filament_arr.end());
}
catch (const json::parse_error& err){
in.close();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": parse " << file_path.string() << " got a nlohmann::detail::parse_error, reason = " << err.what();
filament_temp_type_map[HighTempFilamentStr] = {"ABS","ASA","PC","PA","PA-CF","PA-GF","PA6-CF","PET-CF", "PETG-GF","PPS","PPS-CF","PPA-GF","PPA-CF","ABS-Aero","ABS-GF"};
filament_temp_type_map[LowTempFilamentStr] = {"PLA","TPU","PLA-CF","PLA-AERO","PVA","BVOH","SBS"};
filament_temp_type_map[HighLowCompatibleFilamentStr] = { "HIPS","PETG","PCTG","PE","PP","EVA","PE-CF","PP-CF","PP-GF","PHA"};
}
}
if (filament_temp_type_map[HighLowCompatibleFilamentStr].find(filament_type) != filament_temp_type_map[HighLowCompatibleFilamentStr].end())
return HighLowCompatible;
if (filament_temp_type_map[HighTempFilamentStr].find(filament_type) != filament_temp_type_map[HighTempFilamentStr].end())
return HighTemp;
if (filament_temp_type_map[LowTempFilamentStr].find(filament_type) != filament_temp_type_map[LowTempFilamentStr].end())
return LowTemp;
// Orca: prefer explicit definition from JSON, if the filament type is not defined in json, fallback to temperature-based logic to determine the filament temp type.
// FilamentTempType Temperature-based logic
// Range-based classification only: do not use filament_info.json.
int min_temp, max_temp;
if (MaterialType::get_temperature_range(filament_type, min_temp, max_temp)) {
if (max_temp <= 250)
+9 -4
View File
@@ -877,8 +877,9 @@ enum FilamentTempType {
enum FilamentCompatibilityType {
Compatible,
HighLowMixed,
HighMidMixed,
LowMidMixed
//HighLowMixed,
//HighMidMixed,
InvalidTemperatureRange
};
// The complete print tray with possibly multiple objects.
@@ -905,7 +906,7 @@ public:
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
std::vector<ObjectID> print_object_ids() const override;
ApplyStatus apply(const Model &model, DynamicPrintConfig config) override;
ApplyStatus apply(const Model &model, DynamicPrintConfig config, bool extruder_applied = false) override;
void process(long long *time_cost_with_cache = nullptr, bool use_cache = false) override;
// Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
@@ -1087,7 +1088,11 @@ public:
static FilamentTempType get_filament_temp_type(const std::string& filament_type);
static int get_hrc_by_nozzle_type(const NozzleType& type);
static std::vector<std::string> get_incompatible_filaments_by_nozzle(const float nozzle_diameter, const std::optional<NozzleVolumeType> nozzle_volume_type = std::nullopt);
static FilamentCompatibilityType check_multi_filaments_compatibility(const std::vector<std::string>& filament_types);
static FilamentCompatibilityType check_multi_filaments_compatibility(
const std::vector<std::string>& filament_types,
const std::vector<int>& nozzle_temperatures,
const std::vector<int>& nozzle_temperature_range_lows,
const std::vector<int>& nozzle_temperature_range_highs);
// similar to check_multi_filaments_compatibility, but the input is int, and may be negative (means unset)
static bool is_filaments_compatible(const std::vector<int>& types);
// get the compatible filament type of a multi-material object
+19 -7
View File
@@ -1104,7 +1104,7 @@ static PrintObjectRegions* generate_print_object_regions(
return out.release();
}
Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config)
Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config, bool extruder_applied)
{
#ifdef _DEBUG
check_model_ids_validity(model);
@@ -1156,13 +1156,25 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
}
//apply extruder related values
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
//update print config related with variants
new_full_config.update_values_to_printer_extruders(new_full_config, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
if (!extruder_applied) {
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant");
new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
//update print config related with variants
new_full_config.update_values_to_printer_extruders(new_full_config, print_options_with_variant, "print_extruder_id", "print_extruder_variant");
m_ori_full_print_config = new_full_config;
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant");
}
// else {
// int extruder_count;
// bool different_extruder = new_full_config.support_different_extruders(extruder_count);
// print_variant_index.resize(extruder_count);
// for (int e_index = 0; e_index < extruder_count; e_index++)
// {
// print_variant_index[e_index] = e_index;
// }
// }
m_ori_full_print_config = new_full_config;
new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant");
auto opt_filament_map = new_full_config.option<ConfigOptionInts>("filament_map");
std::vector<int> filament_maps = opt_filament_map ? opt_filament_map->values : std::vector<int>();
+1 -1
View File
@@ -407,7 +407,7 @@ public:
// Some data was changed, which in turn invalidated already calculated steps.
APPLY_STATUS_INVALIDATED,
};
virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config) = 0;
virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config, bool extruder_applied = false) = 0;
const Model& model() const { return m_model; }
struct TaskParams {
+96 -21
View File
@@ -11,6 +11,7 @@
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/log/trivial.hpp>
@@ -172,6 +173,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PowerLossRecoveryMode)
static t_config_enum_values s_keys_map_FuzzySkinType {
{ "none", int(FuzzySkinType::None) },
{ "external", int(FuzzySkinType::External) },
{ "hole", int(FuzzySkinType::Hole) },
{ "all", int(FuzzySkinType::All) },
{ "allwalls", int(FuzzySkinType::AllWalls)},
{ "disabled_fuzzy", int(FuzzySkinType::Disabled_fuzzy)}
@@ -445,7 +447,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(OverhangFanThreshold)
// BBS
static const t_config_enum_values s_keys_map_BedType = {
{ "Default Plate", btDefault },
{ "SuperTack Plate", btSuperTack },
{ "Supertack Plate", btSuperTack },
{ "Cool Plate", btPC },
{ "Engineering Plate", btEP },
{ "High Temp Plate", btPEI },
@@ -1013,7 +1015,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.emplace_back("High Temp Plate");
def->enum_values.emplace_back("Textured PEI Plate");
def->enum_values.emplace_back("Textured Cool Plate");
def->enum_values.emplace_back("SuperTack Plate");
def->enum_values.emplace_back("Supertack Plate");
def->enum_labels.emplace_back(L("Smooth Cool Plate"));
def->enum_labels.emplace_back(L("Engineering Plate"));
def->enum_labels.emplace_back(L("Smooth High Temp Plate"));
@@ -2636,6 +2638,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloats { 15. });
def = this->add("filament_cooling_before_tower", coFloats);
def->label = L("Wipe tower cooling");
def->tooltip = L("Temperature drop before entering filament tower");
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->mode = comDevelop;
def->nullable = true;
def->set_default_value(new ConfigOptionFloatsNullable { 10. });
def = this->add("filament_tower_interface_pre_extrusion_dist", coFloats);
def->label = L("Interface layer pre-extrusion distance");
def->tooltip = L("Pre-extrusion distance for prime tower interface layer (where different materials meet).");
@@ -3354,11 +3364,13 @@ void PrintConfigDef::init_fff_params()
def->enum_keys_map = &ConfigOptionEnum<FuzzySkinType>::get_enum_values();
def->enum_values.push_back("none");
def->enum_values.push_back("external");
def->enum_values.push_back("hole");
def->enum_values.push_back("all");
def->enum_values.push_back("allwalls");
def->enum_values.push_back("disabled_fuzzy");
def->enum_labels.push_back(L("Painted only"));
def->enum_labels.push_back(L("Contour"));
def->enum_labels.push_back(L("Hole"));
def->enum_labels.push_back(L("Contour and hole"));
def->enum_labels.push_back(L("All walls"));
def->enum_labels.push_back(L("Disabled"));
@@ -3472,31 +3484,32 @@ void PrintConfigDef::init_fff_params()
def = this->add("fuzzy_skin_ripples_per_layer", coInt);
def->label = L("Number of ripples per layer");
def->category = L("Others");
def->tooltip = L("When using the Ripple noise type, this controls how many full cycles of ripples will be added per layer.");
def->tooltip = L("Controls how many full cycles of ripples will be added per layer.");
def->min = 1;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(15));
def = this->add("fuzzy_skin_ripple_offset", coFloat);
def = this->add("fuzzy_skin_ripple_offset", coPercent);
def->label = L("Ripple offset");
def->category = L("Others");
def->tooltip = L("When using the Ripple noise type, shifts the ripple pattern forward along the print path by this amount each "
"layer-period. A value of 0 keeps every layer identical. A value equal to 0.5 shifts by a full "
"half-wavelength, inverting the pattern. The shift is applied once per 'Layers between Ripple offset' layers, "
"so consecutive layers within a period are printed identically on top of each other.");
def->tooltip = L("Shifts the ripple phase forward along the print path by the specified percentage of a wavelength each layer period.\n"
"- 0% keeps every layer identical.\n"
"- 50% shifts the pattern by half a wavelength, effectively inverting the phase.\n"
"- 100% shifts the pattern by a full wavelength, returning to the original phase.\n\n"
"The shift is applied once every number of layers set by Layers between ripple offset, so layers within the same group are printed identically.");
def->min = 0;
def->max = 1;
def->max = 100;
def->sidetext = ("%");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.5));
def->set_default_value(new ConfigOptionPercent(50));
def = this->add("fuzzy_skin_layers_between_ripple_offset", coInt);
def->label = L("Layers between ripple offset");
def->category = L("Others");
def->tooltip = L("When using the Ripple noise type with a non-zero layer offset, this controls how "
"many consecutive layers share the same ripple phase before the offset is applied. "
"For example, a period of 3 means layers 0, 1 and 2 are identical, then layers 3, 4 "
"and 5 are shifted by one full 'Ripple layer offset', and so on. "
"Set to 1 to shift on every layer.");
def->tooltip = L("Specifies how many consecutive layers share the same ripple phase before the offset is applied.\n"
"For example:\n"
"- 1 = Layer 1 is printed with the base ripple pattern, then layer 2 is shifted by the configured offset, then layer 3 returns to the base pattern, and so on.\n"
"- 3 = Layers 1 to 3 are printed with the base ripple pattern, then layers 4 to 6 are shifted by the configured offset, then layers 7 to 9 return to the base pattern, etc.");
def->min = 1;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(1));
@@ -4520,6 +4533,33 @@ void PrintConfigDef::init_fff_params()
def->mode = comSimple;
def->set_default_value(new ConfigOptionInts { 0 });
def = this->add("close_additional_fan_first_x_layers", coInts);
def->label = L("For the first");
def->tooltip = L("Set special auxiliary cooling fan for the first certain layers.");
def->sidetext = L("layers");
def->min = 0;
def->max = 1000;
def->mode = comSimple;
def->set_default_value(new ConfigOptionInts { 1 });
def = this->add("additional_fan_full_speed_layer", coInts);
def->label = L("Full fan speed at layer");
def->tooltip = L("Auxiliary fan speed will be ramped up linearly from layer \"For the first\" to maximum at layer \"Full fan speed at layer\". "
"\"Full fan speed at layer\" will be ignored if lower than \"For the first\", in which case the fan will run at maximum allowed speed at layer \"For the first\" + 1.");
def->min = 0;
def->max = 1000;
def->mode = comSimple;
def->set_default_value(new ConfigOptionInts { 0 });
def = this->add("first_x_layer_fan_speed", coFloats);
def->label = L("Fan speed");
def->tooltip = L("Special auxiliary cooling fan speed, effective only for the first x layers.");
def->sidetext = "%";
def->min = 0;
def->max = 100;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloats { 0 });
def = this->add("min_layer_height", coFloats);
def->label = L("Min");
def->tooltip = L("The lowest printable layer height for the extruder. "
@@ -4814,7 +4854,9 @@ void PrintConfigDef::init_fff_params()
def = this->add("raft_contact_distance", coFloat);
def->label = L("Raft contact Z distance");
def->category = L("Support");
def->tooltip = L("Z gap between object and raft. Ignored for soluble interface.");
def->tooltip = L("Z gap between raft and object. "
"If Support Top Z Distance is 0, this value is ignored and "
"the object is printed in direct contact with the raft (no gap).");
def->sidetext = L("mm"); // millimeters, CIS languages need translation
def->min = 0;
def->mode = comAdvanced;
@@ -5799,7 +5841,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Top Z distance");
def->min = 0;
def->category = L("Support");
def->tooltip = L("The Z gap between the top support interface and object.");
def->tooltip = L("Z gap between the support's top and object.");
def->sidetext = L("mm"); // millimeters, CIS languages need translation
// def->min = 0;
#if 0
@@ -5816,7 +5858,9 @@ void PrintConfigDef::init_fff_params()
def = this->add("support_bottom_z_distance", coFloat);
def->label = L("Bottom Z distance");
def->category = L("Support");
def->tooltip = L("The Z gap between the bottom support interface and object.");
def->tooltip = L("Z gap between the object and the support bottom. "
"If Support Top Z Distance is 0 and the bottom has interface layers, this value "
"is ignored and the support is printed in direct contact with the object (no gap).");
def->sidetext = L("mm"); // millimeters, CIS languages need translation
def->min = 0;
def->mode = comAdvanced;
@@ -7676,7 +7720,9 @@ void PrintConfigDef::init_sla_params()
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
{
//BBS: handle legacy options
if (opt_key == "enable_wipe_tower") {
if (opt_key == "curr_bed_type" && value == "SuperTack Plate") {
value = "Supertack Plate";
} else if (opt_key == "enable_wipe_tower") {
opt_key = "enable_prime_tower";
} else if (opt_key == "wipe_tower_width") {
opt_key = "prime_tower_width";
@@ -8019,6 +8065,7 @@ std::set<std::string> filament_options_with_variant = {
"nozzle_temperature",
"filament_flush_volumetric_speed",
"filament_flush_temp",
"filament_cooling_before_tower",
"volumetric_speed_coefficients",
"filament_adaptive_volumetric_speed",
"filament_ironing_flow",
@@ -8577,16 +8624,37 @@ int DynamicPrintConfig::get_index_for_extruder(int extruder_or_filament_id, std:
auto variant_opt = dynamic_cast<const ConfigOptionStrings*>(this->option(variant_name));
const ConfigOptionInts* id_opt = id_name.empty()?nullptr: dynamic_cast<const ConfigOptionInts*>(this->option(id_name));
const ConfigOptionStrings* extruder_variant_list_opt = dynamic_cast<const ConfigOptionStrings*>(this->option("extruder_variant_list"));
auto generated_extruder_id = [extruder_variant_list_opt](int target_index) {
if (!extruder_variant_list_opt)
return 0;
int variant_index = 0;
for (int extruder_index = 0; extruder_index < int(extruder_variant_list_opt->values.size()); ++extruder_index) {
std::vector<std::string> variants_list;
boost::split(variants_list, extruder_variant_list_opt->get_at(extruder_index), boost::is_any_of(","), boost::token_compress_on);
for (std::string variant : variants_list) {
boost::trim(variant);
if (variant.empty())
continue;
if (variant_index == target_index)
return extruder_index + 1;
++variant_index;
}
}
return 0;
};
if (variant_opt != nullptr) {
int v_size = variant_opt->values.size();
//int i_size = id_opt->values.size();
const bool has_complete_id_map = id_opt && int(id_opt->values.size()) >= v_size;
std::string extruder_variant = get_extruder_variant_string(extruder_type, nozzle_volume_type);
for (int index = 0; index < v_size; index++)
{
const std::string variant = variant_opt->get_at(index);
if (extruder_variant == variant) {
if (id_opt) {
const int id = id_opt->get_at(index);
const int id = has_complete_id_map ? id_opt->get_at(index) : generated_extruder_id(index);
if (id == extruder_or_filament_id) {
ret = index * stride;
break;
@@ -9637,6 +9705,13 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi
//nothing to do, keep the original one
}
else {
// Guard: set_with_restore is parent-shaped and would truncate the child's
// vector when the child has more extruders than the parent (e.g. an IDEX
// preset inheriting from a single-nozzle base). The child's saved value is
// authoritative for its own extruder count, so skip the merge for this key.
if (cur_variant_count > target_variant_count)
continue;
int stride = 1;
if (key_set2.find(opt) != key_set2.end())
stride = 2;
+6 -1
View File
@@ -39,6 +39,7 @@ enum GCodeFlavor : unsigned char {
enum class FuzzySkinType {
None,
External,
Hole,
All,
AllWalls,
Disabled_fuzzy,
@@ -1086,7 +1087,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInt, fuzzy_skin_octaves))
((ConfigOptionFloat, fuzzy_skin_persistence))
((ConfigOptionInt, fuzzy_skin_ripples_per_layer))
((ConfigOptionFloat, fuzzy_skin_ripple_offset))
((ConfigOptionPercent, fuzzy_skin_ripple_offset))
((ConfigOptionInt, fuzzy_skin_layers_between_ripple_offset))
((ConfigOptionFloat, gap_infill_speed))
((ConfigOptionInt, sparse_infill_filament))
@@ -1399,6 +1400,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInts, filament_cooling_moves))
((ConfigOptionFloats, filament_cooling_initial_speed))
((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower))
((ConfigOptionFloatsNullable, filament_cooling_before_tower))
((ConfigOptionFloats, filament_tower_interface_pre_extrusion_dist))
((ConfigOptionFloats, filament_tower_interface_pre_extrusion_length))
((ConfigOptionFloats, filament_tower_ironing_area))
@@ -1429,6 +1431,9 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
//BBS
((ConfigOptionInts, additional_cooling_fan_speed))
((ConfigOptionInts, close_additional_fan_first_x_layers))
((ConfigOptionInts, additional_fan_full_speed_layer))
((ConfigOptionFloats, first_x_layer_fan_speed))
((ConfigOptionBool, reduce_crossing_wall))
((ConfigOptionFloatOrPercent, max_travel_detour_distance))
((ConfigOptionPoints, printable_area))
+5
View File
@@ -49,6 +49,11 @@ struct FilamentInfo
int ctype = 0;
std::vector<std::string> colors = std::vector<std::string>();
int mapping_result = 0;
bool used_for_support{false};
bool used_for_object{false};
std::vector<int> group_id;
double nozzle_diameter{0.0};
std::string nozzle_volume_type;
/*for new ams mapping*/
std::string ams_id;
+1 -1
View File
@@ -184,7 +184,7 @@ std::vector<ObjectID> SLAPrint::print_object_ids() const
return out;
}
SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config)
SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config, bool extruder_applied)
{
#ifdef _DEBUG
check_model_ids_validity(model);
+1 -1
View File
@@ -450,7 +450,7 @@ public:
bool empty() const override { return m_objects.empty(); }
// List of existing PrintObject IDs, to remove notifications for non-existent IDs.
std::vector<ObjectID> print_object_ids() const override;
ApplyStatus apply(const Model &model, DynamicPrintConfig config) override;
ApplyStatus apply(const Model &model, DynamicPrintConfig config, bool extruder_applied = false) override;
void set_task(const TaskParams &params) override;
void process(long long *time_cost_with_cache = nullptr, bool use_cache = false) override;
void finalize() override;
+91 -26
View File
@@ -60,14 +60,15 @@ coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_c
}
SlicingParameters SlicingParameters::create_from_config(
const PrintConfig &print_config,
const PrintObjectConfig &object_config,
coordf_t object_height,
const std::vector<unsigned int> &object_extruders,
const Vec3d &object_shrinkage_compensation)
const PrintConfig &print_config,
const PrintObjectConfig &object_config,
coordf_t object_height,
const std::vector<unsigned int> &object_extruders,
const Vec3d &object_shrinkage_compensation)
{
coordf_t initial_layer_print_height = (print_config.initial_layer_print_height.value <= 0) ?
object_config.layer_height.value : print_config.initial_layer_print_height.value;
// If object_config.support_filament == 0 resp. object_config.support_interface_filament == 0,
// print_config.nozzle_diameter.get_at(size_t(-1)) returns the 0th nozzle diameter,
// which is consistent with the requirement that if support_filament == 0 resp. support_interface_filament == 0,
@@ -75,19 +76,47 @@ SlicingParameters SlicingParameters::create_from_config(
// In that case all the nozzles have to be of the same diameter.
coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_filament.value - 1);
coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_interface_filament.value - 1);
bool soluble_interface = object_config.support_top_z_distance.value == 0.;
// ORCA: store Z distance
const coordf_t support_top_z_gap = object_config.support_top_z_distance.value;
const coordf_t support_bottom_z_gap = object_config.support_bottom_z_distance.value;
const coordf_t raft_z_gap = object_config.raft_contact_distance.value;
/* -------------------------------------------------- */
/* ORCA: Zero-gap interface detection (asymmetric) */
/* -------------------------------------------------- */
const bool zero_topZ_contact =
support_top_z_gap == 0.0;
const bool zero_gap_interface_top =
object_config.support_interface_top_layers.value > 0 && // Has some top interface layers
zero_topZ_contact;
const bool zero_gap_interface_bottom =
(object_config.support_interface_bottom_layers.value < 0 // Negative value means "use same as top"
? object_config.support_interface_top_layers.value
: object_config.support_interface_bottom_layers.value) > 0 && // Has some bottom interface layers
(support_bottom_z_gap == 0.0 || zero_topZ_contact);
const bool zero_gap_interface_raft =
raft_z_gap == 0.0 || zero_topZ_contact;
SlicingParameters params;
params.layer_height = object_config.layer_height.value;
params.first_print_layer_height = initial_layer_print_height;
params.first_object_layer_height = initial_layer_print_height;
params.object_print_z_min = 0.;
params.layer_height = object_config.layer_height.value;
params.first_print_layer_height = initial_layer_print_height;
params.first_object_layer_height = initial_layer_print_height;
params.object_print_z_min = 0.0;
// Orca: XYZ filament compensation
params.object_print_z_max = object_height * object_shrinkage_compensation.z();
params.object_print_z_max = object_height * object_shrinkage_compensation.z();
params.object_print_z_uncompensated_max = object_height;
params.object_shrinkage_compensation_z = object_shrinkage_compensation.z();
params.base_raft_layers = object_config.raft_layers.value;
params.soluble_interface = soluble_interface;
params.object_shrinkage_compensation_z = object_shrinkage_compensation.z();
params.base_raft_layers = object_config.raft_layers.value;
params.zero_gap_interface_top = zero_gap_interface_top;
params.zero_gap_interface_bottom = zero_gap_interface_bottom;
params.zero_gap_interface_raft = zero_gap_interface_raft;
// Miniumum/maximum of the minimum layer height over all extruders.
params.min_layer_height = MIN_LAYER_HEIGHT;
@@ -102,6 +131,7 @@ SlicingParameters SlicingParameters::create_from_config(
max_layer_height_from_nozzle(print_config, object_config.support_interface_filament));
params.max_suport_layer_height = params.max_layer_height;
}
if (object_extruders.empty()) {
params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, 0));
params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, 0));
@@ -111,24 +141,58 @@ SlicingParameters SlicingParameters::create_from_config(
params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, extruder_id));
}
}
params.min_layer_height = std::min(params.min_layer_height, params.layer_height);
params.max_layer_height = std::max(params.max_layer_height, params.layer_height);
if (! soluble_interface) {
params.gap_raft_object = object_config.raft_contact_distance.value;
//BBS
params.gap_object_support = object_config.support_bottom_z_distance.value;
params.gap_support_object = object_config.support_top_z_distance.value;
/* -------------------------------------------------- */
/* ORCA: Gap assignment */
/* -------------------------------------------------- */
// ORCA: Raft contact (raft -> object)
if (zero_gap_interface_raft) {
params.gap_raft_object = 0.0;
} else {
params.gap_raft_object = raft_z_gap;
if (!print_config.independent_support_layer_height) {
params.gap_raft_object = std::round(params.gap_raft_object / object_config.layer_height + EPSILON) * object_config.layer_height;
params.gap_object_support = std::round(params.gap_object_support / object_config.layer_height + EPSILON) * object_config.layer_height;
params.gap_support_object = std::round(params.gap_support_object / object_config.layer_height + EPSILON) * object_config.layer_height;
params.gap_raft_object =
std::round(params.gap_raft_object / object_config.layer_height + EPSILON)
* object_config.layer_height;
}
}
// ORCA: BOTTOM contact (object -> support)
if (zero_gap_interface_bottom) {
params.gap_object_support = 0.0;
} else {
params.gap_object_support = support_bottom_z_gap;
if (!print_config.independent_support_layer_height) {
params.gap_object_support =
std::round(params.gap_object_support / object_config.layer_height + EPSILON)
* object_config.layer_height;
}
}
// ORCA: TOP contact (support -> object)
if (zero_gap_interface_top) {
params.gap_support_object = 0.0;
} else {
params.gap_support_object = support_top_z_gap;
if (!print_config.independent_support_layer_height) {
params.gap_support_object =
std::round(params.gap_support_object / object_config.layer_height + EPSILON)
* object_config.layer_height;
}
}
/* -------------------------------------------------- */
/* Raft logic */
/* -------------------------------------------------- */
if (params.base_raft_layers > 0) {
params.interface_raft_layers = (params.base_raft_layers + 1) / 2;
params.interface_raft_layers = (params.base_raft_layers + 1) / 2;
params.base_raft_layers -= params.interface_raft_layers;
// Use as large as possible layer height for the intermediate raft layers.
params.base_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_extruder_dmr);
@@ -141,11 +205,11 @@ SlicingParameters SlicingParameters::create_from_config(
if (params.has_raft()) {
// Raise first object layer Z by the thickness of the raft itself plus the extra distance required by the support material logic.
//FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case.
if (params.raft_layers() == 1) {
if (params.raft_layers() == 1) {
// There is only the contact layer.
params.contact_raft_layer_height = initial_layer_print_height;
params.raft_contact_top_z = initial_layer_print_height;
} else {
} else {
assert(params.base_raft_layers > 0);
assert(params.interface_raft_layers > 0);
// Number of the base raft layers is decreased by the first layer.
@@ -153,7 +217,8 @@ SlicingParameters SlicingParameters::create_from_config(
// Number of the interface raft layers is decreased by the contact layer.
params.raft_interface_top_z = params.raft_base_top_z + coordf_t(params.interface_raft_layers - 1) * params.interface_raft_layer_height;
params.raft_contact_top_z = params.raft_interface_top_z + params.contact_raft_layer_height;
}
}
coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object;
params.object_print_z_min = print_z;
params.object_print_z_max += print_z;
+8 -5
View File
@@ -84,9 +84,10 @@ struct SlicingParameters
// If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow.
bool first_object_layer_bridging { false };
// Soluble interface? (PLA soluble in water, HIPS soluble in lemonen)
// otherwise the interface must be broken off.
bool soluble_interface { false };
// Zero-gap interface flags for top / bottom / raft contact.
bool zero_gap_interface_top { false };
bool zero_gap_interface_bottom { false };
bool zero_gap_interface_raft { false };
// Gap when placing object over raft.
coordf_t gap_raft_object { 0 };
// Gap when placing support over object.
@@ -100,7 +101,7 @@ struct SlicingParameters
coordf_t raft_base_top_z { 0 };
coordf_t raft_interface_top_z { 0 };
coordf_t raft_contact_top_z { 0 };
// In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
// In case of a zero-gap raft interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
coordf_t object_print_z_min { 0 };
// This value of maximum print Z is scaled by shrinkage compensation in the Z-axis.
coordf_t object_print_z_max { 0 };
@@ -133,7 +134,9 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters
// BBS: following are not required for equal layer height.
// Since the z-gap diff may be multiple of layer height.
#if 0
sp1.soluble_interface == sp2.soluble_interface &&
sp1.zero_gap_interface_top == sp2.zero_gap_interface_top &&
sp1.zero_gap_interface_bottom == sp2.zero_gap_interface_bottom &&
sp1.zero_gap_interface_raft == sp2.zero_gap_interface_raft &&
sp1.gap_raft_object == sp2.gap_raft_object &&
sp1.gap_object_support == sp2.gap_object_support &&
sp1.gap_support_object == sp2.gap_support_object &&
+61 -19
View File
@@ -13,6 +13,7 @@
#include <boost/container/static_vector.hpp>
#include <boost/log/trivial.hpp>
#include <algorithm>
#include <tbb/parallel_for.h>
#include "SupportCommon.hpp"
@@ -69,22 +70,30 @@ std::pair<SupportGeneratorLayersPtr, SupportGeneratorLayersPtr> generate_interfa
if (support_params.has_base_interfaces())
base_interface_layers.assign(intermediate_layers.size(), nullptr);
const auto smoothing_distance = support_params.support_material_interface_flow.scaled_spacing() * 1.5;
const auto minimum_island_radius = support_params.support_material_interface_flow.scaled_spacing() / support_params.interface_density;
// ORCA: use top/bottom interface densities for smoothing.
const auto minimum_island_radius_top = support_params.support_material_interface_flow.scaled_spacing() / support_params.top_interface_density;
const auto minimum_island_radius_bottom = support_params.support_material_interface_flow.scaled_spacing() / support_params.bottom_interface_density;
const auto closing_distance = smoothing_distance; // scaled<float>(config.support_material_closing_radius.value);
// Insert a new layer into base_interface_layers, if intersection with base exists.
auto insert_layer = [&layer_storage, smooth_supports, closing_distance, smoothing_distance, minimum_island_radius](
// ORCA: regularize top and bottom interfaces with separate minimum island radii.
auto insert_layer = [&layer_storage, smooth_supports, closing_distance, smoothing_distance, minimum_island_radius_top, minimum_island_radius_bottom](
SupportGeneratorLayer &intermediate_layer, Polygons &bottom, Polygons &&top, SupportGeneratorLayer *top_interface_layer,
const Polygons *subtract, SupporLayerType type) -> SupportGeneratorLayer* {
bool has_top_interface = top_interface_layer && ! top_interface_layer->polygons.empty();
assert(! bottom.empty() || ! top.empty() || has_top_interface);
// Merge top into bottom, unite them with a safety offset.
append(bottom, std::move(top));
// Merge top / bottom interfaces. For snug supports, merge using closing distance and regularize (close concave corners).
bottom = intersection(
smooth_supports ?
smooth_outward(closing(std::move(bottom), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
union_safety_offset(std::move(bottom)),
intermediate_layer.polygons);
// ORCA: regularize interfaces using the top/bottom radii.
auto regularize = [&](Polygons polys, coordf_t minimum_island_radius) -> Polygons {
if (polys.empty())
return polys;
return smooth_supports ?
smooth_outward(closing(std::move(polys), closing_distance + minimum_island_radius, closing_distance, SUPPORT_SURFACES_OFFSET_PARAMETERS), smoothing_distance) :
union_safety_offset(std::move(polys));
};
// ORCA: apply independent smoothing to bottom vs top.
Polygons bottom_polys = regularize(std::move(bottom), minimum_island_radius_bottom);
Polygons top_polys = regularize(std::move(top), minimum_island_radius_top);
append(bottom_polys, std::move(top_polys));
bottom = intersection(std::move(bottom_polys), intermediate_layer.polygons);
if (has_top_interface) {
// Don't trim the precomputed Organic supports top interface with base layer
// as the precomputed top interface likely expands over multiple tree tips.
@@ -1365,7 +1374,8 @@ SupportGeneratorLayersPtr generate_support_layers(
SupportGeneratorLayer &layer = *layers_sorted[u];
if (! layer.polygons.empty()) {
empty = false;
num_interfaces += one_of(layer.layer_type, support_types_interface);
const bool is_base_interface = std::find(base_interface_layers.begin(), base_interface_layers.end(), &layer) != base_interface_layers.end();
num_interfaces += one_of(layer.layer_type, support_types_interface) || is_base_interface;
if (layer.layer_type == SupporLayerType::TopContact) {
++ num_top_contacts;
assert(num_top_contacts <= 1);
@@ -1562,7 +1572,7 @@ void generate_support_toolpaths(
auto filler_raft_contact = filler_raft_contact_ptr ? filler_raft_contact_ptr.get() : filler_interface.get();
// Filler for the base interface (to be used for soluble interface / non soluble base, to produce non soluble interface layer below soluble interface layer).
auto filler_base_interface = std::unique_ptr<Fill>(base_interface_layers.empty() ? nullptr :
Fill::new_from_type(support_params.interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
Fill::new_from_type(support_params.top_interface_density > 0.95 || support_params.with_sheath ? ipRectilinear : ipSupportBase));
auto filler_support = std::unique_ptr<Fill>(Fill::new_from_type(support_params.base_fill_pattern));
filler_interface->set_bounding_box(bbox_object);
if (filler_first_layer_ptr)
@@ -1610,6 +1620,8 @@ void generate_support_toolpaths(
// This layer is a raft contact layer. Any contact polygons at this layer are raft contacts.
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
// ORCA: Organic tree uses projected contacts to build the interface stack; avoid extra bottom-contact extrusion.
const bool organic_tree = support_params.support_style == SupportMaterialStyle::smsTreeOrganic;
if (config.support_interface_top_layers == 0) {
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
// Don't merge the raft contact layer though.
@@ -1638,10 +1650,34 @@ void generate_support_toolpaths(
base_layer.merge(std::move(bottom_contact_layer));
else if (base_layer.empty() && ! bottom_contact_layer.empty() && ! bottom_contact_layer.layer->bridging)
base_layer = std::move(bottom_contact_layer);
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer)
} else if (bottom_contact_layer.could_merge(top_contact_layer) && ! raft_layer) {
top_contact_layer.merge(std::move(bottom_contact_layer));
else if (bottom_contact_layer.could_merge(interface_layer))
} else if (bottom_contact_layer.could_merge(interface_layer) && ! organic_tree) {
bottom_contact_layer.merge(std::move(interface_layer));
}
// Orca: For organic trees the support-material regions are generated from
// expanded wall polygons. With zero top Z gap and separate interface material,
// that expansion can overlap same-layer interface-material regions, so trim
// the support-material regions from those interface footprints here.
if (organic_tree && support_params.zero_gap_interface_top && !support_params.can_merge_support_regions &&
(!base_layer.empty() || !base_interface_layer.empty())) {
Polygons interface_polygons;
if (!top_contact_layer.empty())
polygons_append(interface_polygons, top_contact_layer.polygons_to_extrude());
if (!interface_layer.empty())
polygons_append(interface_polygons, interface_layer.polygons_to_extrude());
if (!interface_polygons.empty()) {
const coord_t trim_margin = std::max(
support_params.support_material_flow.scaled_width(),
support_params.support_material_interface_flow.scaled_width());
Polygons interface_keepout = offset(interface_polygons, trim_margin);
if (!base_layer.empty())
base_layer.set_polygons_to_extrude(diff(base_layer.polygons_to_extrude(), interface_keepout));
if (!base_interface_layer.empty())
base_interface_layer.set_polygons_to_extrude(diff(base_interface_layer.polygons_to_extrude(), interface_keepout));
}
}
#if 0
if ( ! interface_layer.empty() && ! base_layer.empty()) {
@@ -1661,6 +1697,9 @@ void generate_support_toolpaths(
if (! layer_ex.empty() && ! layer_ex.polygons_to_extrude().empty()) {
bool interface_as_base = interface_layer_type == InterfaceLayerType::InterfaceAsBase;
bool raft_contact = interface_layer_type == InterfaceLayerType::RaftContact;
// ORCA: detect bottom interface layers for density selection.
bool bottom_interface = interface_layer_type == InterfaceLayerType::BottomContact ||
(interface_layer_type == InterfaceLayerType::Interface && layer_ex.layer->layer_type == SupporLayerType::BottomInterface);
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
auto *filler = raft_contact ? filler_raft_contact : filler_interface.get();
@@ -1676,7 +1715,10 @@ void generate_support_toolpaths(
raft_contact ?
support_params.raft_interface_angle(support_layer.interface_id()) :
support_interface_angle;
double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density;
// ORCA: pick density based on interface type.
double density = raft_contact ? support_params.raft_interface_density :
interface_as_base ? support_params.support_density :
bottom_interface ? support_params.bottom_interface_density : support_params.top_interface_density;
filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() :
interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
@@ -1694,9 +1736,9 @@ void generate_support_toolpaths(
const bool top_interfaces = config.support_interface_top_layers.value != 0;
const bool bottom_interfaces = top_interfaces && config.support_interface_bottom_layers != 0;
extrude_interface(top_contact_layer, raft_layer ? InterfaceLayerType::RaftContact : top_interfaces ? InterfaceLayerType::TopContact : InterfaceLayerType::InterfaceAsBase);
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
if (!organic_tree)
extrude_interface(bottom_contact_layer, bottom_interfaces ? InterfaceLayerType::BottomContact : InterfaceLayerType::InterfaceAsBase);
extrude_interface(interface_layer, top_interfaces ? InterfaceLayerType::Interface : InterfaceLayerType::InterfaceAsBase);
// Base interface layers under soluble interfaces
if ( ! base_interface_layer.empty() && ! base_interface_layer.polygons_to_extrude().empty()) {
Fill *filler = filler_base_interface.get();
@@ -1706,7 +1748,7 @@ void generate_support_toolpaths(
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = support_interface_angle;
filler->spacing = support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density));
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.top_interface_density));
fill_expolygons_generate_paths(
// Destination
base_interface_layer.extrusions,
@@ -1714,7 +1756,7 @@ void generate_support_toolpaths(
// Regions to fill
union_safety_offset_ex(base_interface_layer.polygons_to_extrude()),
// Filler and its parameters
filler, float(support_params.interface_density),
filler, float(support_params.top_interface_density),
// Extrusion parameters
ExtrusionRole::erSupportMaterial, interface_flow);
}
+8 -8
View File
@@ -1739,7 +1739,7 @@ static inline std::pair<SupportGeneratorLayer*, SupportGeneratorLayer*> new_cont
print_z = slicing_params.raft_contact_top_z;
bottom_z = slicing_params.raft_interface_top_z;
height = slicing_params.contact_raft_layer_height;
} else if (slicing_params.soluble_interface) {
} else if (slicing_params.zero_gap_interface_top) {
// Align the contact surface height with a layer immediately below the supported layer.
// Interface layer will be synchronized with the object.
print_z = layer.bottom_z();
@@ -1862,7 +1862,7 @@ static inline void fill_contact_layer(
#endif // SLIC3R_DEBUG
));
// 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
bool reduce_interfaces = object_config.support_style.value != smsSnug && layer_id > 0 && !slicing_params.soluble_interface;
bool reduce_interfaces = object_config.support_style.value != smsSnug && layer_id > 0 && !slicing_params.zero_gap_interface_top;
if (reduce_interfaces) {
// Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
Polygons dense_interface_polygons = diff(overhang_polygons, lower_layer_polygons_for_dense_interface());
@@ -2421,12 +2421,12 @@ static inline SupportGeneratorLayer* detect_bottom_contacts(
Layer* upper_layer = layer.upper_layer;
if (object.print()->config().independent_support_layer_height) {
// If the layer is extruded with no bridging flow, support just the normal extrusions.
layer_new.height = slicing_params.soluble_interface ?
layer_new.height = slicing_params.zero_gap_interface_bottom ?
// Align the interface layer with the object's layer height.
upper_layer->height :
// Place a bridge flow interface layer or the normal flow interface layer over the top surface.
support_params.support_material_bottom_interface_flow.height();
layer_new.print_z = slicing_params.soluble_interface ? upper_layer->print_z :
layer_new.print_z = slicing_params.zero_gap_interface_bottom ? upper_layer->print_z :
layer.print_z + layer_new.height + slicing_params.gap_object_support;
}
else {
@@ -2436,11 +2436,11 @@ static inline SupportGeneratorLayer* detect_bottom_contacts(
}
layer_new.bottom_z = layer.print_z;
layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = !slicing_params.soluble_interface && object.config().thick_bridges;
layer_new.bridging = !slicing_params.zero_gap_interface_bottom && object.config().thick_bridges;
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
layer_new.polygons = expand(touching, float(support_params.support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (! slicing_params.soluble_interface) {
if (!slicing_params.zero_gap_interface_bottom) {
// Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
// so there will be no support surfaces generated with thickness lower than m_support_layer_height_min.
for (size_t top_idx = size_t(std::max<int>(0, contact_idx));
@@ -2909,7 +2909,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_supp
// Continue printing the other layers up to extr2z.
step = dist / coordf_t(n_layers_extra);
}
if (! m_slicing_params.soluble_interface && extr2->layer_type == SupporLayerType::TopContact) {
if (!m_slicing_params.zero_gap_interface_top && extr2->layer_type == SupporLayerType::TopContact) {
// This is a top interface layer, which does not have a height assigned yet. Do it now.
assert(extr2->height == 0.);
assert(extr1z > m_slicing_params.first_print_layer_height - EPSILON);
@@ -3170,7 +3170,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
}
if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++ i) {
const Layer &object_layer = *object.layers()[i];
+1 -1
View File
@@ -28,7 +28,7 @@ public:
bool has_support() const { return m_object_config->enable_support.value || m_object_config->enforce_support_layers; }
bool build_plate_only() const { return this->has_support() && m_object_config->support_on_build_plate_only.value; }
// BBS
bool synchronize_layers() const { return /*m_slicing_params.soluble_interface && */!m_print_config->independent_support_layer_height.value; }
bool synchronize_layers() const { return /*m_slicing_params.zero_gap_interface_top && */!m_print_config->independent_support_layer_height.value; }
bool has_contact_loops() const { return m_object_config->support_interface_loop_pattern.value; }
// Generate support material for the object.
+52 -40
View File
@@ -14,14 +14,15 @@ struct SupportParameters {
const PrintObjectConfig& object_config = object.config();
const SlicingParameters& slicing_params = object.slicing_parameters();
this->soluble_interface = slicing_params.soluble_interface;
this->soluble_interface_non_soluble_base =
// Zero z-gap between the overhangs and the support interface.
slicing_params.soluble_interface &&
// Interface extruder soluble.
object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble.
(object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1));
this->zero_gap_interface_top = slicing_params.zero_gap_interface_top;
this->zero_gap_interface_bottom = slicing_params.zero_gap_interface_bottom;
const bool soluble_interface_non_soluble_base =
// Interface extruder soluble.
object_config.support_interface_filament.value > 0 && print_config.filament_soluble.get_at(object_config.support_interface_filament.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble.
(object_config.support_filament.value == 0 || ! print_config.filament_soluble.get_at(object_config.support_filament.value - 1));
const bool non_soluble_base_top = this->zero_gap_interface_top && soluble_interface_non_soluble_base;
const bool non_soluble_base_bottom = this->zero_gap_interface_bottom && soluble_interface_non_soluble_base;
{
this->num_top_interface_layers = std::max(0, object_config.support_interface_top_layers.value);
@@ -29,19 +30,25 @@ struct SupportParameters {
num_top_interface_layers : object_config.support_interface_bottom_layers;
this->has_top_contacts = num_top_interface_layers > 0;
this->has_bottom_contacts = num_bottom_interface_layers > 0;
if (this->soluble_interface_non_soluble_base) {
// Try to support soluble dense interfaces with non-soluble dense interfaces.
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
} else {
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
// Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if
// support_filament==0
bool differnt_support_interface_filament = object_config.support_interface_filament != 0 &&
object_config.support_interface_filament != object_config.support_filament;
this->num_top_base_interface_layers = differnt_support_interface_filament ? 1 : 0;
this->num_bottom_base_interface_layers = differnt_support_interface_filament ? 1 : 0;
}
// BBS: if support interface and support base do not use the same filament, add a base layer to improve their adhesion
// Note: support materials (such as Supp.W) can't be used as support base now, so support interface and base are still using different filaments even if
// support_filament==0
bool different_support_interface_filament = object_config.support_interface_filament != 0 &&
object_config.support_interface_filament != object_config.support_filament;
if (non_soluble_base_top) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
this->num_top_base_interface_layers = size_t(std::min(int(num_top_interface_layers) / 2, 2));
} else {
this->num_top_base_interface_layers =
(different_support_interface_filament && this->zero_gap_interface_top) ? 1 : 0;
}
if (non_soluble_base_bottom) { // ORCA: Try to support soluble dense interfaces with non-soluble dense interfaces.
this->num_bottom_base_interface_layers = size_t(std::min(int(num_bottom_interface_layers) / 2, 2));
} else {
this->num_bottom_base_interface_layers =
(different_support_interface_filament && this->zero_gap_interface_bottom) ? 1 : 0;
}
}
this->first_layer_flow = Slic3r::support_material_1st_layer_flow(&object, float(slicing_params.first_print_layer_height));
this->support_material_flow = Slic3r::support_material_flow(&object, float(slicing_params.layer_height));
@@ -78,7 +85,7 @@ struct SupportParameters {
this->gap_xy_first_layer = object_config.support_object_first_layer_gap.value;
bridge_flow_ratio /= object.num_printing_regions();
this->support_material_bottom_interface_flow = slicing_params.soluble_interface || !object_config.thick_bridges ?
this->support_material_bottom_interface_flow = this->zero_gap_interface_bottom || !object_config.thick_bridges ?
this->support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) :
Flow::bridging_flow(bridge_flow_ratio * this->support_material_interface_flow.nozzle_diameter(), this->support_material_interface_flow.nozzle_diameter());
@@ -95,18 +102,21 @@ struct SupportParameters {
this->base_angle = Geometry::deg2rad(float(object_config.support_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_angle.value + 90.));
// Orca: Force solid support interface when using support ironing
this->interface_spacing = (this->ironing ? 0 : object_config.support_interface_spacing.value) + this->support_material_interface_flow.spacing();
this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->interface_spacing);
// Orca: Force solid support interface when using support ironing
// ORCA: split top/bottom interface spacing and density, and force solid top when ironing.
this->top_interface_spacing = (this->ironing ? 0 : object_config.support_interface_spacing.value) + this->support_material_interface_flow.spacing();
this->top_interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->top_interface_spacing);
// ORCA: bottom interface spacing/density separated from top settings.
this->bottom_interface_spacing = object_config.support_bottom_interface_spacing.value + this->support_material_interface_flow.spacing();
this->bottom_interface_density = std::min(1., this->support_material_interface_flow.spacing() / this->bottom_interface_spacing);
// ORCA: force solid raft interface when ironing (top spacing).
double raft_interface_spacing = (this->ironing ? 0 : object_config.support_interface_spacing.value) + this->raft_interface_flow.spacing();
this->raft_interface_density = std::min(1., this->raft_interface_flow.spacing() / raft_interface_spacing);
this->support_spacing = object_config.support_base_pattern_spacing.value + this->support_material_flow.spacing();
this->support_density = std::min(1., this->support_material_flow.spacing() / this->support_spacing);
if (object_config.support_interface_top_layers.value == 0) {
// No interface layers allowed, print everything with the base support pattern.
this->interface_spacing = this->support_spacing;
this->interface_density = this->support_density;
this->top_interface_spacing = this->support_spacing;
this->top_interface_density = this->support_density;
}
SupportMaterialPattern support_pattern = object_config.support_base_pattern;
@@ -114,7 +124,7 @@ struct SupportParameters {
this->base_fill_pattern =
support_pattern == smpHoneycomb ? ipHoneycomb :
this->support_density > 0.95 || this->with_sheath ? ipRectilinear : ipSupportBase;
this->interface_fill_pattern = (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->interface_fill_pattern = (this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->raft_interface_fill_pattern = this->raft_interface_density > 0.95 ? ipRectilinear : ipSupportBase;
if (object_config.support_interface_pattern == smipGrid)
this->contact_fill_pattern = ipGrid;
@@ -122,10 +132,10 @@ struct SupportParameters {
this->contact_fill_pattern = ipRectilinear;
else
this->contact_fill_pattern =
(object_config.support_interface_pattern == smipAuto && slicing_params.soluble_interface) ||
(object_config.support_interface_pattern == smipAuto && this->zero_gap_interface_top) ||
object_config.support_interface_pattern == smipConcentric ?
ipConcentric :
(this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
(this->top_interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->raft_angle_1st_layer = 0.f;
this->raft_angle_base = 0.f;
@@ -186,10 +196,9 @@ struct SupportParameters {
}
}
}
// Both top / bottom contacts and interfaces are soluble.
bool soluble_interface;
// Support contact & interface are soluble, but support base is non-soluble.
bool soluble_interface_non_soluble_base;
// Zero-gap interface flags for top / bottom contact.
bool zero_gap_interface_top;
bool zero_gap_interface_bottom;
// Is there at least a top contact layer extruded above support base?
bool has_top_contacts;
@@ -199,9 +208,9 @@ struct SupportParameters {
size_t num_top_interface_layers;
// Number of bottom interface layers without counting the contact layer.
size_t num_bottom_interface_layers;
// Number of top base interface layers. Zero if not soluble_interface_non_soluble_base.
// Number of top base interface layers.
size_t num_top_base_interface_layers;
// Number of bottom base interface layers. Zero if not soluble_interface_non_soluble_base.
// Number of bottom base interface layers.
size_t num_bottom_base_interface_layers;
bool has_contacts() const { return this->has_top_contacts || this->has_bottom_contacts; }
@@ -233,10 +242,13 @@ struct SupportParameters {
float base_angle;
float interface_angle;
coordf_t interface_spacing;
coordf_t top_interface_spacing;
coordf_t bottom_interface_spacing;
coordf_t support_expansion=0;
// Density of the top / bottom interface and contact layers.
coordf_t interface_density;
// Density of the top interface and contact layers.
coordf_t top_interface_density;
// Density of the bottom interface and contact layers.
coordf_t bottom_interface_density;
// Density of the raft interface and contact layers.
coordf_t raft_interface_density;
coordf_t support_spacing;
+293 -74
View File
@@ -25,6 +25,7 @@
#include <tbb/parallel_for_each.h>
#include <boost/log/trivial.hpp>
#include <algorithm>
#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
@@ -73,6 +74,32 @@ inline Point normal(Point pt, double scale)
return pt * (scale / length);
}
// ORCA:
// Collect all polygons of a given SurfaceType from all regions of a layer.
// Used for top-contact probing across region/modifier boundaries.
static Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type)
{
size_t n_polygons_new = 0;
for (const LayerRegion *region : layer.regions()) {
for (const Surface &surface : region->slices.surfaces) {
if (surface.surface_type == surface_type)
n_polygons_new += surface.expolygon.holes.size() + 1;
}
}
Polygons out;
out.reserve(n_polygons_new);
for (const LayerRegion *region : layer.regions()) {
for (const Surface &surface : region->slices.surfaces) {
if (surface.surface_type == surface_type)
polygons_append(out, surface.expolygon);
}
}
return out;
}
enum TreeSupportStage {
STAGE_DETECT_OVERHANGS,
@@ -1416,7 +1443,7 @@ void TreeSupport::generate_toolpaths()
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
Fill* filler_interface = Fill::new_from_type(ipRectilinear);
filler_interface->angle = PI / 2; // interface should be perpendicular to base
filler_interface->angle = M_PI_2; // interface should be perpendicular to base
filler_interface->spacing = support_flow.spacing();
FillParams fill_params;
@@ -1436,7 +1463,7 @@ void TreeSupport::generate_toolpaths()
SupportLayer *ts_layer = m_object->get_support_layer(layer_nr);
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
Fill* filler_raft = Fill::new_from_type(ipRectilinear);
filler_raft->angle = PI / 2;
filler_raft->angle = M_PI_2;
filler_raft->spacing = support_flow.spacing();
for (auto& poly : first_non_raft_base)
make_perimeter_and_infill(ts_layer->support_fills.entities, poly, std::min(size_t(1), wall_count), support_flow, erSupportMaterial, filler_raft, interface_density, false);
@@ -1446,13 +1473,8 @@ void TreeSupport::generate_toolpaths()
return;
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
std::shared_ptr<Fill> filler_interface = std::shared_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
std::shared_ptr<Fill> filler_Roof1stLayer = std::shared_ptr<Fill>(Fill::new_from_type(ipRectilinear));
filler_interface->set_bounding_box(bbox_object);
filler_Roof1stLayer->set_bounding_box(bbox_object);
filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.);
filler_Roof1stLayer->angle = Geometry::deg2rad(object_config.support_angle.value + 90.);
// ORCA: base angle used for explicit interlaced interface orientation.
const float base_support_angle = Geometry::deg2rad(object_config.support_angle.value);
// generate tree support tool paths
tbb::parallel_for(
@@ -1471,17 +1493,28 @@ void TreeSupport::generate_toolpaths()
coordf_t support_spacing = object_config.support_base_pattern_spacing.value + support_flow.spacing();
coordf_t support_density = std::min(1., support_flow.spacing() / support_spacing);
ts_layer->support_fills.no_sort = false;
// ORCA: per-layer Fill instances to avoid shared-state races during interlaced interfaces.
std::shared_ptr<Fill> filler_interface = std::shared_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
std::shared_ptr<Fill> filler_Roof1stLayer = std::shared_ptr<Fill>(Fill::new_from_type(ipRectilinear));
filler_interface->set_bounding_box(bbox_object);
filler_Roof1stLayer->set_bounding_box(bbox_object);
for (auto& area_group : ts_layer->area_groups) {
ExPolygon& poly = *area_group.area;
ExPolygons polys;
FillParams fill_params;
// ORCA: reset interface Fill state per area group to keep angles deterministic.
filler_interface->fixed_angle = false;
filler_interface->layer_id = size_t(-1);
filler_interface->angle = base_support_angle + M_PI_2; // default interface angle is perpendicular to support angle
if (area_group.type != SupportLayer::BaseType) {
// interface
if (layer_id == 0) {
Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow;
ExtrusionRole brim_role = (area_group.type == SupportLayer::RoofType && !area_group.interface_as_base) ?
erSupportMaterialInterface : erSupportMaterial;
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow,
area_group.type == SupportLayer::RoofType ? erSupportMaterialInterface : erSupportMaterial);
brim_role);
polys = std::move(offset_ex(poly, -flow.scaled_spacing()));
} else if (area_group.type == SupportLayer::Roof1stLayer) {
polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width()));
@@ -1494,12 +1527,18 @@ void TreeSupport::generate_toolpaths()
}
if (area_group.type == SupportLayer::Roof1stLayer) {
// roof_1st_layer
// ORCA: Roof1stLayer may be printed with base material when it acts as a contact layer.
bool interface_as_base = area_group.interface_as_base;
fill_params.density = interface_density;
// Note: spacing means the separation between two lines as if they are tightly extruded
filler_Roof1stLayer->spacing = interface_flow.spacing();
filler_Roof1stLayer->angle = base_support_angle;
fill_params.dont_sort = true;
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
// generate a perimeter first to support interface better
ExtrusionEntityCollection* temp_support_fills = new ExtrusionEntityCollection();
make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_flow, erSupportMaterial,
make_perimeter_and_infill(temp_support_fills->entities, poly, 1, interface_base_flow, interface_role,
filler_Roof1stLayer.get(), interface_density, false);
temp_support_fills->no_sort = true; // make sure loops are first
if (!temp_support_fills->entities.empty())
@@ -1508,23 +1547,49 @@ void TreeSupport::generate_toolpaths()
delete temp_support_fills;
} else if (area_group.type == SupportLayer::FloorType) {
// floor_areas
bool interface_as_base = area_group.interface_as_base;
fill_params.density = bottom_interface_density;
filler_interface->spacing = interface_flow.spacing();
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys,
filler_interface.get(), fill_params, erSupportMaterialInterface, interface_flow);
} else if (area_group.type == SupportLayer::RoofType) {
// roof_areas
fill_params.density = interface_density;
filler_interface->spacing = interface_flow.spacing();
if (m_object_config->support_interface_pattern == smipGrid) {
filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value);
filler_interface->angle = base_support_angle;
fill_params.dont_sort = true;
}
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced)
filler_interface->layer_id = area_group.interface_id;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, erSupportMaterialInterface,
interface_flow);
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
filler_interface->fixed_angle = true;
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
fill_params.dont_sort = true;
}
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys,
filler_interface.get(), fill_params, interface_role, interface_base_flow);
} else if (area_group.type == SupportLayer::RoofType) {
// roof_areas
bool interface_as_base = area_group.interface_as_base;
fill_params.density = interface_density;
filler_interface->spacing = interface_flow.spacing();
if (m_object_config->support_interface_pattern == smipGrid) {
filler_interface->angle = base_support_angle;
fill_params.dont_sort = true;
}
if (m_object_config->support_interface_pattern == smipRectilinearInterlaced) {
// ORCA: explicit 0/90 alternation for rectilinear interlaced interfaces.
filler_interface->fixed_angle = true;
filler_interface->angle = base_support_angle + ((area_group.interface_id & 1) * M_PI_2);
fill_params.dont_sort = true;
}
Flow interface_base_flow = interface_as_base ? support_flow : interface_flow;
ExtrusionRole interface_role = interface_as_base ? erSupportMaterial : erSupportMaterialInterface;
fill_expolygons_generate_paths(ts_layer->support_fills.entities, polys, filler_interface.get(), fill_params, interface_role,
interface_base_flow);
}
else {
// base_areas
@@ -1890,7 +1955,7 @@ Polygons TreeSupport::get_trim_support_regions(
polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
}
if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++i) {
const Layer& object_layer = *object.layers()[i];
@@ -1919,7 +1984,7 @@ void TreeSupport::draw_circles()
const PrintObjectConfig &config = m_object->config();
const Print* print = m_object->print();
bool has_brim = print->has_brim();
int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height);
const coordf_t bottom_gap_height = m_slicing_params.gap_object_support;
const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2;
const coordf_t branch_radius_scaled = scale_(branch_radius);
bool on_buildplate_only = m_object_config->support_on_build_plate_only.value;
@@ -1935,7 +2000,7 @@ void TreeSupport::draw_circles()
{
double angle;
if (SQUARE_SUPPORT)
angle = (double) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle;
angle = (double) i / CIRCLE_RESOLUTION * TAU + M_PI_4 + nodes_angle;
else
angle = (double) i / CIRCLE_RESOLUTION * TAU;
branch_circle.append(Point(cos(angle) * branch_radius_scaled, sin(angle) * branch_radius_scaled));
@@ -1999,7 +2064,7 @@ void TreeSupport::draw_circles()
coordf_t max_layers_above_base = 0;
coordf_t max_layers_above_roof = 0;
coordf_t max_layers_above_roof1 = 0;
int interface_id = 0;
bool floor_interface_as_base = false;
bool has_circle_node = false;
bool need_extra_wall = false;
ExPolygons collision_sharp_tails;
@@ -2033,6 +2098,8 @@ void TreeSupport::draw_circles()
break;
const SupportNode& node = *p_node;
// ORCA: Cap top interface height in mm based on per-node support layer height.
const coordf_t top_interface_height = coordf_t(top_interface_layers) * node.height;
ExPolygons area;
// Generate directly from overhang polygon if one of the following is true:
// 1) node is a normal part of hybrid support
@@ -2084,7 +2151,10 @@ void TreeSupport::draw_circles()
// 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.
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !node.is_sharp_tail) {
// 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) {
ExPolygons overhang_expanded;
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
overhang_expanded.emplace_back(node.overhang);
@@ -2097,16 +2167,19 @@ void TreeSupport::draw_circles()
if (obj_layer_nr>0 && node.distance_to_top < 0)
append(roof_gap_areas, area);
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 && node.is_sharp_tail==false)
// ORCA: Roof1stLayer must also fit inside the mm cap.
else if (obj_layer_nr > 0 && node.support_roof_layers_below == 1 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail==false)
{
append(roof_1st_layer, area);
max_layers_above_roof1 = std::max(max_layers_above_roof1, node.dist_mm_to_top);
}
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 0 && node.is_sharp_tail == false)
// ORCA: Roof layers must also fit inside the mm cap.
else if (obj_layer_nr > 0 && node.support_roof_layers_below > 1 &&
(node.dist_mm_to_top - this->top_z_distance) < top_interface_height + EPSILON && node.is_sharp_tail == false)
{
append(roof_areas, area);
max_layers_above_roof = std::max(max_layers_above_roof, node.dist_mm_to_top);
interface_id = node.obj_layer_nr % top_interface_layers;
}
else
{
@@ -2135,7 +2208,6 @@ void TreeSupport::draw_circles()
roof_1st_layer.clear();
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
max_layers_above_roof1 = 0;
interface_id = obj_layer_nr % top_interface_layers;
}
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
@@ -2148,37 +2220,130 @@ void TreeSupport::draw_circles()
for (auto &area : base_areas) { area.simplify(scale_(line_width / 2), &base_areas_simplified); }
base_areas = std::move(base_areas_simplified);
}
//Subtract support floors. We can only compute floor_areas here instead of with roof_areas,
// or we'll get much wider floor than necessary.
if (bottom_interface_layers + bottom_gap_layers > 0)
// ORCA:
// Bottom interface / bottom gap must be anchored to the *true* support-to-model contact surface.
// Do NOT window the contact search by gap or interface height.
// First find the real contact below, then enforce:
// - an empty gap below (contact_z + gap)
// - exactly N interface layers above that
if (!base_areas.empty() && !m_object_config->support_on_build_plate_only.value &&
(bottom_gap_height > EPSILON || bottom_interface_layers > 0))
{
if (layer_nr >= bottom_interface_layers + bottom_gap_layers)
{
// find the lowest interface layer
// TODO the gap may not be exact when "independent support layer height" is enabled
size_t layer_nr_next = layer_nr - bottom_interface_layers;
size_t obj_layer_nr_next = m_ts_data->layer_heights[layer_nr_next].obj_layer_nr;
for (size_t i = 0; i <= bottom_gap_layers && i <= obj_layer_nr_next; i++)
{
const Layer *below_layer = m_object->get_layer(obj_layer_nr_next - i);
ExPolygons bottom_interface = intersection_ex(base_areas, below_layer->lslices);
floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end());
const coordf_t interface_height =
bottom_interface_layers > 0 ? coordf_t(bottom_interface_layers) * m_slicing_params.layer_height : 0.0;
const coordf_t layer_top_z = ts_layer->print_z;
const coordf_t layer_bottom_z = ts_layer->bottom_z();
ExPolygons new_base_areas;
ExPolygons new_floor_areas;
struct ContactBand {
coordf_t z = 0.0;
Polygons surfaces;
};
for (const ExPolygon& comp : base_areas) {
ExPolygons comp_poly { comp };
bool found_contact = false;
std::vector<ContactBand> bands;
// Search downward for object layers whose TOP/BOTTOM surfaces intersect this component.
for (size_t idx = obj_layer_nr + 1; idx-- > 0;) {
const Layer* below_layer = m_object->get_layer(idx);
Polygons top_surfaces = collect_region_slices_by_type(*below_layer, stTop);
Polygons bottom_surfaces = collect_region_slices_by_type(*below_layer, stBottom);
Polygons surf_union = top_surfaces;
polygons_append(surf_union, bottom_surfaces);
if (surf_union.empty())
continue;
ExPolygons inter = intersection_ex(comp_poly, surf_union);
if (!inter.empty()) {
bands.push_back(ContactBand{ below_layer->print_z, std::move(surf_union) });
found_contact = true;
}
}
if (found_contact) {
std::sort(bands.begin(), bands.end(), [](const ContactBand &a, const ContactBand &b) {
return a.z < b.z;
});
}
if (!found_contact) {
append(new_base_areas, comp_poly);
continue;
}
bool interface_id_set = false;
bool any_gap_cleared = false;
for (const ContactBand &band : bands) {
const coordf_t band_gap_top = band.z + bottom_gap_height;
const coordf_t band_iface_start = band_gap_top;
const bool band_applies = layer_top_z >= band.z - EPSILON;
if (!band_applies)
continue;
// Inside the gap: remove only the part overlapping the contact surface, keep the rest.
if (bottom_gap_height > EPSILON && layer_bottom_z < band_gap_top - EPSILON) {
any_gap_cleared = true;
comp_poly = std::move(diff_ex(comp_poly, band.surfaces));
}
// Overlaps interface band
if (bottom_interface_layers > 0 &&
layer_bottom_z >= band_iface_start - EPSILON &&
layer_bottom_z < band_iface_start + interface_height - EPSILON) {
if (!interface_id_set) {
size_t first_interface_layer = layer_nr;
while (first_interface_layer > 0) {
if (m_ts_data->layer_heights[first_interface_layer - 1].print_z <= band_iface_start + EPSILON)
break;
--first_interface_layer;
}
// ORCA: Use support-layer index for base-interface selection (robust with independent heights).
if (m_support_params.num_bottom_base_interface_layers > 0) {
const int bottom_interface_idx =
std::max(0, int(layer_nr) - int(first_interface_layer));
const int bottom_base_start_idx =
std::max(0, int(bottom_interface_layers) - int(m_support_params.num_bottom_base_interface_layers));
floor_interface_as_base = bottom_interface_idx >= bottom_base_start_idx;
}
interface_id_set = true;
}
ExPolygons band_ex = union_ex(band.surfaces);
if (!band_ex.empty()) {
const coordf_t margin = scale_(m_support_params.support_extrusion_width);
ExPolygons comp_margin = offset_ex(comp_poly, margin);
ExPolygons band_clipped = intersection_ex(band_ex, comp_margin);
band_ex = std::move(band_clipped);
}
ExPolygons comp_interface = band_ex.empty() ? ExPolygons {} : intersection_ex(comp_poly, band_ex);
if (!comp_interface.empty()) {
append(new_floor_areas, comp_interface);
comp_poly = std::move(diff_ex(comp_poly, offset_ex(comp_interface, 10)));
}
}
}
if (any_gap_cleared && comp_poly.empty()) {
continue;
}
if (!comp_poly.empty())
append(new_base_areas, comp_poly);
}
if (floor_areas.empty() == false) {
//floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface));
//floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled));
base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10)));
}
}
if (bottom_gap_layers > 0 && m_ts_data->layer_heights[layer_nr].obj_layer_nr > bottom_gap_layers) {
const Layer* below_layer = m_object->get_layer(m_ts_data->layer_heights[layer_nr].obj_layer_nr - bottom_gap_layers);
ExPolygons bottom_gap_area = intersection_ex(floor_areas, below_layer->lslices);
if (!bottom_gap_area.empty()) {
floor_areas = std::move(diff_ex(floor_areas, bottom_gap_area));
}
base_areas = std::move(new_base_areas);
floor_areas = std::move(new_floor_areas);
}
auto &area_groups = ts_layer->area_groups;
for (auto& expoly : ts_layer->base_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::BaseType, max_layers_above_base);
@@ -2188,11 +2353,11 @@ void TreeSupport::draw_circles()
for (auto& expoly : ts_layer->roof_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::RoofType, max_layers_above_roof);
area_groups.back().interface_id = interface_id;
}
for (auto &expoly : ts_layer->floor_areas) {
//if (area(expoly) < SQ(scale_(1))) continue;
area_groups.emplace_back(&expoly, SupportLayer::FloorType, 10000);
area_groups.back().interface_as_base = floor_interface_as_base;
}
for (auto &expoly : ts_layer->roof_1st_layer) {
//if (area(expoly) < SQ(scale_(1))) continue;
@@ -2216,13 +2381,49 @@ void TreeSupport::draw_circles()
//Must update bounding box which is used in avoid crossing perimeter
ts_layer->lslices_bboxes.clear();
ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size());
for (const ExPolygon& expoly : ts_layer->lslices)
ts_layer->lslices_bboxes.emplace_back(get_extents(expoly));
ts_layer->backup_untyped_slices();
}
});
// ORCA: normalize interface_id sequencing to follow printed interface layers only.
const int top_base_layers = int(m_support_params.num_top_base_interface_layers);
const bool interlaced = m_object_config->support_interface_pattern == smipRectilinearInterlaced;
int roof_interface_id = 0;
int floor_interface_id = 0;
bool has_roof_interface;
bool has_floor_interface;
for (size_t layer_nr = 0; layer_nr < m_ts_data->layer_heights.size(); ++layer_nr) {
SupportLayer *ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
if (ts_layer == nullptr)
continue;
has_roof_interface = false;
has_floor_interface = false;
for (auto &area_group : ts_layer->area_groups) {
if (area_group.type == SupportLayer::RoofType || area_group.type == SupportLayer::Roof1stLayer) {
if (interlaced)
area_group.interface_id = roof_interface_id;
area_group.interface_as_base = top_base_layers > 0 && roof_interface_id < top_base_layers;
has_roof_interface = true;
} else if (area_group.type == SupportLayer::FloorType) {
if (interlaced)
area_group.interface_id = floor_interface_id;
has_floor_interface = true;
}
}
if (has_roof_interface)
++roof_interface_id;
if (has_floor_interface)
++floor_interface_id;
}
if (with_lightning_infill)
{
@@ -2488,6 +2689,7 @@ void TreeSupport::drop_nodes()
layer_radius.emplace(calc_radius(node_dist));
}
}
// parallel pre-compute avoidance
tbb::parallel_for(tbb::blocked_range<size_t>(0, contact_nodes.size() - 1), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) {
@@ -2679,8 +2881,9 @@ void TreeSupport::drop_nodes()
// Make sure the next pass doesn't drop down either of these (since that already happened).
node_parent->merged_neighbours.push_front(node_parent == p_node ? neighbour : p_node);
const bool to_buildplate = !is_inside_ex(get_collision(0, obj_layer_nr_next), next_position);
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next, node_parent->support_roof_layers_below - 1, to_buildplate, node_parent,
print_z_next, height_next);
SupportNode* next_node = m_ts_data->create_node(next_position, node_parent->distance_to_top + 1, obj_layer_nr_next,
node_parent->support_roof_layers_below - (node_parent->distance_to_top > 0 ? 1 : 0),
to_buildplate, node_parent, print_z_next, height_next);
get_max_move_dist(next_node);
m_ts_data->m_mutex.lock();
contact_nodes[layer_nr_next].push_back(next_node);
@@ -2730,7 +2933,8 @@ void TreeSupport::drop_nodes()
ExPolygons overhangs_next = diff_clipped({ node.overhang }, get_collision(0, obj_layer_nr_next));
for(auto& overhang:overhangs_next) {
Point next_pt = overhang.contour.centroid();
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next, p_node->support_roof_layers_below - 1,
SupportNode *next_node = m_ts_data->create_node(next_pt, p_node->distance_to_top + 1, obj_layer_nr_next,
p_node->support_roof_layers_below - (p_node->distance_to_top > 0 ? 1 : 0),
to_buildplate, p_node, print_z_next, height_next);
next_node->max_move_dist = 0;
next_node->overhang = std::move(overhang);
@@ -2876,8 +3080,9 @@ void TreeSupport::drop_nodes()
}
auto next_collision = get_collision(0, obj_layer_nr_next);
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next, node.support_roof_layers_below - 1, to_buildplate, p_node,
print_z_next, height_next);
SupportNode * next_node = m_ts_data->create_node(next_layer_vertex, node.distance_to_top + 1, obj_layer_nr_next,
node.support_roof_layers_below - (node.distance_to_top > 0 ? 1 : 0),
to_buildplate, p_node, print_z_next, height_next);
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
to_outside = projection_onto(next_collision, next_node->position);
direction_to_outer = to_outside - node.position;
@@ -3098,7 +3303,12 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
// add support layers according to layer_heights
int support_layer_nr = m_raft_layers;
for (size_t i = 0; i < layer_heights.size(); i++, support_layer_nr++) {
SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z);
// SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].print_z, layer_heights[i].height, layer_heights[i].print_z);
// ORCA: add_tree_support_layer() argument order is (id, height, print_z, slice_z).
// Passing print_z as height breaks support layer geometry.
SupportLayer *ts_layer = m_object->add_tree_support_layer(support_layer_nr, layer_heights[i].height, layer_heights[i].print_z, layer_heights[i].print_z);
if (ts_layer->id() > m_raft_layers) {
SupportLayer *lower_layer = m_object->get_support_layer(ts_layer->id() - 1);
if (lower_layer) {
@@ -3147,7 +3357,21 @@ std::vector<LayerHeightData> TreeSupport::plan_layer_heights()
for (SupportNode *node : contact_nodes[layer_nr]) {
node->height = new_height;
node->distance_to_top = -num_layers;
node->support_roof_layers_below += num_layers - 1;
}
}
// ORCA: Recompute support_roof_layers_below from remaining interface height (independent heights).
const int top_layers = m_object->config().support_interface_top_layers.value;
if (m_support_params.independent_layer_height && top_layers > 0) {
const coordf_t interface_height_mm = coordf_t(top_layers) * m_slicing_params.layer_height;
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
if (contact_nodes[layer_nr].empty()) continue;
for (SupportNode *node : contact_nodes[layer_nr]) {
if (node->height <= EPSILON) continue;
const coordf_t remaining_mm = interface_height_mm - (node->dist_mm_to_top - this->top_z_distance);
const int layers_fit = remaining_mm < -EPSILON ? 0 : int(std::floor((remaining_mm + EPSILON) / node->height));
node->support_roof_layers_below = std::min(layers_fit, top_layers);
}
}
}
@@ -3166,9 +3390,6 @@ void TreeSupport::generate_contact_points()
const coordf_t max_bridge_length = scale_(config.max_bridge_length.value);
coord_t radius_scaled = scale_(base_radius);
bool on_buildplate_only = m_object_config->support_on_build_plate_only.value;
const bool roof_enabled = config.support_interface_top_layers.value > 0;
const bool force_tip_to_roof = roof_enabled && m_support_params.soluble_interface;
//First generate grid points to cover the entire area of the print.
BoundingBox bounding_box = m_object->bounding_box();
const Point bounding_box_size = bounding_box.max - bounding_box.min;
@@ -3200,7 +3421,7 @@ void TreeSupport::generate_contact_points()
// z_distance_top = round(z_distance_top / layer_height) * layer_height;
// // BBS: add extra distance if thick bridge is enabled
// // Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height
// if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
// if (!m_slicing_params.zero_gap_interface_top && m_object_config->thick_bridges) {
// z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height;
//}
// }
@@ -3208,8 +3429,6 @@ void TreeSupport::generate_contact_points()
int gap_layers = z_distance_top == 0 ? 0 : 1;
size_t support_roof_layers = config.support_interface_top_layers.value;
if (support_roof_layers > 0)
support_roof_layers += 1; // BBS: add a normal support layer below interface (if we have interface)
coordf_t thresh_angle = std::min(89.f, config.support_threshold_angle.value < EPSILON ? 30.f : config.support_threshold_angle.value);
coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2);
@@ -3263,13 +3482,13 @@ void TreeSupport::generate_contact_points()
if (force_add || !already_inserted.count(hash_pos)) {
already_inserted.emplace(hash_pos);
bool to_buildplate = true;
size_t roof_layers = add_interface ? support_roof_layers : 0;
size_t roof_layers = add_interface ? (support_roof_layers > 0 ? support_roof_layers - 1 : 0) : 0; // subtract 1 because the contact node itself counts as one layer
// add a new node as a virtual node which acts as the invisible gap between support and object
// distance_to_top=-1: it's virtual
// print_z=object_layer->bottom_z: it directly contacts the bottom
// height=z_distance_top: it's height is exactly the gap distance
// dist_mm_to_top=0: it directly contacts the bottom
contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers + 1, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0,
contact_node = m_ts_data->create_node(pt, -gap_layers, layer_nr-1, roof_layers, to_buildplate, SupportNode::NO_PARENT, bottom_z, z_distance_top, 0,
radius);
contact_node->overhang = overhang;
contact_node->is_sharp_tail = is_sharp_tail;
@@ -3305,7 +3524,7 @@ void TreeSupport::generate_contact_points()
}
for (auto &overhang : overhangs_regular) {
bool add_interface = (force_tip_to_roof || area(overhang) > minimum_roof_area) && !is_sharp_tail;
bool add_interface = area(overhang) > minimum_roof_area && !is_sharp_tail;
BoundingBox overhang_bounds = get_extents(overhang);
double radius = std::clamp(unscale_(overhang_bounds.radius()), MIN_BRANCH_RADIUS, base_radius);
// add supports at corners for both auto and manual overhangs, github #2008
+205 -126
View File
@@ -26,7 +26,6 @@
#include <cassert>
#include <chrono>
#include <fstream>
#include <optional>
#include <stdio.h>
#include <string>
@@ -54,7 +53,6 @@
#define _L(s) Slic3r::I18N::translate(s)
#endif
//#define TREESUPPORT_DEBUG_SVG
namespace Slic3r
{
@@ -132,7 +130,7 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
const PrintObjectConfig &object_config = print_object.config();
if (object_config.support_top_z_distance < EPSILON)
// || min_feature_size < scaled<coord_t>(0.1) that is the minimum line width
TreeSupportSettings::soluble = true;
TreeSupportSettings::zero_top_z_gap = true;
}
size_t largest_printed_mesh_idx = 0;
@@ -283,16 +281,6 @@ static std::vector<std::pair<TreeSupportSettings, std::vector<size_t>>> group_me
//FIXME enforcer_overhang_offset is a fudge constant!
enforced_overhangs = diff(offset(union_ex(enforced_overhangs), enforcer_overhang_offset),
lower_layer.lslices);
#ifdef TREESUPPORT_DEBUG_SVG
// if (! intersecting_edges(enforced_overhangs).empty())
{
static int irun = 0;
SVG::export_expolygons(debug_out_path("treesupport-self-intersections-%d.svg", ++irun),
{ { { current_layer.lslices }, { "current_layer.lslices", "yellow", 0.5f } },
{ { lower_layer.lslices }, { "lower_layer.lslices", "gray", 0.5f } },
{ { union_ex(enforced_overhangs) }, { "enforced_overhangs", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
}
#endif // TREESUPPORT_DEBUG_SVG
//check_self_intersections(enforced_overhangs, "generate_overhangs - enforced overhangs2");
overhangs = overhangs.empty() ? std::move(enforced_overhangs) : union_(overhangs, enforced_overhangs);
//check_self_intersections(overhangs, "generate_overhangs - enforcers");
@@ -718,7 +706,8 @@ static std::optional<std::pair<Point, size_t>> polyline_sample_next_point_at_dis
(support_params.interface_angle + (layer_idx & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)) :
support_params.base_angle;
fill_params.density = float(roof ? support_params.interface_density : scaled<float>(filler->spacing) / (scaled<float>(filler->spacing) + float(support_infill_distance)));
// ORCA: use top-specific interface density after separating top/bottom settings.
fill_params.density = float(roof ? support_params.top_interface_density : scaled<float>(filler->spacing) / (scaled<float>(filler->spacing) + float(support_infill_distance)));
fill_params.dont_adjust = true;
Polylines out;
@@ -1291,7 +1280,7 @@ static void generate_initial_areas(
;
const size_t num_support_roof_layers = mesh_group_settings.support_roof_layers;
const bool roof_enabled = num_support_roof_layers > 0;
const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.soluble_interface || sqr<double>(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area);
const bool force_tip_to_roof = roof_enabled && (interface_placer.support_parameters.zero_gap_interface_top || sqr<double>(config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area);
// cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point
// may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang
// does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it.
@@ -1806,11 +1795,6 @@ static void increase_areas_one_layer(
// Abstract representation of the model outline. If an influence area would move through it, it could teleport through a wall.
volumes.getWallRestriction(support_element_collision_radius(config, parent.state), layer_idx, parent.state.use_min_xy_dist);
#ifdef TREESUPPORT_DEBUG_SVG
SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-%d-%ld.svg", layer_idx, int(merging_area_idx)),
{ { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(parent.influence_area) }, { "parent", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif // TREESUPPORT_DEBUG_SVG
Polygons to_bp_data, to_model_data;
coord_t radius = support_element_collision_radius(config, elem);
@@ -1941,11 +1925,6 @@ static void increase_areas_one_layer(
// was never made for precision in the single digit micron range.
offset_slow = safe_offset_inc(parent.influence_area, extra_speed + extra_slow_speed + config.maximum_move_distance_slow,
wall_restriction, safe_movement_distance, offset_independant_faster ? safe_movement_distance + radius : 0, 2);
#ifdef TREESUPPORT_DEBUG_SVG
SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-slow-%d-%ld.svg", layer_idx, int(merging_area_idx)),
{ { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(offset_slow) }, { "offset_slow", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif // TREESUPPORT_DEBUG_SVG
}
if (offset_fast.empty() && settings.increase_speed != slow_speed) {
if (offset_independant_faster)
@@ -1955,11 +1934,6 @@ static void increase_areas_one_layer(
const coord_t delta_slow_fast = config.maximum_move_distance - (config.maximum_move_distance_slow + extra_slow_speed);
offset_fast = safe_offset_inc(offset_slow, delta_slow_fast, wall_restriction, safe_movement_distance, safe_movement_distance + radius, offset_independant_faster ? 2 : 1);
}
#ifdef TREESUPPORT_DEBUG_SVG
SVG::export_expolygons(debug_out_path("treesupport-increase_areas_one_layer-fast-%d-%ld.svg", layer_idx, int(merging_area_idx)),
{ { { union_ex(wall_restriction) }, { "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(offset_fast) }, { "offset_fast", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif // TREESUPPORT_DEBUG_SVG
}
}
std::optional<SupportElementState> result;
@@ -3486,18 +3460,6 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
move_bounds, interface_placer, throw_on_cancel);
auto t_gen = std::chrono::high_resolution_clock::now();
#ifdef TREESUPPORT_DEBUG_SVG
for (size_t layer_idx = 0; layer_idx < move_bounds.size(); ++layer_idx) {
Polygons polys;
for (auto& area : move_bounds[layer_idx])
append(polys, area.influence_area);
if (auto begin = move_bounds[layer_idx].begin(); begin != move_bounds[layer_idx].end())
SVG::export_expolygons(debug_out_path("treesupport-initial_areas-%d.svg", layer_idx),
{ { { union_ex(volumes.getWallRestriction(support_element_collision_radius(config, begin->state), layer_idx, begin->state.use_min_xy_dist)) },
{ "wall_restricrictions", "gray", 0.5f } },
{ { union_ex(polys) }, { "parent", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
}
#endif // TREESUPPORT_DEBUG_SVG
// ### Propagate the influence areas downwards. This is an inherently serial operation.
print.set_status(60, _L("Generating support"));
@@ -3829,88 +3791,181 @@ void organic_draw_branches(
const double bottom_z = layer_idx > 0 ? layer_z(slicing_params, config, layer_idx - 1) : 0.;
slice_z.emplace_back(float(0.5 * (bottom_z + print_z)));
}
std::vector<Polygons> slices = slice_mesh(partial_mesh, slice_z, mesh_slicing_params, throw_on_cancel);
// ORCA: guard against empty slices from meshing.
if (slices.empty())
continue;
bottom_contacts.clear();
// ORCA: trim tiny fragments to reduce degenerate polygon booleans.
const double tiny_area = tiny_area_threshold();
//FIXME parallelize?
for (LayerIndex i = 0; i < LayerIndex(slices.size()); ++i) {
slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true)); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist);
slices[i] = intersection(slices[i], volumes.m_bed_area);
// ORCA: safety offset when trimming collision/bed to improve robustness.
slices[i] = diff_clipped(slices[i], volumes.getCollision(0, layer_begin + i, true), ApplySafetyOffset::Yes); // FIXME parent_uses_min || draw_area.element->state.use_min_xy_dist);
slices[i] = intersection(slices[i], volumes.m_bed_area, ApplySafetyOffset::Yes);
remove_small(slices[i], tiny_area);
}
size_t num_empty = 0;
if (slices.front().empty()) {
// Some of the initial layers are empty.
num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin();
} else {
if (branch.has_root) {
if (config.support_rests_on_model && branch.path.front()->state.to_model_gracious) {
if (config.settings.support_floor_layers > 0)
//FIXME one may just take the whole tree slice as bottom interface.
bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {})));
} else if (layer_begin > 0) {
// Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something.
struct BottomExtraSlice {
Polygons polygons;
double area;
};
std::vector<BottomExtraSlice> bottom_extra_slices;
Polygons rest_support;
coord_t bottom_radius = support_element_radius(config, *branch.path.front());
// Don't propagate further than 1.5 * bottom radius.
//LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height;
LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height;
LayerIndex layer_bottommost = branch.path.front()->state.verylost ?
// If the tree bottom is hanging in the air, bring it down to some surface.
0 :
//FIXME the "verylost" branches should stop when crossing another support.
std::max(0, layer_begin - layers_propagate_max);
double support_area_min_radius = M_PI * sqr(double(config.branch_radius));
double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius);
// Only propagate until the rest area is smaller than this threshold.
//double support_area_min = 0.1 * support_area_min_radius;
for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) {
rest_support = diff_clipped(rest_support.empty() ? slices.front() : rest_support, volumes.getCollision(0, layer_idx, false));
double rest_support_area = area(rest_support);
if (rest_support_area < support_area_stop)
// Don't propagate a fraction of the tree contact surface.
break;
bottom_extra_slices.push_back({ rest_support, rest_support_area });
}
// Now remove those bottom slices that are not supported at all.
#if 0
while (! bottom_extra_slices.empty()) {
Polygons this_bottom_contacts = intersection_clipped(
bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {}));
if (area(this_bottom_contacts) < support_area_min)
bottom_extra_slices.pop_back();
else {
// At least a fraction of the tree bottom is considered to be supported.
if (config.settings.support_floor_layers > 0)
// Turn this fraction of the tree bottom into a contact layer.
bottom_contacts.emplace_back(std::move(this_bottom_contacts));
break;
}
}
#endif
if (config.support_rests_on_model && config.settings.support_floor_layers > 0)
for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i)
bottom_contacts.emplace_back(
intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {})));
layer_begin -= LayerIndex(bottom_extra_slices.size());
slices.insert(slices.begin(), bottom_extra_slices.size(), {});
auto it_dst = slices.begin();
for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++ it_src)
*it_dst ++ = std::move(it_src->polygons);
}
}
recover_pending_branch_roofs(interface_placer, branch.path, layer_begin, slices);
}
layer_begin += LayerIndex(num_empty);
// ORCA: trim leading empty slices to keep layer indices aligned.
if (num_empty >= slices.size())
continue;
if (num_empty > 0) {
slices.erase(slices.begin(), slices.begin() + num_empty);
layer_begin += LayerIndex(num_empty);
}
// ORCA: use the trimmed front slice as the contact reference.
Polygons slice_front_contact = slices.front();
if (branch.has_root) {
if (branch.path.front()->state.to_model_gracious) {
if (config.settings.support_floor_layers > 0) {
// If bottom Z gap is non-zero, keep bottom contacts even when not touching the model.
Polygons contacts;
// ORCA: non-zero bottom Z should not be clipped by placeable areas.
if (config.support_rests_on_model && config.z_distance_bottom_layers > 0 && layer_begin > 0)
contacts = slice_front_contact;
else {
Polygons placeable = volumes.getPlaceableAreas(0, layer_begin, [] {});
contacts = intersection_clipped(slice_front_contact, placeable, ApplySafetyOffset::Yes);
}
remove_small(contacts, tiny_area);
// ORCA: ensure bottom contacts exist if clipping removed them.
if (contacts.empty() && config.support_rests_on_model && layer_begin > 0 && !slice_front_contact.empty())
contacts = slice_front_contact;
if (!contacts.empty())
bottom_contacts.emplace_back(std::move(contacts));
}
} else if (layer_begin > 0) {
// Drop down areas that do rest non - gracefully on the model to ensure the branch actually rests on something.
struct BottomExtraSlice {
Polygons polygons;
double area;
};
std::vector<BottomExtraSlice> bottom_extra_slices;
Polygons rest_support;
coord_t bottom_radius = support_element_radius(config, *branch.path.front());
// Don't propagate further than 1.5 * bottom radius.
//LayerIndex layers_propagate_max = 2 * bottom_radius / config.layer_height;
LayerIndex layers_propagate_max = 5 * bottom_radius / config.layer_height;
LayerIndex layer_bottommost = branch.path.front()->state.verylost ?
// If the tree bottom is hanging in the air, bring it down to some surface.
0 :
//FIXME the "verylost" branches should stop when crossing another support.
std::max(0, layer_begin - layers_propagate_max);
double support_area_min_radius = M_PI * sqr(double(config.branch_radius));
double support_area_stop = std::max(0.2 * M_PI * sqr(double(bottom_radius)), 0.5 * support_area_min_radius);
// Only propagate until the rest area is smaller than this threshold.
//double support_area_min = 0.1 * support_area_min_radius;
for (LayerIndex layer_idx = layer_begin - 1; layer_idx >= layer_bottommost; -- layer_idx) {
LayerIndex collision_layer = (layer_idx == layer_begin - 1) ? layer_begin : layer_idx;
Polygons collision = volumes.getCollision(0, collision_layer, false);
rest_support = diff_clipped(rest_support.empty() ? slice_front_contact : rest_support, collision, ApplySafetyOffset::Yes);
remove_small(rest_support, tiny_area);
double rest_support_area = area(rest_support);
if (rest_support_area < support_area_stop)
// Don't propagate a fraction of the tree contact surface.
break;
bottom_extra_slices.push_back({ rest_support, rest_support_area });
}
// Now remove those bottom slices that are not supported at all.
#if 0
while (! bottom_extra_slices.empty()) {
Polygons this_bottom_contacts = intersection_clipped(
bottom_extra_slices.back().polygons, volumes.getPlaceableAreas(0, layer_begin - LayerIndex(bottom_extra_slices.size()), [] {}));
if (area(this_bottom_contacts) < support_area_min)
bottom_extra_slices.pop_back();
else {
// At least a fraction of the tree bottom is considered to be supported.
if (config.settings.support_floor_layers > 0)
// Turn this fraction of the tree bottom into a contact layer.
bottom_contacts.emplace_back(std::move(this_bottom_contacts));
break;
}
}
#endif
if (config.settings.support_floor_layers > 0) {
Polygons contacts;
if (!bottom_extra_slices.empty()) {
const int contact_idx = int(bottom_extra_slices.size()) - 1; // Use the lowest contact slice as the footprint.
// ORCA: non-zero bottom Z should not be clipped by placeable areas.
if (config.support_rests_on_model && config.z_distance_bottom_layers > 0 && layer_begin > 0)
contacts = intersection_clipped(bottom_extra_slices[contact_idx].polygons, Polygons{volumes.m_bed_area}, ApplySafetyOffset::Yes);
else {
Polygons placeable = volumes.getPlaceableAreas(0, layer_begin, [] {});
contacts = intersection_clipped(bottom_extra_slices[contact_idx].polygons, placeable, ApplySafetyOffset::Yes);
}
} else {
// Fallback: use the current contact slice when no propagation happened.
if (config.support_rests_on_model && config.z_distance_bottom_layers > 0 && layer_begin > 0)
contacts = slice_front_contact;
else {
Polygons placeable = volumes.getPlaceableAreas(0, layer_begin, [] {});
contacts = intersection_clipped(slice_front_contact, placeable, ApplySafetyOffset::Yes);
}
}
remove_small(contacts, tiny_area);
if (!contacts.empty())
bottom_contacts.emplace_back(std::move(contacts));
// ORCA: ensure bottom contacts exist if clipping removed them.
if (bottom_contacts.empty() && config.support_rests_on_model && layer_begin > 0 && !slice_front_contact.empty())
bottom_contacts.emplace_back(slice_front_contact);
}
layer_begin -= LayerIndex(bottom_extra_slices.size());
slices.insert(slices.begin(), bottom_extra_slices.size(), {});
auto it_dst = slices.begin();
for (auto it_src = bottom_extra_slices.rbegin(); it_src != bottom_extra_slices.rend(); ++ it_src)
*it_dst ++ = std::move(it_src->polygons);
}
// ORCA: retain bottom contacts even when no placeable areas intersect.
if (branch.has_root && config.support_rests_on_model && branch.path.front()->state.layer_idx > 0 &&
config.settings.support_floor_layers > 0 && config.z_distance_bottom_layers > 0 &&
bottom_contacts.empty() && !slice_front_contact.empty())
bottom_contacts.emplace_back(slice_front_contact);
}
// 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
while (! slices.empty() && slices.back().empty()) {
slices.pop_back();
-- layer_end;
}
// ORCA: recompute layer_end after trimming trailing empty slices.
layer_end = layer_begin + LayerIndex(slices.size());
if (layer_begin < layer_end) {
LayerIndex new_begin = tree.first_layer_id == -1 ? layer_begin : std::min(tree.first_layer_id, layer_begin);
LayerIndex new_end = tree.first_layer_id == -1 ? layer_end : std::max(tree.first_layer_id + LayerIndex(tree.slices.size()), layer_end);
@@ -3926,22 +3981,28 @@ void organic_draw_branches(
} else if (LayerIndex dif = tree.first_layer_id - new_begin; dif > 0)
tree.slices.insert(tree.slices.begin(), tree.first_layer_id - new_begin, {});
tree.slices.insert(tree.slices.end(), new_size - tree.slices.size(), {});
layer_begin -= LayerIndex(num_empty);
for (LayerIndex i = layer_begin; i != layer_end; ++ i) {
int j = i - layer_begin;
if (Polygons &src = slices[j]; ! src.empty()) {
Polygons &src = slices[j];
bool has_bottom_contacts = j < int(bottom_contacts.size()) && !bottom_contacts[j].empty();
// ORCA: preserve bottom contacts even if base polygons are empty.
if (!src.empty() || has_bottom_contacts) {
Slice &dst = tree.slices[i - new_begin];
if (++ dst.num_branches > 1) {
append(dst.polygons, std::move(src));
if (j < int(bottom_contacts.size()))
if (!src.empty())
append(dst.polygons, std::move(src));
if (has_bottom_contacts)
append(dst.bottom_contacts, std::move(bottom_contacts[j]));
} else {
dst.polygons = std::move(std::move(src));
if (j < int(bottom_contacts.size()))
if (!src.empty())
dst.polygons = std::move(src);
if (has_bottom_contacts)
dst.bottom_contacts = std::move(bottom_contacts[j]);
}
}
}
tree.first_layer_id = new_begin;
}
}
@@ -3954,10 +4015,15 @@ void organic_draw_branches(
Tree &tree = trees[tree_id];
for (Slice &slice : tree.slices)
if (slice.num_branches > 1) {
slice.polygons = union_(slice.polygons);
slice.bottom_contacts = union_(slice.bottom_contacts);
// ORCA: avoid union_ on empty containers.
if (!slice.polygons.empty())
slice.polygons = union_(slice.polygons);
if (!slice.bottom_contacts.empty())
slice.bottom_contacts = union_(slice.bottom_contacts);
slice.num_branches = 1;
}
throw_on_cancel();
}
}, tbb::simple_partitioner());
@@ -3970,17 +4036,27 @@ void organic_draw_branches(
std::vector<Slice> slices(num_layers, Slice{});
for (Tree &tree : trees)
if (tree.first_layer_id >= 0) {
for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++ i)
if (Slice &src = tree.slices[i - tree.first_layer_id]; ! src.polygons.empty()) {
for (LayerIndex i = tree.first_layer_id; i != tree.first_layer_id + LayerIndex(tree.slices.size()); ++ i) {
Slice &src = tree.slices[i - tree.first_layer_id];
bool has_bottom_contacts = !src.bottom_contacts.empty();
// ORCA: preserve bottom contacts even if base polygons are empty.
if (!src.polygons.empty() || has_bottom_contacts) {
Slice &dst = slices[i];
if (++ dst.num_branches > 1) {
append(dst.polygons, std::move(src.polygons));
append(dst.bottom_contacts, std::move(src.bottom_contacts));
if (!src.polygons.empty())
append(dst.polygons, std::move(src.polygons));
if (has_bottom_contacts)
append(dst.bottom_contacts, std::move(src.bottom_contacts));
} else {
dst.polygons = std::move(src.polygons);
dst.bottom_contacts = std::move(src.bottom_contacts);
if (!src.polygons.empty())
dst.polygons = std::move(src.polygons);
if (has_bottom_contacts)
dst.bottom_contacts = std::move(src.bottom_contacts);
}
}
}
}
tbb::parallel_for(tbb::blocked_range<size_t>(0, std::min(move_bounds.size(), slices.size()), 1),
@@ -3988,8 +4064,11 @@ void organic_draw_branches(
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
Slice &slice = slices[layer_idx];
assert(intermediate_layers[layer_idx] == nullptr);
Polygons base_layer_polygons = slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons);
Polygons bottom_contact_polygons = slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts);
// ORCA: avoid union_ on empty inputs.
Polygons base_layer_polygons = slice.polygons.empty() ? Polygons{} :
(slice.num_branches > 1 ? union_(slice.polygons) : std::move(slice.polygons));
Polygons bottom_contact_polygons = slice.bottom_contacts.empty() ? Polygons{} :
(slice.num_branches > 1 ? union_(slice.bottom_contacts) : std::move(slice.bottom_contacts));
if (! base_layer_polygons.empty()) {
// Most of the time in this function is this union call. Can take 300+ ms when a lot of areas are to be unioned.
+9 -3
View File
@@ -306,7 +306,7 @@ public:
layer_start_bp_radius = (bp_radius - branch_radius) / bp_radius_increase_per_layer;
if (TreeSupportSettings::soluble) {
if (TreeSupportSettings::zero_top_z_gap) {
// safeOffsetInc can only work in steps of the size xy_min_distance in the worst case => xy_min_distance has to be a bit larger than 0 in this worst case and should be large enough for performance to not suffer extremely
// When for all meshes the z bottom and top distance is more than one layer though the worst case is xy_min_distance + min_feature_size
// This is not the best solution, but the only one to ensure areas can not lag though walls at high maximum_move_distance.
@@ -356,7 +356,7 @@ public:
// some static variables dependent on other meshes that are not currently processed.
// Has to be static because TreeSupportConfig will be used in TreeModelVolumes as this reduces redundancy.
inline static bool soluble = false;
inline static bool zero_top_z_gap = false;
/*!
* \brief Width of a single line of support.
*/
@@ -718,9 +718,15 @@ public:
{
assert(support_parameters.has_top_contacts);
assert(dtt_roof <= support_parameters.num_top_interface_layers);
// ORCA: Reserve one top interface layer but only when top base-interface layers exist.
// This prevents all interface layers from being classified as base-interface layers
// and preserves correct top contact and interface behavior.
size_t interface_threshold = support_parameters.num_top_interface_layers_only();
if (interface_threshold > 0 && support_parameters.num_top_base_interface_layers > 0)
--interface_threshold;
SupportGeneratorLayersPtr &layers =
dtt_roof == 0 ? this->top_contacts :
dtt_roof <= support_parameters.num_top_interface_layers_only() ? this->top_interfaces : this->top_base_interfaces;
dtt_roof <= interface_threshold ? this->top_interfaces : this->top_base_interfaces;
SupportGeneratorLayer*& l = layers[insert_layer_idx];
if (l == nullptr)
l = &layer_allocate_unguarded(layer_storage, dtt_roof == 0 ? SupporLayerType::TopContact : SupporLayerType::TopInterface,
+8
View File
@@ -114,6 +114,14 @@ void set_logging_level(unsigned int level)
{
logSeverity = level_to_boost(level);
// Force at debug level logging for pre-release builds.
const std::string version = SoftFever_VERSION;
if (boost::algorithm::icontains(version, "dev") ||
boost::algorithm::icontains(version, "alpha") ||
boost::algorithm::icontains(version, "beta")) {
logSeverity = boost::log::trivial::debug;
}
boost::log::core::get()->set_filter
(
boost::log::trivial::severity >= logSeverity