mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 02:41:17 +00:00
Merge remote-tracking branch 'upstream/main' into dev/cut-keep-paint
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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({®ion.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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ¤t_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, ¤t_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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ¶ms) override;
|
||||
void process(long long *time_cost_with_cache = nullptr, bool use_cache = false) override;
|
||||
void finalize() override;
|
||||
|
||||
+91
-26
@@ -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;
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user