mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-20 06:01:16 +00:00
Merge remote-tracking branch 'upstream/main' into dev/cut-keep-paint
This commit is contained in:
commit
d5f964ee4f
342 files changed
+56043
-827
No files matched your search
@@ -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
|
||||
|
||||
@@ -1505,6 +1505,9 @@ bool CalibrationPresetPage::is_filaments_compatiable(const std::map<int, Preset*
|
||||
|
||||
bed_temp = 0;
|
||||
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;
|
||||
for (auto &item : prests) {
|
||||
const auto& item_preset = item.second;
|
||||
if (!item_preset)
|
||||
@@ -1533,13 +1536,38 @@ bool CalibrationPresetPage::is_filaments_compatiable(const std::map<int, Preset*
|
||||
std::string display_filament_type;
|
||||
filament_types.push_back(item_preset->config.get_filament_type(display_filament_type, 0));
|
||||
|
||||
int nozzle_temperature = 0;
|
||||
int nozzle_temperature_range_low = 0;
|
||||
int nozzle_temperature_range_high = 0;
|
||||
if (const auto* opt_nozzle_temp = item_preset->config.option<ConfigOptionInts>("nozzle_temperature"))
|
||||
nozzle_temperature = opt_nozzle_temp->get_at(0);
|
||||
if (const auto* opt_nozzle_temp_low = item_preset->config.option<ConfigOptionInts>("nozzle_temperature_range_low"))
|
||||
nozzle_temperature_range_low = opt_nozzle_temp_low->get_at(0);
|
||||
if (const auto* opt_nozzle_temp_high = item_preset->config.option<ConfigOptionInts>("nozzle_temperature_range_high"))
|
||||
nozzle_temperature_range_high = opt_nozzle_temp_high->get_at(0);
|
||||
|
||||
nozzle_temperatures.push_back(nozzle_temperature);
|
||||
nozzle_temperature_range_lows.push_back(nozzle_temperature_range_low);
|
||||
nozzle_temperature_range_highs.push_back(nozzle_temperature_range_high);
|
||||
|
||||
// check is it in the filament blacklist
|
||||
if (!is_filament_in_blacklist(item.first, item_preset, error_tips))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Print::check_multi_filaments_compatibility(filament_types) == FilamentCompatibilityType::HighLowMixed) {
|
||||
error_tips = _u8L("Cannot print multiple filaments which have large difference of temperature together. Otherwise, the extruder and nozzle may be blocked or damaged during printing");
|
||||
auto compatibility = Print::check_multi_filaments_compatibility(
|
||||
filament_types,
|
||||
nozzle_temperatures,
|
||||
nozzle_temperature_range_lows,
|
||||
nozzle_temperature_range_highs);
|
||||
|
||||
if (compatibility == FilamentCompatibilityType::InvalidTemperatureRange) {
|
||||
error_tips = _u8L("Invalid recommended nozzle temperature range. The lower bound must be lower than the upper bound.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (compatibility == FilamentCompatibilityType::HighLowMixed) {
|
||||
error_tips = _u8L("Selected nozzle temperatures are incompatible. For multi-material printing, each filament's nozzle temperature must be within the recommended nozzle temperature range of the other filaments. Otherwise, nozzle clogging or printer damage may occur.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "DevConfigUtil.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include <wx/dir.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@@ -246,4 +249,49 @@ std::map<std::string, std::vector<std::string>> DevPrinterConfigUtil::get_all_su
|
||||
return subseries;
|
||||
}
|
||||
|
||||
};
|
||||
std::string DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
const std::string& type_str,
|
||||
int ext_id,
|
||||
ToolHeadComponent component,
|
||||
ToolHeadNameCase name_case,
|
||||
bool short_name)
|
||||
{
|
||||
static const std::map<ToolHeadComponent, std::string> comp_keys = {
|
||||
{ ToolHeadComponent::Extruder, "extruder" },
|
||||
{ ToolHeadComponent::Nozzle, "nozzle" },
|
||||
{ ToolHeadComponent::Hotend, "hotend" }
|
||||
};
|
||||
|
||||
const int case_index = static_cast<int>(name_case);
|
||||
const std::string role_key = std::to_string(ext_id);
|
||||
const std::string& comp_key = comp_keys.at(component);
|
||||
|
||||
std::string result;
|
||||
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
|
||||
if (!names_json.is_null() && names_json.contains(role_key) && names_json[role_key].contains(comp_key)) {
|
||||
auto& arr = names_json[role_key][comp_key];
|
||||
if (arr.is_array() && case_index < static_cast<int>(arr.size()))
|
||||
result = arr[case_index].get<std::string>();
|
||||
}
|
||||
|
||||
if (result.empty()) {
|
||||
const std::string side = ext_id == DEPUTY_EXTRUDER_ID ? "Left" : "Right";
|
||||
const std::string component_name = component == ToolHeadComponent::Extruder ? "Extruder" :
|
||||
component == ToolHeadComponent::Hotend ? "Hotend" : "Nozzle";
|
||||
result = side + " " + component_name;
|
||||
if (name_case == ToolHeadNameCase::SentenceCase && result.size() > side.size() + 1)
|
||||
result[side.size() + 1] = static_cast<char>(std::tolower(static_cast<unsigned char>(result[side.size() + 1])));
|
||||
else if (name_case == ToolHeadNameCase::LowerCase)
|
||||
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
}
|
||||
|
||||
if (short_name) {
|
||||
auto sp = result.find(' ');
|
||||
if (sp != std::string::npos)
|
||||
result = result.substr(0, sp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -30,6 +30,17 @@ public:
|
||||
~dePrinterConfigFactory() = default;
|
||||
};
|
||||
|
||||
enum class ToolHeadComponent {
|
||||
Extruder,
|
||||
Nozzle,
|
||||
Hotend
|
||||
};
|
||||
|
||||
enum class ToolHeadNameCase {
|
||||
TitleCase = 0,
|
||||
SentenceCase = 1,
|
||||
LowerCase = 2
|
||||
};
|
||||
|
||||
class DevPrinterConfigUtil
|
||||
{
|
||||
@@ -70,6 +81,12 @@ public:
|
||||
|
||||
/*extruder*/
|
||||
static bool get_printer_can_set_nozzle(std::string type_str) { return get_value_from_config<bool>(type_str, "enable_set_nozzle_info"); }// can set nozzle from studio
|
||||
static std::string get_toolhead_display_name(
|
||||
const std::string& type_str,
|
||||
int ext_id,
|
||||
ToolHeadComponent component,
|
||||
ToolHeadNameCase name_case = ToolHeadNameCase::TitleCase,
|
||||
bool short_name = false);
|
||||
|
||||
/*print job*/
|
||||
static bool support_ams_ext_mix_print(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_ext_mix_print"); }
|
||||
@@ -200,4 +217,4 @@ static std::string _parse_printer_type(const std::string& type_str)
|
||||
return type_str;
|
||||
}
|
||||
|
||||
};// namespace Slic3r
|
||||
};// namespace Slic3r
|
||||
@@ -354,6 +354,27 @@ namespace Slic3r
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: special cases that no AMS available, we select ext slot automatically because we don't have other choice anyway
|
||||
if (tray_filaments.size() == 1 && devPrinterUtil::IsVirtualSlot(tray_filaments.begin()->first)) {
|
||||
auto ext_tray = tray_filaments.begin();
|
||||
for (auto & r : result) {
|
||||
if (r.tray_id < 0) {
|
||||
r.tray_id = ext_tray->first;
|
||||
|
||||
r.color = ext_tray->second.color;
|
||||
r.type = ext_tray->second.type;
|
||||
r.distance = ext_tray->second.distance;
|
||||
r.filament_id = ext_tray->second.filament_id;
|
||||
r.ctype = ext_tray->second.ctype;
|
||||
r.colors = ext_tray->second.colors;
|
||||
|
||||
/*for new ams mapping*/
|
||||
r.ams_id = ext_tray->second.ams_id;
|
||||
r.slot_id = ext_tray->second.slot_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check ams mapping result
|
||||
if (DevMappingUtil::is_valid_mapping_result(obj, result, true))
|
||||
{
|
||||
|
||||
@@ -240,7 +240,14 @@ wxString DeviceErrorDialog::show_error_code(int error_code)
|
||||
Show();
|
||||
Raise();
|
||||
|
||||
#ifndef __linux__
|
||||
// On Linux (especially Wayland) RequestUserAttention(wxUSER_ATTENTION_ERROR) maps to
|
||||
// gtk_window_set_urgency_hint(TRUE) which can leave the window in an urgent-but-unfocused
|
||||
// state — clicks no longer reach any widget in the app and the user has to kill the
|
||||
// process to recover. Same root cause as #9874, where SecondaryCheckDialog had its
|
||||
// RequestUserAttention call removed for the identical reason.
|
||||
this->RequestUserAttention(wxUSER_ATTENTION_ERROR);
|
||||
#endif
|
||||
|
||||
return error_msg;
|
||||
}
|
||||
|
||||
@@ -291,7 +291,6 @@ public:
|
||||
|
||||
bool is_target_slot_unload() const;
|
||||
bool can_unload_filament();
|
||||
bool is_support_amx_ext_mix_mapping() const { return true;}
|
||||
|
||||
void get_ams_colors(std::vector<wxColour>& ams_colors);
|
||||
|
||||
|
||||
@@ -1920,6 +1920,14 @@ void GLCanvas3D::render(bool only_init)
|
||||
|
||||
if (!is_initialized() && !init())
|
||||
return;
|
||||
|
||||
// If a scene reload was postponed while the canvas was hidden, consume it on first visible render.
|
||||
if (m_reload_delayed) {
|
||||
reload_scene(true);
|
||||
if (m_reload_delayed)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D && m_gizmos.get_current_type() == GLGizmosManager::Undefined) {
|
||||
enable_return_toolbar(false);
|
||||
}
|
||||
@@ -2398,10 +2406,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
if (m_canvas == nullptr || m_config == nullptr || m_model == nullptr)
|
||||
return;
|
||||
|
||||
if (!m_initialized)
|
||||
if (!m_initialized || !_set_current()) {
|
||||
m_reload_delayed = true;
|
||||
set_as_dirty();
|
||||
return;
|
||||
|
||||
_set_current();
|
||||
}
|
||||
|
||||
m_hover_volume_idxs.clear();
|
||||
|
||||
@@ -2456,6 +2465,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; };
|
||||
|
||||
m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh;
|
||||
if (m_reload_delayed) {
|
||||
set_as_dirty();
|
||||
return;
|
||||
}
|
||||
|
||||
PrinterTechnology printer_technology = current_printer_technology();
|
||||
|
||||
@@ -2617,9 +2630,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||
//BBS clean hover_volume_idxs
|
||||
m_hover_volume_idxs.clear();
|
||||
|
||||
if (m_reload_delayed)
|
||||
return;
|
||||
|
||||
// BBS: do not check wipe tower changes
|
||||
bool update_object_list = false;
|
||||
if (deleted_volumes.size() != deleted_wipe_towers.size())
|
||||
@@ -3897,7 +3907,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
||||
if (m_gizmos.on_mouse_wheel(evt))
|
||||
return;
|
||||
|
||||
if (m_canvas_type == CanvasAssembleView && (evt.AltDown() || evt.CmdDown())) {
|
||||
if (m_canvas_type == CanvasAssembleView && (evt.AltDown() || evt.CmdDown()) && m_gizmos.m_assemble_view_data != nullptr) {
|
||||
float rotation = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
|
||||
if (evt.AltDown()) {
|
||||
auto clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position();
|
||||
@@ -8915,6 +8925,9 @@ float GLCanvas3D::_render_assembly_tooltip_button(ImGuiWrapper* imgui_wrapper) c
|
||||
//BBS
|
||||
void GLCanvas3D::_render_assemble_control()
|
||||
{
|
||||
if(m_gizmos.m_assemble_view_data == nullptr)
|
||||
return;
|
||||
|
||||
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
GLVolume::explosion_ratio = m_explosion_ratio = 1.0;
|
||||
return;
|
||||
|
||||
@@ -4469,6 +4469,21 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
boost::optional<std::string> command = root.get_optional<std::string>("command");
|
||||
if (command.has_value()) {
|
||||
std::string command_str = command.value();
|
||||
static const std::unordered_set<std::string> stealth_blocked_commands = {
|
||||
"get_login_info",
|
||||
"get_orca_login_info",
|
||||
"get_bambu_login_info",
|
||||
"homepage_login_or_register",
|
||||
"homepage_orca_login_or_register",
|
||||
"homepage_bambu_login_or_register",
|
||||
};
|
||||
if (app_config->get_stealth_mode() && stealth_blocked_commands.count(command_str)) {
|
||||
CallAfter([this] {
|
||||
if (mainframe && mainframe->m_webview)
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
});
|
||||
return "";
|
||||
}
|
||||
if (command_str.compare("request_project_download") == 0) {
|
||||
if (root.get_child_optional("data") != boost::none) {
|
||||
pt::ptree data_node = root.get_child("data");
|
||||
@@ -5792,12 +5807,16 @@ bool GUI_App::maybe_migrate_user_presets_on_login()
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Migrate user presets to the OrcaCloud user folder if needed.";
|
||||
|
||||
if (!m_agent || !m_agent->is_user_login())
|
||||
return false;
|
||||
|
||||
std::string new_user_id = m_agent->get_user_id();
|
||||
if (new_user_id.empty())
|
||||
if (new_user_id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to get user ID, skipping migration.";
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::path user_base = fs::path(data_dir()) / PRESET_USER_DIR;
|
||||
fs::path target_dir = user_base / new_user_id;
|
||||
@@ -6731,6 +6750,16 @@ void GUI_App::stop_sync_user_preset()
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::on_stealth_mode_enter()
|
||||
{
|
||||
stop_sync_user_preset();
|
||||
request_user_logout(ORCA_CLOUD_PROVIDER);
|
||||
request_user_logout(BBL_CLOUD_PROVIDER);
|
||||
if (mainframe && mainframe->m_webview) {
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::start_http_server(const std::string& provider)
|
||||
{
|
||||
m_http_server.set_request_handler([provider](const std::string& url) {
|
||||
|
||||
@@ -530,6 +530,7 @@ public:
|
||||
void sync_preset(Preset* preset);
|
||||
void start_sync_user_preset(bool with_progress_dlg = false);
|
||||
void stop_sync_user_preset();
|
||||
void on_stealth_mode_enter();
|
||||
|
||||
// Bundle subscription sync
|
||||
void check_bundle_updates();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "ParamsPanel.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
@@ -111,6 +112,21 @@ namespace {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Orca: Resolve the type of a validation option based on its key
|
||||
Preset::Type resolve_validation_option_type(const std::string& opt_key)
|
||||
{
|
||||
if (opt_key.empty())
|
||||
return Preset::TYPE_PRINT;
|
||||
|
||||
if (wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config()->def()->has(opt_key))
|
||||
return Preset::TYPE_PRINTER;
|
||||
|
||||
if (wxGetApp().get_tab(Preset::TYPE_FILAMENT)->get_config()->def()->has(opt_key))
|
||||
return Preset::TYPE_FILAMENT;
|
||||
|
||||
return Preset::TYPE_PRINT;
|
||||
}
|
||||
}
|
||||
|
||||
#if 1
|
||||
@@ -1917,9 +1933,12 @@ void NotificationManager::push_validate_error_notification(StringObjectException
|
||||
}
|
||||
|
||||
if (!opt.empty()) {
|
||||
if ((!is_inst && id.id) || (is_inst && parent_id.id)) // if object found
|
||||
const Preset::Type opt_type = resolve_validation_option_type(opt);
|
||||
|
||||
if (opt_type == Preset::TYPE_PRINT && ((!is_inst && id.id) || (is_inst && parent_id.id))) // if object found and it's a print preset option, switch to object first
|
||||
wxGetApp().params_panel()->switch_to_object();
|
||||
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
|
||||
|
||||
wxGetApp().sidebar().jump_to_option(opt, opt_type, L"");
|
||||
}
|
||||
else {
|
||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
||||
|
||||
@@ -51,6 +51,14 @@ ParamsDialog::ParamsDialog(wxWindow * parent)
|
||||
Hide();
|
||||
}
|
||||
#else
|
||||
auto tab = dynamic_cast<Tab *>(m_panel->get_current_tab());
|
||||
// ORCA: Validate filament temperature pairs before closing the material settings dialog.
|
||||
if (tab && !tab->validate_filament_temperature_pairs()) {
|
||||
if (event.CanVeto())
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
|
||||
Hide();
|
||||
if (!m_editing_filament_id.empty()) {
|
||||
Filamentinformation *filament_info = new Filamentinformation();
|
||||
|
||||
@@ -3414,12 +3414,13 @@ int PartPlate::load_gcode_from_file(const std::string& filename)
|
||||
int ret = 0;
|
||||
|
||||
// process gcode
|
||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config();
|
||||
std::vector<int> filament_maps = this->get_filament_maps();
|
||||
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps);
|
||||
full_config.apply(m_config, true);
|
||||
m_print->apply(*m_model, full_config);
|
||||
m_print->apply(*m_model, full_config, false);
|
||||
//BBS: need to apply two times, for after the first apply, the m_print got its object,
|
||||
//which will affect the config when new_full_config.normalize_fdm(used_filaments);
|
||||
m_print->apply(*m_model, full_config);
|
||||
m_print->apply(*m_model, full_config, false);
|
||||
|
||||
// BBS: use backup path to save temp gcode
|
||||
// auto path = get_tmp_gcode_path();
|
||||
@@ -6102,6 +6103,9 @@ int PartPlateList::store_to_3mf_structure(PlateDataPtrs& plate_data_list, bool w
|
||||
plate_data_item->is_label_object_enabled = m_plate_list[i]->m_gcode_result->label_object_enabled;
|
||||
plate_data_item->limit_filament_maps = m_plate_list[i]->m_gcode_result->limit_filament_maps;
|
||||
plate_data_item->layer_filaments = m_plate_list[i]->m_gcode_result->layer_filaments;
|
||||
plate_data_item->filament_change_sequence = m_plate_list[i]->m_gcode_result->filament_change_sequence;
|
||||
plate_data_item->nozzle_change_sequence = m_plate_list[i]->m_gcode_result->nozzle_change_sequence;
|
||||
plate_data_item->optimal_assignment = m_plate_list[i]->m_gcode_result->optimal_assignment;
|
||||
plate_data_item->first_layer_time = std::to_string(m_plate_list[i]->cali_bboxes_data.first_layer_time);
|
||||
Print *print = nullptr;
|
||||
m_plate_list[i]->get_print((PrintBase **) &print, nullptr, nullptr);
|
||||
@@ -6177,6 +6181,9 @@ int PartPlateList::load_from_3mf_structure(PlateDataPtrs& plate_data_list, int f
|
||||
gcode_result->label_object_enabled = plate_data_list[i]->is_label_object_enabled;
|
||||
gcode_result->timelapse_warning_code = plate_data_list[i]->timelapse_warning_code;
|
||||
m_plate_list[index]->set_timelapse_warning_code(plate_data_list[i]->timelapse_warning_code);
|
||||
gcode_result->filament_change_sequence = plate_data_list[i]->filament_change_sequence;
|
||||
gcode_result->nozzle_change_sequence = plate_data_list[i]->nozzle_change_sequence;
|
||||
gcode_result->optimal_assignment = plate_data_list[i]->optimal_assignment;
|
||||
m_plate_list[index]->slice_filaments_info = plate_data_list[i]->slice_filaments_info;
|
||||
gcode_result->warnings = plate_data_list[i]->warnings;
|
||||
gcode_result->filament_maps = plate_data_list[i]->filament_maps;
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevDefs.h"
|
||||
|
||||
using boost::optional;
|
||||
namespace fs = boost::filesystem;
|
||||
@@ -443,6 +445,7 @@ struct ExtruderGroup : StaticGroup
|
||||
}
|
||||
|
||||
void update_ams();
|
||||
void SetTitle(const wxString& title);
|
||||
|
||||
void sync_ams(MachineObject const *obj, std::vector<DevAms *> const &ams4, std::vector<DevAms *> const &ams1);
|
||||
|
||||
@@ -1263,6 +1266,16 @@ void ExtruderGroup::sync_ams(MachineObject const *obj, std::vector<DevAms *> con
|
||||
update_ams();
|
||||
}
|
||||
|
||||
void ExtruderGroup::SetTitle(const wxString& title)
|
||||
{
|
||||
m_label = title;
|
||||
int tW, tH, descent, externalLeading;
|
||||
GetTextExtent(m_label.IsEmpty() ? "Orca" : m_label, &tW, &tH, &descent, &externalLeading, &m_font);
|
||||
m_label_height = tH - externalLeading;
|
||||
m_label_width = tW;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool Sidebar::priv::switch_diameter(bool single)
|
||||
{
|
||||
wxString diameter;
|
||||
@@ -1272,13 +1285,16 @@ bool Sidebar::priv::switch_diameter(bool single)
|
||||
auto diameter_left = left_extruder->combo_diameter->GetValue();
|
||||
auto diameter_right = right_extruder->combo_diameter->GetValue();
|
||||
if (diameter_left != diameter_right) {
|
||||
std::string printer_type = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle);
|
||||
auto left_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::SentenceCase));
|
||||
auto right_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::SentenceCase));
|
||||
MessageDialog dlg(this->plater,
|
||||
_L("The software does not support using different diameter of nozzles for one print. "
|
||||
"If the left and right nozzles are inconsistent, we can only proceed with single-head printing. "
|
||||
"Please confirm which nozzle you would like to use for this project."),
|
||||
_L("Switch diameter"), wxYES_NO | wxNO_DEFAULT);
|
||||
dlg.SetButtonLabel(wxID_YES, wxString::Format(_L("Left nozzle: %smm"), diameter_left));
|
||||
dlg.SetButtonLabel(wxID_NO, wxString::Format(_L("Right nozzle: %smm"), diameter_right));
|
||||
dlg.SetButtonLabel(wxID_YES, wxString::Format("%s: %smm", left_name, diameter_left));
|
||||
dlg.SetButtonLabel(wxID_NO, wxString::Format("%s: %smm", right_name, diameter_right));
|
||||
int result = dlg.ShowModal();
|
||||
if (result == wxID_YES)
|
||||
diameter = diameter_left;
|
||||
@@ -2661,6 +2677,9 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
};
|
||||
auto image_path = get_cur_select_bed_image();
|
||||
if (is_dual_extruder) {
|
||||
std::string printer_type = printer_preset.get_printer_type(wxGetApp().preset_bundle);
|
||||
p->left_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
|
||||
p->right_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
|
||||
AMSCountPopupWindow::UpdateAMSCount(0, p->left_extruder);
|
||||
AMSCountPopupWindow::UpdateAMSCount(1, p->right_extruder);
|
||||
//if (!p->is_switching_diameter) {
|
||||
@@ -6923,6 +6942,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
|
||||
if (load_model) {
|
||||
if (!q->m_exported_file && view3D != nullptr) {
|
||||
// Force a 3D scene refresh after view/plate selection to avoid losing the first load
|
||||
// on platforms where the GL canvas mapping lags behind model loading.
|
||||
view3D->reload_scene(true);
|
||||
view3D->set_as_dirty();
|
||||
}
|
||||
if (!silence) wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().make_preferred().string());
|
||||
// XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames...
|
||||
}
|
||||
@@ -9150,6 +9175,20 @@ void Plater::priv::set_current_panel(wxPanel* panel, bool no_slice)
|
||||
|
||||
if (current_panel == panel)
|
||||
{
|
||||
if (panel == view3D) {
|
||||
if (view3D->is_reload_delayed()) {
|
||||
// Delayed loading of the 3D scene when caller requests the already active tab.
|
||||
if (printer_technology == ptSLA)
|
||||
update_restart_background_process(true, false);
|
||||
else
|
||||
view3D->reload_scene(true);
|
||||
}
|
||||
|
||||
view3D->set_as_dirty();
|
||||
view3D->get_canvas3d()->reset_old_size();
|
||||
if (notification_manager != nullptr)
|
||||
notification_manager->set_in_preview(false);
|
||||
}
|
||||
//BBS: add slice logic when switch to preview page
|
||||
//BBS: add only gcode mode
|
||||
if (!q->only_gcode_mode() && (current_panel == preview) && (wxGetApp().is_editor())) {
|
||||
|
||||
@@ -53,6 +53,7 @@ std::string PrePrintChecker::get_print_status_info(PrintDialogStatus status)
|
||||
case PrintStatusFilamentWarningHighChamberTempCloseDoor: return "PrintStatusFilamentWarningHighChamberTempCloseDoor";
|
||||
case PrintStatusFilamentWarningHighChamberTempSoft: return "PrintStatusFilamentWarningHighChamberTempSoft";
|
||||
case PrintStatusFilamentWarningUnknownHighChamberTempSoft: return "PrintStatusFilamentWarningUnknownHighChamberTempSoft";
|
||||
case PrintStatusWarningExtFilamentNotMatch: return "PrintStatusWarningExtFilamentNotMatch";
|
||||
case PrintStatusReadingFinished: return "PrintStatusReadingFinished";
|
||||
case PrintStatusSendingCanceled: return "PrintStatusSendingCanceled";
|
||||
case PrintStatusAmsMappingSuccess: return "PrintStatusAmsMappingSuccess";
|
||||
@@ -92,6 +93,7 @@ wxString PrePrintChecker::get_pre_state_msg(PrintDialogStatus status)
|
||||
case PrintStatusWarningKvalueNotUsed: return _L("Set dynamic flow calibration to 'OFF' to enable custom dynamic flow value.");
|
||||
case PrintStatusNotSupportedPrintAll: return _L("This printer does not support printing all plates.");
|
||||
case PrintStatusColorQuantityExceed: return _L("The current firmware supports a maximum of 16 materials. You can either reduce the number of materials to 16 or fewer on the Preparation Page, or try updating the firmware. If you are still restricted after the update, please wait for subsequent firmware support.");
|
||||
case PrintStatusWarningExtFilamentNotMatch: return _L("The type of external filament is unknown or does not match with the filament type in the slicing file. Please make sure you have installed the correct filament in the external spool.");
|
||||
}
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ enum PrintDialogStatus : unsigned int {
|
||||
PrintStatusFilamentWarningHighChamberTempCloseDoor,
|
||||
PrintStatusFilamentWarningHighChamberTempSoft,
|
||||
PrintStatusFilamentWarningUnknownHighChamberTempSoft,
|
||||
PrintStatusWarningExtFilamentNotMatch,
|
||||
PrintStatusFilamentWarningEnd,
|
||||
|
||||
PrintStatusWarningEnd,//->end error<-
|
||||
|
||||
@@ -924,6 +924,8 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
checkbox->SetValue(app_config->get_bool(param));
|
||||
checkbox->SetToolTip(tip);
|
||||
|
||||
if (param == "sync_user_preset") { m_sync_user_preset_checkbox = checkbox; }
|
||||
|
||||
m_sizer_checkbox->Add(checkbox_title, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, FromDIP(3));
|
||||
m_sizer_checkbox->Add(checkbox , 0, wxALIGN_CENTER | wxRIGHT | wxLEFT, FromDIP(5));
|
||||
|
||||
@@ -955,6 +957,12 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: " << (sync ? "true" : "false");
|
||||
}
|
||||
else if (param == "stealth_mode") {
|
||||
bool enabled = app_config->get_stealth_mode();
|
||||
if (enabled) wxGetApp().on_stealth_mode_enter();
|
||||
if (m_sync_user_preset_checkbox) m_sync_user_preset_checkbox->Enable(!enabled);
|
||||
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
if (param == "associate_3mf") {
|
||||
@@ -1528,7 +1536,7 @@ void PreferencesDialog::create_items()
|
||||
auto item_region = create_item_region_combobox(_L("Login region"), "");
|
||||
g_sizer->Add(item_region);
|
||||
|
||||
auto item_stealth_mode = create_item_checkbox(_L("Stealth mode"), _L("This stops the transmission of data to Bambu's cloud services. Users who don't use BBL machines or use LAN mode only can safely turn on this function."), "stealth_mode");
|
||||
auto item_stealth_mode = create_item_checkbox(_L("Stealth mode"), _L("This disables all cloud services e.g. Orca Cloud and Bambu Cloud. This stops the transmission of data to Bambu's cloud services too. Users who don't use BBL machines or use LAN mode only can safely turn on this function."), "stealth_mode");
|
||||
g_sizer->Add(item_stealth_mode);
|
||||
|
||||
auto item_network_test = create_item_button(_L("Network test"), _L("Test") + " " + dots, "", _L("Open Network Test"), []() {
|
||||
@@ -1552,6 +1560,7 @@ void PreferencesDialog::create_items()
|
||||
text->Wrap(DESIGN_TITLE_SIZE.x);
|
||||
|
||||
auto cb = new ::CheckBox(m_parent);
|
||||
m_bambu_cloud_checkbox = cb;
|
||||
cb->SetValue(app_config->has_cloud_provider(BBL_CLOUD_PROVIDER));
|
||||
cb->SetToolTip(text->GetToolTipText());
|
||||
|
||||
@@ -1585,6 +1594,11 @@ void PreferencesDialog::create_items()
|
||||
auto item_user_sync = create_item_checkbox(_L("Auto sync user presets (Printer/Filament/Process)"), "", "sync_user_preset");
|
||||
g_sizer->Add(item_user_sync);
|
||||
|
||||
if (app_config->get_stealth_mode()) {
|
||||
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(false);
|
||||
if (m_sync_user_preset_checkbox) m_sync_user_preset_checkbox->Enable(false);
|
||||
}
|
||||
|
||||
auto item_system_sync = create_item_checkbox(_L("Update built-in Presets automatically."), "", "sync_system_preset");
|
||||
g_sizer->Add(item_system_sync);
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ public:
|
||||
::CheckBox * m_developer_mode_ckeckbox = {nullptr};
|
||||
::CheckBox * m_internal_developer_mode_ckeckbox = {nullptr};
|
||||
::CheckBox * m_dark_mode_ckeckbox = {nullptr};
|
||||
::CheckBox * m_sync_user_preset_checkbox = {nullptr};
|
||||
::CheckBox * m_bambu_cloud_checkbox = {nullptr};
|
||||
::TextInput *m_backup_interval_textinput = {nullptr};
|
||||
::ComboBox * m_network_version_combo = {nullptr};
|
||||
wxBoxSizer * m_network_version_sizer = {nullptr};
|
||||
|
||||
@@ -243,24 +243,23 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
|
||||
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
|
||||
|
||||
wxBoxSizer *m_sizer_top = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer *m_sizer_desc = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
wxBoxSizer *m_sizer_body = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
|
||||
|
||||
auto sm = create_scaled_bitmap("OrcaSlicer", nullptr, 70);
|
||||
m_brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, wxSize(FromDIP(70), FromDIP(70)));
|
||||
|
||||
|
||||
|
||||
wxBoxSizer *m_sizer_right = new wxBoxSizer(wxVERTICAL);
|
||||
auto sm = create_scaled_bitmap("OrcaSlicer", nullptr, 64);
|
||||
m_brand = new wxStaticBitmap(this, wxID_ANY, sm, wxDefaultPosition, FromDIP(wxSize(64, 64)));
|
||||
|
||||
m_text_up_info = new Label(this, Label::Head_14, wxEmptyString, LB_AUTO_WRAP);
|
||||
m_text_up_info->SetForegroundColour(wxColour(0x26, 0x2E, 0x30));
|
||||
|
||||
m_simplebook_release_note = new wxSimplebook(this);
|
||||
m_simplebook_release_note->SetSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_simplebook_release_note->SetMinSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
auto github_link = new HyperLink(this, _L("Check on Github"), "", LB_AUTO_WRAP);
|
||||
github_link->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) {
|
||||
EndModal(wxID_YES);
|
||||
});
|
||||
|
||||
m_simplebook_release_note = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER);
|
||||
//m_simplebook_release_note->SetSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_simplebook_release_note->SetMinSize(FromDIP(wxSize(640,420)));
|
||||
m_simplebook_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
|
||||
m_scrollwindows_release_note = new wxScrolledWindow(m_simplebook_release_note, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(560), FromDIP(430)), wxVSCROLL);
|
||||
@@ -269,8 +268,9 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
|
||||
//webview
|
||||
m_vebview_release_note = CreateTipView(m_simplebook_release_note);
|
||||
m_vebview_release_note->SetSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_vebview_release_note->SetMinSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_vebview_release_note->SetBackgroundColour(wxColour(0xF8, 0xF8, 0xF8));
|
||||
//m_vebview_release_note->SetSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
//m_vebview_release_note->SetMinSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
//m_vebview_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
if (wxGetApp().app_config->get_bool("developer_mode"))
|
||||
m_vebview_release_note->EnableAccessToDevTools();
|
||||
@@ -300,8 +300,6 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
m_simplebook_release_note->AddPage(m_scrollwindows_release_note, wxEmptyString, false);
|
||||
m_simplebook_release_note->AddPage(m_vebview_release_note, wxEmptyString, false);
|
||||
|
||||
|
||||
|
||||
auto sizer_button = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
m_button_download = new Button(this, _L("Download"));
|
||||
@@ -327,8 +325,6 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
});
|
||||
|
||||
auto stable_only_label = new Label(this, _L("Check for stable updates only"));
|
||||
stable_only_label->SetFont(Label::Body_13);
|
||||
stable_only_label->SetForegroundColour(wxColour(38, 46, 48));
|
||||
stable_only_label->SetFont(Label::Body_12);
|
||||
|
||||
m_button_cancel = new Button(this, _L("Cancel"));
|
||||
@@ -338,25 +334,27 @@ UpdateVersionDialog::UpdateVersionDialog(wxWindow *parent)
|
||||
EndModal(wxID_NO);
|
||||
});
|
||||
|
||||
m_sizer_main->Add(m_line_top, 0, wxEXPAND | wxBOTTOM, 0);
|
||||
|
||||
//sizer_button->Add(m_remind_choice, 0, wxALL | wxEXPAND, FromDIP(5));
|
||||
|
||||
sizer_button->Add(m_cb_stable_only , 0, wxALIGN_CENTER);
|
||||
sizer_button->Add(stable_only_label , 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
|
||||
sizer_button->AddStretchSpacer();
|
||||
sizer_button->Add(stable_only_label, 0, wxALIGN_CENTER | wxLEFT, FromDIP(7));
|
||||
sizer_button->Add(m_cb_stable_only, 0, wxALIGN_CENTER | wxLEFT, FromDIP(5));
|
||||
sizer_button->Add(m_button_download, 0, wxALL, FromDIP(5));
|
||||
sizer_button->Add(m_button_skip_version, 0, wxALL, FromDIP(5));
|
||||
sizer_button->Add(m_button_cancel, 0, wxALL, FromDIP(5));
|
||||
sizer_button->Add(m_button_download , 0, wxLEFT, FromDIP(10));
|
||||
sizer_button->Add(m_button_skip_version, 0, wxLEFT, FromDIP(10));
|
||||
sizer_button->Add(m_button_cancel , 0, wxLEFT, FromDIP(10));
|
||||
|
||||
m_sizer_right->Add(m_text_up_info, 0, wxEXPAND | wxBOTTOM | wxTOP, FromDIP(15));
|
||||
m_sizer_right->Add(m_simplebook_release_note, 1, wxEXPAND | wxRIGHT, 0);
|
||||
m_sizer_right->Add(sizer_button, 0, wxEXPAND | wxRIGHT, FromDIP(20));
|
||||
m_sizer_desc->AddStretchSpacer();
|
||||
m_sizer_desc->Add(m_text_up_info, 0, wxEXPAND | wxBOTTOM, FromDIP(5));
|
||||
m_sizer_desc->Add(github_link);
|
||||
m_sizer_desc->AddStretchSpacer();
|
||||
|
||||
m_sizer_body->Add(m_brand, 0, wxTOP|wxRIGHT|wxLEFT, FromDIP(15));
|
||||
m_sizer_body->Add(0, 0, 0, wxRIGHT, 0);
|
||||
m_sizer_body->Add(m_sizer_right, 1, wxBOTTOM | wxEXPAND, FromDIP(8));
|
||||
m_sizer_main->Add(m_sizer_body, 1, wxEXPAND, 0);
|
||||
m_sizer_main->Add(0, 0, 0, wxBOTTOM, 10);
|
||||
m_sizer_top->Add(m_brand , 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(15));
|
||||
m_sizer_top->Add(m_sizer_desc, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
m_sizer_main->Add(m_line_top , 0, wxEXPAND);
|
||||
m_sizer_main->Add(m_sizer_top , 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(15));
|
||||
m_sizer_main->Add(m_simplebook_release_note, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(15));
|
||||
m_sizer_main->Add(sizer_button , 0, wxEXPAND | wxALL , FromDIP(15));
|
||||
|
||||
SetSizer(m_sizer_main);
|
||||
Layout();
|
||||
@@ -479,14 +477,17 @@ void UpdateVersionDialog::update_version_info(wxString release_note, wxString ve
|
||||
// m_vebview_release_note->LoadURL(from_u8(url_line));
|
||||
// }
|
||||
// else {
|
||||
m_simplebook_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
//m_simplebook_release_note->SetMaxSize(wxSize(FromDIP(560), FromDIP(430)));
|
||||
m_simplebook_release_note->SetSelection(1);
|
||||
m_text_up_info->SetLabel(wxString::Format(_L("Click to download new version in default browser: %s"), version));
|
||||
auto data_buf_in = release_note.utf8_str();
|
||||
auto bg_color = StateColor::darkModeColorFor(*wxWHITE).GetAsString();
|
||||
auto fg_color = StateColor::darkModeColorFor(*wxBLACK).GetAsString();
|
||||
html_source = (boost::format("<html><head><style>body { color: %1%; background-color: %2%; font-family: sans-serif; } a { color: #1E90FF }</style></head><body>")
|
||||
% fg_color % bg_color).str();
|
||||
auto bg_color = StateColor::darkModeColorFor(wxColour("#FFFFFF")).GetAsString();
|
||||
auto fg_color = StateColor::darkModeColorFor(wxColour("#262E30")).GetAsString();
|
||||
auto style = "body {color:" + fg_color + "; background-color:" + bg_color + "; font-family:sans-serif}"
|
||||
+ "a {color: #009688}" // matches hyperlink colors
|
||||
+ "img {max-width:100%; height:auto}" // fixes overflowing images
|
||||
+ "ul {padding-inline-start: 20px}"; // reduce left padding on list items
|
||||
html_source = (boost::format("<html><head><style>%1%</style></head><body>") % style).str();
|
||||
md_html(data_buf_in.data(), data_buf_in.length(), [](const MD_CHAR* text, MD_SIZE size, void* userdata) {
|
||||
std::string* out_buf = (std::string*)userdata;
|
||||
out_buf->append(text, size);
|
||||
|
||||
@@ -1092,23 +1092,13 @@ bool SelectMachineDialog::do_ams_mapping(MachineObject *obj_,bool use_ams)
|
||||
|
||||
//single nozzle
|
||||
else {
|
||||
if (obj_->is_support_amx_ext_mix_mapping()){
|
||||
map_opt = { false, true, false, false }; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext
|
||||
if (!use_ams) {
|
||||
map_opt[1] = false;
|
||||
map_opt[3] = true;
|
||||
}
|
||||
filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt);
|
||||
//auto_supply_with_ext(obj_->vt_slot);
|
||||
}
|
||||
else {
|
||||
map_opt = { false, true, false, false };
|
||||
if (!use_ams) {
|
||||
map_opt[1] = false;
|
||||
map_opt[3] = true;
|
||||
}
|
||||
filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt);
|
||||
map_opt = { false, true, false, false }; //four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext
|
||||
if (!use_ams) {
|
||||
map_opt[1] = false;
|
||||
map_opt[3] = true;
|
||||
}
|
||||
filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt);
|
||||
//auto_supply_with_ext(obj_->vt_slot);
|
||||
}
|
||||
|
||||
if (filament_result == 0) {
|
||||
@@ -1147,7 +1137,7 @@ bool SelectMachineDialog::do_ams_mapping(MachineObject *obj_,bool use_ams)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectMachineDialog::get_ams_mapping_result(std::string &mapping_array_str, std::string& mapping_array_str2, std::string &ams_mapping_info)
|
||||
bool SelectMachineDialog::get_ams_mapping_result(std::string &mapping_array_str, std::string& mapping_array_str2, std::string &ams_mapping_info) const
|
||||
{
|
||||
if (m_ams_mapping_result.empty())
|
||||
return false;
|
||||
@@ -1438,7 +1428,7 @@ bool SelectMachineDialog::is_nozzle_type_match(DevExtderSystem data, wxString& e
|
||||
return true;
|
||||
}
|
||||
|
||||
int SelectMachineDialog::convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id)
|
||||
int SelectMachineDialog::convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id) const
|
||||
{
|
||||
if (nozzle_id == (int)FilamentMapNozzleId::NOZZLE_LEFT) {
|
||||
return (int)CloudTaskNozzleId::NOZZLE_LEFT;
|
||||
@@ -1691,6 +1681,9 @@ void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxSt
|
||||
} else if (status == PrintDialogStatus::PrintStatusFilamentWarningHighChamberTempSoft || status == PrintDialogStatus::PrintStatusFilamentWarningUnknownHighChamberTempSoft) {
|
||||
Enable_Refresh_Button(true);
|
||||
Enable_Send_Button(true);
|
||||
} else if (status == PrintStatusWarningExtFilamentNotMatch) {
|
||||
Enable_Refresh_Button(true);
|
||||
Enable_Send_Button(true);
|
||||
}
|
||||
|
||||
/*enter perpare mode*/
|
||||
@@ -3458,7 +3451,7 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
|
||||
std::string filament_type = boost::to_upper_copy(m_ams_mapping_result[i].type);
|
||||
std::string filament_brand;
|
||||
|
||||
for (auto fs : m_filaments) {
|
||||
for (auto& fs : m_filaments) {
|
||||
if (fs.id == m_ams_mapping_result[i].id) { filament_brand = m_filaments[i].brand; }
|
||||
}
|
||||
|
||||
@@ -3520,6 +3513,19 @@ void SelectMachineDialog::update_show_status(MachineObject* obj_)
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: show warning if external filament does not match
|
||||
for (auto& m : m_ams_mapping_result) {
|
||||
if (devPrinterUtil::IsVirtualSlot(m.ams_id)) {
|
||||
for (auto& fs : m_filaments) {
|
||||
if (fs.id == m.id && m.type != fs.type) {
|
||||
show_status(PrintDialogStatus::PrintStatusWarningExtFilamentNotMatch);
|
||||
goto ext_mismatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ext_mismatch:
|
||||
|
||||
/*STUDIO-10970 check the k value and flow cali option*/
|
||||
if (m_checkbox_list["flow_cali"]->IsShown() && m_checkbox_list["flow_cali"]->getValue() == "auto") {
|
||||
const auto ¬_default_ams_names = _check_kval_not_default(obj_, m_ams_mapping_result);
|
||||
@@ -5147,7 +5153,7 @@ void PrinterInfoBox::UpdatePlate(const std::string& plate_name)
|
||||
name = _L("Textured PEI Plate");
|
||||
m_bed_image->SetBitmap(create_scaled_bitmap("bed_pei", this, 40));
|
||||
}
|
||||
else if (plate_name == "SuperTack Plate") {
|
||||
else if (plate_name == "Supertack Plate" || plate_name == "SuperTack Plate") {
|
||||
name = _L("Cool Plate (SuperTack)");
|
||||
m_bed_image->SetBitmap(create_scaled_bitmap("bed_cool_supertack", this, 40));
|
||||
}
|
||||
|
||||
@@ -500,12 +500,12 @@ public:
|
||||
bool Show(bool show);
|
||||
void show_init();
|
||||
bool do_ams_mapping(MachineObject *obj_,bool use_ams);
|
||||
bool get_ams_mapping_result(std::string& mapping_array_str, std::string& mapping_array_str2, std::string& ams_mapping_info);
|
||||
bool get_ams_mapping_result(std::string& mapping_array_str, std::string& mapping_array_str2, std::string& ams_mapping_info) const;
|
||||
bool build_nozzles_info(std::string& nozzles_info);
|
||||
bool can_hybrid_mapping(DevExtderSystem data);
|
||||
void auto_supply_with_ext(std::vector<DevAmsTray> slots);
|
||||
bool is_nozzle_type_match(DevExtderSystem data, wxString& error_message) const;
|
||||
int convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id);
|
||||
int convert_filament_map_nozzle_id_to_task_nozzle_id(int nozzle_id) const;
|
||||
|
||||
PrintFromType get_print_type() {return m_print_type;};
|
||||
wxString format_steel_name(NozzleType type);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "PrePrintChecker.hpp"
|
||||
|
||||
#include "DeviceCore/DevConfig.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevMapping.h"
|
||||
@@ -1261,15 +1262,10 @@ bool SyncAmsInfoDialog::do_ams_mapping(MachineObject *obj_)
|
||||
}
|
||||
// single nozzle
|
||||
else {
|
||||
if (obj_->is_support_amx_ext_mix_mapping()) {
|
||||
map_opt = {false, true, false, true}; // four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext
|
||||
filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt, std::vector<int>(),
|
||||
wxGetApp().app_config->get_bool("ams_sync_match_full_use_color_dist") ? false : true);
|
||||
// auto_supply_with_ext(obj_->vt_slot);
|
||||
} else {
|
||||
map_opt = {false, true, false, false};
|
||||
filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt);
|
||||
}
|
||||
map_opt = {false, true, false, true}; // four values: use_left_ams, use_right_ams, use_left_ext, use_right_ext
|
||||
filament_result = DevMappingUtil::ams_filament_mapping(obj_, m_filaments, m_ams_mapping_result, map_opt, std::vector<int>(),
|
||||
wxGetApp().app_config->get_bool("ams_sync_match_full_use_color_dist") ? false : true);
|
||||
// auto_supply_with_ext(obj_->vt_slot);
|
||||
}
|
||||
|
||||
if (filament_result == 0) {
|
||||
@@ -1577,10 +1573,11 @@ bool SyncAmsInfoDialog::is_nozzle_type_match(DevExtderSystem data, wxString &err
|
||||
if (target_machine_nozzle_id < flow_type_of_machine.size()) {
|
||||
if (flow_type_of_machine[target_machine_nozzle_id] != used_extruders_flow[it->first]) {
|
||||
wxString pos;
|
||||
auto sai_nz_pt = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle);
|
||||
if (target_machine_nozzle_id == DEPUTY_EXTRUDER_ID) {
|
||||
pos = _L("left nozzle");
|
||||
pos = _L(DevPrinterConfigUtil::get_toolhead_display_name(sai_nz_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase));
|
||||
} else if ((target_machine_nozzle_id == MAIN_EXTRUDER_ID)) {
|
||||
pos = _L("right nozzle");
|
||||
pos = _L(DevPrinterConfigUtil::get_toolhead_display_name(sai_nz_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase));
|
||||
}
|
||||
|
||||
error_message = wxString::Format(_L("The nozzle flow setting of %s(%s) doesn't match with the slicing file(%s). "
|
||||
|
||||
+202
-7
@@ -61,6 +61,8 @@
|
||||
#endif // WIN32
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -499,6 +501,22 @@ void Tab::create_preset_tab()
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dynamic_cast<TabFilament *>(this)) {
|
||||
m_variant_combo = new MultiSwitchButton(panel);
|
||||
m_variant_combo->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) {
|
||||
evt.Skip();
|
||||
switch_excluder(evt.GetInt());
|
||||
reload_config();
|
||||
update_changed_ui();
|
||||
toggle_options();
|
||||
if (m_active_page)
|
||||
m_active_page->update_visibility(m_mode, true);
|
||||
m_page_view->GetParent()->Layout();
|
||||
});
|
||||
m_variant_combo->Hide();
|
||||
m_main_sizer->Add(m_variant_combo, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, m_em_unit);
|
||||
}
|
||||
|
||||
this->SetSizer(m_main_sizer);
|
||||
//this->Layout();
|
||||
m_page_view = m_parent->get_paged_view();
|
||||
@@ -1241,6 +1259,8 @@ void Tab::reload_config()
|
||||
{
|
||||
if (m_active_page)
|
||||
m_active_page->reload_config();
|
||||
if (m_type == Preset::TYPE_PRINT && m_config != nullptr)
|
||||
m_last_sparse_infill_rotate_template_value = m_config->opt_string("sparse_infill_rotate_template");
|
||||
}
|
||||
|
||||
void Tab::update_mode()
|
||||
@@ -1293,6 +1313,8 @@ void Tab::msw_rescale()
|
||||
{
|
||||
m_mode_view->Rescale();
|
||||
}
|
||||
if (m_variant_combo)
|
||||
m_variant_combo->Rescale();
|
||||
|
||||
if (m_detach_preset_btn)
|
||||
m_detach_preset_btn->msw_rescale();
|
||||
@@ -1358,6 +1380,8 @@ void Tab::sys_color_changed()
|
||||
m_active_page->sys_color_changed();
|
||||
if (m_extruder_switch)
|
||||
m_extruder_switch->Rescale();
|
||||
if (m_variant_combo)
|
||||
m_variant_combo->Rescale();
|
||||
|
||||
//BBS: GUI refactor
|
||||
//Layout();
|
||||
@@ -1744,8 +1768,9 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
|
||||
auto new_value = boost::any_cast<std::string>(value);
|
||||
is_safe_to_rotate = is_safe_to_rotate || new_value.empty();
|
||||
const bool had_previous_value = !m_last_sparse_infill_rotate_template_value.empty();
|
||||
|
||||
if (!is_safe_to_rotate) {
|
||||
if (!is_safe_to_rotate && !had_previous_value) {
|
||||
wxString msg_text = _(
|
||||
L("Infill patterns are typically designed to handle rotation automatically to ensure proper printing and achieve their "
|
||||
"intended effects (e.g., Gyroid, Cubic). Rotating the current sparse infill pattern may lead to insufficient support. "
|
||||
@@ -1762,6 +1787,8 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
wxGetApp().plater()->update();
|
||||
}
|
||||
}
|
||||
|
||||
m_last_sparse_infill_rotate_template_value = m_config->opt_string("sparse_infill_rotate_template");
|
||||
}
|
||||
|
||||
if(opt_key=="layer_height"){
|
||||
@@ -3742,7 +3769,7 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
|
||||
// "filament_seam_gap"
|
||||
};
|
||||
|
||||
const int selection = 0; //m_variant_combo->GetSelection(); // TODO: Orca hack
|
||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||
auto opt = dynamic_cast<ConfigOptionVectorBase *>(m_config->option("filament_retraction_length"));
|
||||
const int extruder_idx = selection < 0 || selection >= static_cast<int>(opt->size()) ? 0 : selection;
|
||||
|
||||
@@ -4272,9 +4299,11 @@ void TabFilament::toggle_options()
|
||||
|
||||
toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control"));
|
||||
|
||||
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", 0u);
|
||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
|
||||
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx);
|
||||
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
|
||||
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + 0u);
|
||||
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx);
|
||||
}
|
||||
|
||||
if (m_active_page->title() == L("Setting Overrides"))
|
||||
@@ -4292,7 +4321,8 @@ void TabFilament::toggle_options()
|
||||
toggle_option("filament_multitool_ramming_flow", multitool_ramming);
|
||||
|
||||
bool is_BBL_multi_extruder = is_BBL_printer && printer_cfg.option<ConfigOptionFloats>("nozzle_diameter")->size() > 1;
|
||||
const int extruder_idx = 0; // m_variant_combo->GetSelection(); // TODO: Orca hack
|
||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||
const int extruder_idx = std::max(selection, 0);
|
||||
toggle_line("long_retractions_when_ec", is_BBL_multi_extruder, 256 + extruder_idx);
|
||||
toggle_line("retraction_distances_when_ec", is_BBL_multi_extruder && m_config->opt_bool("long_retractions_when_ec", extruder_idx), 256 + extruder_idx);
|
||||
}
|
||||
@@ -6301,6 +6331,9 @@ bool Tab::tree_sel_change_delayed(wxCommandEvent& event)
|
||||
if (m_extruder_switch) {
|
||||
m_main_sizer->Show(m_extruder_switch, !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
} else if (m_variant_combo) {
|
||||
m_main_sizer->Show(m_variant_combo, m_variant_combo->IsEnabled() && !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
auto throw_if_canceled = std::function<void()>([this](){
|
||||
@@ -6398,6 +6431,10 @@ void Tab::transfer_options(const std::string &name_from, const std::string &name
|
||||
//BBS: add project embedded preset relate logic
|
||||
void Tab::save_preset(std::string name /*= ""*/, bool detach, bool save_to_project, bool from_input, std::string input_name )
|
||||
{
|
||||
// ORCA: Validate before opening any save-name UI for filament presets.
|
||||
if (!validate_filament_temperature_pairs())
|
||||
return;
|
||||
|
||||
// since buttons(and choices too) don't get focus on Mac, we set focus manually
|
||||
// to the treectrl so that the EVT_* events are fired for the input field having
|
||||
// focus currently.is there anything better than this ?
|
||||
@@ -6952,6 +6989,104 @@ bool Tab::validate_custom_gcodes()
|
||||
return valid;
|
||||
}
|
||||
|
||||
// ORCA: Session-only suppression keys for temperature-pair safety warnings.
|
||||
static std::unordered_set<std::string> s_filament_temp_pair_warning_suppressed_for_session;
|
||||
|
||||
// ORCA: Validate that first-layer and other-layer temperature pairs are within safety limits, and warn the user if not.
|
||||
bool Tab::validate_filament_temperature_pairs()
|
||||
{
|
||||
if (m_type != Preset::TYPE_FILAMENT || m_presets == nullptr)
|
||||
return true;
|
||||
|
||||
// Warn only for newly edited state, not for unchanged presets.
|
||||
if (!m_presets->current_is_dirty())
|
||||
return true;
|
||||
|
||||
Preset& edited_preset = m_presets->get_edited_preset();
|
||||
DynamicPrintConfig& config = edited_preset.config;
|
||||
const std::string suppress_key = edited_preset.name;
|
||||
// User opted out for this preset during current app session.
|
||||
if (!suppress_key.empty() && s_filament_temp_pair_warning_suppressed_for_session.count(suppress_key) > 0)
|
||||
return true;
|
||||
|
||||
struct TempPairRule
|
||||
{
|
||||
wxString label;
|
||||
std::string first_layer_key;
|
||||
std::string other_layer_key;
|
||||
int max_delta;
|
||||
};
|
||||
|
||||
std::vector<TempPairRule> temp_pair_rules;
|
||||
temp_pair_rules.push_back({_L("Nozzle"), "nozzle_temperature_initial_layer", "nozzle_temperature", 30});
|
||||
|
||||
// Derive bed labels/keys from curr_bed_type metadata (BedType order excludes btDefault).
|
||||
if (const ConfigOptionDef* bed_type_def = print_config_def.get("curr_bed_type");
|
||||
bed_type_def != nullptr) {
|
||||
for (int bt = static_cast<int>(btPC); bt < static_cast<int>(btCount); ++bt) {
|
||||
const BedType bed_type = static_cast<BedType>(bt);
|
||||
const size_t label_idx = static_cast<size_t>(bt - static_cast<int>(btPC));
|
||||
const std::string first_key = get_bed_temp_1st_layer_key(bed_type);
|
||||
const std::string other_key = get_bed_temp_key(bed_type);
|
||||
if (first_key.empty() || other_key.empty())
|
||||
continue;
|
||||
|
||||
wxString label = _(bed_type_def->enum_labels[label_idx]);
|
||||
temp_pair_rules.push_back({label, first_key, other_key, 15});
|
||||
}
|
||||
}
|
||||
|
||||
wxString invalid_pairs;
|
||||
int invalid_count = 0;
|
||||
|
||||
for (const TempPairRule& rule : temp_pair_rules) {
|
||||
if (!config.has(rule.first_layer_key) || !config.has(rule.other_layer_key))
|
||||
continue;
|
||||
|
||||
const ConfigOptionInts* first_opt = config.option<ConfigOptionInts>(rule.first_layer_key);
|
||||
const ConfigOptionInts* other_opt = config.option<ConfigOptionInts>(rule.other_layer_key);
|
||||
if (first_opt == nullptr || other_opt == nullptr || first_opt->values.empty() || other_opt->values.empty())
|
||||
continue;
|
||||
|
||||
const int first_temp = first_opt->get_at(0);
|
||||
const int other_temp = other_opt->get_at(0);
|
||||
|
||||
// Keep existing semantics: 0 means unsupported/off for these temperatures.
|
||||
if (first_temp <= 0 || other_temp <= 0)
|
||||
continue;
|
||||
|
||||
const int delta = std::abs(first_temp - other_temp);
|
||||
if (delta <= rule.max_delta)
|
||||
continue;
|
||||
|
||||
const wxString deg_c = wxString::FromUTF8("°C");
|
||||
const wxString bullet = wxString::FromUTF8("•");
|
||||
invalid_pairs += wxString::Format(_L(" - %s:\n %s first layer %d %s, other layers %d %s\n %s max delta %d %s, current delta %d %s\n"),
|
||||
rule.label, bullet, first_temp, deg_c, other_temp, deg_c, bullet, rule.max_delta, deg_c, delta, deg_c);
|
||||
++invalid_count;
|
||||
}
|
||||
|
||||
if (invalid_count == 0)
|
||||
return true;
|
||||
|
||||
wxString msg_text = _L("Some first-layer and other-layer temperature pairs exceed safety limits.\n");
|
||||
msg_text += _L("\nInvalid pairs:\n");
|
||||
msg_text += invalid_pairs;
|
||||
msg_text += _L("\nYou can go back to edit values, or continue if this is intentional.");
|
||||
msg_text += _L("\n\nContinue anyway?");
|
||||
|
||||
RichMessageDialog dialog(parent(), msg_text, _L("Temperature Safety Check"), wxYES | wxNO | wxICON_WARNING);
|
||||
dialog.SetButtonLabel(wxID_YES, _L("Continue"), true);
|
||||
dialog.SetButtonLabel(wxID_NO, _L("Back"));
|
||||
dialog.ShowCheckBox(_L("Don't warn again for this preset"));
|
||||
const int answer = dialog.ShowModal();
|
||||
// Session-only suppression (does not modify/save filament preset data).
|
||||
if (dialog.IsCheckBoxChecked() && !suppress_key.empty())
|
||||
s_filament_temp_pair_warning_suppressed_for_session.insert(suppress_key);
|
||||
|
||||
return answer == wxID_YES;
|
||||
}
|
||||
|
||||
void Tab::set_just_edit(bool just_edit)
|
||||
{
|
||||
m_just_edit = just_edit;
|
||||
@@ -6971,6 +7106,41 @@ void Tab::set_just_edit(bool just_edit)
|
||||
/// </summary>
|
||||
/// <param name="extruder_id"></param>
|
||||
|
||||
std::vector<wxString> Tab::generate_extruder_options()
|
||||
{
|
||||
std::vector<wxString> options;
|
||||
if (m_type != Preset::TYPE_FILAMENT)
|
||||
return options;
|
||||
|
||||
auto *variants = m_config->option<ConfigOptionStrings>("filament_extruder_variant");
|
||||
if (!variants)
|
||||
return options;
|
||||
|
||||
const std::vector<std::string> known_nozzle_types = {
|
||||
get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow),
|
||||
get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard),
|
||||
};
|
||||
|
||||
for (const std::string &variant : variants->values) {
|
||||
std::string drive;
|
||||
std::string nozzle;
|
||||
|
||||
for (const std::string &nozzle_type : known_nozzle_types) {
|
||||
if (variant.size() > nozzle_type.size() &&
|
||||
variant.substr(variant.size() - nozzle_type.size()) == nozzle_type &&
|
||||
variant[variant.size() - nozzle_type.size() - 1] == ' ') {
|
||||
drive = variant.substr(0, variant.size() - nozzle_type.size() - 1);
|
||||
nozzle = nozzle_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
options.push_back(nozzle.empty() ? from_u8(variant) : wxString::Format(wxT("%s: %s"), from_u8(drive), from_u8(nozzle)));
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
void Tab::update_extruder_variants(int extruder_id)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << extruder_id;
|
||||
@@ -6995,11 +7165,26 @@ void Tab::update_extruder_variants(int extruder_id)
|
||||
GetParent()->Layout();
|
||||
return;
|
||||
}
|
||||
} else if (m_variant_combo) {
|
||||
if (extruder_id >= 0)
|
||||
return;
|
||||
|
||||
const int selection = m_variant_combo->GetSelection();
|
||||
auto options = generate_extruder_options();
|
||||
m_variant_combo->SetOptions(options);
|
||||
|
||||
if (!options.empty())
|
||||
m_variant_combo->SetSelection(selection < 0 || selection >= (int) options.size() ? 0 : selection);
|
||||
|
||||
m_variant_combo->Enable(options.size() > 1);
|
||||
}
|
||||
switch_excluder(extruder_id);
|
||||
if (m_extruder_switch) {
|
||||
m_main_sizer->Show(m_extruder_switch, m_active_page && !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
} else if (m_variant_combo) {
|
||||
m_main_sizer->Show(m_variant_combo, m_variant_combo->IsEnabled() && m_active_page && !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7013,7 +7198,7 @@ void Tab::switch_excluder(int extruder_id)
|
||||
{}, {"", "filament_extruder_variant"}, // Preset::TYPE_FILAMENT filament don't use id anymore
|
||||
{}, {"printer_extruder_id", "printer_extruder_variant"}, // Preset::TYPE_PRINTER
|
||||
};
|
||||
if (extruder_id >= nozzle_volumes->size() || extruder_id >= extruders->size())
|
||||
if (!m_variant_combo && (extruder_id >= nozzle_volumes->size() || extruder_id >= extruders->size()))
|
||||
extruder_id = 0;
|
||||
if (m_extruder_switch && m_type != Preset::TYPE_PRINTER) {
|
||||
int current_extruder = m_extruder_switch->GetValue() ? 1 : 0;
|
||||
@@ -7021,15 +7206,25 @@ void Tab::switch_excluder(int extruder_id)
|
||||
extruder_id = current_extruder;
|
||||
else if (extruder_id != current_extruder)
|
||||
return;
|
||||
} else if (m_variant_combo) {
|
||||
int current_variant = m_variant_combo->GetSelection();
|
||||
if (current_variant < 0)
|
||||
current_variant = 0;
|
||||
if (extruder_id == -1)
|
||||
extruder_id = current_variant;
|
||||
else if (extruder_id != current_variant)
|
||||
return;
|
||||
}
|
||||
auto get_index_for_extruder =
|
||||
[this, &extruders, &nozzle_volumes, variant_keys = variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, int stride = 1) {
|
||||
return m_config->get_index_for_extruder(extruder_id + 1, variant_keys.first,
|
||||
ExtruderType(extruders->values[extruder_id]), NozzleVolumeType(nozzle_volumes->values[extruder_id]), variant_keys.second, stride);
|
||||
};
|
||||
auto index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id);
|
||||
auto index = m_variant_combo ? extruder_id : get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id);
|
||||
if (index < 0)
|
||||
return;
|
||||
if (m_variant_combo)
|
||||
m_variant_combo->SetClientData(reinterpret_cast<void *>(static_cast<std::uintptr_t>(index)));
|
||||
for (auto page : m_pages) {
|
||||
bool is_extruder = false;
|
||||
if (m_type == Preset::TYPE_PRINTER) {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
class TabCtrl;
|
||||
class ModeSwitchButton;
|
||||
class SwitchButton;
|
||||
class MultiSwitchButton;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -305,6 +306,7 @@ public:
|
||||
|
||||
ModeSwitchButton *m_mode_view = nullptr;
|
||||
SwitchButton *m_extruder_switch = nullptr;
|
||||
MultiSwitchButton *m_variant_combo = nullptr;
|
||||
|
||||
public:
|
||||
// BBS
|
||||
@@ -417,6 +419,7 @@ public:
|
||||
|
||||
static bool validate_custom_gcode(const wxString& title, const std::string& gcode);
|
||||
bool validate_custom_gcodes();
|
||||
bool validate_filament_temperature_pairs();
|
||||
bool validate_custom_gcodes_was_shown{ false };
|
||||
void set_just_edit(bool just_edit);
|
||||
|
||||
@@ -426,6 +429,7 @@ public:
|
||||
|
||||
void update_extruder_variants(int extruder_id = -1);
|
||||
void switch_excluder(int extruder_id = -1);
|
||||
std::vector<wxString> generate_extruder_options();
|
||||
|
||||
protected:
|
||||
void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget);
|
||||
@@ -445,6 +449,7 @@ protected:
|
||||
void filter_diff_option(std::vector<std::string> &options);
|
||||
|
||||
ConfigManipulation m_config_manipulation;
|
||||
std::string m_last_sparse_infill_rotate_template_value;
|
||||
ConfigManipulation get_config_manipulation();
|
||||
friend class EditGCodeDialog;
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "Widgets/RoundedRectangle.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/DialogButtons.hpp"
|
||||
#include "Widgets/HyperLink.hpp"
|
||||
|
||||
using boost::optional;
|
||||
|
||||
@@ -959,6 +960,11 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection *dependent_
|
||||
m_sizer_button->Add(checkbox_sizer, 0, wxLEFT, FromDIP(22));
|
||||
checkbox_sizer->Show(bool(m_buttons & REMEMBER_CHOISE));
|
||||
|
||||
if (dependent_presets != nullptr) {
|
||||
auto wiki = new HyperLink(this, _L("Help"), "https://www.orcaslicer.com/wiki/transfer_discard_changes");
|
||||
m_sizer_button->Add(wiki, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(22));
|
||||
}
|
||||
|
||||
m_sizer_button->Add(0, 0, 1, 0, 0);
|
||||
|
||||
// Add Buttons
|
||||
|
||||
@@ -418,8 +418,9 @@ void WebViewPanel::OnFreshLoginStatus(wxTimerEvent &event)
|
||||
{
|
||||
auto mainframe = Slic3r::GUI::wxGetApp().mainframe;
|
||||
if (mainframe && mainframe->m_webview == this) {
|
||||
Slic3r::GUI::wxGetApp().get_login_info(ORCA_CLOUD_PROVIDER);
|
||||
auto* app_config = Slic3r::GUI::wxGetApp().app_config;
|
||||
if (app_config && app_config->get_stealth_mode()) return;
|
||||
Slic3r::GUI::wxGetApp().get_login_info(ORCA_CLOUD_PROVIDER);
|
||||
if (app_config && app_config->has_cloud_provider(BBL_CLOUD_PROVIDER)) {
|
||||
Slic3r::GUI::wxGetApp().get_login_info(BBL_CLOUD_PROVIDER);
|
||||
}
|
||||
@@ -520,14 +521,18 @@ void WebViewPanel::SendCloudProvidersInfo()
|
||||
if (!app_config)
|
||||
return;
|
||||
|
||||
auto providers = app_config->get_cloud_providers();
|
||||
json j;
|
||||
j["command"] = "cloud_providers_info";
|
||||
json data;
|
||||
json provider_array = json::array();
|
||||
for (const auto& p : providers) {
|
||||
provider_array.push_back(p);
|
||||
|
||||
if (!app_config->get_stealth_mode()) {
|
||||
auto providers = app_config->get_cloud_providers();
|
||||
for (const auto& p : providers) {
|
||||
provider_array.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
data["providers"] = provider_array;
|
||||
j["data"] = data;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "SwitchButton.hpp"
|
||||
#include "Button.hpp"
|
||||
#include "Label.hpp"
|
||||
#include "StaticBox.hpp"
|
||||
|
||||
@@ -19,7 +20,10 @@
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dcgraph.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
wxDEFINE_EVENT(wxCUSTOMEVT_SWITCH_POS, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxCUSTOMEVT_MULTISWITCH_SELECTION, wxCommandEvent);
|
||||
|
||||
SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id)
|
||||
: wxBitmapToggleButton(parent, id, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxBU_EXACTFIT)
|
||||
@@ -391,6 +395,192 @@ void ModeSwitchButton::update_tooltip()
|
||||
SetToolTip(m_tooltips[m_selection]);
|
||||
}
|
||||
|
||||
MultiSwitchButton::MultiSwitchButton(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
|
||||
: StaticBox(parent, id, pos, size, style)
|
||||
, m_bg_color(StateColor(
|
||||
std::make_pair(0xE8E8E8, (int) StateColor::NotChecked),
|
||||
std::make_pair(0x009688, (int) StateColor::Normal)))
|
||||
, m_text_color(StateColor(
|
||||
std::make_pair(0x6B6B6B, (int) StateColor::NotChecked),
|
||||
std::make_pair(0xFFFFFE, (int) StateColor::Normal)))
|
||||
, m_button_radius(10.0)
|
||||
, m_button_padding(10, 6)
|
||||
{
|
||||
SetCornerRadius(m_button_radius);
|
||||
SetBorderWidth(0);
|
||||
|
||||
sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *hsizer = new wxBoxSizer(wxVERTICAL);
|
||||
hsizer->Add(sizer, 1, wxEXPAND);
|
||||
SetSizer(hsizer);
|
||||
SetMinSize(wxSize(-1, 20));
|
||||
|
||||
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MultiSwitchButton::button_clicked, this);
|
||||
SetFont(Label::Body_12);
|
||||
}
|
||||
|
||||
MultiSwitchButton::~MultiSwitchButton()
|
||||
{
|
||||
DeleteAllOptions();
|
||||
}
|
||||
|
||||
int MultiSwitchButton::AppendOption(const wxString &option, void *clientData)
|
||||
{
|
||||
Button *btn = new Button();
|
||||
btn->Create(this, option, "", wxBORDER_NONE);
|
||||
btn->SetFont(GetFont());
|
||||
btn->SetBackgroundColor(m_bg_color);
|
||||
btn->SetTextColor(m_text_color);
|
||||
btn->SetCornerRadius(m_button_radius);
|
||||
btn->SetPaddingSize(m_button_padding);
|
||||
btn->SetClientData(clientData);
|
||||
|
||||
btns.push_back(btn);
|
||||
sizer->Add(btn, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
wxSize text_size = btn->GetTextExtent(option);
|
||||
btn->SetMinSize(wxSize(text_size.x + m_button_padding.x * 2 + 6, -1));
|
||||
|
||||
return int(btns.size()) - 1;
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetOptions(const std::vector<wxString> &options)
|
||||
{
|
||||
DeleteAllOptions();
|
||||
for (const auto &option : options)
|
||||
AppendOption(option);
|
||||
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::DeleteAllOptions()
|
||||
{
|
||||
sel = -1;
|
||||
for (auto *btn : btns) {
|
||||
if (btn)
|
||||
btn->Destroy();
|
||||
}
|
||||
btns.clear();
|
||||
if (sizer)
|
||||
sizer->Clear();
|
||||
}
|
||||
|
||||
unsigned int MultiSwitchButton::GetCount() const
|
||||
{
|
||||
return (unsigned int) btns.size();
|
||||
}
|
||||
|
||||
int MultiSwitchButton::GetSelection() const
|
||||
{
|
||||
return sel;
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetSelection(int index)
|
||||
{
|
||||
if (index < 0 || index >= (int) btns.size() || index == sel)
|
||||
return;
|
||||
|
||||
sel = index;
|
||||
update_button_styles();
|
||||
send_selection_event();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
wxString MultiSwitchButton::GetSelectedText() const
|
||||
{
|
||||
return sel >= 0 && sel < (int) btns.size() ? btns[sel]->GetLabel() : wxString();
|
||||
}
|
||||
|
||||
wxString MultiSwitchButton::GetOptionText(unsigned int index) const
|
||||
{
|
||||
return index < btns.size() ? btns[index]->GetLabel() : wxString();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetOptionText(unsigned int index, const wxString &text)
|
||||
{
|
||||
if (index >= btns.size())
|
||||
return;
|
||||
btns[index]->SetLabel(text);
|
||||
}
|
||||
|
||||
void *MultiSwitchButton::GetOptionData(unsigned int index) const
|
||||
{
|
||||
return index < btns.size() ? btns[index]->GetClientData() : nullptr;
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetOptionData(unsigned int index, void *clientData)
|
||||
{
|
||||
if (index >= btns.size())
|
||||
return;
|
||||
btns[index]->SetClientData(clientData);
|
||||
}
|
||||
|
||||
void MultiSwitchButton::update_button_styles()
|
||||
{
|
||||
for (int i = 0; i < (int) btns.size(); ++i) {
|
||||
btns[i]->SetValue(i == sel);
|
||||
btns[i]->SetBackgroundColor(m_bg_color);
|
||||
btns[i]->SetTextColor(m_text_color);
|
||||
btns[i]->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetBackgroundColor(const StateColor &color)
|
||||
{
|
||||
m_bg_color = color;
|
||||
update_button_styles();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetTextColor(const StateColor &color)
|
||||
{
|
||||
m_text_color = color;
|
||||
update_button_styles();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetButtonCornerRadius(double radius)
|
||||
{
|
||||
m_button_radius = radius;
|
||||
SetCornerRadius(radius);
|
||||
for (auto *btn : btns)
|
||||
btn->SetCornerRadius(radius);
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetButtonPadding(const wxSize &padding)
|
||||
{
|
||||
m_button_padding = padding;
|
||||
for (auto *btn : btns)
|
||||
btn->SetPaddingSize(padding);
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::Rescale()
|
||||
{
|
||||
for (auto *btn : btns)
|
||||
btn->Rescale();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::button_clicked(wxCommandEvent &event)
|
||||
{
|
||||
SetFocus();
|
||||
auto *btn = event.GetEventObject();
|
||||
auto iter = std::find(btns.begin(), btns.end(), btn);
|
||||
SetSelection(iter == btns.end() ? -1 : int(iter - btns.begin()));
|
||||
}
|
||||
|
||||
bool MultiSwitchButton::send_selection_event()
|
||||
{
|
||||
wxCommandEvent evt(wxCUSTOMEVT_MULTISWITCH_SELECTION, GetId());
|
||||
evt.SetEventObject(this);
|
||||
evt.SetInt(sel);
|
||||
evt.SetString(GetSelectedText());
|
||||
GetEventHandler()->ProcessEvent(evt);
|
||||
return true;
|
||||
}
|
||||
|
||||
SwitchBoard::SwitchBoard(wxWindow *parent, wxString leftL, wxString right, wxSize size)
|
||||
: wxWindow(parent, wxID_ANY, wxDefaultPosition, size)
|
||||
{
|
||||
@@ -554,4 +744,4 @@ bool SwitchBoard::Enable(bool enable /* = true */)
|
||||
is_enable = enable;
|
||||
Refresh();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,14 @@
|
||||
#include "StateColor.hpp"
|
||||
#include "StaticBox.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/tglbtn.h>
|
||||
|
||||
wxDECLARE_EVENT(wxCUSTOMEVT_SWITCH_POS, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxCUSTOMEVT_MULTISWITCH_SELECTION, wxCommandEvent);
|
||||
|
||||
class Button;
|
||||
|
||||
class SwitchButton : public wxBitmapToggleButton
|
||||
{
|
||||
@@ -76,6 +81,53 @@ private:
|
||||
wxString m_tooltips[3];
|
||||
};
|
||||
|
||||
class MultiSwitchButton : public StaticBox
|
||||
{
|
||||
public:
|
||||
MultiSwitchButton(wxWindow *parent = nullptr, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition,
|
||||
const wxSize &size = wxDefaultSize, long style = 0);
|
||||
~MultiSwitchButton();
|
||||
|
||||
int AppendOption(const wxString &option, void *clientData = nullptr);
|
||||
void SetOptions(const std::vector<wxString> &options);
|
||||
void DeleteAllOptions();
|
||||
|
||||
unsigned int GetCount() const;
|
||||
|
||||
int GetSelection() const;
|
||||
void SetSelection(int index);
|
||||
wxString GetSelectedText() const;
|
||||
|
||||
wxString GetOptionText(unsigned int index) const;
|
||||
void SetOptionText(unsigned int index, const wxString &text);
|
||||
|
||||
void *GetOptionData(unsigned int index) const;
|
||||
void SetOptionData(unsigned int index, void *clientData);
|
||||
|
||||
void SetBackgroundColor(const StateColor &color);
|
||||
void SetTextColor(const StateColor &color);
|
||||
void SetButtonCornerRadius(double radius);
|
||||
void SetButtonPadding(const wxSize &padding);
|
||||
|
||||
void Rescale();
|
||||
|
||||
protected:
|
||||
void button_clicked(wxCommandEvent &event);
|
||||
void update_button_styles();
|
||||
|
||||
bool send_selection_event();
|
||||
|
||||
private:
|
||||
std::vector<Button *> btns;
|
||||
wxBoxSizer *sizer = nullptr;
|
||||
int sel = -1;
|
||||
|
||||
StateColor m_bg_color;
|
||||
StateColor m_text_color;
|
||||
double m_button_radius;
|
||||
wxSize m_button_padding;
|
||||
};
|
||||
|
||||
class SwitchBoard : public wxWindow
|
||||
{
|
||||
public:
|
||||
|
||||
Reference in new issue
Block a user