mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-05 09:07:39 +00:00
Merge branch 'main' into fix/ota-profile-race
This commit is contained in:
@@ -202,10 +202,25 @@ void AppConfig::set_defaults()
|
||||
if (get("seq_top_layer_only").empty())
|
||||
set("seq_top_layer_only", "1");
|
||||
|
||||
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
|
||||
// ORCA: darken the layers the preview layer slider is not scrubbed to
|
||||
if (get("preview_dim_previous_layers").empty())
|
||||
set_bool("preview_dim_previous_layers", false);
|
||||
|
||||
// ORCA: brightness of those dimmed layers, in percent. 0 = black, capped at 99 because
|
||||
// 100 would render them unchanged, which is what disabling the option already does
|
||||
if (get("preview_dim_previous_layers_brightness").empty())
|
||||
set("preview_dim_previous_layers_brightness", "40");
|
||||
else {
|
||||
int brightness = 40;
|
||||
try {
|
||||
brightness = std::stoi(get("preview_dim_previous_layers_brightness"));
|
||||
}
|
||||
catch (...) {
|
||||
brightness = 40;
|
||||
}
|
||||
set("preview_dim_previous_layers_brightness", std::to_string(std::max(0, std::min(brightness, 99))));
|
||||
}
|
||||
|
||||
if (get("filaments_area_preferred_count").empty())
|
||||
set("filaments_area_preferred_count", "10");
|
||||
|
||||
|
||||
@@ -32,15 +32,13 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
|
||||
for (; dst_idx < dst.size(); ++dst_idx)
|
||||
dst[dst_idx].translate(instance_shift);
|
||||
}
|
||||
// BBS: generate brim area by objs
|
||||
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
|
||||
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
||||
// Orca: Translate the brim area into print coordinates and store it per instance.
|
||||
static void append_and_translate(const ExPolygons& src, const PrintInstance& instance,
|
||||
size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
||||
ExPolygons srcShifted = src;
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
|
||||
srcShifted[src_idx].translate(instance_shift);
|
||||
srcShifted = diff_ex(srcShifted, dst);
|
||||
//expolygons_append(dst, temp2);
|
||||
for (ExPolygon& expoly : srcShifted)
|
||||
expoly.translate(instance_shift);
|
||||
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
|
||||
}
|
||||
|
||||
@@ -572,7 +570,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
|
||||
const PrintInstance& instance = object->instances()[instance_idx];
|
||||
if (!brim_area_object.empty())
|
||||
append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap);
|
||||
append_and_translate(brim_area_object, instance, instance_idx, brimAreaMap);
|
||||
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
||||
append_and_translate(holes, holes_object, instance);
|
||||
append_and_translate(objectIslands, objectIsland, instance);
|
||||
@@ -875,6 +873,14 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
||||
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
||||
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
|
||||
|
||||
if (!print.config().combine_brims) {
|
||||
ExPolygons claimed_area;
|
||||
for (auto& [_, areas] : brimAreaMap) {
|
||||
areas = diff_ex(areas, claimed_area);
|
||||
expolygons_append(claimed_area, areas);
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: Find boundingbox of the first layer
|
||||
for (const ObjectID printObjID : print.print_object_ids()) {
|
||||
BoundingBox bbx;
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
Standard_Boolean UserBreak() override { return should_stop.load(); }
|
||||
|
||||
void Show(const Message_ProgressScope&, const Standard_Boolean) override {
|
||||
std::cout << "Progress: " << GetPosition() << "%" << std::endl;
|
||||
std::cout << "Progress: " << std::fixed << std::setprecision(2) << 100.0 * GetPosition() << "%" << std::endl;
|
||||
}
|
||||
private:
|
||||
std::atomic<bool>& should_stop;
|
||||
|
||||
@@ -360,6 +360,14 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path& subject, con
|
||||
return clipped_paths;
|
||||
}
|
||||
|
||||
static double clipper_z_path_length(const ClipperLib_Z::Path &path)
|
||||
{
|
||||
double len = 0.;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
len += (Vec2d(double(path[i].x()), double(path[i].y())) - Vec2d(double(path[i - 1].x()), double(path[i - 1].y()))).norm();
|
||||
return len;
|
||||
}
|
||||
|
||||
struct PerimeterGeneratorArachneExtrusion
|
||||
{
|
||||
Arachne::ExtrusionLine* extrusion = nullptr;
|
||||
@@ -571,17 +579,156 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||
return extrusion_coll;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top detects the top as "slice − upper", so a feature rising from the middle of a
|
||||
// top surface becomes an enclosed hole that gets ringed with extra inner walls. Fill those holes back
|
||||
// into the top. Only holes that are both covered by the upper layer (excludes bridges) and backed by
|
||||
// solid material (excludes voids) are filled.
|
||||
static ExPolygons fill_enclosed_top_feature_holes(const ExPolygons &top, const Polygons &covered_by_upper, const ExPolygons &solid)
|
||||
// ORCA: only_one_wall_top acts on top surfaces, so without a top shell there is nothing for it to act on: zero top
|
||||
// shell layers retype the top surfaces as internal, see LayerRegion::prepare_fill_surfaces(). A 0% top surface
|
||||
// density does leave a top surface - just an unfilled one - so it does not disable the feature.
|
||||
// ConfigManipulation::toggle_print_fff_options() hides the option under the same condition, so a profile that left
|
||||
// it enabled does not act behind a hidden checkbox.
|
||||
static bool has_top_shell_layers(const PrintRegionConfig &config)
|
||||
{
|
||||
ExPolygons filled = top;
|
||||
for (ExPolygon &ex : filled)
|
||||
ex.holes.clear();
|
||||
const ExPolygons feature_holes = intersection_ex(intersection_ex(diff_ex(filled, top), covered_by_upper), solid);
|
||||
return feature_holes.empty() ? top : union_ex(top, feature_holes);
|
||||
return config.top_shell_layers.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_first_layer thins the first layer to a single wall, the bottom counterpart of the above and
|
||||
// gated the same way: zero bottom shell layers retype the bottom surfaces as internal, so that wall would ring
|
||||
// sparse infill on the bed. The bottom surface density plays no part - an unfilled bottom surface is still a bottom
|
||||
// surface, exactly as for the top - and it cannot reach zero anyway, being capped at a 10% minimum.
|
||||
static bool has_bottom_shell_layers(const PrintRegionConfig &config)
|
||||
{
|
||||
return config.bottom_shell_layers.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: the inner walls are only given up when a top fill takes their space, and it has to actually reach it -
|
||||
// a 0% top surface density leaves no fill at all, and without top_surface_expansion the fill never grows over
|
||||
// them. Either way the original generation is kept (re-onion the not-top region), which is what users of
|
||||
// only_one_wall_top alone have always got.
|
||||
static bool top_fill_replaces_inner_walls(const PrintRegionConfig &config)
|
||||
{
|
||||
return has_top_shell_layers(config) && config.top_surface_density.value > 0 && config.top_surface_expansion.value > 0;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top - cheap per-vertex classification of a wall against the top surface. Only Partial
|
||||
// needs the geometry clipped or measured; a segment crossing the top with no vertex inside is rare enough to ignore.
|
||||
enum class TopOverlap { None, Partial, Full };
|
||||
|
||||
static bool point_over_top(const Point &p, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
if (! top_region_bbox.contains(p))
|
||||
return false;
|
||||
for (const ExPolygon &ex : top_region)
|
||||
if (ex.contains(p, false))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static TopOverlap classify_over_top(const Points &pts, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
size_t inside = 0;
|
||||
for (const Point &p : pts)
|
||||
if (point_over_top(p, top_region, top_region_bbox))
|
||||
++ inside;
|
||||
return inside == 0 ? TopOverlap::None : inside == pts.size() ? TopOverlap::Full : TopOverlap::Partial;
|
||||
}
|
||||
|
||||
static TopOverlap classify_over_top(const Arachne::ExtrusionLine &el, const ExPolygons &top_region, const BoundingBox &top_region_bbox)
|
||||
{
|
||||
size_t inside = 0;
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
if (point_over_top(j.p, top_region, top_region_bbox))
|
||||
++ inside;
|
||||
return inside == 0 ? TopOverlap::None : inside == el.junctions.size() ? TopOverlap::Full : TopOverlap::Partial;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top for Arachne - cut out of the already generated inner walls the parts running over the top
|
||||
// surface, so geometry that continues upward keeps its walls. A wall too short over the top to be worth slitting open
|
||||
// is left whole, its footprint reported in kept_over_top for the caller to withhold from the top fill.
|
||||
static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &inner_perimeters, const ExPolygons &top_region, coord_t perimeter_width, Polygons &kept_over_top)
|
||||
{
|
||||
const BoundingBox top_region_bbox = get_extents(top_region).inflated(SCALED_EPSILON);
|
||||
auto covered_by = [](const Arachne::ExtrusionLine &el) {
|
||||
Polyline centerline;
|
||||
centerline.points.reserve(el.junctions.size());
|
||||
coord_t width = 0;
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions) {
|
||||
centerline.points.emplace_back(j.p);
|
||||
width = std::max(width, j.w);
|
||||
}
|
||||
return offset(centerline, float(width) / 2.f);
|
||||
};
|
||||
// Pull the cut back by half a wall width: the clip severs the centerline, but the bead's rounded end
|
||||
// extends half a width past its endpoint and would otherwise overlap the top fill.
|
||||
ClipperLib_Z::Paths top_paths_z;
|
||||
for (const Polygon &poly : to_polygons(offset_ex(top_region, float(perimeter_width) / 2.f))) {
|
||||
top_paths_z.emplace_back();
|
||||
ClipperLib_Z::Path &out = top_paths_z.back();
|
||||
out.reserve(poly.points.size());
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(pt.x(), pt.y(), 0);
|
||||
}
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) {
|
||||
Arachne::VariableWidthLines kept;
|
||||
kept.reserve(inner_perimeter.size());
|
||||
for (Arachne::ExtrusionLine &el : inner_perimeter) {
|
||||
if (el.empty())
|
||||
continue;
|
||||
const TopOverlap overlap = classify_over_top(el, top_region, top_region_bbox);
|
||||
if (overlap == TopOverlap::None) {
|
||||
kept.emplace_back(std::move(el));
|
||||
continue;
|
||||
}
|
||||
if (overlap == TopOverlap::Full)
|
||||
continue; // the clip below would return nothing anyway
|
||||
ClipperLib_Z::Path subject;
|
||||
subject.reserve(el.size());
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
subject.emplace_back(j.p.x(), j.p.y(), j.w);
|
||||
ClipperLib_Z::Paths pieces = clip_extrusion(subject, top_paths_z, ClipperLib_Z::ctDifference);
|
||||
|
||||
// Clipper treats the subject as an open polyline, so it also cuts a closed loop at its (arbitrary)
|
||||
// start vertex and may reverse pieces. Stitch pieces sharing an endpoint back together.
|
||||
auto same_pt = [](const ClipperLib_Z::IntPoint &p, const ClipperLib_Z::IntPoint &q) {
|
||||
return std::abs(p.x() - q.x()) <= SCALED_EPSILON && std::abs(p.y() - q.y()) <= SCALED_EPSILON;
|
||||
};
|
||||
for (size_t i = 0; i < pieces.size(); ++ i) {
|
||||
for (size_t j = i + 1; j < pieces.size();) {
|
||||
ClipperLib_Z::Path &a = pieces[i];
|
||||
ClipperLib_Z::Path &b = pieces[j];
|
||||
if (same_pt(a.front(), b.front()) || same_pt(a.front(), b.back()))
|
||||
std::reverse(a.begin(), a.end());
|
||||
if (same_pt(a.back(), b.back()))
|
||||
std::reverse(b.begin(), b.end());
|
||||
if (same_pt(a.back(), b.front())) {
|
||||
a.insert(a.end(), b.begin() + 1, b.end());
|
||||
pieces.erase(pieces.begin() + j);
|
||||
j = i + 1; // the merged path has new endpoints, restart the scan
|
||||
} else
|
||||
++ j;
|
||||
}
|
||||
}
|
||||
|
||||
// If the clip removed next to nothing, keep the loop untouched instead of slitting it open. The
|
||||
// half-width pull-back above already costs about one width per crossing, hence two widths.
|
||||
double kept_length = 0.;
|
||||
for (const ClipperLib_Z::Path &path : pieces)
|
||||
kept_length += clipper_z_path_length(path);
|
||||
if (clipper_z_path_length(subject) - kept_length < 2. * double(perimeter_width)) {
|
||||
append(kept_over_top, covered_by(el));
|
||||
kept.emplace_back(std::move(el));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const ClipperLib_Z::Path &path : pieces) {
|
||||
Arachne::ExtrusionLine clipped(el.inset_idx, el.is_odd);
|
||||
clipped.junctions.reserve(path.size());
|
||||
for (const ClipperLib_Z::IntPoint &pt : path)
|
||||
clipped.junctions.emplace_back(Point(pt.x(), pt.y()), coord_t(pt.z()), el.inset_idx);
|
||||
// Discard tiny leftovers that would print as zits.
|
||||
if (clipped.size() >= 2 && clipped.getLength() >= perimeter_width)
|
||||
kept.emplace_back(std::move(clipped));
|
||||
}
|
||||
}
|
||||
inner_perimeter = std::move(kept);
|
||||
}
|
||||
}
|
||||
|
||||
void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills,
|
||||
@@ -649,7 +796,6 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP
|
||||
ExPolygons delete_bridge = diff_ex(orig_polygons, bridge_checker, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons top_polygons = diff_ex(delete_bridge, upper_polygons_series_clipped, ApplySafetyOffset::Yes);
|
||||
top_polygons = fill_enclosed_top_feature_holes(top_polygons, upper_polygons_series_clipped, orig_polygons);
|
||||
|
||||
// get the not-top surface, from the "real top" but enlarged by external_infill_margin (and the
|
||||
// min_width_top_surface we removed a bit before)
|
||||
@@ -1234,6 +1380,11 @@ void PerimeterGenerator::process_classic()
|
||||
for (const Surface &surface : all_surfaces)
|
||||
surface_exp.push_back(surface.expolygon);
|
||||
std::vector<size_t> surface_order = chain_expolygons(surface_exp);
|
||||
// ORCA: neither one-wall option has a surface to act on without the shell behind it, see
|
||||
// has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
|
||||
// topmost and first layers - sees the same answer.
|
||||
const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
|
||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
|
||||
for (size_t order_idx = 0; order_idx < surface_order.size(); order_idx++) {
|
||||
const Surface &surface = all_surfaces[surface_order[order_idx]];
|
||||
// detect how many perimeters must be generated for this island
|
||||
@@ -1241,16 +1392,23 @@ void PerimeterGenerator::process_classic()
|
||||
int sparse_infill_density = this->config->sparse_infill_density.value;
|
||||
if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall
|
||||
loop_number++;
|
||||
if (this->layer_id == object_config->raft_layers && this->config->only_one_wall_first_layer)
|
||||
if (this->layer_id == object_config->raft_layers && only_one_wall_first_layer)
|
||||
loop_number = 0;
|
||||
// Set the topmost layer to be one wall
|
||||
if (loop_number > 0 && config->only_one_wall_top && this->upper_slices == nullptr)
|
||||
if (loop_number > 0 && only_one_wall_top && this->upper_slices == nullptr)
|
||||
loop_number = 0;
|
||||
|
||||
ExPolygons last = union_ex(surface.expolygon.simplify_p(surface_simplify_resolution));
|
||||
ExPolygons gaps;
|
||||
ExPolygons top_fills;
|
||||
ExPolygons fill_clip;
|
||||
// ORCA: only_one_wall_top, all empty unless this island has a top surface on this layer. See the
|
||||
// post-onion reduction below: the region to keep clear of inner walls, the space freed by the dropped
|
||||
// walls (goes to infill, not left as a void) and the space held by the kept ones (withheld from the fill).
|
||||
ExPolygons one_wall_top_region;
|
||||
ExPolygons one_wall_top_reclaimed;
|
||||
Polygons one_wall_top_kept_bands;
|
||||
bool apply_one_wall_top = false;
|
||||
if (loop_number >= 0) {
|
||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
||||
@@ -1389,8 +1547,19 @@ void PerimeterGenerator::process_classic()
|
||||
|
||||
//BBS: refer to superslicer
|
||||
//store surface for top infill if only_one_wall_top
|
||||
if (i == 0 && i!=loop_number && config->only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
|
||||
this->split_top_surfaces(last, top_fills, last, fill_clip);
|
||||
if (i == 0 && i!=loop_number && only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
|
||||
if (top_fill_replaces_inner_walls(*this->config)) {
|
||||
// ORCA: take the top fill and the keep-out region but leave `last` as the real geometry,
|
||||
// so the onion follows it and the walls over the top are reduced in one step below.
|
||||
ExPolygons non_top_polygons;
|
||||
this->split_top_surfaces(last, top_fills, non_top_polygons, fill_clip);
|
||||
apply_one_wall_top = !top_fills.empty();
|
||||
if (apply_one_wall_top)
|
||||
one_wall_top_region = diff_ex(last, non_top_polygons);
|
||||
} else {
|
||||
// Onion the not-top region only, so the remaining walls stop at the top boundary.
|
||||
this->split_top_surfaces(last, top_fills, last, fill_clip);
|
||||
}
|
||||
}
|
||||
|
||||
if (i == loop_number && (! has_gap_fill || this->config->sparse_infill_density.value == 0)) {
|
||||
@@ -1400,6 +1569,46 @@ void PerimeterGenerator::process_classic()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top reduction - drop the inner walls (depth > 0) running over the top surface and
|
||||
// take that space back from the gaps, leaving the top with the outer wall and the top infill. Classic
|
||||
// perimeters are closed loops, so a wall can only be kept or dropped whole; one that merely grazes the
|
||||
// top (same tolerance as the Arachne clip) is kept and withheld from the top fill instead.
|
||||
if (apply_one_wall_top) {
|
||||
const BoundingBox top_region_bbox = get_extents(one_wall_top_region).inflated(SCALED_EPSILON);
|
||||
const double grazing_tolerance = 2. * double(perimeter_width);
|
||||
// The band a wall covers, taken around its centerline so the orientation of holes does not matter.
|
||||
auto wall_band = [perimeter_spacing](const Polygon &poly) {
|
||||
Polygon centerline = poly;
|
||||
centerline.make_counter_clockwise();
|
||||
return diff(offset(centerline, float(perimeter_spacing) / 2.f),
|
||||
offset(centerline, -float(perimeter_spacing) / 2.f));
|
||||
};
|
||||
Polygons dropped_wall_bands;
|
||||
auto reduce_over_top = [&](PerimeterGeneratorLoops &loops) {
|
||||
loops.erase(std::remove_if(loops.begin(), loops.end(), [&](const PerimeterGeneratorLoop &loop) {
|
||||
const TopOverlap overlap = classify_over_top(loop.polygon.points, one_wall_top_region, top_region_bbox);
|
||||
if (overlap == TopOverlap::None)
|
||||
return false;
|
||||
// Only a wall straddling the boundary is worth measuring; a wall wholly over the top goes.
|
||||
if (overlap == TopOverlap::Partial &&
|
||||
total_length(intersection_pl(Polylines{ loop.polygon.split_at_first_point() }, one_wall_top_region)) < grazing_tolerance) {
|
||||
append(one_wall_top_kept_bands, wall_band(loop.polygon));
|
||||
return false;
|
||||
}
|
||||
append(dropped_wall_bands, wall_band(loop.polygon));
|
||||
return true;
|
||||
}), loops.end());
|
||||
};
|
||||
for (int d = 1; d <= loop_number; ++ d) {
|
||||
reduce_over_top(contours[d]);
|
||||
reduce_over_top(holes[d]);
|
||||
}
|
||||
if (! gaps.empty())
|
||||
gaps = diff_ex(gaps, one_wall_top_region);
|
||||
if (! dropped_wall_bands.empty())
|
||||
one_wall_top_reclaimed = diff_ex(dropped_wall_bands, one_wall_top_region);
|
||||
}
|
||||
|
||||
// nest loops: holes first
|
||||
for (int d = 0; d <= loop_number; ++ d) {
|
||||
PerimeterGeneratorLoops &holes_d = holes[d];
|
||||
@@ -1634,7 +1843,10 @@ void PerimeterGenerator::process_classic()
|
||||
and use zigzag). */
|
||||
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
||||
// therefore it may cover the area, but no the volume.
|
||||
last = diff_ex(last, gap_fill.polygons_covered_by_width(10.f));
|
||||
Polygons gap_fill_covered = gap_fill.polygons_covered_by_width(10.f);
|
||||
last = diff_ex(last, gap_fill_covered);
|
||||
if (! one_wall_top_reclaimed.empty())
|
||||
one_wall_top_reclaimed = diff_ex(one_wall_top_reclaimed, gap_fill_covered);
|
||||
this->gap_fill->append(std::move(gap_fill.entities));
|
||||
|
||||
}
|
||||
@@ -1679,9 +1891,15 @@ void PerimeterGenerator::process_classic()
|
||||
// append infill areas to fill_surfaces
|
||||
//if any top_fills, grow them by ext_perimeter_spacing/2 to have the real un-anchored fill
|
||||
ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2)));
|
||||
// ORCA: only_one_wall_top - route the top fill around the walls kept despite grazing the top.
|
||||
if (!one_wall_top_kept_bands.empty())
|
||||
top_infill_exp = diff_ex(top_infill_exp, one_wall_top_kept_bands);
|
||||
if (!top_fills.empty()) {
|
||||
infill_exp = union_ex(infill_exp, offset_ex(top_infill_exp, double(top_infill_peri_overlap)));
|
||||
}
|
||||
// ORCA: only_one_wall_top - what the top fill does not cover of the dropped walls goes to infill.
|
||||
if (!one_wall_top_reclaimed.empty())
|
||||
infill_exp = union_ex(infill_exp, one_wall_top_reclaimed);
|
||||
this->fill_surfaces->append(infill_exp, stInternal);
|
||||
|
||||
apply_extra_perimeters(infill_exp);
|
||||
@@ -1700,6 +1918,8 @@ void PerimeterGenerator::process_classic()
|
||||
double(-inset - infill_peri_overlap));
|
||||
if (!top_fills.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp);
|
||||
if (!one_wall_top_reclaimed.empty())
|
||||
polyWithoutOverlap = union_ex(polyWithoutOverlap, one_wall_top_reclaimed);
|
||||
this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
|
||||
}
|
||||
|
||||
@@ -2138,6 +2358,11 @@ void PerimeterGenerator::process_arachne()
|
||||
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
|
||||
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
||||
double surface_simplify_resolution = (print_config->enable_arc_fitting && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
||||
// ORCA: neither one-wall option has a surface to act on without the shell behind it, see
|
||||
// has_top_shell_layers() / has_bottom_shell_layers(). Gated here so every use below - including the
|
||||
// topmost and first layers - sees the same answer.
|
||||
const bool only_one_wall_top = this->config->only_one_wall_top && has_top_shell_layers(*this->config);
|
||||
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
|
||||
// we need to process each island separately because we might have different
|
||||
// extra perimeters for each one
|
||||
for (const Surface& surface : all_surfaces) {
|
||||
@@ -2150,12 +2375,12 @@ void PerimeterGenerator::process_arachne()
|
||||
|
||||
// Set the bottommost layer to be one wall
|
||||
const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false;
|
||||
if (is_bottom_layer && this->config->only_one_wall_first_layer)
|
||||
if (is_bottom_layer && only_one_wall_first_layer)
|
||||
loop_number = 0;
|
||||
|
||||
// Orca: set the topmost layer to be one wall according to the config
|
||||
const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false;
|
||||
if (is_topmost_layer && loop_number > 0 && config->only_one_wall_top)
|
||||
if (is_topmost_layer && loop_number > 0 && only_one_wall_top)
|
||||
loop_number = 0;
|
||||
|
||||
auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter;
|
||||
@@ -2175,10 +2400,10 @@ void PerimeterGenerator::process_arachne()
|
||||
//PS: One wall top surface for Arachne
|
||||
ExPolygons top_expolygons;
|
||||
// Calculate how many inner loops remain when TopSurfaces is selected.
|
||||
const int inner_loop_number = (config->only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
|
||||
const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
|
||||
|
||||
// Set one perimeter when TopSurfaces is selected.
|
||||
if (config->only_one_wall_top && loop_number > 0)
|
||||
if (only_one_wall_top && loop_number > 0)
|
||||
loop_number = 0;
|
||||
|
||||
Arachne::WallToolPathsParams input_params_tmp = input_params;
|
||||
@@ -2209,7 +2434,6 @@ void PerimeterGenerator::process_arachne()
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox);
|
||||
|
||||
top_expolygons = diff_ex(infill_contour, upper_slices_clipped);
|
||||
top_expolygons = fill_enclosed_top_feature_holes(top_expolygons, upper_slices_clipped, infill_contour);
|
||||
|
||||
if (!top_expolygons.empty()) {
|
||||
if (lower_slices != nullptr) {
|
||||
@@ -2230,25 +2454,33 @@ void PerimeterGenerator::process_arachne()
|
||||
// due to thin lines being generated
|
||||
top_expolygons = offset2_ex(top_expolygons, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
|
||||
|
||||
// Get the not-top ExPolygons (including bridges) from current slices and expanded real top ExPolygons (without bridges).
|
||||
const ExPolygons not_top_expolygons = diff_ex(infill_contour, top_expolygons);
|
||||
|
||||
// Get final top ExPolygons.
|
||||
// Get final top ExPolygons (bridges were excluded above, so they stay walled).
|
||||
top_expolygons = intersection_ex(top_expolygons, infill_contour);
|
||||
|
||||
const Polygons not_top_polygons = to_polygons(offset_ex(not_top_expolygons,wall_0_inset));
|
||||
Arachne::WallToolPaths inner_wall_tool_paths(not_top_polygons, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
|
||||
// ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual
|
||||
// geometry, then cut away the parts over the top surface. Re-onioning the non-top complement
|
||||
// instead - the fallback when there is no top fill - walls the top/non-top interface and rings
|
||||
// top-surface islands with inner walls that don't exist when the feature is disabled.
|
||||
const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config);
|
||||
const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour
|
||||
: diff_ex(infill_contour, top_expolygons),
|
||||
wall_0_inset));
|
||||
Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
|
||||
std::vector<Arachne::VariableWidthLines> inner_perimeters = inner_wall_tool_paths.getToolPaths();
|
||||
|
||||
// Recalculate indexes of inner perimeters before merging them.
|
||||
if (!perimeters.empty()) {
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) {
|
||||
if (inner_perimeter.empty())
|
||||
continue;
|
||||
if (clip_walls_over_top) {
|
||||
Polygons kept_over_top;
|
||||
clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top);
|
||||
// Route the top fill around the walls kept despite grazing the top.
|
||||
if (! kept_over_top.empty())
|
||||
top_expolygons = diff_ex(top_expolygons, kept_over_top);
|
||||
}
|
||||
|
||||
// Recalculate indexes of inner perimeters before merging them: they come after the single outer wall.
|
||||
if (!perimeters.empty())
|
||||
for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters)
|
||||
for (Arachne::ExtrusionLine &el : inner_perimeter)
|
||||
++el.inset_idx;
|
||||
}
|
||||
}
|
||||
|
||||
perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end());
|
||||
infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour());
|
||||
|
||||
@@ -1364,7 +1364,6 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_combination_max_layer_height"
|
||||
|| opt_key == "bottom_shell_thickness"
|
||||
|| opt_key == "top_shell_thickness"
|
||||
|| opt_key == "top_surface_expansion"
|
||||
|| opt_key == "top_surface_expansion_margin"
|
||||
|| opt_key == "top_surface_expansion_direction"
|
||||
|| opt_key == "minimum_sparse_infill_area"
|
||||
@@ -1400,7 +1399,6 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_anchor"
|
||||
|| opt_key == "infill_anchor_max"
|
||||
|| opt_key == "top_surface_line_width"
|
||||
|| opt_key == "top_surface_density"
|
||||
|| opt_key == "bottom_surface_density"
|
||||
|| opt_key == "center_of_surface_pattern"
|
||||
|| opt_key == "separated_infills"
|
||||
@@ -1434,6 +1432,24 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
is_approx(new_density->value, 0.) || is_approx(new_density->value, 100.))
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (opt_key == "top_surface_density") {
|
||||
// ORCA: 0% means no top solid fill, which switches off both the top surface expansion and the wall
|
||||
// removal over top surfaces. Only crossing zero matters; posPerimeters cascades to posPrepareInfill.
|
||||
const auto *old_density = old_config.option<ConfigOptionPercent>(opt_key);
|
||||
const auto *new_density = new_config.option<ConfigOptionPercent>(opt_key);
|
||||
assert(old_density && new_density);
|
||||
if (is_approx(old_density->value, 0.) || is_approx(new_density->value, 0.))
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "top_surface_expansion") {
|
||||
// ORCA: without the expansion the top fill never reaches the space freed by only_one_wall_top, so the
|
||||
// walls over top surfaces are kept. Only crossing zero matters; posPerimeters cascades to posPrepareInfill.
|
||||
const auto *old_expansion = old_config.option<ConfigOptionFloat>(opt_key);
|
||||
const auto *new_expansion = new_config.option<ConfigOptionFloat>(opt_key);
|
||||
assert(old_expansion && new_expansion);
|
||||
if (old_expansion->value <= 0. || new_expansion->value <= 0.)
|
||||
steps.emplace_back(posPerimeters);
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (opt_key == "internal_solid_infill_line_width") {
|
||||
// This value is used for calculating perimeter - infill overlap, thus perimeters need to be recalculated.
|
||||
steps.emplace_back(posPerimeters);
|
||||
@@ -1760,51 +1776,50 @@ void PrintObject::detect_surfaces_type()
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: Expand the top surfaces outward by top_surface_expansion in every direction. This
|
||||
// enlarges the top solid infill and, in particular, grows it over the covered material left
|
||||
// by features rising from the middle of a top surface (filling holes and joining tops so the
|
||||
// features rest on it). The expansion stays inside the section it belongs to: each connected
|
||||
// solid island has its own outer wall, so the top is grown within each island separately and
|
||||
// clipped to it - growing one island's top across the gap into another island (which may have
|
||||
// no top surface, leaving a partially filled layer) is never allowed. The top infill sits
|
||||
// inside the perimeters, so the margin is measured from the walls: the island is inset by the
|
||||
// band the walls consume (outer wall + inner walls) plus the configured margin, making that
|
||||
// value the real clearance between the expanded top and the walls (avoiding a hull line). The
|
||||
// original top is unioned back in, so where it already sits within that band it is kept as-is.
|
||||
// Never claims a bottom surface.
|
||||
const double top_expansion = layerm->region().config().top_surface_expansion.value;
|
||||
if (top_expansion > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const auto jt = Clipper2Lib::JoinType::Miter;
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
const int wall_loops = layerm->region().config().wall_loops.value;
|
||||
// ORCA: Grow the top surfaces by top_surface_expansion, so the top solid infill also covers the
|
||||
// material left by features rising from the middle of a top surface (filling the holes and
|
||||
// joining the tops, so the features rest on solid infill). Each connected island is grown and
|
||||
// clipped separately: growing one island's top across a gap into another - which may have no top
|
||||
// surface at all, leaving a partially filled layer - is never allowed. The original top is
|
||||
// unioned back in and bottom surfaces are never claimed, so this can only add area.
|
||||
const PrintRegionConfig ®ion_config = layerm->region().config();
|
||||
const double top_expansion = region_config.top_surface_expansion.value;
|
||||
// Nothing to expand without a top fill: a 0% top surface density leaves the top layer with
|
||||
// walls only, and zero top shell layers retypes it as internal in prepare_fill_surfaces().
|
||||
if (top_expansion > 0. && region_config.top_shell_layers.value > 0 &&
|
||||
region_config.top_surface_density.value > 0. && ! top.empty()) {
|
||||
const double d = scale_(top_expansion);
|
||||
const ExPolygons T = union_ex(to_expolygons(top));
|
||||
// Walls are laid out on spacing, not width; and only_one_wall_top leaves a single wall over
|
||||
// a top surface, which is exactly the situation handled here.
|
||||
const int wall_loops = region_config.only_one_wall_top.value ? std::min(region_config.wall_loops.value, 1)
|
||||
: region_config.wall_loops.value;
|
||||
const double wall_band = wall_loops <= 0 ? 0. :
|
||||
double(layerm->flow(frExternalPerimeter).scaled_width()) +
|
||||
double(layerm->flow(frPerimeter).scaled_width()) * double(wall_loops - 1);
|
||||
const double margin = scale_(layerm->region().config().top_surface_expansion_margin.value);
|
||||
double(layerm->flow(frPerimeter).scaled_spacing()) * double(wall_loops - 1);
|
||||
const double margin = scale_(region_config.top_surface_expansion_margin.value);
|
||||
// minimum real top to act on: ignore anything thinner than ~2 top-infill lines
|
||||
const float min_top = float(layerm->flow(frTopSolidInfill).scaled_width());
|
||||
const auto direction = layerm->region().config().top_surface_expansion_direction.value;
|
||||
const auto direction = region_config.top_surface_expansion_direction.value;
|
||||
|
||||
ExPolygons grown;
|
||||
for (const ExPolygon &island : union_ex(layerm_slices_surfaces)) {
|
||||
// The top infill only exists inside the perimeters, so seed and measure from the infill
|
||||
// region (the island minus the wall band), not the raw slice. A section whose only
|
||||
// exposed top lies in the wall band - i.e. a layer where the top is just the walls
|
||||
// themselves - has no infill here and is skipped, instead of being flooded inward by
|
||||
// the expansion. Thin slivers inside the infill region are dropped by the opening too.
|
||||
// region (the island minus the wall band), not the raw slice: a section whose exposed top
|
||||
// is just the walls themselves is then skipped instead of being flooded inward. Clip the
|
||||
// layer's tops to the island first, to keep the boolean ops proportional to the island.
|
||||
const ExPolygons infill_region = wall_band > 0. ? offset_ex(island, -float(wall_band)) : ExPolygons{ island };
|
||||
const ExPolygons island_top = intersection_ex(T, infill_region);
|
||||
const ExPolygons island_top = intersection_ex(
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(T, get_extents(island).inflated(SCALED_EPSILON)),
|
||||
infill_region);
|
||||
if (opening_ex(island_top, min_top).empty())
|
||||
continue; // no real top infill in this section - never expand into it
|
||||
|
||||
// grow by d, then keep only the part allowed by the configured direction: inward fills
|
||||
// the holes/gaps left by features (clip the growth back to the top's own filled outline,
|
||||
// which leaves the outer edge fixed), outward grows the outer edge toward the walls (drop
|
||||
// the growth that fell into the original holes), and inward+outward keeps both.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, jt);
|
||||
// Grow, then keep only what the configured direction allows, using the top's own filled
|
||||
// outline (same outer edge, holes closed) to tell the two apart.
|
||||
ExPolygons expanded = offset_ex_2(island_top, d, Clipper2Lib::JoinType::Miter);
|
||||
if (direction != TopSurfaceExpansionDirection::InwardAndOutward) {
|
||||
ExPolygons outline; // the top with its holes filled (same outer edge)
|
||||
ExPolygons outline;
|
||||
outline.reserve(island_top.size());
|
||||
for (const ExPolygon &ex : island_top)
|
||||
outline.emplace_back(ex.contour);
|
||||
|
||||
@@ -91,12 +91,15 @@ public:
|
||||
//
|
||||
void toggle_top_layer_only_view_range();
|
||||
//
|
||||
// Dim previous layers (ORCA, ported from preFlight)
|
||||
// Whether the layers below the current top layer are rendered darkened while
|
||||
// scrubbing below the full print, so only the current layer is shown at full brightness.
|
||||
// Dim previous layers (ORCA)
|
||||
// Whether the layers the layer slider is not scrubbed to are rendered darkened while
|
||||
// showing less than the full print, so only the inspected layer(s) are at full brightness.
|
||||
// How bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black.
|
||||
//
|
||||
bool is_dim_previous_layers() const;
|
||||
void set_dim_previous_layers(bool value);
|
||||
float get_dim_previous_layers_brightness() const;
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
//
|
||||
// Returns true if the given option is visible.
|
||||
//
|
||||
|
||||
@@ -19,9 +19,11 @@ struct Settings
|
||||
EViewType view_type{ EViewType::FeatureType };
|
||||
ETimeMode time_mode{ ETimeMode::Normal };
|
||||
bool top_layer_only_view_range{ false };
|
||||
// ORCA: when enabled, all layers below the current top layer are rendered
|
||||
// darkened (keeping their color) while scrubbing below the full print (ported from preFlight)
|
||||
// ORCA: when enabled, every layer the layer slider is not scrubbed to is rendered
|
||||
// darkened (keeping its color) while showing less than the full print
|
||||
bool dim_previous_layers{ false };
|
||||
// ORCA: how bright those darkened layers are rendered, 1.0 = unchanged, 0.0 = black
|
||||
float dim_previous_layers_brightness{ 0.4f };
|
||||
bool spiral_vase_mode{ false };
|
||||
//
|
||||
// Required update flags
|
||||
|
||||
@@ -82,6 +82,16 @@ void Viewer::set_dim_previous_layers(bool value)
|
||||
m_impl->set_dim_previous_layers(value);
|
||||
}
|
||||
|
||||
float Viewer::get_dim_previous_layers_brightness() const
|
||||
{
|
||||
return m_impl->get_dim_previous_layers_brightness();
|
||||
}
|
||||
|
||||
void Viewer::set_dim_previous_layers_brightness(float value)
|
||||
{
|
||||
m_impl->set_dim_previous_layers_brightness(value);
|
||||
}
|
||||
|
||||
bool Viewer::is_option_visible(EOptionType type) const
|
||||
{
|
||||
return m_impl->is_option_visible(type);
|
||||
|
||||
@@ -1223,16 +1223,12 @@ static float encode_color(const Color& color) {
|
||||
return static_cast<float>(i_color);
|
||||
}
|
||||
|
||||
// ORCA: how much the layers below the current top layer are darkened when
|
||||
// Settings::dim_previous_layers is enabled (ported from preFlight). 0.0 = no change, 1.0 = black.
|
||||
static constexpr float PREVIOUS_LAYER_DARKEN_FACTOR = 0.60f;
|
||||
|
||||
// ORCA: returns the encoded color scaled towards black by 'factor', preserving its hue
|
||||
static float encode_color_darkened(const Color& color, float factor) {
|
||||
const float keep = 1.0f - factor;
|
||||
const int r = static_cast<int>(color[0] * keep);
|
||||
const int g = static_cast<int>(color[1] * keep);
|
||||
const int b = static_cast<int>(color[2] * keep);
|
||||
// ORCA: returns the encoded color scaled towards black by 'brightness', preserving its hue.
|
||||
// 1.0 = no change, 0.0 = black.
|
||||
static float encode_color_dimmed(const Color& color, float brightness) {
|
||||
const int r = static_cast<int>(color[0] * brightness);
|
||||
const int g = static_cast<int>(color[1] * brightness);
|
||||
const int b = static_cast<int>(color[2] * brightness);
|
||||
const int i_color = r << 16 | g << 8 | b;
|
||||
return static_cast<float>(i_color);
|
||||
}
|
||||
@@ -1248,15 +1244,20 @@ void ViewerImpl::update_colors_texture()
|
||||
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
|
||||
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
|
||||
|
||||
// ORCA: when dim_previous_layers is enabled, darken every layer below the current top layer
|
||||
// (keeping its color) whenever we are not rendering the whole print, so that only the layer
|
||||
// being scrubbed to is shown at full brightness (ported from preFlight). This shares
|
||||
// top_layer_id with the greying path, so it only applies while in top-layer-only mode - that
|
||||
// way the moves slider still animates normally across all layers when that mode is disabled.
|
||||
const bool dim_previous_layers = m_settings.dim_previous_layers && !m_layers.empty();
|
||||
const bool full_render = (m_layers.get_view_range()[0] == 0) &&
|
||||
(m_layers.get_view_range()[1] >= static_cast<uint32_t>(m_layers.count()) - 1) &&
|
||||
(m_view_range.get_visible()[1] == m_view_range.get_full()[1]);
|
||||
// ORCA: when dim_previous_layers is enabled, darken every layer (keeping its color) except the
|
||||
// one(s) the layer slider is being scrubbed to, so that only those are shown at full brightness.
|
||||
// A slider thumb marks a layer as inspected only once it is moved away from
|
||||
// its end of the print: the upper one while it is below the last layer (or while the moves
|
||||
// slider is not at the end of the layer), the lower one while it is above the first layer, so
|
||||
// trimming the print from the bottom lights up the lowest visible layer and using the slider as
|
||||
// a range lights up both ends. When neither thumb is moved the whole print is rendered normally.
|
||||
// Gated on top-layer-only mode, which the greying path below also keys off of, so that the moves
|
||||
// slider still animates normally across all layers when that mode is disabled.
|
||||
const Interval& layers_range = m_layers.get_view_range();
|
||||
const bool inspecting_top_layer = layers_range[1] + 1 < m_layers.count() || color_top_layer_only;
|
||||
const bool inspecting_bottom_layer = layers_range[0] > 0;
|
||||
const bool dim_previous_layers = m_settings.dim_previous_layers && m_settings.top_layer_only_view_range &&
|
||||
!m_layers.empty() && (inspecting_top_layer || inspecting_bottom_layer);
|
||||
|
||||
// Based on current settings and slider position, we might want to render some
|
||||
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
|
||||
@@ -1265,9 +1266,13 @@ void ViewerImpl::update_colors_texture()
|
||||
for (size_t i=0; i<m_vertices.size(); ++i) {
|
||||
const PathVertex& v = m_vertices[i];
|
||||
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
|
||||
if (dim_previous_layers && !full_render && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
colors[i] = encode_color_darkened(get_vertex_color(v), PREVIOUS_LAYER_DARKEN_FACTOR);
|
||||
else if (color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
// ORCA: layers kept at full brightness by the dimming above are excluded from the greying below too
|
||||
const bool inspected_layer = dim_previous_layers &&
|
||||
((inspecting_top_layer && v.layer_id == layers_range[1]) ||
|
||||
(inspecting_bottom_layer && v.layer_id == layers_range[0]));
|
||||
if (dim_previous_layers && !inspected_layer && !keep_spiral_seam)
|
||||
colors[i] = encode_color_dimmed(get_vertex_color(v), m_settings.dim_previous_layers_brightness);
|
||||
else if (!inspected_layer && color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
|
||||
colors[i] = encode_color(DUMMY_COLOR);
|
||||
else
|
||||
colors[i] = m_vertices_colors[i];
|
||||
@@ -1379,7 +1384,7 @@ void ViewerImpl::toggle_top_layer_only_view_range()
|
||||
update_colors_texture();
|
||||
}
|
||||
|
||||
// ORCA: enable/disable darkening of the layers below the current top layer (ported from preFlight)
|
||||
// ORCA: enable/disable darkening of the layers the layer slider is not scrubbed to
|
||||
void ViewerImpl::set_dim_previous_layers(bool value)
|
||||
{
|
||||
if (m_settings.dim_previous_layers == value)
|
||||
@@ -1390,6 +1395,16 @@ void ViewerImpl::set_dim_previous_layers(bool value)
|
||||
m_settings.update_colors = true;
|
||||
}
|
||||
|
||||
// ORCA: set how bright the darkened layers are rendered, 1.0 = unchanged, 0.0 = black
|
||||
void ViewerImpl::set_dim_previous_layers_brightness(float value)
|
||||
{
|
||||
value = std::clamp(value, 0.0f, 1.0f);
|
||||
if (m_settings.dim_previous_layers_brightness == value)
|
||||
return;
|
||||
m_settings.dim_previous_layers_brightness = value;
|
||||
m_settings.update_colors = true;
|
||||
}
|
||||
|
||||
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
|
||||
{
|
||||
std::vector<ETimeMode> ret;
|
||||
|
||||
@@ -85,9 +85,14 @@ public:
|
||||
bool is_top_layer_only_view_range() const { return m_settings.top_layer_only_view_range; }
|
||||
void toggle_top_layer_only_view_range();
|
||||
|
||||
// ORCA: darken layers below the current top layer while scrubbing (ported from preFlight)
|
||||
// ORCA: darken every layer the layer slider is not scrubbed to, so that only the inspected
|
||||
// one(s) - the top thumb's layer, the bottom thumb's layer, or both - stay at full
|
||||
// brightness. dim_previous_layers_brightness sets how dark the rest go, 1.0 = unchanged,
|
||||
// 0.0 = black
|
||||
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
|
||||
void set_dim_previous_layers(bool value);
|
||||
float get_dim_previous_layers_brightness() const { return m_settings.dim_previous_layers_brightness; }
|
||||
void set_dim_previous_layers_brightness(float value);
|
||||
|
||||
bool is_spiral_vase_mode() const { return m_settings.spiral_vase_mode; }
|
||||
|
||||
|
||||
@@ -703,12 +703,13 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("spiral_mode_max_xy_smoothing", has_spiral_vase && config->opt_bool("spiral_mode_smooth"));
|
||||
toggle_line("spiral_starting_flow_ratio", has_spiral_vase);
|
||||
toggle_line("spiral_finishing_flow_ratio", has_spiral_vase);
|
||||
bool has_top_shell = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
|
||||
bool has_top_shell_layers = config->opt_int("top_shell_layers") > 0 || (has_spiral_vase && config->opt_int("bottom_shell_layers") > 1);
|
||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_shell || has_bottom_shell;
|
||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
||||
toggle_field("top_surface_pattern", has_top_shell);
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell);
|
||||
toggle_field("top_surface_density", has_top_shell_layers);
|
||||
toggle_field("bottom_surface_density", has_bottom_shell);
|
||||
toggle_field("top_layer_direction", has_top_shell);
|
||||
toggle_field("bottom_layer_direction", has_bottom_shell);
|
||||
@@ -751,7 +752,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
for (auto el : { "sparse_infill_speed", "bridge_speed", "internal_bridge_speed"})
|
||||
toggle_field(el, have_infill || has_solid_infill, variant_index);
|
||||
|
||||
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell);
|
||||
toggle_field("top_shell_thickness", ! has_spiral_vase && has_top_shell_layers);
|
||||
toggle_field("bottom_shell_thickness", ! has_spiral_vase && has_bottom_shell);
|
||||
|
||||
// Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476).
|
||||
@@ -1008,7 +1009,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("make_overhang_printable_angle", have_make_overhang_printable);
|
||||
toggle_line("make_overhang_printable_hole_size", have_make_overhang_printable);
|
||||
|
||||
toggle_line("min_width_top_surface", config->opt_bool("only_one_wall_top") || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value
|
||||
// Orca: the one-wall options act on top/bottom surfaces, which exist only with a shell. An unfilled surface
|
||||
// (0% surface density) is still a surface, so these are gated on the layer counts alone.
|
||||
toggle_line("only_one_wall_first_layer", has_bottom_shell);
|
||||
toggle_line("only_one_wall_top", has_top_shell_layers);
|
||||
toggle_line("min_width_top_surface", (has_top_shell_layers && config->opt_bool("only_one_wall_top")) || ((config->opt_float("min_length_factor") > 0.5f) && have_arachne)); // 0.5 is default value
|
||||
|
||||
for (auto el : { "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", "hole_to_polyhole_max_edges" })
|
||||
toggle_line(el, config->opt_bool("hole_to_polyhole"));
|
||||
|
||||
@@ -1334,27 +1334,7 @@ void SpinCtrl::BUILD() {
|
||||
if (!parsed || value < INT_MIN || value > INT_MAX)
|
||||
tmp_value = UNDEF_VALUE;
|
||||
else {
|
||||
tmp_value = std::min(std::max((int)value, temp->GetMin()), temp->GetMax());
|
||||
#ifdef __WXOSX__
|
||||
#ifdef UNDEFINED__WXOSX__ // BBS
|
||||
// Forcibly set the input value for SpinControl, since the value
|
||||
// inserted from the keyboard or clipboard is not updated under OSX
|
||||
SpinInput* spin = static_cast<SpinInput*>(window);
|
||||
spin->SetValue(tmp_value);
|
||||
// But in SetValue() is executed m_text_ctrl->SelectAll(), so
|
||||
// discard this selection and set insertion point to the end of string
|
||||
// temp->GetText()->SetInsertionPointEnd();
|
||||
#endif
|
||||
#else
|
||||
// update value for the control only if it was changed in respect to the Min/max values
|
||||
if (tmp_value != (int)value) {
|
||||
temp->SetValue(tmp_value);
|
||||
// But after SetValue() cursor ison the first position
|
||||
// so put it to the end of string
|
||||
// int pos = std::to_string(tmp_value).length();
|
||||
// temp->SetSelection(pos, pos);
|
||||
}
|
||||
#endif
|
||||
tmp_value = (int)value;
|
||||
}
|
||||
}), temp->GetTextCtrl()->GetId());
|
||||
|
||||
@@ -1375,6 +1355,10 @@ void SpinCtrl::propagate_value()
|
||||
on_kill_focus();
|
||||
} else {
|
||||
auto ctrl = dynamic_cast<SpinInput *>(window);
|
||||
tmp_value = std::min(std::max(tmp_value, ctrl->GetMin()), ctrl->GetMax());
|
||||
if (ctrl->GetValue() != tmp_value)
|
||||
ctrl->SetValue(tmp_value); // Clamp now when the user is done typing (kill focus / Enter / spin arrows)
|
||||
|
||||
if (m_value.empty()
|
||||
? !ctrl->GetTextCtrl()->GetLabel().IsEmpty()
|
||||
: ctrl->GetValue() != boost::any_cast<int>(m_value))
|
||||
|
||||
@@ -1134,8 +1134,9 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
if (current_top_layer_only != required_top_layer_only)
|
||||
m_viewer.toggle_top_layer_only_view_range();
|
||||
|
||||
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
|
||||
// ORCA: darken the layers the preview layer slider is not scrubbed to
|
||||
m_viewer.set_dim_previous_layers(get_app_config()->get_bool("preview_dim_previous_layers"));
|
||||
m_viewer.set_dim_previous_layers_brightness(0.01f * std::stoi(get_app_config()->get("preview_dim_previous_layers_brightness")));
|
||||
|
||||
// avoid processing if called with the same gcode_result
|
||||
if (m_last_result_id == gcode_result.id && wxGetApp().is_editor()) {
|
||||
|
||||
@@ -333,9 +333,12 @@ public:
|
||||
|
||||
libvgcode::EViewType get_view_type() const { return m_viewer.get_view_type(); }
|
||||
|
||||
// ORCA: darken layers below the current top layer while scrubbing the preview (ported from preFlight)
|
||||
// ORCA: darken the layers not scrubbed to while using the preview layer slider
|
||||
void set_dim_previous_layers(bool value) { m_viewer.set_dim_previous_layers(value); }
|
||||
bool is_dim_previous_layers() const { return m_viewer.is_dim_previous_layers(); }
|
||||
// ORCA: brightness of those darkened layers, 1.0 = unchanged, 0.0 = black
|
||||
void set_dim_previous_layers_brightness(float value) { m_viewer.set_dim_previous_layers_brightness(value); }
|
||||
float get_dim_previous_layers_brightness() const { return m_viewer.get_dim_previous_layers_brightness(); }
|
||||
|
||||
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ void ArrangeJob::process(Ctl &ctl)
|
||||
params.stopcondition = [&ctl]() { return ctl.was_canceled(); };
|
||||
|
||||
params.progressind = [this, &ctl](unsigned num_finished, std::string str = "") {
|
||||
ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging") + str);
|
||||
ctl.update_status(num_finished * 100 / status_range(), _u8L("Arranging ") + str);
|
||||
};
|
||||
|
||||
{
|
||||
|
||||
@@ -700,6 +700,12 @@ wxBoxSizer *PreferencesDialog::create_item_spinctrl(wxString title, wxString tit
|
||||
auto input = new SpinInput(m_parent, wxEmptyString, side_label, wxDefaultPosition, DESIGN_INPUT_SIZE, wxSP_ARROW_KEYS, min, max, stoi(app_config->get(param)));
|
||||
input->SetToolTip(tip);
|
||||
|
||||
// ORCA: this one is only meaningful while the dimming it controls is enabled
|
||||
if (param == "preview_dim_previous_layers_brightness") {
|
||||
m_dim_previous_layers_brightness_input = input;
|
||||
input->Enable(app_config->get_bool("preview_dim_previous_layers"));
|
||||
}
|
||||
|
||||
m_sizer->Add(input, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
if(!title2.empty()){
|
||||
@@ -1050,8 +1056,10 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview (ported from preFlight)
|
||||
// ORCA: apply the preview dimming change immediately to the currently loaded preview
|
||||
else if (param == "preview_dim_previous_layers") {
|
||||
if (m_dim_previous_layers_brightness_input)
|
||||
m_dim_previous_layers_brightness_input->Enable(app_config->get_bool(param));
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_dim_previous_layers(app_config->get_bool(param));
|
||||
@@ -1914,6 +1922,28 @@ void PreferencesDialog::create_items()
|
||||
);
|
||||
g_sizer->Add(item_dim_previous_layers);
|
||||
|
||||
auto item_dim_previous_layers_brightness = create_item_spinctrl(
|
||||
_L("Dimmed layer brightness"),
|
||||
"",
|
||||
_L("%"),
|
||||
_L("How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."),
|
||||
"preview_dim_previous_layers_brightness",
|
||||
0,
|
||||
99,
|
||||
// ORCA: apply the new brightness immediately to the currently loaded preview
|
||||
[](int value) {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
if (GLCanvas3D* canvas = plater->get_preview_canvas3D()) {
|
||||
canvas->get_gcode_viewer().set_dim_previous_layers_brightness(0.01f * value);
|
||||
canvas->set_as_dirty();
|
||||
canvas->request_extra_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
g_sizer->Add(item_dim_previous_layers_brightness);
|
||||
|
||||
g_sizer->AddSpacer(FromDIP(10));
|
||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "Widgets/ComboBox.hpp"
|
||||
#include "Widgets/CheckBox.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/SpinInput.hpp"
|
||||
#include "Widgets/TabCtrl.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
@@ -71,6 +72,7 @@ public:
|
||||
::CheckBox * m_sync_user_preset_checkbox = {nullptr};
|
||||
::CheckBox * m_bambu_cloud_checkbox = {nullptr};
|
||||
::TextInput *m_backup_interval_textinput = {nullptr};
|
||||
::SpinInput *m_dim_previous_layers_brightness_input = {nullptr};
|
||||
::ComboBox * m_network_version_combo = {nullptr};
|
||||
std::vector<NetworkLibraryVersionInfo> m_available_versions;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/PresetBundle.hpp>
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
#include <slic3r/GUI/GUI_App.hpp>
|
||||
#include <slic3r/GUI/Plater.hpp>
|
||||
|
||||
@@ -55,6 +56,15 @@ void host_bindings::register_app(py::module_& host)
|
||||
return current_plater()->model();
|
||||
}, py::return_value_policy::reference);
|
||||
host.def("preset_bundle", ¤t_preset_bundle, py::return_value_policy::reference);
|
||||
// UI language of the running app ("en_US", "ru_RU", ...), so plugins can
|
||||
// localize their own dialogs. The app config file that stores this value
|
||||
// is deny-listed by the audit hook (it sits next to cloud secrets), so a
|
||||
// read-only accessor is the supported way to get just the language.
|
||||
host.def("app_language", []() -> std::string {
|
||||
if (wxTheApp == nullptr)
|
||||
throw std::runtime_error("OrcaSlicer application is not initialized");
|
||||
return GUI::into_u8(GUI::wxGetApp().current_language_code_safe());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
Reference in New Issue
Block a user