mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 11:51:24 +00:00
Merge branch 'main' into feat/plugin-pages
This commit is contained in:
+1
-1
@@ -7054,7 +7054,7 @@ int CLI::run(int argc, char **argv)
|
||||
gcode_viewer.render_calibration_thumbnail(*calibration_data, cali_thumbnail_width, cali_thumbnail_height,
|
||||
calibration_params, partplate_list, opengl_mgr);
|
||||
//generate_calibration_thumbnail(*calibration_data, thumbnail_width, thumbnail_height, calibration_params);
|
||||
//*plate_bboxes[index] = p->generate_first_layer_bbox();
|
||||
// *plate_bboxes[index] = p->generate_first_layer_bbox();
|
||||
calibration_thumbnails.push_back(calibration_data);*/
|
||||
|
||||
PlateBBoxData* plate_bbox = new PlateBBoxData();
|
||||
|
||||
@@ -54,12 +54,12 @@ public:
|
||||
int & i,
|
||||
Eigen::Matrix<double, 1, 3> &closest)
|
||||
{
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist =
|
||||
size_t idx_unsigned { 0 };
|
||||
Vec3d closest_vec3d { Vec3d::Zero() };
|
||||
const double dist {
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
its.vertices, its.indices, m_tree, point, idx_unsigned,
|
||||
closest_vec3d);
|
||||
closest_vec3d) };
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
@@ -311,10 +311,9 @@ AABBMesh::hit_result IndexedMesh::filter_hits(
|
||||
|
||||
|
||||
double AABBMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double sqdst = 0;
|
||||
Eigen::Matrix<double, 1, 3> pp = p;
|
||||
Eigen::Matrix<double, 1, 3> cc;
|
||||
sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc);
|
||||
const Eigen::Matrix<double, 1, 3> pp { p };
|
||||
Eigen::Matrix<double, 1, 3> cc { Vec3d::Zero() };
|
||||
const double sqdst { m_aabb->squared_distance(*m_tm, pp, i, cc) };
|
||||
c = cc;
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ namespace AABBTreeLines {
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType& squared_distance) const
|
||||
{
|
||||
Vec<LineType::Dim, typename LineType::Scalar> nearest_point;
|
||||
Vec<LineType::Dim, typename LineType::Scalar> cast_origin = origin.template cast<typename LineType::Scalar>();
|
||||
const LineType& line = lines[primitive_index];
|
||||
squared_distance = line_alg::distance_to_squared(line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
|
||||
squared_distance = line_alg::distance_to_squared(line, cast_origin, &nearest_point);
|
||||
return nearest_point.template cast<ScalarType>();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -611,6 +626,12 @@ void AppConfig::set_defaults()
|
||||
set_bool("window_buttons_on_left", false);
|
||||
#endif
|
||||
|
||||
if (get("use_printer_agents").empty())
|
||||
{
|
||||
// false = legacy behavior using print hosts
|
||||
set_bool("use_printer_agents", false);
|
||||
}
|
||||
|
||||
// Remove legacy window positions/sizes
|
||||
erase("app", "main_frame_maximized");
|
||||
erase("app", "main_frame_pos");
|
||||
@@ -862,7 +883,7 @@ std::string AppConfig::load()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(std::exception err) {
|
||||
} catch(const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(info) << format("parse app config \"%1%\", error: %2%", AppConfig::loading_path(), err.what());
|
||||
|
||||
return err.what();
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
BoundingBoxBase(It from, It to) : BoundingBoxBase()
|
||||
{ construct(*this, from, to); }
|
||||
|
||||
BoundingBoxBase(const PointsType &points)
|
||||
|
||||
+6
-10
@@ -349,7 +349,7 @@ static ExPolygons make_brim_ears_auto(const ExPolygons& obj_expoly, coord_t size
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
|
||||
static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWidth, float brim_offset, Flow &flow, bool is_outer_brim)
|
||||
static ExPolygons make_brim_ears(const PrintObject* object)
|
||||
{
|
||||
ExPolygons mouse_ears_ex;
|
||||
BrimPoints brim_ear_points = object->model_object()->brim_points;
|
||||
@@ -373,12 +373,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
Vec3f world_pos = pt.transform(trsf.get_matrix());
|
||||
if ( world_pos.z() > 0) continue;
|
||||
Polygon point_round;
|
||||
float brim_width = floor(scale_(pt.head_front_radius) / flowWidth / 2) * flowWidth * 2;
|
||||
if (is_outer_brim) {
|
||||
double flowWidthScale = flowWidth / SCALING_FACTOR;
|
||||
brim_width = floor(brim_width / flowWidthScale / 2) * flowWidthScale * 2;
|
||||
}
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
const coord_t size_ear = scale_(pt.head_front_radius);
|
||||
for (size_t i = 0; i < POLY_SIDE_COUNT; i++) {
|
||||
double angle = (2.0 * PI * i) / POLY_SIDE_COUNT;
|
||||
point_round.points.emplace_back(size_ear * cos(angle), size_ear * sin(angle));
|
||||
@@ -452,7 +447,8 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
bool has_brim_auto = object->config().brim_type == btAutoBrim;
|
||||
const bool use_auto_brim_ears = object->config().brim_type == btEar;
|
||||
const bool use_brim_ears = object->config().brim_type == btPainted;
|
||||
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_auto_brim_ears || use_brim_ears;
|
||||
const bool use_inner_brim_ears = (use_auto_brim_ears || use_brim_ears) && !object->config().brim_ears_outer_only.value;
|
||||
const bool has_inner_brim = brim_type == btInnerOnly || brim_type == btOuterAndInner || use_inner_brim_ears;
|
||||
const bool has_outer_brim = brim_type == btOuterOnly || brim_type == btOuterAndInner || brim_type == btAutoBrim || use_auto_brim_ears || use_brim_ears;
|
||||
coord_t ear_detection_length = scale_(object->config().brim_ears_detection_length.value);
|
||||
coordf_t brim_ears_max_angle = object->config().brim_ears_max_angle.value;
|
||||
@@ -531,7 +527,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
auto innerExpoly = offset_ex(ex_poly.contour, brim_offset, jtRound, SCALED_RESOLUTION);
|
||||
ExPolygons outerExpoly;
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, true);
|
||||
outerExpoly = make_brim_ears(object);
|
||||
//outerExpoly = offset_ex(outerExpoly, brim_width_mod, jtRound, SCALED_RESOLUTION);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width_mod - brim_offset - flow.scaled_spacing());
|
||||
@@ -545,7 +541,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
||||
ExPolygons outerExpoly;
|
||||
auto innerExpoly = offset_ex(ex_poly_holes_reversed, -brim_width - brim_offset);
|
||||
if (use_brim_ears) {
|
||||
outerExpoly = make_brim_ears(object, flowWidth, brim_offset, flow, false);
|
||||
outerExpoly = make_brim_ears(object);
|
||||
} else if (use_auto_brim_ears) {
|
||||
coord_t size_ear = (brim_width - brim_offset - flow.scaled_spacing());
|
||||
outerExpoly = make_brim_ears_auto(offset_ex(ex_poly_holes_reversed, -brim_offset), size_ear, ear_detection_length, brim_ears_max_angle, false);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define slic3r_Config_hpp_
|
||||
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <climits>
|
||||
#include <cfloat>
|
||||
@@ -780,10 +781,14 @@ public:
|
||||
this->values[i] = rhs_vec->values[i];
|
||||
modified = true;
|
||||
} else {
|
||||
if ((i < default_index.size()) && (default_index[i] < default_value.size()))
|
||||
// Orca: a negative slot (failed variant lookup) must not silently collapse the
|
||||
// whole array to the first slot's value — the int-vs-size_t comparison used to
|
||||
// promote -1 past the bounds check. Keep the slot's own value (get_at-style
|
||||
// clamp) when no valid index is available.
|
||||
if ((i < default_index.size()) && (default_index[i] >= 0) && (size_t(default_index[i]) < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i]];
|
||||
else
|
||||
this->values[i] = default_value[0];
|
||||
this->values[i] = default_value[std::min(i, default_value.size() - 1)];
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
@@ -2106,6 +2111,11 @@ public:
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = rhs->getInt();
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() can emit names.
|
||||
if (this->keys_map == nullptr)
|
||||
if (auto rhs_generic = dynamic_cast<const ConfigOptionEnumGeneric *>(rhs))
|
||||
this->keys_map = rhs_generic->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
@@ -2162,7 +2172,12 @@ public:
|
||||
if (rhs->type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
||||
this->values = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs)->values;
|
||||
auto rhs_enums = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs);
|
||||
this->values = rhs_enums->values;
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() emits names instead of empty tokens.
|
||||
if (this->keys_map == nullptr)
|
||||
this->keys_map = rhs_enums->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
@@ -2258,6 +2273,8 @@ public:
|
||||
plugin_picker,
|
||||
// Raw JSON string value, edited through a dialog behind a button rather than in the row.
|
||||
plugin_config,
|
||||
// PrinterAgentChoice
|
||||
printer_agent_select,
|
||||
};
|
||||
|
||||
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
|
||||
|
||||
@@ -220,12 +220,12 @@ double Extruder::retract_restart_extra() const
|
||||
|
||||
double Extruder::retract_length_toolchange() const
|
||||
{
|
||||
return m_config->retract_length_toolchange.get_at(extruder_id());
|
||||
return m_config->retract_length_toolchange.get_at(m_config_index);
|
||||
}
|
||||
|
||||
double Extruder::retract_restart_extra_toolchange() const
|
||||
{
|
||||
return m_config->retract_restart_extra_toolchange.get_at(extruder_id());
|
||||
return m_config->retract_restart_extra_toolchange.get_at(m_config_index);
|
||||
}
|
||||
|
||||
double Extruder::travel_slope() const
|
||||
|
||||
@@ -278,6 +278,9 @@ struct SurfaceFillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" wave.
|
||||
bool gyroid_optimized = false;
|
||||
|
||||
// Orca: corner smoothing factor in the range [0, 1].
|
||||
double smooth_factor { 0. };
|
||||
|
||||
CenterOfSurfacePattern center_of_surface_pattern{CenterOfSurfacePattern::Each_Surface};
|
||||
bool separated_infills{false};
|
||||
|
||||
@@ -316,6 +319,7 @@ struct SurfaceFillParams
|
||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
|
||||
RETURN_COMPARE_NON_EQUAL(smooth_factor);
|
||||
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
|
||||
RETURN_COMPARE_NON_EQUAL(separated_infills);
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, fill_order);
|
||||
@@ -348,6 +352,7 @@ struct SurfaceFillParams
|
||||
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
|
||||
this->separated_infills == rhs.separated_infills &&
|
||||
this->gyroid_optimized == rhs.gyroid_optimized &&
|
||||
this->smooth_factor == rhs.smooth_factor &&
|
||||
this->fill_order == rhs.fill_order;
|
||||
}
|
||||
};
|
||||
@@ -964,6 +969,11 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
|
||||
region_config.sparse_infill_rotate_template.value);
|
||||
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
|
||||
|
||||
// Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
|
||||
// FillHilbertCurve::generate clamps and validates the value itself.
|
||||
if (params.pattern == ipHilbertCurve)
|
||||
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
|
||||
} else {
|
||||
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
|
||||
const bool bottom_layer_direction_set = surface.is_bottom() && region_config.bottom_layer_direction.value >= 0.;
|
||||
@@ -1328,6 +1338,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
|
||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||
|
||||
// BBS
|
||||
params.flow = surface_fill.params.flow;
|
||||
@@ -1569,6 +1580,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
|
||||
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
|
||||
params.multiline = surface_fill.params.multiline;
|
||||
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
|
||||
params.smooth_factor = surface_fill.params.smooth_factor;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
|
||||
@@ -1857,12 +1857,12 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI
|
||||
const bool first = graph.first(cp);
|
||||
int extend_next_idx = -1;
|
||||
int extend_prev_idx = -1;
|
||||
coord_t dist_y_prev;
|
||||
coord_t dist_y_next;
|
||||
double arc_len_prev;
|
||||
double arc_len_next;
|
||||
coord_t dist_y_prev = 0;
|
||||
coord_t dist_y_next = 0;
|
||||
double arc_len_prev = 0;
|
||||
double arc_len_next = 0;
|
||||
|
||||
if (! graph.next_vertical(cp)){
|
||||
if (! graph.next_vertical(cp)) {
|
||||
size_t i = cp.point_idx;
|
||||
size_t j = next_idx_modulo(i, contour);
|
||||
while (j != cp.next_on_contour->point_idx) {
|
||||
|
||||
@@ -82,6 +82,9 @@ struct FillParams
|
||||
// For Gyroid: when true, use the parameterized "optimized" variant.
|
||||
bool gyroid_optimized { false };
|
||||
|
||||
// Orca: corner smoothing factor in the range [0, 1].
|
||||
double smooth_factor { 0. };
|
||||
|
||||
// For Lateral lattice
|
||||
coordf_t lateral_lattice_angle_1 { 0.f };
|
||||
coordf_t lateral_lattice_angle_2 { 0.f };
|
||||
|
||||
@@ -114,12 +114,12 @@ void FillPlanePath::_fill_surface_single(
|
||||
// Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box.
|
||||
snug_bounding_box.translate(-shift.x(), -shift.y());
|
||||
InfillPolylineClipper output(snug_bounding_box, distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
|
||||
polyline.points = std::move(output.result());
|
||||
} else {
|
||||
// Filling in a snug bounding box, no need to clip.
|
||||
InfillPolylineOutput output(distance_between_lines);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, params, output);
|
||||
polyline.points = std::move(output.result());
|
||||
}
|
||||
}
|
||||
@@ -288,6 +288,147 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x,
|
||||
}
|
||||
}
|
||||
|
||||
using QuinticBezier = std::array<Vec2d, 6>;
|
||||
|
||||
static bool is_bezier_flat(const QuinticBezier &curve, const double deviation)
|
||||
{
|
||||
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
|
||||
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
|
||||
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
|
||||
// comparing squared values avoids a square root.
|
||||
const Vec2d chord = curve.back() - curve.front();
|
||||
const double chord_length_sq = chord.squaredNorm();
|
||||
const double max_cross_sq = deviation * deviation * chord_length_sq;
|
||||
|
||||
for (size_t i = 1; i + 1 < curve.size(); ++i) {
|
||||
const Vec2d offset = curve[i] - curve.front();
|
||||
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
|
||||
if (cross * cross > max_cross_sq)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
|
||||
{
|
||||
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
|
||||
// control point to the left half and one to the right half; the latter is filled backwards to keep
|
||||
// both resulting control polygons in their original parameter direction.
|
||||
QuinticBezier subdivision = curve;
|
||||
left.front() = subdivision.front();
|
||||
right.back() = subdivision.back();
|
||||
for (size_t level = 1; level < curve.size(); ++level) {
|
||||
for (size_t i = 0; i + level < curve.size(); ++i)
|
||||
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
|
||||
left[level] = subdivision.front();
|
||||
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
|
||||
}
|
||||
}
|
||||
|
||||
static void flatten_bezier(const QuinticBezier &curve, const double deviation, std::vector<Vec2d> &output)
|
||||
{
|
||||
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
|
||||
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
|
||||
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
|
||||
static constexpr size_t max_depth = 16;
|
||||
|
||||
std::vector<QuinticBezier> subcurves(2);
|
||||
subdivide_bezier(curve, subcurves[0], subcurves[1]);
|
||||
|
||||
for (size_t depth = 1; depth < max_depth; ++depth) {
|
||||
bool all_flat = true;
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
if (!is_bezier_flat(c, deviation)) {
|
||||
all_flat = false;
|
||||
break;
|
||||
}
|
||||
if (all_flat)
|
||||
break;
|
||||
std::vector<QuinticBezier> finer(subcurves.size() * 2);
|
||||
for (size_t i = 0; i < subcurves.size(); ++i)
|
||||
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
|
||||
subcurves = std::move(finer);
|
||||
}
|
||||
|
||||
// The curve start is deliberately omitted so consecutive curve pieces can share it without duplication.
|
||||
output.reserve(output.size() + subcurves.size());
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
output.emplace_back(c.back());
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_smooth_hilbert_curve(
|
||||
coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const double corner_distance, Output &output)
|
||||
{
|
||||
// A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
|
||||
// generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
|
||||
// clipper or the later region intersection removes the padded part of the traversal.
|
||||
size_t sz = 2;
|
||||
const size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
|
||||
while (sz < sz0)
|
||||
sz <<= 1;
|
||||
|
||||
const size_t point_count = sz * sz;
|
||||
output.reserve(point_count);
|
||||
|
||||
// The caller normalizes resolution to the unit Hilbert grid; retain a finite positive tolerance
|
||||
// if this helper is invoked with an invalid resolution.
|
||||
const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
|
||||
// Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
|
||||
// At each end, the first three control points are collinear and equally spaced: the tangent follows
|
||||
// the adjoining straight leg and the second derivative is zero. The endpoint curvature is therefore
|
||||
// zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
|
||||
// it only once to the requested chordal-deviation tolerance.
|
||||
const QuinticBezier corner_curve {{
|
||||
{-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
|
||||
{0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
|
||||
}};
|
||||
std::vector<Vec2d> curve_coefficients;
|
||||
flatten_bezier(corner_curve, deviation, curve_coefficients);
|
||||
|
||||
auto translated_point = [min_x, min_y](size_t idx) {
|
||||
Point p = hilbert_n_to_xy(idx);
|
||||
return Point(p.x() + min_x, p.y() + min_y);
|
||||
};
|
||||
auto to_vec2d = [](const Point &p) { return Vec2d(double(p.x()), double(p.y())); };
|
||||
bool has_last_output = false;
|
||||
Vec2d last_output;
|
||||
// Fully smoothed adjacent corners may meet at the same segment midpoint. Suppress such duplicates
|
||||
// to avoid emitting zero-length extrusion segments.
|
||||
auto add_point = [&output, &has_last_output, &last_output](const Vec2d &point) {
|
||||
if (!has_last_output || point.x() != last_output.x() || point.y() != last_output.y()) {
|
||||
output.add_point(point);
|
||||
last_output = point;
|
||||
has_last_output = true;
|
||||
}
|
||||
};
|
||||
|
||||
Vec2d previous = to_vec2d(translated_point(0));
|
||||
Vec2d corner = to_vec2d(translated_point(1));
|
||||
add_point(previous);
|
||||
// Replace each non-collinear Hilbert vertex by the canonical curve expressed in the local basis of
|
||||
// its incoming and outgoing unit vectors. Collinear vertices remain part of the straight polyline.
|
||||
for (size_t i = 1; i + 1 < point_count; ++i) {
|
||||
const Vec2d next = to_vec2d(translated_point(i + 1));
|
||||
const Vec2d incoming = (corner - previous).normalized();
|
||||
const Vec2d outgoing = (next - corner).normalized();
|
||||
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
|
||||
|
||||
if (std::abs(cross) < EPSILON) {
|
||||
add_point(corner);
|
||||
} else {
|
||||
add_point(corner - corner_distance * incoming);
|
||||
for (const Vec2d &coefficient : curve_coefficients)
|
||||
add_point(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
|
||||
}
|
||||
|
||||
previous = corner;
|
||||
corner = next;
|
||||
}
|
||||
add_point(corner);
|
||||
}
|
||||
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
{
|
||||
if (output.clips())
|
||||
@@ -296,6 +437,24 @@ void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coo
|
||||
generate_hilbert_curve(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output)
|
||||
{
|
||||
const double smooth_factor = std::isfinite(params.smooth_factor) ?
|
||||
std::clamp(params.smooth_factor, 0., 1.) : 0.;
|
||||
if (smooth_factor == 0.) {
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
return;
|
||||
}
|
||||
|
||||
const double corner_distance = 0.5 * smooth_factor;
|
||||
if (output.clips())
|
||||
generate_smooth_hilbert_curve(
|
||||
min_x, min_y, max_x, max_y, resolution, corner_distance, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output)
|
||||
{
|
||||
|
||||
@@ -53,6 +53,11 @@ protected:
|
||||
friend class InfillPolylineClipper;
|
||||
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0;
|
||||
virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams & /* params */, InfillPolylineOutput &output)
|
||||
{
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
}
|
||||
};
|
||||
|
||||
class FillArchimedeanChords : public FillPlanePath
|
||||
@@ -75,6 +80,8 @@ public:
|
||||
protected:
|
||||
bool centered() const override { return false; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
class FillOctagramSpiral : public FillPlanePath
|
||||
|
||||
@@ -3576,7 +3576,7 @@ Polylines FillLateralHoneycomb::fill_surface(const Surface *surface, const FillP
|
||||
// |
|
||||
// |
|
||||
// 0 --+--
|
||||
// / \
|
||||
// ⟋ ⟍
|
||||
// why inverted?
|
||||
// it makes determining some of the properties easier
|
||||
// and the two angled legs provide additional horizontal stiffness
|
||||
|
||||
@@ -712,7 +712,7 @@ unsigned int Step::get_triangle_num(double linear_deflection, double angle_defle
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
} catch(const Exception &e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+157
-63
@@ -13,6 +13,7 @@
|
||||
#include "GCode/PrintExtents.hpp"
|
||||
#include "GCode/Thumbnails.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/WipeTower2.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include "GCode/OrderingStrategies.hpp"
|
||||
#include "Print.hpp"
|
||||
@@ -889,6 +890,65 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
return res;
|
||||
}
|
||||
|
||||
// Type2 tower-local point -> bed frame. The rib-wall offset is tower-local, so it
|
||||
// rotates with the tower (unlike the BBL tower in append_tcr, which never rotates).
|
||||
Vec2f WipeTowerIntegration::transform_wt2_pt(const Vec2f &pt) const
|
||||
{
|
||||
const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
return Eigen::Rotation2Df(alpha) * (pt + m_rib_offset) + m_wipe_tower_pos;
|
||||
}
|
||||
|
||||
// Printable-area bounds for tower-approach routing, in object coordinates (shared by
|
||||
// the BBL avoid-perimeter path in append_tcr and the Type2 skip-points router).
|
||||
// Multi-nozzle: clamp the travel bounds to the region every extruder can reach
|
||||
// (get_extruder_shared_printable_polygon) instead of the full bed. Gated on the
|
||||
// multi-nozzle predicate so every existing single/dual printer keeps the historic
|
||||
// full-printable_area routing byte-identical.
|
||||
BoundingBox WipeTowerIntegration::printer_travel_bounds(GCode &gcodegen) const
|
||||
{
|
||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
BoundingBox printer_bbx;
|
||||
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
||||
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
||||
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
||||
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
||||
} else {
|
||||
Points bed_points;
|
||||
for (const auto& p : gcodegen.m_config.printable_area.values)
|
||||
bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d));
|
||||
printer_bbx = BoundingBox(bed_points);
|
||||
}
|
||||
return printer_bbx;
|
||||
}
|
||||
|
||||
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
|
||||
// entry (tcr.start_pos): route the approach around the tower's bounding box so the
|
||||
// nozzle enters through that opening instead of dragging across the printed wall
|
||||
// (append_tcr parity). Emits only the waypoints leading up to the opening — the
|
||||
// caller still travels to start_wipe_pos itself. Returns an empty string when the
|
||||
// gap wall is off (option off or cone wall) or the approach already starts inside
|
||||
// the tower: such hops never cross the wall and must stay direct.
|
||||
std::string WipeTowerIntegration::travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const
|
||||
{
|
||||
if (!WipeTower2::use_gap_wall(gcodegen.m_config))
|
||||
return {};
|
||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
// Transform the tower-local bbx corners exactly like the tcr points; a rotated
|
||||
// tower gets a conservative axis-aligned envelope.
|
||||
Polygon avoid_points = scaled(m_wipe_tower_bbx).polygon();
|
||||
for (auto& p : avoid_points.points)
|
||||
p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
|
||||
BoundingBox avoid_bbx(avoid_points.points);
|
||||
if (avoid_bbx.contains(route_start))
|
||||
return {};
|
||||
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_travel_bounds(gcodegen));
|
||||
std::string gcode;
|
||||
// The polyline's last point is start_wipe_pos itself — emitted by the caller.
|
||||
for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
|
||||
gcode += gcodegen.travel_to(travel_polyline.points[i], erMixed, "Travel to a Wipe Tower");
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
|
||||
{
|
||||
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
|
||||
@@ -999,6 +1059,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value;
|
||||
|
||||
bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value;
|
||||
if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) is_used_travel_avoid_perimeter = false;
|
||||
|
||||
// add nozzle change gcode into change filament gcode
|
||||
std::string nozzle_change_gcode_trans;
|
||||
@@ -1076,8 +1137,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
|
||||
float old_retract_length = (old_filament_id != -1) ? full_config.retraction_length.get_at(old_fi) : 0;
|
||||
float new_retract_length = full_config.retraction_length.get_at(new_fi);
|
||||
float old_retract_length_toolchange = (old_filament_id != -1) ? full_config.retract_length_toolchange.get_at(old_filament_id) : 0;
|
||||
float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_filament_id);
|
||||
float old_retract_length_toolchange = (old_filament_id != -1) ? full_config.retract_length_toolchange.get_at(old_fi) : 0;
|
||||
float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_fi);
|
||||
int old_filament_temp = (old_filament_id != -1) ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(old_fi) : full_config.nozzle_temperature.get_at(old_fi)) : 210;
|
||||
int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_fi) : full_config.nozzle_temperature.get_at(new_fi);
|
||||
Vec3d nozzle_pos = gcode_writer.get_position();
|
||||
@@ -1261,24 +1322,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
|
||||
Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
|
||||
Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
|
||||
BoundingBox avoid_bbx, printer_bbx;
|
||||
{
|
||||
// set printer_bbx
|
||||
// Multi-nozzle: clamp the avoid-perimeter travel bounds to the region every
|
||||
// extruder can reach (get_extruder_shared_printable_polygon) instead of the full
|
||||
// bed. Gated on the multi-nozzle predicate so H2D and every existing single/dual
|
||||
// printer keep the historic full-printable_area routing byte-identical.
|
||||
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
||||
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
||||
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
||||
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
||||
} else {
|
||||
Pointfs bed_pointsf = gcodegen.m_config.printable_area.values;
|
||||
Points bed_points;
|
||||
for (auto p : bed_pointsf) { bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d)); }
|
||||
printer_bbx = BoundingBox(bed_points);
|
||||
}
|
||||
}
|
||||
BoundingBox avoid_bbx, printer_bbx = printer_travel_bounds(gcodegen);
|
||||
{
|
||||
// set avoid_bbx
|
||||
avoid_bbx = scaled(m_wipe_tower_bbx);
|
||||
@@ -1308,20 +1352,23 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
}
|
||||
|
||||
// do unretract after setting current extruder_id
|
||||
// PETG filaments on a device with a filament switcher get a small (2 mm) pre-extrusion
|
||||
// before the tool change. has_filament_switcher is a develop-only key read defensively from the
|
||||
// full config (Orca does not carry it as a static PrintConfig member — same convention as
|
||||
// enable_filament_dynamic_map); no shipping profile sets it (grep resources/profiles = 0), so
|
||||
// is_petg_pre_extrusion is always false -> extra_unretract stays 0 -> byte-identical to the plain
|
||||
// unretract() fleet-wide. The tower-interface contact pre-extrusion length (the
|
||||
// is_contact_pre_extrusion branch) is NOT applied here; it is only computed as the guard used to
|
||||
// give the contact path priority over PETG.
|
||||
// BBS pattern: the wipe tower shifts the toolchange start position outward for the
|
||||
// tower-interface (contact) pre-extrusion and for the PETG-with-filament-switcher case;
|
||||
// the pre-extrusion material itself is laid down here as extra unretract on the approach.
|
||||
// has_filament_switcher is a develop-only key read defensively from the full config (Orca
|
||||
// does not carry it as a static PrintConfig member — same convention as
|
||||
// enable_filament_dynamic_map); no shipping profile sets it, so is_petg_pre_extrusion is
|
||||
// always false fleet-wide.
|
||||
const ConfigOptionBool* has_filament_switcher_opt = gcodegen.m_print->full_print_config().option<ConfigOptionBool>("has_filament_switcher");
|
||||
bool is_contact_pre_extrusion = tcr.is_contact && gcodegen.m_config.enable_tower_interface_features;
|
||||
bool is_petg_pre_extrusion = !is_contact_pre_extrusion
|
||||
&& gcodegen.config().filament_type.get_at(tcr.new_tool) == "PETG"
|
||||
&& has_filament_switcher_opt && has_filament_switcher_opt->value;
|
||||
float extra_unretract = is_petg_pre_extrusion ? 2.f : 0.f;
|
||||
float extra_unretract = 0.f;
|
||||
if (is_contact_pre_extrusion)
|
||||
extra_unretract = gcodegen.m_config.filament_tower_interface_pre_extrusion_length.get_at(tcr.new_tool);
|
||||
else if (is_petg_pre_extrusion)
|
||||
extra_unretract = 2.f;
|
||||
std::string toolchange_unretract_str = (extra_unretract > 0.f) ? gcodegen.unretract(extra_unretract) : gcodegen.unretract();
|
||||
check_add_eol(toolchange_unretract_str);
|
||||
|
||||
@@ -1419,20 +1466,16 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
|
||||
auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f {
|
||||
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
|
||||
out += m_wipe_tower_pos;
|
||||
return out;
|
||||
};
|
||||
|
||||
// Priming lines are absolute bed moves; everything else is tower-local
|
||||
// (transform_wt2_pt).
|
||||
Vec2f start_pos = tcr.start_pos;
|
||||
Vec2f end_pos = tcr.end_pos;
|
||||
if (!tcr.priming) {
|
||||
start_pos = transform_wt_pt(start_pos);
|
||||
end_pos = transform_wt_pt(end_pos);
|
||||
start_pos = transform_wt2_pt(start_pos);
|
||||
end_pos = transform_wt2_pt(end_pos);
|
||||
}
|
||||
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : Vec2f(m_wipe_tower_pos + Eigen::Rotation2Df(alpha) * m_rib_offset);
|
||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||
Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
|
||||
@@ -1462,16 +1505,34 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
|| is_ramming
|
||||
|| tool_change_on_wipe_tower);
|
||||
|
||||
if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) {
|
||||
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
|
||||
const bool travel_to_tower_now = should_travel_to_tower || gcodegen.m_need_change_layer_lift_z;
|
||||
if (travel_to_tower_now) {
|
||||
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
|
||||
// then we could simplify the condition and make it more readable.
|
||||
gcode += gcodegen.retract();
|
||||
|
||||
// Orca: pass the configured lift type, as append_tcr does above. lazy_lift() keeps
|
||||
// the first type it is given, so the NormalLift default would pin this hop to a
|
||||
// standing move. Slope and spiral both need a known head position.
|
||||
LiftType lift_type = LiftType::NormalLift;
|
||||
if (gcodegen.writer().filament() != nullptr && gcodegen.writer().is_current_position_clear()) {
|
||||
ZHopType z_hop_type = ZHopType(gcodegen.config().z_hop_types.get_at(
|
||||
gcodegen.get_filament_config_index((int) gcodegen.writer().filament()->id())));
|
||||
if (z_hop_type == ZHopType::zhtAuto)
|
||||
z_hop_type = ZHopType::zhtSpiral;
|
||||
lift_type = gcodegen.to_lift_type(z_hop_type);
|
||||
}
|
||||
gcode += gcodegen.retract(false, false, lift_type);
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, "Travel to a Wipe Tower");
|
||||
if (!tcr.priming && gcodegen.last_pos_defined())
|
||||
gcode += travel_to_tower_gap(gcodegen, gcodegen.last_pos(), start_wipe_pos);
|
||||
gcode += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
// the tool without travelling to the tower.
|
||||
// the tool without travelling to the tower. The tower entry travel then lives
|
||||
// inside the tcr gcode; with skip points on it is rerouted below, once the
|
||||
// toolchange gcode (and the head position it ends at) is known.
|
||||
}
|
||||
|
||||
if (will_go_down) {
|
||||
@@ -1494,6 +1555,36 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
toolchange_temp_override = interface_temp;
|
||||
}
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
|
||||
if (!travel_to_tower_now && !tcr.priming && WipeTower2::use_gap_wall(gcodegen.m_config)) {
|
||||
// The tool changed in place (multi-tool printer without ramming), so the
|
||||
// tower entry is the tcr's own positioning move — a straight line across
|
||||
// the printed wall. Route it around the tower and in through the wall
|
||||
// opening instead, riding at the end of the change_filament_gcode
|
||||
// substitution so the generator's positioning move degrades to a
|
||||
// zero-length one (append_tcr parity: travel after the filament change,
|
||||
// retracted, with the new filament).
|
||||
Vec3f last_gcode_pos = gcodegen.writer().get_position().cast<float>();
|
||||
Point route_start;
|
||||
bool have_start = false;
|
||||
if (GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, last_gcode_pos)) {
|
||||
// A custom change_filament_gcode may have moved the head (tool docks
|
||||
// etc.); recover the real position from the emitted gcode.
|
||||
route_start = gcodegen.gcode_to_point(Vec2d(last_gcode_pos.x(), last_gcode_pos.y()) + plate_origin_2d.cast<double>());
|
||||
have_start = true;
|
||||
} else if (gcodegen.last_pos_defined()) {
|
||||
route_start = gcodegen.last_pos();
|
||||
have_start = true;
|
||||
}
|
||||
if (have_start) {
|
||||
gcodegen.set_last_pos(route_start);
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
std::string travel = travel_to_tower_gap(gcodegen, route_start, start_wipe_pos);
|
||||
travel += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
||||
check_add_eol(travel);
|
||||
toolchange_gcode_str += travel;
|
||||
gcodegen.set_last_pos(start_wipe_pos);
|
||||
}
|
||||
}
|
||||
if (gcodegen.config().enable_prime_tower) {
|
||||
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
|
||||
Vec3d position{gcodegen.writer().get_position()};
|
||||
@@ -1679,7 +1770,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// Prepare a future wipe.
|
||||
gcodegen.m_wipe.reset_path();
|
||||
for (const Vec2f& wipe_pt : tcr.wipe_path)
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d));
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(wipe_pt) + plate_origin_2d));
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
@@ -2793,6 +2884,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled,
|
||||
print.get_layered_nozzle_group_result());
|
||||
const bool is_bbl_printers = print.is_BBL_printer();
|
||||
const bool skip_config_block = print.config().gcode_skip_config_block;
|
||||
const WipeTowerType wipe_tower_type = print.wipe_tower_type();
|
||||
m_calib_config.clear();
|
||||
// resets analyzer's tracking data
|
||||
@@ -2968,7 +3060,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
// as configuration key / value pairs to be parsable by older versions of
|
||||
// PrusaSlicer G-code viewer.
|
||||
{
|
||||
if (is_bbl_printers) {
|
||||
if (is_bbl_printers && !skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
@@ -3995,23 +4087,25 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
||||
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
|
||||
.c_str());
|
||||
file.write("\n");
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
if (!skip_config_block) {
|
||||
file.write("; CONFIG_BLOCK_START\n");
|
||||
std::string full_config;
|
||||
append_full_config(print, full_config);
|
||||
if (!full_config.empty())
|
||||
file.write(full_config);
|
||||
|
||||
// SoftFever: write compatiple info
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
|
||||
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
|
||||
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
|
||||
|
||||
//SF TODO
|
||||
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
|
||||
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
// SoftFever: write compatiple info
|
||||
int first_layer_bed_temperature = get_bed_temperature(0, true, print.config().curr_bed_type);
|
||||
file.write_format("; first_layer_bed_temperature = %d\n", first_layer_bed_temperature);
|
||||
file.write_format("; bed_shape = %s\n", print.full_print_config().opt_serialize("printable_area").c_str());
|
||||
file.write_format("; first_layer_temperature = %d\n", print.config().nozzle_temperature_initial_layer.get_at(0));
|
||||
file.write_format("; first_layer_height = %.3f\n", print.config().initial_layer_print_height.value);
|
||||
|
||||
//SF TODO
|
||||
// file.write_format("; variable_layer_height = %d\n", print.ad.adaptive_layer_height ? 1 : 0);
|
||||
|
||||
file.write("; CONFIG_BLOCK_END\n\n");
|
||||
} // !skip_config_block
|
||||
|
||||
}
|
||||
file.write("\n");
|
||||
@@ -5417,7 +5511,7 @@ LayerResult GCode::process_layer(
|
||||
// add tag for processor
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n";
|
||||
// export layer z
|
||||
char buf[64];
|
||||
char buf[80];
|
||||
sprintf(buf, print.is_BBL_printer() ? "; Z_HEIGHT: %g\n" : ";Z:%g\n", print_z);
|
||||
gcode += buf;
|
||||
// export layer height
|
||||
@@ -8944,7 +9038,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
||||
// per-layer nozzle grouping; resolve the column instead of indexing by the filament id.
|
||||
size_t new_fi = get_filament_config_index((int)new_filament_id);
|
||||
float new_retract_length = m_config.retraction_length.get_at(new_fi);
|
||||
float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(new_filament_id);
|
||||
float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(new_fi);
|
||||
int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(new_fi) : m_config.nozzle_temperature.get_at(new_fi);
|
||||
// BBS: if print_z == 0 use first layer temperature
|
||||
if (abs(print_z) < EPSILON)
|
||||
@@ -8975,7 +9069,7 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
|
||||
// gap-filled carry-forward, so its current-layer column matches the nozzle it occupies.
|
||||
size_t old_fi = get_filament_config_index(old_filament_id);
|
||||
old_retract_length = m_config.retraction_length.get_at(old_fi);
|
||||
old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(old_filament_id);
|
||||
old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(old_fi);
|
||||
old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(old_fi) : m_config.nozzle_temperature.get_at(old_fi);
|
||||
|
||||
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
||||
|
||||
@@ -132,6 +132,9 @@ private:
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
|
||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
||||
Vec2f transform_wt2_pt(const Vec2f &pt) const;
|
||||
BoundingBox printer_travel_bounds(GCode &gcodegen) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
@@ -181,7 +184,7 @@ struct LayerResult {
|
||||
// It is used for the pressure equalizer because it needs to buffer one layer back.
|
||||
bool nop_layer_result { false };
|
||||
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
|
||||
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<size_t>::max(), false, false, true}; }
|
||||
};
|
||||
|
||||
class GCode {
|
||||
|
||||
@@ -1450,8 +1450,8 @@ void GCodeProcessor::run_post_process()
|
||||
// flag) runs none of this. It is pure data construction — it only fills m_filament_blocks /
|
||||
// m_extruder_blocks / m_machine_*_gcode_*_line_id and never touches the exported g-code, so even
|
||||
// the enable_pre_heating fleet stays byte-identical (nothing reads the blocks until the injection
|
||||
// pass). In practice it also stays empty/degenerate today because no template/code yet emits the
|
||||
// MACHINE_*_GCODE_* / NOZZLE_CHANGE_* / CP_TOOLCHANGE_WIPE markers it keys off.
|
||||
// pass). The wipe tower emits the NOZZLE_CHANGE_* (ramming) and CP_TOOLCHANGE_WIPE markers this
|
||||
// builder keys off; the MACHINE_*_GCODE_* markers come from the machine g-code templates.
|
||||
m_filament_blocks.clear();
|
||||
m_extruder_blocks.clear();
|
||||
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
|
||||
@@ -5926,8 +5926,11 @@ void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line)
|
||||
GCodeReader::GCodeLine g10;
|
||||
g10.set(Axis::E, -this->m_parser.config().retraction_length.get_at(m_extruder_id));
|
||||
g10.set(Axis::F, this->m_parser.config().retraction_speed.get_at(m_extruder_id) * 60);
|
||||
//Orca: Firmware retract emulation must not change the modal G1 feedrate.
|
||||
const float feedrate = m_feedrate;
|
||||
--m_g1_line_id;
|
||||
process_G1(g10);
|
||||
m_feedrate = feedrate;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
|
||||
@@ -5936,8 +5939,11 @@ void GCodeProcessor::process_G11(const GCodeReader::GCodeLine& line)
|
||||
GCodeReader::GCodeLine g11;
|
||||
g11.set(Axis::E, this->m_parser.config().retraction_length.get_at(m_extruder_id) + this->m_parser.config().retract_restart_extra.get_at(m_extruder_id));
|
||||
g11.set(Axis::F, this->m_parser.config().deretraction_speed.get_at(m_extruder_id) * 60);
|
||||
// Orca: Firmware unretract emulation must not change the modal G1 feedrate.
|
||||
const float feedrate = m_feedrate;
|
||||
--m_g1_line_id;
|
||||
process_G1(g11);
|
||||
m_feedrate = feedrate;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G20(const GCodeReader::GCodeLine& line)
|
||||
|
||||
@@ -143,7 +143,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
||||
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
||||
Transform2d trafo =
|
||||
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)) *
|
||||
Eigen::Translation2d(print.wipe_tower_data().rib_offset.cast<double>()); // tower-local rib-wall shift, zero unless rib
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||
|
||||
+1308
-833
File diff suppressed because it is too large
Load Diff
+118
-112
@@ -12,7 +12,7 @@
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
@@ -20,6 +20,17 @@ class WipeTowerWriter;
|
||||
class PrintConfig;
|
||||
enum GCodeFlavor : unsigned char;
|
||||
|
||||
// Cuts the tower wall polygon open at each skip point (a toolchange's entry position)
|
||||
// so the entry travel can pass through instead of crossing the printed wall. Defined in
|
||||
// WipeTower.cpp, shared by WipeTower and WipeTower2.
|
||||
Polylines construct_gap_for_skip_points(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
|
||||
|
||||
// Klipper acts on commands the instant it parses them, and its G4 reads only P (milliseconds),
|
||||
// so the zero-second and seconds-valued dwells every other flavor uses neither synchronize nor
|
||||
// pause there. Both defined in WipeTower.cpp, shared by WipeTower and WipeTower2.
|
||||
const char* flush_planner_queue_command(GCodeFlavor flavor); // finish queued moves, e.g. around M104/M109
|
||||
std::string wait_command(GCodeFlavor flavor, float seconds); // pause for `seconds`
|
||||
|
||||
class WipeTower
|
||||
{
|
||||
@@ -84,7 +95,6 @@ public:
|
||||
bool priming;
|
||||
|
||||
bool is_tool_change{false};
|
||||
bool is_contact{false};
|
||||
Vec2f tool_change_start_pos;
|
||||
|
||||
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
||||
@@ -108,6 +118,7 @@ public:
|
||||
// executing the gcode finish_layer_tcr.
|
||||
bool is_finish_first = false;
|
||||
|
||||
bool is_contact = false;
|
||||
NozzleChangeResult nozzle_change_result;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
@@ -122,6 +133,8 @@ public:
|
||||
}
|
||||
return e_length;
|
||||
}
|
||||
// Orca: set by WipeTower2 (non-BBL tower) to force a travel to the tower even when the
|
||||
// previous position is unknown; read by WipeTowerIntegration::append_tcr2 (GCode.cpp).
|
||||
bool force_travel = false;
|
||||
};
|
||||
|
||||
@@ -162,15 +175,12 @@ public:
|
||||
bool priming,
|
||||
size_t old_tool,
|
||||
bool is_finish,
|
||||
bool is_tool_change,
|
||||
float purge_volume,
|
||||
bool is_contact = false) const;
|
||||
bool is_tool_change, float purge_volume, bool is_contact) const;
|
||||
|
||||
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
|
||||
bool priming,
|
||||
size_t filament_id,
|
||||
bool is_finish,
|
||||
float purge_volume) const;
|
||||
bool is_finish, float purge_volume) const;
|
||||
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
@@ -184,9 +194,14 @@ public:
|
||||
// Set the extruder properties.
|
||||
void set_extruder(size_t idx, const PrintConfig& config);
|
||||
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member here, so it is pushed in from
|
||||
// Print via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// Appends into internal structure m_plan containing info about the future wipe tower
|
||||
// to be used before building begins. The entries must be added ordered in z.
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume_ec = 0.f, float wipe_volume_nc = 0.f, float prime_volume = 0.f);
|
||||
|
||||
|
||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
@@ -219,9 +234,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void set_wipe_volume(std::vector<std::vector<float>>& wiping_matrix) {
|
||||
wipe_volumes = wiping_matrix;
|
||||
}
|
||||
|
||||
// Switch to a next layer.
|
||||
void set_layer(
|
||||
@@ -250,7 +262,6 @@ public:
|
||||
|
||||
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
||||
m_extrusion_flow = extrusion_flow(layer_height);
|
||||
|
||||
// Advance m_layer_info iterator, making sure we got it right
|
||||
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
||||
++m_layer_info;
|
||||
@@ -309,20 +320,9 @@ public:
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
||||
// Vortek H2C: filament_id → physical nozzle_id for carousel rotation detection
|
||||
void set_filament_nozzle_map(const std::vector<int> &nozzle_map) { m_filament_nozzle_map = nozzle_map; }
|
||||
|
||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member, so it is pushed in from Print
|
||||
// via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// The region every extruder can reach, used to clamp the PETG pre-extrusion offset to the
|
||||
// printable bed.
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
int category;
|
||||
@@ -331,15 +331,15 @@ public:
|
||||
bool is_support = false;
|
||||
int nozzle_temperature = 0;
|
||||
int nozzle_temperature_initial_layer = 0;
|
||||
int interface_print_temperature = 0;
|
||||
float loading_speed = 0.f;
|
||||
float loading_speed_start = 0.f;
|
||||
float unloading_speed = 0.f;
|
||||
float unloading_speed_start = 0.f;
|
||||
float delay = 0.f ;
|
||||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float loading_speed = 0.f;
|
||||
//float loading_speed_start = 0.f;
|
||||
//float unloading_speed = 0.f;
|
||||
//float unloading_speed_start = 0.f;
|
||||
//float delay = 0.f ;
|
||||
//int cooling_moves = 0;
|
||||
//float cooling_initial_speed = 0.f;
|
||||
//float cooling_final_speed = 0.f;
|
||||
float ramming_line_width_multiplicator = 1.f;
|
||||
float ramming_step_multiplicator = 1.f;
|
||||
float max_e_speed = std::numeric_limits<float>::max();
|
||||
@@ -349,41 +349,41 @@ public:
|
||||
float retract_length;
|
||||
float retract_speed;
|
||||
float wipe_dist;
|
||||
float tower_interface_pre_extrusion_dist = 0.f;
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
// Outward shift of the wipe start for a PETG pre-extrusion on filament-switcher devices;
|
||||
// set from filament_tower_interface_pre_extrusion_dist.
|
||||
float petg_pre_extrusion_offset_dist = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
||||
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
// .first = extruder change, .second = nozzle change (carousel)
|
||||
std::pair<float,float> max_e_ramming_speed{0.f, 0.f};
|
||||
std::pair<float,float> ramming_travel_time{0.f, 0.f};
|
||||
std::pair<int,int> precool_target_temp{0, 0};
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t_first_layer;
|
||||
std::pair<float,float> max_e_ramming_speed;//[0]extruder change [1]nozzle change
|
||||
std::pair<float, float> ramming_travel_time; // Travel time after ramming
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed.
|
||||
std::pair<std::vector<float>, std::vector<float>> precool_t_first_layer;
|
||||
std::pair<int,int> precool_target_temp;
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
float flat_iron_area;
|
||||
float filament_tower_interface_print_temp;
|
||||
float filament_tower_interface_pre_extrusion_dist = 0;
|
||||
float filament_tower_interface_pre_extrusion_length = 0;
|
||||
float filament_petg_pre_extrusion_offset_dist = 0;
|
||||
};
|
||||
|
||||
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
|
||||
std::vector<int> m_used_filament_ids;
|
||||
void set_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult &multi_nozzle_group_result) { m_multi_nozzle_group_result = &multi_nozzle_group_result; };
|
||||
std::vector<int> m_used_filament_ids;
|
||||
std::vector<int> m_filament_categories;
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult *m_multi_nozzle_group_result{nullptr};
|
||||
|
||||
enum class WipeTowerLayerType : unsigned char { Normal, Contact, Solid, Contact_UP};// Contact layer should be solid and reduce feed
|
||||
|
||||
struct WipeTowerBlock
|
||||
{
|
||||
int block_id{0};
|
||||
int filament_adhesiveness_category{0};
|
||||
std::vector<float> layer_depths;
|
||||
std::vector<bool> solid_infill;
|
||||
//std::vector<bool> solid_infill;
|
||||
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
|
||||
std::vector<WipeTowerLayerType> layers_type; // type of the layer, normal, Contact or Solid
|
||||
float depth{0};
|
||||
float start_depth{0};
|
||||
float cur_depth{0};
|
||||
int last_filament_change_id{-1};
|
||||
int last_filament_change_id{-1};
|
||||
int last_nozzle_change_id{-1};
|
||||
};
|
||||
|
||||
@@ -403,25 +403,33 @@ public:
|
||||
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
|
||||
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
||||
int get_filament_category(int filament_id);
|
||||
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
||||
// Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload
|
||||
std::string format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const;
|
||||
void reset_block_status();
|
||||
int get_wall_filament_for_all_layer();
|
||||
// for generate new wipe tower
|
||||
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
||||
|
||||
void plan_tower_new();
|
||||
void generate_wipe_tower_blocks();
|
||||
void generate_wipe_tower_blocks(bool add_solid_flag);
|
||||
void update_all_layer_depth(float wipe_tower_depth);
|
||||
|
||||
void set_nozzle_last_layer_id();
|
||||
void set_first_layer_flow_ratio(const float flow_ratio);
|
||||
// Orca: default/initial-layer/travel acceleration are object-scope options here (PrintConfig
|
||||
// members in BBS), so Print pushes the resolved per-variant columns in via this setter.
|
||||
void set_accelerations(const std::vector<double> &normal, const std::vector<double> &first_layer_normal,
|
||||
const std::vector<double> &travel, const std::vector<double> &first_layer_travel);
|
||||
void calc_block_infill_gap();
|
||||
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
|
||||
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
|
||||
NozzleChangeResult ramming(int old_filament_id, int new_filament_id, bool solid_change = false, bool extruder_change = true); // extruder_chang means nozzle_change
|
||||
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
|
||||
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true, WipeTowerLayerType layer_type = WipeTowerLayerType::Normal);
|
||||
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
bool is_need_ramming(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_extruder(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_nozzle(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
int get_nozzle_id(int filament_id, int layer_id) const;
|
||||
int get_extruder_id(int filament_id, int layer_id) const;
|
||||
|
||||
private:
|
||||
enum wipe_shape // A fill-in direction
|
||||
@@ -441,7 +449,6 @@ private:
|
||||
bool m_enable_wrapping_detection = false;
|
||||
bool m_enable_timelapse_print = false;
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
bool m_purge_in_prime_tower = false; // Do we purge in the prime tower?
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
@@ -459,12 +466,11 @@ private:
|
||||
float m_travel_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
|
||||
std::vector<double> m_filaments_change_length;
|
||||
Vec2f m_origin;
|
||||
std::vector<int> m_last_layer_id;
|
||||
std::pair<std::vector<double>,std::vector<double>> m_filaments_change_length;//[0]extruder change [1]nozzle change
|
||||
size_t m_cur_layer_id;
|
||||
NozzleChangeResult m_nozzle_change_result;
|
||||
std::vector<int> m_filament_map;
|
||||
std::vector<int> m_filament_nozzle_map; // Vortek H2C: filament_id → physical nozzle_id
|
||||
bool m_has_tpu_filament{false};
|
||||
bool m_is_multi_extruder{false};
|
||||
bool m_use_gap_wall{false};
|
||||
@@ -475,33 +481,32 @@ private:
|
||||
bool m_used_fillet{false};
|
||||
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
|
||||
bool m_tower_framework{false};
|
||||
|
||||
bool m_need_reverse_travel{false};
|
||||
bool m_enable_tower_interface_features{false};
|
||||
// G-code generator parameters.
|
||||
float m_cooling_tube_retraction = 0.f;
|
||||
float m_cooling_tube_length = 0.f;
|
||||
float m_parking_pos_retraction = 0.f;
|
||||
float m_extra_loading_move = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float m_cooling_tube_retraction = 0.f;
|
||||
//float m_cooling_tube_length = 0.f;
|
||||
//float m_parking_pos_retraction = 0.f;
|
||||
//float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_no_sparse_layers = false;
|
||||
bool m_set_extruder_trimpot = false;
|
||||
// BBS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
// Multi-nozzle prime-tower heating during wipe. m_is_multiple_nozzle gates the whole
|
||||
// feature; it is false for every current (single-nozzle) printer (extruder_max_nozzle_count
|
||||
// defaults to 1), so the pre-heat/pre-cool path is inert and wipe-tower g-code is unchanged.
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<double> m_hotend_heating_rate; // config.hotend_heating_rate (deg/s per extruder)
|
||||
std::vector<int> m_physical_extruder_map; // logical extruder -> physical tool number (M104 T param)
|
||||
|
||||
// Per-extruder printable-height clamp. m_printable_height = config.extruder_printable_height
|
||||
// (per-extruder Z limit; empty for single-extruder printers, [320,325] for H2D). m_last_layer_id
|
||||
// records, per extruder, the last wipe-tower layer that uses it. is_valid_last_layer() is gated on
|
||||
// m_is_multi_extruder so single-extruder wipe-tower g-code is unchanged; the clamp only bites a
|
||||
// multi-extruder wipe tower whose final per-extruder layer exceeds that extruder's printable
|
||||
// height (near the Z limit).
|
||||
std::vector<double> m_printable_height;
|
||||
std::vector<int> m_last_layer_id;
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<unsigned int> m_normal_accels;
|
||||
std::vector<unsigned int> m_first_layer_normal_accels;
|
||||
std::vector<unsigned int> m_travel_accels;
|
||||
std::vector<unsigned int> m_first_layer_travel_accels;
|
||||
unsigned int m_max_accels;
|
||||
bool m_accel_to_decel_enable;
|
||||
float m_accel_to_decel_factor;
|
||||
bool m_enable_arc_fitting = true;
|
||||
std::vector<double> m_hotend_heating_rate;
|
||||
std::vector<double> m_hotend_cooling_rate;
|
||||
Polygons m_shared_print_bed;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
@@ -512,10 +517,11 @@ private:
|
||||
float m_bed_width; // width of the bed bounding box
|
||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||
|
||||
float m_first_layer_flow_ratio;
|
||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
|
||||
std::unordered_map<int, std::pair<float,float>> m_block_infill_gap_width; // categories to infill_gap: toolchange gap, nozzlechange gap
|
||||
// Extruder specific parameters.
|
||||
std::vector<FilamentParameters> m_filpar;
|
||||
|
||||
@@ -528,50 +534,52 @@ private:
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
// Orca: support mmu wipe tower
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
// BBS
|
||||
//const std::vector<std::vector<float>> wipe_volumes;
|
||||
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
float m_tpu_fixed_spacing = 2;
|
||||
std::vector<Vec2f> m_wall_skip_points;
|
||||
float m_max_speed = 5400.f; // the maximum printing speed on the prime tower.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
std::map<float,Polylines> m_outer_wall;
|
||||
std::vector<double> m_printable_height;
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
bool m_flat_ironing=false;
|
||||
bool m_enable_tower_interface_features=false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower=false;
|
||||
// Filament-switcher device flag + shared printable bed for the PETG pre-extrusion offset.
|
||||
// m_has_filament_switcher is false for the whole shipping fleet (no profile sets the key), so
|
||||
// the PETG branch in get_next_pos never runs -> no change fleet-wide.
|
||||
bool m_has_filament_switcher=false;
|
||||
Polygons m_shared_print_bed;
|
||||
bool m_prev_layer_had_interface=false;
|
||||
bool m_current_layer_has_interface=false;
|
||||
bool m_contact_ironing = false;
|
||||
bool m_has_filament_switcher = false;
|
||||
float m_contact_speed = 20 * 60.f;
|
||||
std::vector<int> m_physical_extruder_map;
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates volume of extrusion line
|
||||
float length_to_volume(float length,float line_width, float layer_height) const
|
||||
{
|
||||
return std::max(0.f, length * (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool);
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool solid_toolchange);
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
|
||||
bool is_tpu_filament(int filament_id) const;
|
||||
bool is_petg_filament(int filament_id) const;
|
||||
bool is_need_reverse_travel(int filament_id, bool extruder_change) const;
|
||||
|
||||
bool is_need_reverse_travel(int filament, bool extruder_change) const;
|
||||
// BBS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
void set_for_wipe_tower_writer(WipeTowerWriter &writer);
|
||||
|
||||
// to store information about tool changes for a given layer
|
||||
struct WipeTowerInfo{
|
||||
@@ -584,6 +592,7 @@ private:
|
||||
float wipe_volume;
|
||||
float wipe_length;
|
||||
float nozzle_change_depth{0};
|
||||
float nozzle_change_length{0};
|
||||
// BBS
|
||||
float purge_volume;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
||||
@@ -613,7 +622,7 @@ private:
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
WipeTowerInfo::ToolChange set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume,int layer_id);
|
||||
void toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
@@ -633,13 +642,10 @@ private:
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||
|
||||
// Per-extruder printable-height clamp (see m_printable_height). is_valid_last_layer returns
|
||||
// false only for a multi-extruder wipe tower's final per-extruder layer that exceeds that
|
||||
// extruder's printable height; returns true (no clamp) in every other case.
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
void set_nozzle_last_layer_id();
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer,int layer_id);
|
||||
void get_all_wall_skip_points();
|
||||
ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second);
|
||||
float get_block_gap_width(int tool, bool is_nozzlechangle = false);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+266
-326
@@ -24,7 +24,6 @@
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
static constexpr float flat_iron_area = 4.f;
|
||||
constexpr float flat_iron_speed = 10.f * 60.f;
|
||||
static const double wipe_tower_wall_infill_overlap = 0.0;
|
||||
static constexpr double WIPE_TOWER_RESOLUTION = 0.1;
|
||||
@@ -234,24 +233,6 @@ static Polygon rounding_rectangle(Polygon& polygon, double rounding = 2., double
|
||||
return res;
|
||||
}
|
||||
|
||||
static std::pair<bool, Vec2f> ray_intersetion_line(const Vec2f& a, const Vec2f& v1, const Vec2f& b, const Vec2f& c)
|
||||
{
|
||||
const Vec2f v2 = c - b;
|
||||
double denom = cross2(v1, v2);
|
||||
if (fabs(denom) < EPSILON)
|
||||
return {false, Vec2f(0, 0)};
|
||||
const Vec2f v12 = (a - b);
|
||||
double nume_a = cross2(v2, v12);
|
||||
double nume_b = cross2(v1, v12);
|
||||
double t1 = nume_a / denom;
|
||||
double t2 = nume_b / denom;
|
||||
if (t1 >= 0 && t2 >= 0 && t2 <= 1.) {
|
||||
// Get the intersection point.
|
||||
Vec2f res = a + t1 * v1;
|
||||
return std::pair<bool, Vec2f>(true, res);
|
||||
}
|
||||
return std::pair<bool, Vec2f>(false, Vec2f{0, 0});
|
||||
}
|
||||
static Polygon scale_polygon(const std::vector<Vec2f>& points)
|
||||
{
|
||||
Polygon res;
|
||||
@@ -296,6 +277,7 @@ static Polygon generate_rectange(const Line& line, coord_t offset)
|
||||
return poly;
|
||||
};
|
||||
|
||||
// Straight or arc-fitted wall segment used by WipeTowerWriter2::generate_path().
|
||||
struct Segment
|
||||
{
|
||||
Vec2f start;
|
||||
@@ -306,234 +288,6 @@ struct Segment
|
||||
bool is_valid() const { return start.y() < end.y(); }
|
||||
};
|
||||
|
||||
static std::vector<Segment> remove_points_from_segment(const Segment& segment, const std::vector<Vec2f>& skip_points, double range)
|
||||
{
|
||||
std::vector<Segment> result;
|
||||
result.push_back(segment);
|
||||
float x = segment.start.x();
|
||||
|
||||
for (const Vec2f& point : skip_points) {
|
||||
std::vector<Segment> newResult;
|
||||
for (const auto& seg : result) {
|
||||
if (point.y() + range <= seg.start.y() || point.y() - range >= seg.end.y()) {
|
||||
newResult.push_back(seg);
|
||||
} else {
|
||||
if (point.y() - range > seg.start.y()) {
|
||||
newResult.push_back(Segment(Vec2f(x, seg.start.y()), Vec2f(x, point.y() - range)));
|
||||
}
|
||||
if (point.y() + range < seg.end.y()) {
|
||||
newResult.push_back(Segment(Vec2f(x, point.y() + range), Vec2f(x, seg.end.y())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = newResult;
|
||||
}
|
||||
|
||||
result.erase(std::remove_if(result.begin(), result.end(), [](const Segment& seg) { return !seg.is_valid(); }), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
struct IntersectionInfo
|
||||
{
|
||||
Vec2f pos;
|
||||
int idx;
|
||||
int pair_idx; // gap_pair idx
|
||||
float dis_from_idx;
|
||||
bool is_forward;
|
||||
};
|
||||
|
||||
struct PointWithFlag
|
||||
{
|
||||
Vec2f pos;
|
||||
int pair_idx; // gap_pair idx
|
||||
bool is_forward;
|
||||
};
|
||||
static IntersectionInfo move_point_along_polygon(
|
||||
const std::vector<Vec2f>& points, const Vec2f& startPoint, int startIdx, float offset, bool forward, int pair_idx)
|
||||
{
|
||||
float remainingDistance = offset;
|
||||
IntersectionInfo res;
|
||||
int mod = points.size();
|
||||
if (forward) {
|
||||
int next = (startIdx + 1) % mod;
|
||||
remainingDistance -= (points[next] - startPoint).norm();
|
||||
if (remainingDistance <= 0) {
|
||||
res.idx = startIdx;
|
||||
res.pos = startPoint + (points[next] - startPoint).normalized() * offset;
|
||||
res.pair_idx = pair_idx;
|
||||
res.dis_from_idx = (points[startIdx] - res.pos).norm();
|
||||
return res;
|
||||
} else {
|
||||
for (int i = (startIdx + 1) % mod; i != startIdx; i = (i + 1) % mod) {
|
||||
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
|
||||
if (remainingDistance <= segmentLength) {
|
||||
float ratio = remainingDistance / segmentLength;
|
||||
res.idx = i;
|
||||
res.pos = points[i] + ratio * (points[(i + 1) % mod] - points[i]);
|
||||
res.dis_from_idx = remainingDistance;
|
||||
res.pair_idx = pair_idx;
|
||||
return res;
|
||||
}
|
||||
remainingDistance -= segmentLength;
|
||||
}
|
||||
res.idx = (startIdx - 1 + mod) % mod;
|
||||
res.pos = points[startIdx];
|
||||
res.pair_idx = pair_idx;
|
||||
res.dis_from_idx = (res.pos - points[res.idx]).norm();
|
||||
}
|
||||
} else {
|
||||
int next = (startIdx + 1) % mod;
|
||||
remainingDistance -= (points[startIdx] - startPoint).norm();
|
||||
if (remainingDistance <= 0) {
|
||||
res.idx = startIdx;
|
||||
res.pos = startPoint - (points[next] - points[startIdx]).normalized() * offset;
|
||||
res.dis_from_idx = (res.pos - points[startIdx]).norm();
|
||||
res.pair_idx = pair_idx;
|
||||
return res;
|
||||
}
|
||||
for (int i = (startIdx - 1 + mod) % mod; i != startIdx; i = (i - 1 + mod) % mod) {
|
||||
float segmentLength = (points[(i + 1) % mod] - points[i]).norm();
|
||||
if (remainingDistance <= segmentLength) {
|
||||
float ratio = remainingDistance / segmentLength;
|
||||
res.idx = i;
|
||||
res.pos = points[(i + 1) % mod] - ratio * (points[(i + 1) % mod] - points[i]);
|
||||
res.dis_from_idx = segmentLength - remainingDistance;
|
||||
res.pair_idx = pair_idx;
|
||||
return res;
|
||||
}
|
||||
remainingDistance -= segmentLength;
|
||||
}
|
||||
res.idx = startIdx;
|
||||
res.pos = points[res.idx];
|
||||
res.pair_idx = pair_idx;
|
||||
res.dis_from_idx = 0;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
static void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, int pair_idx, bool is_forward)
|
||||
{
|
||||
int next = (idx + 1) % pl.size();
|
||||
Vec2f pos1 = pl[idx].pos;
|
||||
Vec2f pos2 = pl[next].pos;
|
||||
if ((pos - pos1).squaredNorm() < EPSILON) {
|
||||
pl[idx].pair_idx = pair_idx;
|
||||
pl[idx].is_forward = is_forward;
|
||||
} else if ((pos - pos2).squaredNorm() < EPSILON) {
|
||||
pl[next].pair_idx = pair_idx;
|
||||
pl[next].is_forward = is_forward;
|
||||
} else {
|
||||
pl.insert(pl.begin() + idx + 1, PointWithFlag{pos, pair_idx, is_forward});
|
||||
}
|
||||
}
|
||||
|
||||
static Polylines remove_points_from_polygon(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, double range, bool is_left, Polygon& insert_skip_pg)
|
||||
{
|
||||
assert(polygon.size() > 2);
|
||||
Polylines result;
|
||||
std::vector<PointWithFlag> new_pl; // add intersection points for gaps, where bool indicates whether it's a gap point.
|
||||
std::vector<IntersectionInfo> inter_info;
|
||||
Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0);
|
||||
auto polygon_box = get_extents(polygon);
|
||||
Point anchor_point = is_left ? Point{polygon_box.max[0], polygon_box.min[1]} : polygon_box.min; // rd:ld
|
||||
std::vector<Vec2f> points;
|
||||
{
|
||||
points.reserve(polygon.points.size());
|
||||
int idx = polygon.closest_point_index(anchor_point);
|
||||
Polyline tmp_poly = polygon.split_at_index(idx);
|
||||
for (auto& p : tmp_poly)
|
||||
points.push_back(unscale(p).cast<float>());
|
||||
points.pop_back();
|
||||
}
|
||||
|
||||
for (int i = 0; i < skip_points.size(); i++) {
|
||||
for (int j = 0; j < points.size(); j++) {
|
||||
Vec2f& p1 = points[j];
|
||||
Vec2f& p2 = points[(j + 1) % points.size()];
|
||||
auto [is_inter, inter_pos] = ray_intersetion_line(skip_points[i], ray, p1, p2);
|
||||
if (is_inter) {
|
||||
IntersectionInfo forward = move_point_along_polygon(points, inter_pos, j, range, true, i);
|
||||
IntersectionInfo backward = move_point_along_polygon(points, inter_pos, j, range, false, i);
|
||||
backward.is_forward = false;
|
||||
forward.is_forward = true;
|
||||
inter_info.push_back(backward);
|
||||
inter_info.push_back(forward);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert point to new_pl
|
||||
for (const auto& p : points)
|
||||
new_pl.push_back({p, -1});
|
||||
std::sort(inter_info.begin(), inter_info.end(), [](const IntersectionInfo& lhs, const IntersectionInfo& rhs) {
|
||||
if (rhs.idx == lhs.idx)
|
||||
return lhs.dis_from_idx < rhs.dis_from_idx;
|
||||
return lhs.idx < rhs.idx;
|
||||
});
|
||||
for (int i = inter_info.size() - 1; i >= 0; i--) {
|
||||
insert_points(new_pl, inter_info[i].idx, inter_info[i].pos, inter_info[i].pair_idx, inter_info[i].is_forward);
|
||||
}
|
||||
|
||||
{
|
||||
// set insert_pg for wipe_path
|
||||
for (auto& p : new_pl)
|
||||
insert_skip_pg.points.push_back(scaled(p.pos));
|
||||
}
|
||||
|
||||
int beg = 0;
|
||||
bool skip = true;
|
||||
int i = beg;
|
||||
Polyline pl;
|
||||
|
||||
do {
|
||||
if (skip || new_pl[i].pair_idx == -1) {
|
||||
pl.points.push_back(scaled(new_pl[i].pos));
|
||||
i = (i + 1) % new_pl.size();
|
||||
skip = false;
|
||||
} else {
|
||||
if (!pl.points.empty()) {
|
||||
pl.points.push_back(scaled(new_pl[i].pos));
|
||||
result.push_back(pl);
|
||||
pl.points.clear();
|
||||
}
|
||||
int left = new_pl[i].pair_idx;
|
||||
int j = (i + 1) % new_pl.size();
|
||||
while (j != beg && new_pl[j].pair_idx != left) {
|
||||
if (new_pl[j].pair_idx != -1 && !new_pl[j].is_forward)
|
||||
left = new_pl[j].pair_idx;
|
||||
j = (j + 1) % new_pl.size();
|
||||
}
|
||||
i = j;
|
||||
skip = true;
|
||||
}
|
||||
} while (i != beg);
|
||||
|
||||
if (!pl.points.empty()) {
|
||||
if (new_pl[i].pair_idx == -1)
|
||||
pl.points.push_back(scaled(new_pl[i].pos));
|
||||
result.push_back(pl);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Polylines contrust_gap_for_skip_points(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon)
|
||||
{
|
||||
if (skip_points.empty()) {
|
||||
insert_skip_polygon = polygon;
|
||||
return Polylines{to_polyline(polygon)};
|
||||
}
|
||||
bool is_left = false;
|
||||
const auto& pt = skip_points.front();
|
||||
if (abs(pt.x()) < wt_width / 2.f) {
|
||||
is_left = true;
|
||||
}
|
||||
return remove_points_from_polygon(polygon, skip_points, gap_length, is_left, insert_skip_polygon);
|
||||
};
|
||||
|
||||
static Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
|
||||
{
|
||||
Polygon res;
|
||||
@@ -632,7 +386,8 @@ public:
|
||||
}
|
||||
|
||||
WipeTowerWriter2& switch_filament_monitoring(bool enable) {
|
||||
m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n";
|
||||
flush_planner_queue();
|
||||
m_gcode += enable ? "M591 R\n" : "M591 S0\n";
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -871,7 +626,7 @@ public:
|
||||
// Set extruder temperature, don't wait by default.
|
||||
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false)
|
||||
{
|
||||
m_gcode += "G4 S0\n"; // to flush planner queue
|
||||
flush_planner_queue();
|
||||
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
|
||||
return *this;
|
||||
}
|
||||
@@ -881,7 +636,7 @@ public:
|
||||
{
|
||||
if (time==0.f)
|
||||
return *this;
|
||||
m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n";
|
||||
m_gcode += wait_command(m_gcode_flavor, time);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -923,8 +678,8 @@ public:
|
||||
}
|
||||
|
||||
WipeTowerWriter2& flush_planner_queue()
|
||||
{
|
||||
m_gcode += "G4 S0\n";
|
||||
{
|
||||
m_gcode += flush_planner_queue_command(m_gcode_flavor);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1245,6 +1000,12 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer,
|
||||
|
||||
|
||||
|
||||
bool WipeTower2::use_gap_wall(const PrintConfig& config)
|
||||
{
|
||||
// The cone wall has its own fully separate generator with no gap machinery.
|
||||
return config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone;
|
||||
}
|
||||
|
||||
WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& default_region_config,int plate_idx, Vec3d plate_origin, const std::vector<std::vector<float>>& wiping_matrix, size_t initial_tool) :
|
||||
m_semm(config.single_extruder_multi_material.value),
|
||||
m_enable_filament_ramming(config.enable_filament_ramming.value),
|
||||
@@ -1272,7 +1033,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
m_rib_width(config.wipe_tower_rib_width),
|
||||
m_extra_rib_length(config.wipe_tower_extra_rib_length),
|
||||
m_wall_type((int)config.wipe_tower_wall_type),
|
||||
m_flat_ironing(config.prime_tower_flat_ironing.value),
|
||||
m_use_gap_wall(use_gap_wall(config)),
|
||||
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
|
||||
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
||||
{
|
||||
@@ -1342,6 +1103,7 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1));
|
||||
else
|
||||
m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
|
||||
m_filpar[idx].is_support = config.filament_is_support.get_at(idx);
|
||||
m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx);
|
||||
m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx);
|
||||
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
||||
@@ -1478,11 +1240,11 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
|
||||
toolchange_Load(writer, cleaning_box); // Prime the tool.
|
||||
if (idx_tool + 1 == tools.size()) {
|
||||
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false);
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false, true);
|
||||
} else {
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
//writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f, false);
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f, false, true);
|
||||
WipeTower::box_coordinates box = cleaning_box;
|
||||
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
@@ -1525,8 +1287,9 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
|
||||
float wipe_area = 0.f;
|
||||
float wipe_volume = 0.f;
|
||||
float ramming_depth = 0.f;
|
||||
bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface;
|
||||
|
||||
|
||||
// Finds this toolchange info
|
||||
if (tool != (unsigned int)(-1))
|
||||
{
|
||||
@@ -1534,6 +1297,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
if ( b.new_tool == tool ) {
|
||||
wipe_volume = b.wipe_volume;
|
||||
wipe_area = b.required_depth;
|
||||
ramming_depth = b.ramming_depth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1571,7 +1335,9 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
writer.speed_override_backup();
|
||||
writer.speed_override(100);
|
||||
|
||||
Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed);
|
||||
// On a boundary wipe start this enters at the wall gap on the first wipe row;
|
||||
// toolchange_Unload() then climbs back up to the ram band along the box interior.
|
||||
Vec2f initial_position = toolchange_entry_pos(m_depth_traversed, ramming_depth, is_first_layer());
|
||||
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||
|
||||
// Increase the extruder driver current to allow fast ramming.
|
||||
@@ -1580,6 +1346,11 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
if (tool != (unsigned int)-1){ // This is not the last change.
|
||||
// Without a ram — or with the boundary wipe start, where the ram band is
|
||||
// quantized to whole rows — the box is planned as whole wipe rows; the wipe
|
||||
// then fills it completely so adjacent purge blocks stay contiguous. Uses the
|
||||
// old tool (m_current_tool before toolchange_Change).
|
||||
const bool fill_box = !tool_ramming_enabled(m_current_tool) || boundary_wipe_start_enabled(m_current_tool);
|
||||
auto new_tool_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
|
||||
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
|
||||
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
|
||||
@@ -1602,7 +1373,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
|
||||
writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f);
|
||||
}
|
||||
}
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer, false, fill_box); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
if (interface_layer) {
|
||||
int interface_temp = m_filpar[tool].interface_print_temperature;
|
||||
if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
|
||||
@@ -1657,11 +1428,20 @@ void WipeTower2::toolchange_Unload(
|
||||
float remaining = xr - xl ; // keeps track of distance to the next turnaround
|
||||
float e_done = 0; // measures E move done from each segment
|
||||
|
||||
// Orca: Do ramming when SEMM and ramming is enabled or when multi tool head when ramming is enabled on the multi tool.
|
||||
const bool do_ramming = (m_semm && m_enable_filament_ramming) || m_filpar[m_current_tool].multitool_ramming;
|
||||
const bool do_ramming = tool_ramming_enabled(m_current_tool);
|
||||
const bool cold_ramming = m_is_mk4mmu3;
|
||||
// Orca: see set_toolchange() — quantized ram band + wipe restart at the boundary.
|
||||
const bool boundary_wipe_start = boundary_wipe_start_enabled(m_current_tool);
|
||||
float planned_ramming_depth = 0.f;
|
||||
if (boundary_wipe_start && m_layer_info != m_plan.end())
|
||||
for (const auto& tch : m_layer_info->tool_changes)
|
||||
if (tch.old_tool == m_current_tool) { planned_ramming_depth = tch.ramming_depth; break; }
|
||||
|
||||
if (do_ramming) {
|
||||
if (boundary_wipe_start)
|
||||
// The entry sits at the wall gap on the first wipe row past the reserved
|
||||
// ram band; step inward first, then move to the band clear of the wall.
|
||||
writer.travel(Vec2f(ramming_start_pos.x(), writer.y()));
|
||||
writer.travel(ramming_start_pos); // move to starting position
|
||||
if (! m_is_mk4mmu3)
|
||||
writer.disable_linear_advance();
|
||||
@@ -1672,7 +1452,8 @@ void WipeTower2::toolchange_Unload(
|
||||
writer.set_position(ramming_start_pos);
|
||||
|
||||
// if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
|
||||
if (do_ramming && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||
// (with a boundary wipe start the band is quantized to whole rows below, so no phase alignment is needed)
|
||||
if (do_ramming && !boundary_wipe_start && (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion ))) {
|
||||
|
||||
// this is y of the center of previous sparse infill border
|
||||
float sparse_beginning_y = 0.f;
|
||||
@@ -1731,6 +1512,28 @@ void WipeTower2::toolchange_Unload(
|
||||
e_done = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: quantize the ram band up to the whole reserved rows (BBL quantizes the
|
||||
// old-tool purge the same way) so no unprinted void is left between the band and
|
||||
// the wipe restarting at the boundary below it.
|
||||
if (planned_ramming_depth > 0.f) {
|
||||
const int reserved_rows = std::max(1, int(std::round(planned_ramming_depth / y_step)));
|
||||
const float last_row_y = ramming_start_pos.y() + (reserved_rows - 1) * y_step;
|
||||
// Same bead model as the ramming segments above: E per mm of ram line.
|
||||
const float e_per_mm = 1.f / (volume_to_length(1.f, line_width, m_layer_height) * filament_area());
|
||||
const float fill_feed = m_filpar[m_current_tool].ramming_speed.empty() ? 3000.f :
|
||||
60.f * volume_to_length(m_filpar[m_current_tool].ramming_speed.back(), line_width, m_layer_height);
|
||||
while (true) {
|
||||
const float target_x = m_left_to_right ? xr : xl;
|
||||
if (std::abs(target_x - writer.x()) > WT_EPSILON)
|
||||
writer.ram(writer.x(), target_x, 0.f, 0.f, e_per_mm * std::abs(target_x - writer.x()), fill_feed);
|
||||
if (writer.y() + 0.5f * y_step > last_row_y)
|
||||
break;
|
||||
writer.travel(writer.x(), writer.y() + y_step, 7200);
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
}
|
||||
|
||||
Vec2f end_of_ramming(writer.x(),writer.y());
|
||||
writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
|
||||
|
||||
@@ -1838,10 +1641,27 @@ void WipeTower2::toolchange_Unload(
|
||||
// this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
|
||||
// the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
|
||||
Vec2f pos = Vec2f(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing_ramming-m_perimeter_width) / 2.f + m_perimeter_width);
|
||||
if (do_ramming)
|
||||
if (planned_ramming_depth > 0.f) {
|
||||
// Orca: restart the wipe at the left-edge boundary on a fresh row below the
|
||||
// quantized ram band so the entry scrub always runs at the wall gap (BBL keeps
|
||||
// CP_TOOLCHANGE_WIPE starting at a box corner the same way). Same lattice
|
||||
// formula as the no-ram branch below, offset by the ram band.
|
||||
writer.travel(Vec2f(ramming_start_pos.x(),
|
||||
cleaning_box.ld.y() + m_depth_traversed +
|
||||
wipe_start_offset_after_ram(planned_ramming_depth, is_first_layer()) + m_perimeter_width), 2400.f);
|
||||
m_left_to_right = true;
|
||||
}
|
||||
else if (do_ramming)
|
||||
writer.travel(pos, 2400.f);
|
||||
else
|
||||
writer.set_position(pos);
|
||||
else {
|
||||
// Orca: with no ram printed there is no ramming geometry to align with. Start the
|
||||
// first wipe row so the purge row lattice continues across the block boundary
|
||||
// (previous box's last row top edge sits at its box top): with the planned depth
|
||||
// of rows * dy, the last row's top edge then lands exactly on this box's top and
|
||||
// no blank band is left between adjacent purge blocks.
|
||||
writer.set_position(Vec2f(end_of_ramming.x(),
|
||||
cleaning_box.ld.y() + m_depth_traversed + wipe_start_offset_after_ram(0.f, is_first_layer()) + m_perimeter_width));
|
||||
}
|
||||
|
||||
writer.resume_preview()
|
||||
.flush_planner_queue();
|
||||
@@ -1920,7 +1740,9 @@ void WipeTower2::toolchange_Wipe(
|
||||
WipeTowerWriter2 &writer,
|
||||
const WipeTower::box_coordinates &cleaning_box,
|
||||
float wipe_volume,
|
||||
bool interface_layer)
|
||||
bool interface_layer,
|
||||
bool priming,
|
||||
bool fill_box)
|
||||
{
|
||||
// Increase flow on first layer, slow down print.
|
||||
writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f))
|
||||
@@ -1929,7 +1751,7 @@ void WipeTower2::toolchange_Wipe(
|
||||
const float& xr = cleaning_box.rd.x();
|
||||
|
||||
writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow);
|
||||
const float line_width = m_perimeter_width * m_extra_flow;
|
||||
const float line_width = wipe_line_width();
|
||||
writer.change_analyzer_line_width(line_width);
|
||||
|
||||
// Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
|
||||
@@ -1937,7 +1759,7 @@ void WipeTower2::toolchange_Wipe(
|
||||
// wipe until the end of the assigned area.
|
||||
|
||||
float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow;
|
||||
float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||
float dy = wipe_row_spacing(is_first_layer()); // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow.
|
||||
// All the calculations in all other places take the spacing into account for all the layers.
|
||||
|
||||
// If spare layers are excluded->if 1 or less toolchange has been done, it must be sill the first layer, too.So slow down.
|
||||
@@ -1950,9 +1772,6 @@ void WipeTower2::toolchange_Wipe(
|
||||
m_left_to_right = !m_left_to_right;
|
||||
}
|
||||
|
||||
const bool do_ironing = m_flat_ironing && (!interface_layer || !m_enable_tower_interface_features);
|
||||
const float ironing_area = m_filpar[m_current_tool].tower_ironing_area;
|
||||
|
||||
// now the wiping itself:
|
||||
for (int i = 0; true; ++i) {
|
||||
if (i!=0) {
|
||||
@@ -1963,22 +1782,45 @@ void WipeTower2::toolchange_Wipe(
|
||||
}
|
||||
|
||||
float traversed_x = writer.x();
|
||||
|
||||
// BBS gap wall: iron the first few mm of the purge, then drag the retracted nozzle
|
||||
// back out through the wall gap and scrub it with a dry spiral centred on the entry
|
||||
// point so the toolchange start blob is not left on the wall (same sequence as the
|
||||
// BBL tower's toolchange_wipe_new; the spiral self-disables when the filament's
|
||||
// tower ironing area is 0). WT2's entry gap always sits at the left-edge entry
|
||||
// point, so only iron when the purge actually starts there heading right (in-place
|
||||
// toolchangers do; SEMM ram/cooling moves leave the nozzle mid-box, far from any gap).
|
||||
if (i == 0 && m_use_gap_wall && !interface_layer && !priming && m_left_to_right &&
|
||||
writer.x() - xl < 2.5f * line_width) {
|
||||
float ironing_length = 3.f;
|
||||
if (xr - writer.x() < ironing_length)
|
||||
ironing_length = std::max(xr - writer.x(), 0.f);
|
||||
const float retract_length = m_filpar[m_current_tool].retract_length;
|
||||
const float retract_speed = m_filpar[m_current_tool].retract_speed * 60.f;
|
||||
writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed);
|
||||
writer.retract(retract_length, retract_speed);
|
||||
writer.travel(writer.x() - 1.5f * ironing_length, writer.y(), 600.f);
|
||||
writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.f);
|
||||
const Vec2f iron_end(writer.x() + ironing_length, writer.y());
|
||||
writer.spiral_flat_ironing(writer.pos(), m_filpar[m_current_tool].tower_ironing_area, m_perimeter_width, flat_iron_speed);
|
||||
writer.travel(iron_end, wipe_speed);
|
||||
writer.retract(-retract_length, retract_speed);
|
||||
}
|
||||
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
else
|
||||
writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
|
||||
if (i == 0 && do_ironing && ironing_area > 0.f) {
|
||||
writer.travel(writer.x(), writer.y(), 600.f);
|
||||
writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, 10.f * 60.f);
|
||||
}
|
||||
|
||||
if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width)
|
||||
break; // in case next line would not fit
|
||||
|
||||
traversed_x -= writer.x();
|
||||
x_to_wipe -= std::abs(traversed_x);
|
||||
if (x_to_wipe < WT_EPSILON) {
|
||||
// Orca: with no ram printed the box was planned as whole wipe rows; fill it
|
||||
// completely (quantizing the purge up to the planned rows) so the next block
|
||||
// can start right above it without a blank band in between.
|
||||
if (!fill_box && x_to_wipe < WT_EPSILON) {
|
||||
writer.travel(m_left_to_right ? xl + 1.5f*line_width : xr - 1.5f*line_width, writer.y(), 7200);
|
||||
break;
|
||||
}
|
||||
@@ -2110,7 +1952,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
||||
poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing);
|
||||
} else {
|
||||
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, false);
|
||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true);
|
||||
}
|
||||
|
||||
// brim (first layer only)
|
||||
@@ -2228,15 +2070,32 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
|
||||
return;
|
||||
|
||||
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height_par);
|
||||
// Orca: Set ramming depth to 0 if ramming is disabled.
|
||||
float ramming_depth = m_enable_filament_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
|
||||
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
|
||||
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
||||
m_plan.back().tool_changes.push_back(set_toolchange(old_tool, new_tool, layer_height_par, wipe_volume, first_layer_plan));
|
||||
}
|
||||
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
|
||||
WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan)
|
||||
{
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length((m_semm ? 0.25f : m_filpar[old_tool].multitool_ramming_time) * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height);
|
||||
// Orca: Reserve ramming depth only when toolchange_Unload() will actually ram,
|
||||
// otherwise the unprinted reservation leaves blank bands between the purge boxes.
|
||||
const bool do_ramming = tool_ramming_enabled(old_tool);
|
||||
// Orca: with the gap wall on a multi-tool printer the ram band is quantized up to
|
||||
// the whole reserved rows and the wipe restarts at the left-edge boundary on a
|
||||
// fresh row below it (BBL parity: the old-tool purge is whole rows and the wipe
|
||||
// always starts at the box corner, where the entry scrub runs).
|
||||
const bool boundary_wipe_start = boundary_wipe_start_enabled(old_tool);
|
||||
float ramming_depth = do_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
|
||||
// first_wipe_line rides for free on the last (partially used) ramming row, which
|
||||
// is already covered by ramming_depth. Without ramming that row does not exist
|
||||
// (and with a boundary wipe start the ram band is quantized to whole rows), so
|
||||
// the whole wipe volume needs reserved wiping depth.
|
||||
float first_wipe_line = (do_ramming && !boundary_wipe_start) ? - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width) : 0.f;
|
||||
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height);
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
@@ -2245,12 +2104,11 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
return WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume);
|
||||
}
|
||||
|
||||
|
||||
@@ -2288,49 +2146,64 @@ void WipeTower2::save_on_last_wipe()
|
||||
continue;
|
||||
|
||||
// Which toolchange will finish_layer extrusions be subtracted from?
|
||||
int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes);
|
||||
int idx = first_toolchange_to_nonsoluble_nonsupport(m_layer_info->tool_changes);
|
||||
|
||||
if (idx == -1) {
|
||||
// In this case, finish_layer will be called at the very beginning.
|
||||
finish_layer().total_extrusion_length_in_plane();
|
||||
}
|
||||
|
||||
const float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
auto recompute_toolchange = [this, width](WipeTowerInfo::ToolChange& toolchange, float volume_to_save) {
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
||||
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
};
|
||||
|
||||
for (int i=0; i<int(m_layer_info->tool_changes.size()); ++i) {
|
||||
auto& toolchange = m_layer_info->tool_changes[i];
|
||||
tool_change(toolchange.new_tool);
|
||||
|
||||
if (i == idx) {
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
|
||||
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
||||
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
recompute_toolchange(toolchange, length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height));
|
||||
} else if (toolchange.wipe_volume < m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower) {
|
||||
// Keep filament_minimal_purge_on_wipe_tower enforced for toolchanges that get
|
||||
// no finish-layer saving, e.g. a support/soluble filament skipped as the
|
||||
// finish filament above. Recomputing only when the clamp binds leaves all
|
||||
// other toolchanges with their planned values bit-for-bit.
|
||||
recompute_toolchange(toolchange, 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int WipeTower2::first_toolchange_to_nonsoluble(
|
||||
// Return the index of the toolchange whose new filament should print the layer's
|
||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
||||
// layer's incoming filament before any toolchange happens.
|
||||
// Like WipeTower::first_toolchange_to_nonsoluble_nonsupport(): support and soluble
|
||||
// filaments bond poorly to the material printed on top of them, so they must not
|
||||
// print the tower's shell when another filament is available on the layer.
|
||||
int WipeTower2::first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const
|
||||
{
|
||||
if (tool_changes.empty())
|
||||
return -1;
|
||||
|
||||
// If a specific wipe tower filament is forced, use it to decide where to finish the layer.
|
||||
if (m_wipe_tower_filament > 0) {
|
||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx) {
|
||||
@@ -2339,8 +2212,19 @@ int WipeTower2::first_toolchange_to_nonsoluble(
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
// Orca: allow calculation of the required depth and wipe volume for soluble toolchanges as well.
|
||||
return tool_changes.empty() ? -1 : 0;
|
||||
|
||||
auto is_wall_filament = [this](size_t tool) {
|
||||
return !m_filpar[tool].is_soluble && !m_filpar[tool].is_support;
|
||||
};
|
||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx)
|
||||
if (is_wall_filament(tool_changes[idx].new_tool))
|
||||
return idx;
|
||||
if (is_wall_filament(tool_changes.front().old_tool))
|
||||
return -1;
|
||||
// Only support/soluble filaments on this layer: keep the first toolchange so the
|
||||
// finish-layer saving and the minimal-purge clamp still apply to it (Orca depth
|
||||
// and wipe volume accounting, see save_on_last_wipe()).
|
||||
return 0;
|
||||
}
|
||||
|
||||
static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
||||
@@ -2363,6 +2247,24 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
||||
}
|
||||
|
||||
|
||||
// Precompute, for every plan layer, the wall openings ("skip points") at each toolchange's
|
||||
// entry position, like WipeTower::get_all_wall_skip_points(). toolchange_entry_pos()
|
||||
// reproduces from the finalized plan where tool_change() will start, so each gap coincides
|
||||
// with the entry travel's target (tcr.start_pos, pre-rotation frame). BBL parity: the gap
|
||||
// sits at the CP_TOOLCHANGE_WIPE start row, never at the ram band.
|
||||
void WipeTower2::compute_wall_skip_points()
|
||||
{
|
||||
m_wall_skip_points.assign(m_plan.size(), std::vector<Vec2f>());
|
||||
for (size_t layer_id = 0; layer_id < m_plan.size(); ++layer_id) {
|
||||
float depth_traversed = 0.f;
|
||||
for (const auto& toolchange : m_plan[layer_id].tool_changes) {
|
||||
m_wall_skip_points[layer_id].emplace_back(
|
||||
toolchange_entry_pos(depth_traversed, toolchange.ramming_depth, layer_id == m_first_layer_idx));
|
||||
depth_traversed += toolchange.required_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
|
||||
// Resulting ToolChangeResults are appended into vector "result"
|
||||
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
|
||||
@@ -2378,12 +2280,41 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
}
|
||||
#endif
|
||||
|
||||
m_rib_length = std::max({m_rib_length, sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width)});
|
||||
if (m_wall_type == (int)wtwRib) {
|
||||
// Rib wall: force a square tower like WipeTower::plan_tower_new(), ignoring the
|
||||
// configured prime_tower_width (the GUI greys it out in rib mode). The planned depths
|
||||
// already include the extra-spacing factors, so sqrt(depth * width) preserves the
|
||||
// purge area. Replan every toolchange for the new width, then re-derive the depths.
|
||||
float max_depth = 0.f;
|
||||
for (const auto& current_plan : m_plan)
|
||||
max_depth = std::max(max_depth, current_plan.depth);
|
||||
if (max_depth > EPSILON) {
|
||||
m_wipe_tower_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width), m_perimeter_width);
|
||||
for (size_t idx = 0; idx < m_plan.size(); ++idx)
|
||||
for (auto& toolchange : m_plan[idx].tool_changes)
|
||||
toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool,
|
||||
m_plan[idx].height, toolchange.wipe_volume,
|
||||
idx == m_first_layer_idx);
|
||||
plan_tower();
|
||||
}
|
||||
|
||||
// Like WipeTower::plan_tower_new(): extend the ribs instead of the tower when the
|
||||
// tower is smaller than the height-based stability minimum.
|
||||
const float min_depth = WipeTower::get_limit_depth_by_height(m_wipe_tower_height);
|
||||
if (m_wipe_tower_depth + EPSILON < min_depth)
|
||||
m_rib_length = std::max(m_rib_length, min_depth * (float)std::sqrt(2.f));
|
||||
}
|
||||
|
||||
const float diagonal = std::sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width);
|
||||
m_rib_length = std::max(m_rib_length, diagonal);
|
||||
m_rib_length += m_extra_rib_length;
|
||||
m_rib_length = std::max(0.f, m_rib_length);
|
||||
m_rib_length = std::max(diagonal, m_rib_length); // a negative extra length must not shrink the ribs below the diagonal
|
||||
m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) /
|
||||
2.f); // Ensure that the rib wall of the wipetower are attached to the infill.
|
||||
|
||||
if (m_use_gap_wall)
|
||||
compute_wall_skip_points();
|
||||
|
||||
m_layer_info = m_plan.begin();
|
||||
m_current_height = 0.f;
|
||||
|
||||
@@ -2410,7 +2341,7 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
|
||||
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
|
||||
|
||||
int idx = first_toolchange_to_nonsoluble(layer.tool_changes);
|
||||
int idx = first_toolchange_to_nonsoluble_nonsupport(layer.tool_changes);
|
||||
WipeTower::ToolChangeResult finish_layer_tcr;
|
||||
|
||||
if (idx == -1) {
|
||||
@@ -2503,8 +2434,7 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
||||
double feedrate,
|
||||
bool first_layer,
|
||||
bool rib_wall,
|
||||
bool extrude_perimeter,
|
||||
bool skip_points)
|
||||
bool extrude_perimeter)
|
||||
{
|
||||
|
||||
float retract_length = m_filpar[m_current_tool].retract_length;
|
||||
@@ -2524,18 +2454,28 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
||||
if (!extrude_perimeter)
|
||||
return wall_polygon;
|
||||
|
||||
if (skip_points) {
|
||||
result_wall = contrust_gap_for_skip_points(wall_polygon, std::vector<Vec2f>(), m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
if (m_use_gap_wall) {
|
||||
// Cut the wall open at each toolchange's entry (see compute_wall_skip_points()).
|
||||
// The vector is empty during the save_on_last_wipe planning passes, which therefore
|
||||
// measure the un-gapped wall — same approximation as the BBL tower.
|
||||
static const std::vector<Vec2f> no_skip_points;
|
||||
const size_t layer_id = size_t(m_layer_info - m_plan.begin());
|
||||
const std::vector<Vec2f>& layer_skip_points =
|
||||
layer_id < m_wall_skip_points.size() ? m_wall_skip_points[layer_id] : no_skip_points;
|
||||
result_wall = construct_gap_for_skip_points(wall_polygon, layer_skip_points, m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
insert_skip_polygon);
|
||||
} else {
|
||||
result_wall.push_back(to_polyline(wall_polygon));
|
||||
insert_skip_polygon = wall_polygon;
|
||||
}
|
||||
writer.generate_path(result_wall, feedrate, retract_length, retract_speed, m_used_fillet);
|
||||
//if (m_cur_layer_id == 0) {
|
||||
// BoundingBox bbox = get_extents(result_wall);
|
||||
// m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
||||
//}
|
||||
// Tower-local shift that puts the rib wall's protruding first-layer min corner at the
|
||||
// configured tower position, like WipeTower::generate_support_wall_new(). Measured on
|
||||
// the un-gapped outline so a wall gap cannot shift the tower.
|
||||
if (rib_wall && is_first_layer()) {
|
||||
BoundingBox bbox = get_extents(insert_skip_polygon);
|
||||
m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
||||
}
|
||||
|
||||
return insert_skip_polygon;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ public:
|
||||
bool is_finish,
|
||||
bool is_contact = false) const;
|
||||
|
||||
// Whether this print cuts wall openings ("skip points") at the toolchange entries.
|
||||
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
|
||||
static bool use_gap_wall(const PrintConfig& config);
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||
@@ -69,9 +73,9 @@ public:
|
||||
const float brim = m_wipe_tower_brim_width_real;
|
||||
return BoundingBoxf(Vec2d(-brim, -brim), Vec2d(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim));
|
||||
}
|
||||
// WT2 doesn't currently compute a rib-origin compensation like WipeTower (m_rib_offset),
|
||||
// so expose a zero offset for consistency purposes (to maintain API parity).
|
||||
Vec2f get_rib_offset() const { return Vec2f::Zero(); }
|
||||
// Tower-local shift that puts the rib wall's first-layer min corner at the configured
|
||||
// tower position, like WipeTower::get_rib_offset(). Zero unless the rib wall is used.
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
float get_rib_width() const { return m_rib_width; }
|
||||
float get_rib_length() const { return m_rib_length; }
|
||||
|
||||
@@ -149,6 +153,7 @@ public:
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
bool is_soluble = false;
|
||||
bool is_support = false;
|
||||
int temperature = 0;
|
||||
int first_layer_temperature = 0;
|
||||
int interface_print_temperature = 0;
|
||||
@@ -220,7 +225,6 @@ private:
|
||||
float m_perimeter_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
bool m_flat_ironing = false;
|
||||
bool m_enable_tower_interface_features = false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower = false;
|
||||
bool m_prev_layer_had_interface = false;
|
||||
@@ -231,6 +235,12 @@ private:
|
||||
float m_rib_width = 10;
|
||||
float m_extra_rib_length = 0;
|
||||
float m_rib_length = 0;
|
||||
Vec2f m_rib_offset = Vec2f::Zero();
|
||||
bool m_use_gap_wall = false;
|
||||
// Per plan layer, each toolchange's entry position (tower-local, un-shifted frame):
|
||||
// where the wall is cut open so the entry travel does not cross the printed wall.
|
||||
// Filled by compute_wall_skip_points() once the plan is final.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
|
||||
bool m_enable_arc_fitting = false;
|
||||
|
||||
@@ -278,6 +288,37 @@ private:
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
// Purge row lattice of toolchange_Wipe(): row pitch and extrusion width.
|
||||
float wipe_row_spacing(bool first_layer) const { return (first_layer ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; }
|
||||
float wipe_line_width() const { return m_perimeter_width * m_extra_flow; }
|
||||
|
||||
// Whether toolchange_Unload() rams this (old) tool out.
|
||||
bool tool_ramming_enabled(size_t tool) const { return (m_semm && m_enable_filament_ramming) || m_filpar[tool].multitool_ramming; }
|
||||
// Whether the wipe restarts at the box boundary on a fresh row below the quantized
|
||||
// ram band after ramming this (old) tool out (multi-tool gap wall; SEMM keeps the
|
||||
// stock continue-from-ram-end behavior).
|
||||
bool boundary_wipe_start_enabled(size_t tool) const { return tool_ramming_enabled(tool) && !m_semm && m_use_gap_wall; }
|
||||
|
||||
// With a boundary wipe start the wipe begins on a fresh row below the quantized ram
|
||||
// band. Y offset from the box start to that first wipe row.
|
||||
float wipe_start_offset_after_ram(float ramming_depth, bool first_layer) const
|
||||
{
|
||||
return ramming_depth + wipe_row_spacing(first_layer) - (m_perimeter_width + wipe_line_width()) / 2.f;
|
||||
}
|
||||
|
||||
// Tower-local entry position of a toolchange whose box starts depth_traversed into
|
||||
// the layer: the box corner, moved down to the first wipe row when the plan gives
|
||||
// it a boundary wipe start (ramming_depth > 0 iff the unload rams). tool_change()
|
||||
// enters here and compute_wall_skip_points() cuts the wall gap here, so the routed
|
||||
// entry, the gap and the wipe scrub all share one opening.
|
||||
Vec2f toolchange_entry_pos(float depth_traversed, float ramming_depth, bool first_layer) const
|
||||
{
|
||||
Vec2f pos(m_perimeter_width / 2.f, m_perimeter_width / 2.f + depth_traversed);
|
||||
if (!m_semm && m_use_gap_wall && ramming_depth > 0.f)
|
||||
pos.y() += wipe_start_offset_after_ram(ramming_depth, first_layer);
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Calculates extrusion flow needed to produce required line width for given layer height
|
||||
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
|
||||
{
|
||||
@@ -328,9 +369,10 @@ private:
|
||||
std::vector<float> m_used_filament_length;
|
||||
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble(
|
||||
// Return the index of the toolchange whose new filament should print the layer's
|
||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
||||
// layer's incoming filament before any toolchange happens.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
void toolchange_Unload(
|
||||
@@ -353,7 +395,9 @@ private:
|
||||
WipeTowerWriter2 &writer,
|
||||
const WipeTower::box_coordinates &cleaning_box,
|
||||
float wipe_volume,
|
||||
bool interface_layer);
|
||||
bool interface_layer,
|
||||
bool priming = false,
|
||||
bool fill_box = false);
|
||||
|
||||
|
||||
Polygon generate_support_rib_wall(WipeTowerWriter2& writer,
|
||||
@@ -361,8 +405,7 @@ private:
|
||||
double feedrate,
|
||||
bool first_layer,
|
||||
bool rib_wall,
|
||||
bool extrude_perimeter,
|
||||
bool skip_points);
|
||||
bool extrude_perimeter);
|
||||
|
||||
Polygon generate_support_cone_wall(
|
||||
WipeTowerWriter2& writer,
|
||||
@@ -372,6 +415,12 @@ private:
|
||||
float spacing);
|
||||
|
||||
Polygon generate_rib_polygon(const WipeTower::box_coordinates& wt_box);
|
||||
|
||||
void compute_wall_skip_points();
|
||||
|
||||
// Computes the depth reserved for a toolchange (shared by plan_toolchange() and the
|
||||
// rib-wall square-tower replanning in generate()).
|
||||
WipeTowerInfo::ToolChange set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "I18N.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry/ArcWelder.hpp"
|
||||
#include "Line.hpp"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
@@ -1018,45 +1019,48 @@ std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, c
|
||||
}
|
||||
|
||||
if (!this->config.enable_arc_fitting) { // Orca: if arc fitting is disabled, approximate the arc with small linear segments
|
||||
std::ostringstream oss;
|
||||
const double z_start = m_pos(2); // starting Z height
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// Determine number of segments based on Resolution
|
||||
// --------------------------------------------------------------------
|
||||
const double ref_resolution = 0.01; // reference resolution in mm
|
||||
const double ref_segments = 8.0; // reference number of segments at reference resolution
|
||||
|
||||
// number of linear segments to use for approximating the arc, clamp between 4 and 16
|
||||
const int segments = std::clamp(int(std::round(ref_segments * (ref_resolution / m_resolution))), 4, 16);
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
const double px = m_pos(0) - m_x_offset; // take plate offset into consideration
|
||||
const double py = m_pos(1) - m_y_offset; // take plate offset into consideration
|
||||
const double cx = px + ij_offset(0); // center x
|
||||
const double cy = py + ij_offset(1); // center y
|
||||
const double radius = ij_offset.norm(); // radius
|
||||
|
||||
// Number of linear segments approximating the circle, chosen so that a chord never deviates
|
||||
// from the true arc by more than the slicing resolution. A resolution of 0 means "no
|
||||
// simplification", which has no finite segment count, so it takes the upper bound.
|
||||
constexpr size_t min_segments = 8; // keep a small spiral visibly round
|
||||
constexpr size_t max_segments = 128; // bound the emitted G-code
|
||||
const int segments = int(m_resolution > 0. ?
|
||||
std::clamp(Geometry::ArcWelder::arc_discretization_steps(radius, 2. * M_PI, m_resolution), min_segments, max_segments) :
|
||||
max_segments);
|
||||
|
||||
const double a0 = std::atan2(py - cy, px - cx); // start angle
|
||||
const double delta = 2.0 * M_PI; // CCW full circle
|
||||
|
||||
if (full_gcode_comment)
|
||||
oss << ";" << comment << "\n";
|
||||
auto emit_point = [&output](const Vec3d &point) {
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
output += w.string();
|
||||
};
|
||||
|
||||
oss << "G1 F" << (speed * 60.0) << "\n"; // set feedrate
|
||||
output.reserve(size_t(segments) * 40); // ~40 characters per emitted G1 line
|
||||
|
||||
GCodeG1Formatter w; // set feedrate
|
||||
w.emit_f(speed * 60.0);
|
||||
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
|
||||
output += w.string();
|
||||
|
||||
// approximate the arc with small linear segments (without the last point which is added later to ensure exactness)
|
||||
for (int i = 1; i < segments; ++i) {
|
||||
double t = double(i) / segments; // parametric position along arc
|
||||
double a = a0 + delta * t; // CCW arc param
|
||||
double x = cx + radius * std::cos(a); // point on circle
|
||||
double y = cy + radius * std::sin(a); // point on circle
|
||||
double zz = z_start + (z - z_start) * t; // interpolated Z height
|
||||
|
||||
oss << "G1 X" << x << " Y" << y << " Z" << zz << "\n";
|
||||
const double t = double(i) / segments; // parametric position along arc
|
||||
const double a = a0 + 2. * M_PI * t; // CCW arc param, full circle
|
||||
emit_point(Vec3d(cx + radius * std::cos(a), // point on circle
|
||||
cy + radius * std::sin(a),
|
||||
z_start + (z - z_start) * t)); // interpolated Z height
|
||||
}
|
||||
|
||||
oss << "G1 X" << px << " Y" << py << " Z" << z << "\n"; // final point to ensure exactness
|
||||
output = oss.str();
|
||||
emit_point(Vec3d(px, py, z)); // final point to ensure exactness
|
||||
} else { // Orca: if arc fitting is enabled emit a G2/G3 command for the spiral lift
|
||||
output = std::string("G17") + (full_gcode_comment ? " ; XY plane for arc\n" : "\n");
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
|
||||
void* volume{nullptr};
|
||||
std::vector<int>* plane_indices{nullptr};
|
||||
Transform3d world_tran;
|
||||
Transform3d world_tran = Transform3d::Identity();
|
||||
std::shared_ptr<std::vector<SurfaceFeature>> world_plane_features{nullptr};
|
||||
std::shared_ptr<SurfaceFeature> origin_surface_feature{nullptr};
|
||||
|
||||
|
||||
+35
-85
@@ -48,100 +48,50 @@ struct OrientMesh {
|
||||
|
||||
};
|
||||
|
||||
// params for minimizing support area
|
||||
struct OrientParamsArea {
|
||||
float TAR_A = 0.015f;
|
||||
float TAR_B = 0.177f;
|
||||
float RELATIVE_F = 20;
|
||||
float CONTOUR_F = 0.5f;
|
||||
float BOTTOM_F = 2.5f;
|
||||
float BOTTOM_HULL_F = 0.1f;
|
||||
float TAR_C = 0.1f;
|
||||
float TAR_D = 1;
|
||||
float TAR_E = 0.0115f;
|
||||
float FIRST_LAY_H = 0.2f;//0.0475;
|
||||
float VECTOR_TOL = -0.00083f;
|
||||
float NEGL_FACE_SIZE = 0.01f;
|
||||
float ASCENT = -0.5f;
|
||||
float PLAFOND_ADV = 0.0599f;
|
||||
float CONTOUR_AMOUNT = 0.0182427f;
|
||||
float OV_H = 2.574f;
|
||||
float height_offset = 2.3728f;
|
||||
float height_log = 0.041375f;
|
||||
float height_log_k = 1.9325457f;
|
||||
float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face 0.9997f
|
||||
float LAF_MIN = 0.97f; // cos(14\degree) 0.9703f
|
||||
float TAR_LAF = 0.001f; //0.01f
|
||||
float TAR_PROJ_AREA = 0.1f;
|
||||
float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the object may be unstable
|
||||
float BOTTOM_MAX = 2000; // max bottom area. If get to it the object is stable enough (further increase bottom area won't do more help)
|
||||
float height_to_bottom_hull_ratio_MIN = 1;
|
||||
float BOTTOM_HULL_MAX = 2000;// max bottom hull area
|
||||
float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle = 60.f;
|
||||
bool use_low_angle_face = true;
|
||||
bool min_volume = false;
|
||||
Eigen::Vector3f fun_dir;
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = true;
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = {};
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition = {};
|
||||
|
||||
OrientParamsArea() = default;
|
||||
};
|
||||
|
||||
struct OrientParams {
|
||||
float TAR_A = 0.01f;//0.128f;
|
||||
float TAR_B = 0.177f;
|
||||
float RELATIVE_F= 6.610621027964314f;
|
||||
float CONTOUR_F = 0.23228623269775997f;
|
||||
float BOTTOM_F = 1.167152017941474f;
|
||||
float BOTTOM_HULL_F = 0.1f;
|
||||
float TAR_C = 0.24308070476924726f;
|
||||
float TAR_D = 0.6284515508160871f;
|
||||
float TAR_E = 0;//0.032157292647062234;
|
||||
float FIRST_LAY_H = 0.2f;//0.029;
|
||||
float VECTOR_TOL = -0.0011163303070972383f;
|
||||
float NEGL_FACE_SIZE = 0.1f;
|
||||
float ASCENT= -0.5f;
|
||||
float PLAFOND_ADV = 0.04079208948120519f;
|
||||
float CONTOUR_AMOUNT = 0.0101472219892684f;
|
||||
float OV_H = 1.0370178217794535f;
|
||||
float height_offset = 2.7417608343142073f;
|
||||
float height_log = 0.06442030687034085f;
|
||||
float height_log_k = 0.3933594673063997f;
|
||||
float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face //0.9997f;
|
||||
float LAF_MIN= 0.9703f; // cos(14\degree) 0.9703f;
|
||||
float TAR_LAF = 0.01f; //0.1f
|
||||
float TAR_PROJ_AREA = 0.1f;
|
||||
float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the objects may be unstable
|
||||
float BOTTOM_MAX = 2000; //400
|
||||
float height_to_bottom_hull_ratio_MIN = 1;
|
||||
float BOTTOM_HULL_MAX = 2000;// max bottom hull area to clip //600
|
||||
float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle = 60.f;
|
||||
bool use_low_angle_face = true;
|
||||
bool min_volume = false;
|
||||
Eigen::Vector3f fun_dir;
|
||||
float TAR_A { 0.01f }; // 0.128f;
|
||||
float TAR_B { 0.177f };
|
||||
float RELATIVE_F { 6.610621027964314f };
|
||||
float CONTOUR_F { 0.23228623269775997f };
|
||||
float BOTTOM_F { 1.167152017941474f };
|
||||
float BOTTOM_HULL_F { 0.1f };
|
||||
float TAR_C { 0.24308070476924726f };
|
||||
float TAR_D { 0.6284515508160871f };
|
||||
float TAR_E { 0}; // 0.032157292647062234;
|
||||
float FIRST_LAY_H { 0.2f}; // 0.029;
|
||||
float VECTOR_TOL { -0.0011163303070972383f };
|
||||
float NEGL_FACE_SIZE { 0.1f };
|
||||
float ASCENT { -0.5f };
|
||||
float PLAFOND_ADV { 0.04079208948120519f };
|
||||
float CONTOUR_AMOUNT { 0.0101472219892684f };
|
||||
float OV_H { 1.0370178217794535f };
|
||||
float height_offset { 2.7417608343142073f };
|
||||
float height_log { 0.06442030687034085f };
|
||||
float height_log_k { 0.3933594673063997f };
|
||||
float LAF_MAX { 0.999f }; // cos(1.4\degree) for low angle face //0.9997f;
|
||||
float LAF_MIN { 0.9703f }; // cos(14\degree) 0.9703f;
|
||||
float TAR_LAF { 0.01f }; // 0.1f
|
||||
float TAR_PROJ_AREA { 0.1f };
|
||||
float BOTTOM_MIN { 0.1f }; // min bottom area. If lower than it the objects may be unstable
|
||||
float BOTTOM_MAX { 2000 }; // 400
|
||||
float height_to_bottom_hull_ratio_MIN { 1 };
|
||||
float BOTTOM_HULL_MAX { 2000 }; // max bottom hull area to clip //600
|
||||
float APPERANCE_FACE_SUPP { 3 }; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle { 60.f };
|
||||
bool use_low_angle_face { true };
|
||||
bool min_volume { false };
|
||||
Eigen::Vector3f fun_dir {};
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = false;
|
||||
bool parallel { false };
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = {};
|
||||
std::function<void(unsigned, std::string)> progressind {};
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition = {};
|
||||
std::function<bool(void)> stopcondition {};
|
||||
|
||||
OrientParams() = default;
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -1757,7 +1977,7 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
ExPolygons unsupported = diff_ex(last, *this->lower_slices, ApplySafetyOffset::Yes);
|
||||
if (!unsupported.empty()) {
|
||||
//remove small overhangs
|
||||
ExPolygons unsupported_filtered = offset2_ex(unsupported, double(-perimeter_spacing), double(perimeter_spacing));
|
||||
ExPolygons unsupported_filtered = opening_ex(unsupported, perimeter_spacing);
|
||||
|
||||
if (!unsupported_filtered.empty()) {
|
||||
//to_draw.insert(to_draw.end(), last.begin(), last.end());
|
||||
@@ -1870,35 +2090,40 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
|
||||
//TODO: add other polys as holes inside this one (-margin)
|
||||
} else { // if(this->config->counterbore_hole_bridging.value == chbBridges)
|
||||
// Orca: Partial counterbore bridging is mask-based. Preserve the supported
|
||||
// remainder (`last`) and use simplified BridgeDetector coverage to derive the
|
||||
// remainder and use simplified BridgeDetector coverage to derive the
|
||||
// bridgeable counterbore span. The span is grown from supported material,
|
||||
// shrunk back, stripped from `last`, and expanded back. It is then prevented
|
||||
// from intruding deeper into `last` than the explicit anchor overlap.
|
||||
// Finally, add the allowed anchor band from `last` then remove the
|
||||
// shrunk back, stripped from the remaining normal surface, and expanded back.
|
||||
// It is then prevented from intruding deeper into it than the explicit anchor overlap.
|
||||
// Finally, add the allowed anchor band from it then remove the
|
||||
// narrow hole-side wall contact, which must remain unbridgeable.
|
||||
|
||||
last = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
const ExPolygons remaining = diff_ex(last, unsupported_filtered, ApplySafetyOffset::Yes);
|
||||
|
||||
ExPolygons bridgeable_filtered;
|
||||
|
||||
for (ExPolygon& poly : bridgeable) {
|
||||
poly.simplify(perimeter_spacing, &bridgeable_filtered);
|
||||
}
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, ext_perimeter_width);
|
||||
|
||||
// Get rid of coarseness of the resulted bridgeable area by using the original supported area as reference.
|
||||
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
|
||||
bridgeable_filtered = union_ex(offset_ex(last, perimeter_spacing), bridgeable_filtered);
|
||||
// This is to avoid keeping tiny bridgeable areas that are far from the supported area, or protrude into it.
|
||||
bridgeable_filtered = union_ex(offset_ex(remaining, perimeter_spacing), bridgeable_filtered);
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, -perimeter_spacing);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, last, ApplySafetyOffset::Yes);
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, remaining, ApplySafetyOffset::Yes);
|
||||
bridgeable_filtered = opening_ex(bridgeable_filtered, perimeter_spacing); // filter noise from the diff_ex
|
||||
bridgeable_filtered = offset_ex(bridgeable_filtered, perimeter_spacing); // restore the size to the original bridgeable area
|
||||
// Safety measure: Keep the bridge mask from intruding deeper into the
|
||||
// supported anchor region (`last`) than the explicit anchor overlap.
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(last, -bridge_anchor_offset));
|
||||
// supported anchor region than the explicit anchor overlap.
|
||||
bridgeable_filtered = diff_ex(bridgeable_filtered, offset_ex(remaining, -bridge_anchor_offset));
|
||||
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(last, offset_ex(unsupported_filtered, bridge_anchor_offset));
|
||||
ExPolygons bridge_anchor_areas = intersection_ex(remaining, offset_ex(unsupported_filtered, bridge_anchor_offset));
|
||||
unsupported_filtered = union_ex(bridgeable_filtered, bridge_anchor_areas); // add bridge anchor
|
||||
unsupported_filtered = opening_ex(unsupported_filtered, bridge_anchor_offset); // remove anchor area from hole-side walls, it must remain unbridgeable
|
||||
|
||||
// update 'last' only if we have a valid bridgeable area, otherwise we will lose the original unsupported area
|
||||
if (!unsupported_filtered.empty())
|
||||
last = remaining;
|
||||
// TODO: Fix the case with thin outer walls around the bridge (1~2 walls) where classic wall
|
||||
// might generate two walls in a tiny space or non at all if "Detect thin walls" is not activated
|
||||
}
|
||||
@@ -2138,6 +2363,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 +2380,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 +2405,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 +2439,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 +2459,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());
|
||||
|
||||
@@ -1791,6 +1791,8 @@ namespace client
|
||||
// from UTF8 to UTF16 don't bail out.
|
||||
msg += boost::nowide::narrow(boost::nowide::widen(error_line));
|
||||
msg += '\n';
|
||||
// The error dialog (MsgDialog.cpp) renders this excerpt monospaced. It recognizes a source
|
||||
// line directly above a caret line of spaces and a single '^'.
|
||||
for (size_t i = 0; i < error_pos; ++ i)
|
||||
msg += ' ';
|
||||
msg += "^\n";
|
||||
|
||||
@@ -129,13 +129,13 @@ public:
|
||||
std::vector<PathFittingData> fitting_result;
|
||||
//BBS: simplify points by arc fitting
|
||||
void simplify_by_fitting_arc(double tolerance);
|
||||
//BBS:
|
||||
void reset_to_linear_move();
|
||||
//BBS:
|
||||
Polylines equally_spaced_lines(double distance) const;
|
||||
|
||||
private:
|
||||
void append_fitting_result_after_append_points();
|
||||
void append_fitting_result_after_append_polyline(const Polyline& src);
|
||||
void reset_to_linear_move();
|
||||
bool split_fitting_result_before_index(const size_t index, Point &new_endpoint, std::vector<PathFittingData>& data) const;
|
||||
bool split_fitting_result_after_index(const size_t index, Point &new_startpoint, std::vector<PathFittingData>& data) const;
|
||||
};
|
||||
|
||||
@@ -1037,6 +1037,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"fill_multiline",
|
||||
"gyroid_optimized",
|
||||
"sparse_infill_pattern",
|
||||
"sparse_infill_smooth_factor",
|
||||
"lateral_lattice_angle_1",
|
||||
"lateral_lattice_angle_2",
|
||||
"infill_overhang_angle",
|
||||
@@ -1090,7 +1091,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
"top_surface_speed", "support_speed", "support_object_xy_distance", "support_object_first_layer_gap", "support_interface_speed",
|
||||
"bridge_speed", "internal_bridge_speed", "gap_infill_speed", "travel_speed", "travel_speed_z", "initial_layer_speed",
|
||||
"outer_wall_acceleration", "initial_layer_acceleration", "top_surface_acceleration", "default_acceleration", "skirt_type", "skirt_loops", "skirt_speed","min_skirt_length", "skirt_distance", "skirt_start_angle", "skirt_height","single_loop_draft_shield", "draft_shield",
|
||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"brim_width", "brim_object_gap", "brim_flow_ratio", "brim_use_efc_outline", "combine_brims", "brim_type", "brim_ears_max_angle", "brim_ears_detection_length", "brim_ears_outer_only", "enable_support", "support_type", "support_threshold_angle", "support_threshold_overlap","enforce_support_layers",
|
||||
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
|
||||
"support_base_pattern", "support_base_pattern_spacing", "support_expansion", "support_style",
|
||||
// BBS
|
||||
@@ -1350,6 +1351,8 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
||||
"filament_retraction_length",
|
||||
"filament_retraction_minimum_travel",
|
||||
"filament_retraction_speed",
|
||||
"filament_retract_length_toolchange",
|
||||
"filament_retract_restart_extra_toolchange",
|
||||
"filament_wipe",
|
||||
"filament_z_hop",
|
||||
"filament_z_hop_types",
|
||||
@@ -1403,7 +1406,7 @@ static std::vector<std::string> s_Preset_machine_limits_options {
|
||||
static std::vector<std::string> s_Preset_printer_options {
|
||||
"printer_technology",
|
||||
"printable_area", "extruder_printable_area", "support_parallel_printheads", "parallel_printheads_count", "parallel_printheads_bed_exclude_areas", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor",
|
||||
"fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||
"gcode_skip_config_block", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
|
||||
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
|
||||
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
|
||||
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
|
||||
@@ -3783,12 +3786,14 @@ void PresetCollection::update_library_profile_excluded_from()
|
||||
}
|
||||
|
||||
// Check all presets that has the same alias as the filament presets with empty compatible_printers in Orca Filament Library.
|
||||
// A printer specific profile supersedes the generic one, no matter whether it lives in a vendor bundle or in the
|
||||
// library itself.
|
||||
for (const Preset& preset : m_presets) {
|
||||
if (preset.vendor == nullptr || preset.vendor->name == PresetBundle::ORCA_FILAMENT_LIBRARY)
|
||||
if (preset.vendor == nullptr)
|
||||
continue;
|
||||
|
||||
const auto* compatible_printers = dynamic_cast<const ConfigOptionStrings*>(preset.config.option("compatible_printers"));
|
||||
// All profiles in concrete vendor profile shouldn't have empty compatible_printers, but here we check it for safety.
|
||||
// Profiles with empty compatible_printers are the generic ones, they never supersede anything.
|
||||
if (compatible_printers == nullptr || compatible_printers->values.empty())
|
||||
continue;
|
||||
auto itr = excluded_froms.find(preset.alias);
|
||||
|
||||
+48
-48
@@ -3409,7 +3409,11 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
|
||||
}
|
||||
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
||||
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
||||
m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
// Orca: when the process variant columns cannot be matched (degenerate
|
||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||
// above instead of poisoning the map with -1.
|
||||
int slot_index = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : f_maps[index] - 1;
|
||||
}
|
||||
|
||||
m_full_print_config = m_ori_full_print_config;
|
||||
@@ -4017,10 +4021,33 @@ void Print::_make_wipe_tower()
|
||||
// in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume.
|
||||
WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(),
|
||||
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
||||
// Orca: the tower's first-layer flow follows the user's first-layer flow ratio (BBS reads
|
||||
// its initial_layer_flow_ratio here — STUDIO-14254; first_layer_flow_ratio is Orca's analog,
|
||||
// default 1.0 in both). Honor the set_other_flow_ratios gate that governs the option
|
||||
// everywhere else.
|
||||
wipe_tower.set_first_layer_flow_ratio(m_default_object_config.set_other_flow_ratios
|
||||
? float(m_default_region_config.first_layer_flow_ratio)
|
||||
: 1.f);
|
||||
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
||||
wipe_tower.set_filament_map(this->get_filament_maps());
|
||||
// Vortek H2C: pass nozzle-level map for carousel rotation detection in tool_change_new()
|
||||
wipe_tower.set_filament_nozzle_map(this->get_filament_nozzle_maps());
|
||||
// Per-layer filament->nozzle grouping. sort_and_build_data() above publishes it on the Print
|
||||
// for by-layer prints; by-object prints publish only later (psSkirtBrim), so fall back to the
|
||||
// ToolOrdering's own copy there. set_extruder() below dereferences it, so it must be set first.
|
||||
auto print_group_result = get_layered_nozzle_group_result();
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult &nozzle_group_result =
|
||||
print_group_result ? *print_group_result : m_wipe_tower_data.tool_ordering.get_layered_nozzle_group_result();
|
||||
wipe_tower.set_nozzle_group_result(nozzle_group_result);
|
||||
{
|
||||
// Orca: acceleration options are object-scope (PrintConfig members in BBS), so resolve
|
||||
// the per-variant columns here; initial_layer_travel_acceleration is FloatOrPercent
|
||||
// over travel_acceleration and needs the full config to resolve.
|
||||
std::vector<double> first_layer_travel_accels;
|
||||
for (size_t i = 0; i < m_config.initial_layer_travel_acceleration.values.size(); ++i)
|
||||
first_layer_travel_accels.emplace_back(m_full_print_config.get_abs_value_at("initial_layer_travel_acceleration", i));
|
||||
wipe_tower.set_accelerations(m_default_object_config.default_acceleration.values,
|
||||
m_default_object_config.initial_layer_acceleration.values,
|
||||
m_default_object_config.travel_acceleration.values,
|
||||
first_layer_travel_accels);
|
||||
}
|
||||
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
||||
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
||||
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
||||
@@ -4056,27 +4083,19 @@ void Print::_make_wipe_tower()
|
||||
multi_extruder_flush.emplace_back(wipe_volumes);
|
||||
}
|
||||
|
||||
// Use NozzleStatusRecorder for per-carousel-slot tracking (BBS pattern).
|
||||
// The original Orca code tracked per-extruder (2 slots), which collapsed all
|
||||
// carousel filaments into one slot and caused massive redundant AMS flushing.
|
||||
auto group_result = get_layered_nozzle_group_result();
|
||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder (BBS pattern); the layered
|
||||
// group result set on the tower above resolves each filament to its nozzle slot per layer.
|
||||
MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder;
|
||||
// Fallback (group_result == null) per-physical-nozzle tracking, matching the original
|
||||
// pre-port behavior: remembers the last filament loaded in each physical nozzle slot.
|
||||
std::vector<unsigned int> nozzle_cur_filament_ids(nozzle_nums, (unsigned int) -1);
|
||||
|
||||
std::vector<int>filament_maps = get_filament_maps();
|
||||
int layer_idx = -1;
|
||||
|
||||
unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
||||
// Initialize NozzleStatusRecorder with the first filament's carousel slot
|
||||
if (group_result) {
|
||||
auto nozzle = group_result->get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||
{
|
||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||
if (nozzle)
|
||||
nozzle_recorder.set_nozzle_status(nozzle->group_id, current_filament_id, nozzle->extruder_id);
|
||||
} else {
|
||||
size_t cur_nozzle_id = filament_maps[current_filament_id] - 1;
|
||||
nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id;
|
||||
}
|
||||
|
||||
for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
|
||||
@@ -4095,8 +4114,8 @@ void Print::_make_wipe_tower()
|
||||
float volume_to_purge = 0;
|
||||
|
||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder
|
||||
if (group_result) {
|
||||
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_idx);
|
||||
{
|
||||
auto nozzle_info = nozzle_group_result.get_nozzle_for_filament(filament_id, layer_idx);
|
||||
if (nozzle_info) {
|
||||
int extruder_id = nozzle_info->extruder_id;
|
||||
int nozzle_id = nozzle_info->group_id;
|
||||
@@ -4115,22 +4134,6 @@ void Print::_make_wipe_tower()
|
||||
}
|
||||
nozzle_recorder.set_nozzle_status(nozzle_id, filament_id, extruder_id);
|
||||
}
|
||||
} else {
|
||||
// Fallback: original Orca per-physical-nozzle path (non-carousel printers).
|
||||
// Flush source is the last filament that occupied THIS nozzle, guarded so the
|
||||
// first use of a nozzle incurs no flush.
|
||||
int nozzle_id = filament_maps[filament_id] - 1;
|
||||
unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id];
|
||||
if (pre_filament_id != (unsigned int) -1 && pre_filament_id != filament_id) {
|
||||
volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id];
|
||||
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast)
|
||||
? m_config.flush_multiplier_fast.get_at(nozzle_id)
|
||||
: m_config.flush_multiplier.get_at(nozzle_id);
|
||||
volume_to_purge *= flush_multiplier;
|
||||
volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(
|
||||
*this, current_filament_id, filament_id, volume_to_purge);
|
||||
}
|
||||
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
||||
}
|
||||
|
||||
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
||||
@@ -4138,29 +4141,21 @@ void Print::_make_wipe_tower()
|
||||
float grab_purge_volume = m_config.grab_length.get_at(grab_extruder_id) * 2.4; //(diameter/2)^2*PI=2.4
|
||||
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
||||
|
||||
// Select prime volume per-filament: nozzle change (carousel rotation) uses
|
||||
// filament_prime_volume_nc, filament change (same nozzle slot) uses filament_prime_volume.
|
||||
// Prime volume per-filament: the tower now picks extruder-change vs nozzle-change
|
||||
// (carousel) internally per plan layer, so pass both candidates (BBS pattern).
|
||||
float wipe_volume_ec = filament_id < m_config.filament_prime_volume.values.size()
|
||||
? m_config.filament_prime_volume.values[filament_id]
|
||||
: (float) m_config.prime_volume;
|
||||
float wipe_volume_nc = filament_id < m_config.filament_prime_volume_nc.values.size()
|
||||
? m_config.filament_prime_volume_nc.values[filament_id]
|
||||
: (float) m_config.prime_volume;
|
||||
|
||||
float prime_volume = wipe_volume_ec;
|
||||
if (group_result) {
|
||||
bool is_nozzle_change = group_result->are_filaments_same_extruder(current_filament_id, filament_id, layer_idx) &&
|
||||
!group_result->are_filaments_same_nozzle(current_filament_id, filament_id, layer_idx);
|
||||
if (is_nozzle_change) {
|
||||
prime_volume = wipe_volume_nc;
|
||||
}
|
||||
}
|
||||
if (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) {
|
||||
prime_volume = 15.f;
|
||||
wipe_volume_ec = 15.f;
|
||||
wipe_volume_nc = 15.f;
|
||||
}
|
||||
|
||||
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
||||
prime_volume, volume_to_purge);
|
||||
wipe_volume_ec, wipe_volume_nc, volume_to_purge);
|
||||
current_filament_id = filament_id;
|
||||
}
|
||||
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||
@@ -4338,7 +4333,12 @@ void Print::_make_wipe_tower()
|
||||
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
||||
config().wipe_tower_fillet_wall.value);
|
||||
const Vec3d origin = Vec3d::Zero();
|
||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
||||
// FakeWipeTower::pos is a bed-frame translation applied after rotation
|
||||
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
|
||||
// tower-local rib offset must be rotated into the bed frame first.
|
||||
m_fake_wipe_tower.rib_offset = Eigen::Rotation2Df(Geometry::deg2rad((float)config().wipe_tower_rotation_angle.value)) *
|
||||
wipe_tower.get_rib_offset();
|
||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
||||
config().initial_layer_print_height, m_wipe_tower_data.depth,
|
||||
m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width,
|
||||
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
||||
|
||||
@@ -1355,7 +1355,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
||||
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
||||
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
||||
m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
// Orca: when the process variant columns cannot be matched (degenerate
|
||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||
// above instead of poisoning the map with -1.
|
||||
int slot_index = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : filament_maps[index] - 1;
|
||||
}
|
||||
|
||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||
@@ -1411,6 +1415,16 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
num_extruders_changed = true;
|
||||
}
|
||||
}
|
||||
else if (! print_diff.empty()) {
|
||||
// Orca: m_config can diverge from an unchanged full config (e.g. the in-slice retract
|
||||
// override recompute writing different values than the apply-time computation). The
|
||||
// invalidation above already fired for print_diff, so repair m_config here as well;
|
||||
// otherwise the divergence is never corrected and every subsequent apply of the same
|
||||
// config invalidates the result again, forever.
|
||||
m_placeholder_parser.apply_config(filament_overrides);
|
||||
m_config.apply_only(new_full_config, print_diff, true);
|
||||
m_config.apply(filament_overrides);
|
||||
}
|
||||
|
||||
ModelObjectStatusDB model_object_status_db;
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ const std::vector<std::string> filament_extruder_override_keys = {
|
||||
"filament_deretraction_speed",
|
||||
"filament_retract_restart_extra", //not in filament_options_with_variant, added on 20250816
|
||||
"filament_retraction_minimum_travel",
|
||||
"filament_retract_length_toolchange",
|
||||
"filament_retract_restart_extra_toolchange",
|
||||
// BBS: floats
|
||||
"filament_wipe_distance",
|
||||
// bools
|
||||
@@ -1937,6 +1939,13 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(1));
|
||||
|
||||
def = this->add("brim_ears_outer_only", coBool);
|
||||
def->label = L("Brim ears outer only");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("Generate mouse ears only on the outer contour of the model, excluding holes and enclosed sections.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("compatible_printers", coStrings);
|
||||
def->label = L("Select printers");
|
||||
def->mode = comAdvanced;
|
||||
@@ -3457,6 +3466,18 @@ void PrintConfigDef::init_fff_params()
|
||||
def->enum_labels.push_back(L("Octagram Spiral"));
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipCrossHatch));
|
||||
|
||||
def = this->add("sparse_infill_smooth_factor", coPercent);
|
||||
def->label = L("Sparse infill smooth factor");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original right-angle path, "
|
||||
"while 100% produces the largest possible curves between adjacent infill lines. "
|
||||
"Currently applies only to the Hilbert Curve.");
|
||||
def->sidetext = "%";
|
||||
def->min = 0;
|
||||
def->max = 100;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionPercent(0));
|
||||
|
||||
def = this->add("top_surface_acceleration", coFloats);
|
||||
def->label = L("Top surface");
|
||||
def->category = L("Speed");
|
||||
@@ -4240,6 +4261,15 @@ void PrintConfigDef::init_fff_params()
|
||||
def->readonly = false;
|
||||
def->set_default_value(new ConfigOptionEnum<GCodeFlavor>(gcfMarlinLegacy));
|
||||
|
||||
def = this->add("gcode_skip_config_block", coBool);
|
||||
def->label = L("Skip G-code config block");
|
||||
def->tooltip = L("Do not write the CONFIG_BLOCK (slicer configuration key/value pairs) into the G-code file. "
|
||||
"This can help with printers whose firmware crashes when parsing these comment lines "
|
||||
"(e.g. Anycubic go-klipper). Note: the G-code file will no longer contain slicer settings, "
|
||||
"so importing it back into OrcaSlicer will not restore the configuration.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("pellet_modded_printer", coBool);
|
||||
def->label = L("Pellet Modded Printer");
|
||||
def->tooltip = L("Enable this option if your printer uses pellets instead of filaments.");
|
||||
@@ -4273,7 +4303,7 @@ void PrintConfigDef::init_fff_params()
|
||||
"slow down.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(0));
|
||||
|
||||
|
||||
//BBS
|
||||
def = this->add("infill_combination", coBool);
|
||||
def->label = L("Infill combination");
|
||||
@@ -5706,12 +5736,10 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionFloatsNullable{10});
|
||||
|
||||
def = this->add("retract_length_toolchange", coFloats);
|
||||
def->label = L("Length");
|
||||
//def->full_label = L("Retraction Length (Toolchange)");
|
||||
def->full_label = "Retraction Length (Toolchange)";
|
||||
//def->tooltip = L("When retraction is triggered before changing tool, filament is pulled back "
|
||||
// "by the specified amount (the length is measured on raw filament, before it enters "
|
||||
// "the extruder).");
|
||||
def->label = L("Retraction Length (Toolchange)");
|
||||
def->tooltip = L("When retraction is triggered before changing tool, filament is pulled back "
|
||||
"by the specified amount (the length is measured on raw filament, before it enters "
|
||||
"the extruder).");
|
||||
def->sidetext = L("mm"); // millimeters, CIS languages need translation
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats { 10. });
|
||||
@@ -5965,7 +5993,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionFloats { 0. });
|
||||
|
||||
def = this->add("retract_restart_extra_toolchange", coFloats);
|
||||
def->label = L("Extra length on restart");
|
||||
def->label = L("Extra length on restart (Toolchange)");
|
||||
def->tooltip = L("When the retraction is compensated after changing tool, the extruder will push "
|
||||
"this additional amount of filament.");
|
||||
def->sidetext = L("mm"); // millimeters, CIS languages need translation
|
||||
@@ -8135,10 +8163,12 @@ void PrintConfigDef::init_extruder_option_keys()
|
||||
"long_retractions_when_cut",
|
||||
"retract_after_wipe",
|
||||
"retract_before_wipe",
|
||||
"retract_length_toolchange",
|
||||
"retract_lift_above",
|
||||
"retract_lift_below",
|
||||
"retract_lift_enforce",
|
||||
"retract_restart_extra",
|
||||
"retract_restart_extra_toolchange",
|
||||
"retract_when_changing_layer",
|
||||
"retraction_distances_when_cut",
|
||||
"retraction_length",
|
||||
@@ -9226,6 +9256,8 @@ std::set<std::string> filament_options_with_variant = {
|
||||
"filament_retract_lift_below",
|
||||
"filament_retract_lift_enforce",
|
||||
"filament_retract_restart_extra",
|
||||
"filament_retract_length_toolchange",
|
||||
"filament_retract_restart_extra_toolchange",
|
||||
"filament_retraction_speed",
|
||||
"filament_deretraction_speed",
|
||||
"filament_retraction_minimum_travel",
|
||||
@@ -10516,6 +10548,44 @@ int DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std
|
||||
return count;
|
||||
}
|
||||
|
||||
// Orca: BBL system profiles ship full-width print_extruder_id/print_extruder_variant columns, but
|
||||
// custom multi-extruder printers only ever get the machine-scope columns synthesized for them (see
|
||||
// extend_extruder_variant); the process scope keeps the length-1 defaults, both in presets and in
|
||||
// 3mf project configs. Expanding with that degenerate map makes every per-extruder lookup fail, and
|
||||
// because both keys are themselves in print_options_with_variant, the expansion then latches a
|
||||
// full-width-but-wrong [1,1,...] map that also defeats the generated_extruder_id fallback in
|
||||
// get_index_for_extruder. Synthesize the process columns from the printer's extruder_variant_list
|
||||
// (same token walk as extend_extruder_variant) before expanding.
|
||||
static void ensure_process_variant_columns(DynamicPrintConfig &config, const DynamicPrintConfig &printer_config)
|
||||
{
|
||||
auto id_opt = dynamic_cast<ConfigOptionInts *>(config.option("print_extruder_id"));
|
||||
auto variant_opt = dynamic_cast<ConfigOptionStrings *>(config.option("print_extruder_variant"));
|
||||
auto list_opt = dynamic_cast<const ConfigOptionStrings *>(printer_config.option("extruder_variant_list"));
|
||||
if (!id_opt || !variant_opt || !list_opt)
|
||||
return;
|
||||
if (id_opt->values.size() != 1 || variant_opt->values.size() != 1)
|
||||
return;
|
||||
|
||||
std::vector<int> ids;
|
||||
std::vector<std::string> variants;
|
||||
for (int i = 0; i < int(list_opt->values.size()); ++i) {
|
||||
std::vector<std::string> tokens;
|
||||
boost::split(tokens, list_opt->get_at(i), boost::is_any_of(","), boost::token_compress_on);
|
||||
for (std::string &token : tokens) {
|
||||
boost::trim(token);
|
||||
if (token.empty())
|
||||
continue;
|
||||
ids.push_back(i + 1);
|
||||
variants.push_back(token);
|
||||
}
|
||||
}
|
||||
// A single column is the legitimate single-extruder layout, not a degenerate one.
|
||||
if (ids.size() <= 1)
|
||||
return;
|
||||
id_opt->values = std::move(ids);
|
||||
variant_opt->values = std::move(variants);
|
||||
}
|
||||
|
||||
std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt)
|
||||
{
|
||||
@@ -10557,6 +10627,8 @@ std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicP
|
||||
variant_count = 1;
|
||||
}
|
||||
else {
|
||||
if (id_name == "print_extruder_id")
|
||||
ensure_process_variant_columns(*this, printer_config);
|
||||
// Orca: emit the slots first, then size variant_count from what was actually
|
||||
// emitted. extruder_nozzle_volume_count only equals the emitted total when every
|
||||
// extruder carries per-type stats; an extruder with an empty stats entry combined
|
||||
|
||||
@@ -1082,6 +1082,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionFloat, brim_width))
|
||||
((ConfigOptionFloat, brim_ears_detection_length))
|
||||
((ConfigOptionFloat, brim_ears_max_angle))
|
||||
((ConfigOptionBool, brim_ears_outer_only))
|
||||
((ConfigOptionFloat, skirt_start_angle))
|
||||
((ConfigOptionBool, bridge_no_support))
|
||||
((ConfigOptionFloat, elefant_foot_compensation))
|
||||
@@ -1264,6 +1265,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionString, sparse_infill_rotate_template))
|
||||
((ConfigOptionPercent, sparse_infill_density))
|
||||
((ConfigOptionEnum<InfillPattern>, sparse_infill_pattern))
|
||||
((ConfigOptionPercent, sparse_infill_smooth_factor))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_1))
|
||||
((ConfigOptionFloat, lateral_lattice_angle_2))
|
||||
((ConfigOptionFloat, infill_overhang_angle))
|
||||
@@ -1545,7 +1547,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionBool, gcode_add_line_number))
|
||||
((ConfigOptionBool, bbl_bed_temperature_gcode))
|
||||
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
|
||||
|
||||
((ConfigOptionBool, gcode_skip_config_block))
|
||||
((ConfigOptionFloat, time_cost))
|
||||
((ConfigOptionString, layer_change_gcode))
|
||||
((ConfigOptionString, time_lapse_gcode))
|
||||
|
||||
@@ -1175,6 +1175,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "brim_type"
|
||||
|| opt_key == "brim_ears_max_angle"
|
||||
|| opt_key == "brim_ears_detection_length"
|
||||
|| opt_key == "brim_ears_outer_only"
|
||||
// BBS: brim generation depends on printing speed
|
||||
|| opt_key == "outer_wall_speed"
|
||||
|| opt_key == "small_perimeter_speed"
|
||||
@@ -1364,7 +1365,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 +1400,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"
|
||||
@@ -1411,6 +1410,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "infill_overhang_angle") {
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "sparse_infill_pattern"
|
||||
|| opt_key == "sparse_infill_smooth_factor"
|
||||
|| opt_key == "symmetric_infill_y_axis"
|
||||
|| opt_key == "infill_shift_step"
|
||||
|| opt_key == "sparse_infill_rotate_template"
|
||||
@@ -1434,6 +1434,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 +1778,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);
|
||||
|
||||
@@ -56,12 +56,12 @@ public:
|
||||
int & i,
|
||||
Eigen::Matrix<double, 1, 3> &closest)
|
||||
{
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist =
|
||||
size_t idx_unsigned { 0 };
|
||||
Vec3d closest_vec3d { Vec3d::Zero() };
|
||||
const double dist {
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
its.vertices, its.indices, m_tree, point, idx_unsigned,
|
||||
closest_vec3d);
|
||||
closest_vec3d) };
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
|
||||
@@ -51,9 +51,4 @@
|
||||
// Enable extension of tool position imgui dialog to show actual speed profile
|
||||
#define ENABLE_ACTUAL_SPEED_DEBUG 1
|
||||
|
||||
// Disable layout inspector for public release
|
||||
#if BBL_RELEASE_TO_PUBLIC
|
||||
#define WXINSPECTOR_DISABLE
|
||||
#endif
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
+64
-14
@@ -70,6 +70,9 @@ float FullTransparentModdifiedToFixAlpha = 0.3f;
|
||||
// value like 0.18f could not because in C++ (int)(0.18f * 255) == 45 however in OpenGL it renders this as 46
|
||||
// which breaks the `SelectMachineDialog::record_edge_pixels_data()` function!
|
||||
float FULL_BLACK_THRESHOLD = 0.2f;
|
||||
// Keep depth_tex away from texture unit 0 to avoid sampler-type aliasing with
|
||||
// shadow/environment samplers when realistic view is disabled.
|
||||
static constexpr int OUTLINE_DEPTH_TEX_UNIT = 5;
|
||||
|
||||
Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors)
|
||||
{
|
||||
@@ -518,6 +521,37 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glStencilMask(0xFF));
|
||||
glsafe(::glDisable(GL_STENCIL_TEST));
|
||||
// render the outline using depth buffer and discard the pixels that are not on the outline
|
||||
// The silhouette is resolved per sample in the shader (see DetectSilho in gouraud.fs/phong.fs).
|
||||
// That needs the GL 3.2 entry points and a shader that declares depth_tex as sampler2DMS, which
|
||||
// only the 140 ones do and only under GL_ARB_texture_multisample - so ask the compiled program
|
||||
// rather than the GL version, or a sampler2D ends up bound to a multisample texture.
|
||||
// Only the Arb branch below allocates a multisample texture, so keep the target consistent with it.
|
||||
const bool use_msaa_outline = framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb &&
|
||||
GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 2) &&
|
||||
shader->get_uniform_location("msaa_samples") >= 0;
|
||||
const GLenum depth_tex_target = use_msaa_outline ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
||||
// Keep the depth texture off image unit 0. The object shaders leave shadow_map (and
|
||||
// environment_tex) at the default sampler value 0 whenever the shadow pass is skipped - which is
|
||||
// the case with realistic view off - and GL forbids two sampler types referring to the same image
|
||||
// unit. A sampler2DMS on unit 0 then makes every draw fail with INVALID_OPERATION on drivers that
|
||||
// enforce it (Mesa), i.e. the model disappears entirely. Unit 5 is unused (shadow_map takes 4).
|
||||
const int depth_tex_unit = OUTLINE_DEPTH_TEX_UNIT;
|
||||
int aa_samples = 1;
|
||||
if (use_msaa_outline) {
|
||||
if (const AppConfig* app_config = GUI::wxGetApp().app_config; app_config != nullptr) {
|
||||
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
|
||||
if (value == "2" || value == "4" || value == "8" || value == "16")
|
||||
aa_samples = ::atoi(value.c_str());
|
||||
}
|
||||
// Never request more samples than the driver supports for depth textures (a 1-sample texture
|
||||
// is used when MSAA is disabled, keeping a single code path for the sampler2DMS shader).
|
||||
GLint max_samples = 1;
|
||||
glsafe(::glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &max_samples));
|
||||
if (aa_samples > max_samples)
|
||||
aa_samples = max_samples < 1 ? 1 : max_samples;
|
||||
if (aa_samples < 1)
|
||||
aa_samples = 1;
|
||||
}
|
||||
// 1st. render pass, render the model into a separate render target that has only depth buffer
|
||||
GLuint depth_fbo = 0;
|
||||
GLuint depth_tex = 0;
|
||||
@@ -525,21 +559,26 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
glsafe(::glGenFramebuffers(1, &depth_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
if (use_msaa_outline) {
|
||||
// Multisample textures do not take filter/wrap parameters.
|
||||
glsafe(::glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, aa_samples, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), GL_TRUE));
|
||||
} else {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
||||
}
|
||||
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_tex_target, depth_tex, 0));
|
||||
} else {
|
||||
glsafe(::glGenFramebuffersEXT(1, &depth_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fbo));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glGenTextures(1, &depth_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
||||
@@ -550,12 +589,15 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
||||
}
|
||||
// Unbind before drawing: the texture is this framebuffer's depth attachment, so leaving it bound
|
||||
// to a sampled unit would be a feedback loop.
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
||||
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
||||
model.render(shader);
|
||||
else
|
||||
model.render(this->tverts_range, shader);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
// 2nd. render pass, just a normal render with the depth buffer passed as a texture
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
@@ -565,13 +607,17 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
||||
}
|
||||
shader->set_uniform("is_outline", true);
|
||||
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
||||
shader->set_uniform("depth_tex", 0);
|
||||
shader->set_uniform("msaa_samples", aa_samples);
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("depth_tex", depth_tex_unit);
|
||||
simple_render(shader, model_objects, colors);
|
||||
|
||||
// Some clean up to do
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
||||
glsafe(::glBindTexture(depth_tex_target, 0));
|
||||
glsafe(::glActiveTexture(GL_TEXTURE0));
|
||||
shader->set_uniform("is_outline", false);
|
||||
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
@@ -1075,6 +1121,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
||||
|
||||
const float support_normal_z = get_selection_support_normal_z();
|
||||
|
||||
// Prime depth_tex on every frame so non-outline draws do not keep the
|
||||
// default sampler unit 0, which can conflict with other sampler types.
|
||||
shader->set_uniform("depth_tex", OUTLINE_DEPTH_TEX_UNIT);
|
||||
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
||||
if (type == ERenderType::Transparent) {
|
||||
|
||||
@@ -70,6 +70,12 @@ void ConfigManipulation::toggle_line(const std::string& opt_key, const bool togg
|
||||
cb_toggle_line(opt_key, toggle, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::set_option_label(const std::string& opt_key, const wxString& label, int opt_index)
|
||||
{
|
||||
if (cb_set_option_label)
|
||||
cb_set_option_label(opt_key, label, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) {
|
||||
if (is_msg_dlg_already_exist)
|
||||
return;
|
||||
@@ -703,12 +709,14 @@ 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_line("sparse_infill_smooth_factor", pattern == ipHilbertCurve);
|
||||
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 +759,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).
|
||||
@@ -806,14 +814,19 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_field("outer_wall_filament_id", have_perimeters || have_brim);
|
||||
toggle_field("inner_wall_filament_id", have_perimeters || have_brim);
|
||||
|
||||
bool have_brim_ear = (config->opt_enum<BrimType>("brim_type") == btEar);
|
||||
const BrimType brim_type = config->opt_enum<BrimType>("brim_type");
|
||||
const bool have_auto_brim_ear = brim_type == btEar;
|
||||
const bool have_painted_brim_ear = brim_type == btPainted;
|
||||
set_option_label("brim_width", have_auto_brim_ear ? _L("Brim ear radius") : _L("Brim width"));
|
||||
const auto brim_width = config->opt_float("brim_width");
|
||||
// disable brim_ears_max_angle and brim_ears_detection_length if brim_width is 0
|
||||
// Automatic brim ear settings require a non-zero brim width.
|
||||
toggle_field("brim_ears_max_angle", brim_width > 0.0f);
|
||||
toggle_field("brim_ears_detection_length", brim_width > 0.0f);
|
||||
// hide brim_ears_max_angle and brim_ears_detection_length if brim_ear is not selected
|
||||
toggle_line("brim_ears_max_angle", have_brim_ear);
|
||||
toggle_line("brim_ears_detection_length", have_brim_ear);
|
||||
// Painted ears carry their own radius and do not depend on brim_width.
|
||||
toggle_field("brim_ears_outer_only", have_painted_brim_ear || brim_width > 0.0f);
|
||||
toggle_line("brim_ears_max_angle", have_auto_brim_ear);
|
||||
toggle_line("brim_ears_detection_length", have_auto_brim_ear);
|
||||
toggle_line("brim_ears_outer_only", have_auto_brim_ear || have_painted_brim_ear);
|
||||
|
||||
// Hide Elephant foot compensation layers if elefant_foot_compensation is not enabled
|
||||
toggle_line("elefant_foot_compensation_layers", config->opt_float("elefant_foot_compensation") > 0 || config->option<ConfigOptionPercent>("elefant_foot_layers_density")->get_abs_value(1.0f) < 1.0f);
|
||||
@@ -1008,7 +1021,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"));
|
||||
|
||||
@@ -29,6 +29,7 @@ class ConfigManipulation
|
||||
std::function<void()> load_config = nullptr;
|
||||
std::function<void (const std::string&, bool toggle, int opt_index)> cb_toggle_field = nullptr;
|
||||
std::function<void(const std::string &, bool toggle, int opt_index)> cb_toggle_line = nullptr;
|
||||
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr;
|
||||
// callback to propagation of changed value, if needed
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
|
||||
//BBS: change local config to const DynamicPrintConfig
|
||||
@@ -45,10 +46,12 @@ public:
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change,
|
||||
//BBS: change local config to DynamicPrintConfig
|
||||
const DynamicPrintConfig* local_config = nullptr,
|
||||
wxWindow* msg_dlg_parent = nullptr) :
|
||||
wxWindow* msg_dlg_parent = nullptr,
|
||||
std::function<void(const std::string &, const wxString &, int opt_index)> cb_set_option_label = nullptr) :
|
||||
load_config(load_config),
|
||||
cb_toggle_field(cb_toggle_field),
|
||||
cb_toggle_line(cb_toggle_line),
|
||||
cb_set_option_label(cb_set_option_label),
|
||||
cb_value_change(cb_value_change),
|
||||
m_msg_dlg_parent(msg_dlg_parent),
|
||||
local_config(local_config) {}
|
||||
@@ -58,6 +61,7 @@ public:
|
||||
load_config = nullptr;
|
||||
cb_toggle_field = nullptr;
|
||||
cb_toggle_line = nullptr;
|
||||
cb_set_option_label = nullptr;
|
||||
cb_value_change = nullptr;
|
||||
}
|
||||
|
||||
@@ -67,6 +71,7 @@ public:
|
||||
t_config_option_keys const &applying_keys() const;
|
||||
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
void toggle_line(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
void set_option_label(const std::string& field_key, const wxString& label, int opt_index = -1);
|
||||
|
||||
// FFF print
|
||||
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false, const bool is_plate_config = false);
|
||||
|
||||
@@ -496,6 +496,26 @@ namespace Slic3r
|
||||
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
|
||||
}
|
||||
|
||||
void DeviceManager::clear_other_devices()
|
||||
{
|
||||
// why: on agent swap, keep "My Devices" but drop the transient "Other Devices"
|
||||
// Those belong to the previous agent's network scan; the new agent's start_discovery re-populates its own.
|
||||
const auto my = get_my_machine_list();
|
||||
for (auto it = localMachineList.begin(); it != localMachineList.end();)
|
||||
{
|
||||
if (my.find(it->first) == my.end())
|
||||
{
|
||||
// not a "My Device" -> an "Other Device"
|
||||
delete it->second;
|
||||
it = localMachineList.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceManager::set_selected_machine(std::string dev_id)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "set_selected_machine=" << dev_id
|
||||
@@ -558,7 +578,6 @@ namespace Slic3r
|
||||
}
|
||||
else
|
||||
{
|
||||
Slic3r::GUI::wxGetApp().reset_unsigned_plugin_warning();
|
||||
if (m_agent)
|
||||
{
|
||||
if (it->second->connection_type() != "lan" || it->second->connection_type().empty())
|
||||
@@ -851,7 +870,9 @@ namespace Slic3r
|
||||
int result = m_agent->get_user_print_info(&http_code, &body, provider);
|
||||
if (result == 0)
|
||||
{
|
||||
parse_user_print_info(body);
|
||||
// parse_user_print_info and on_machine_alive (SSDP for discovery) both mutate the same userMachineList map.
|
||||
// on_machine_alive mutates the map on the UI thread, do the same for parse_user_print_info.
|
||||
Slic3r::GUI::wxGetApp().CallAfter([this, body]() { parse_user_print_info(body); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,17 +899,15 @@ namespace Slic3r
|
||||
|
||||
void DeviceManager::load_last_machine()
|
||||
{
|
||||
if (userMachineList.empty()) return;
|
||||
else if (userMachineList.size() == 1) {
|
||||
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
|
||||
} else {
|
||||
const auto& last_monitor_machine = get_user_last_machine();
|
||||
if (userMachineList.find(last_monitor_machine) != userMachineList.end()) {
|
||||
set_selected_machine(last_monitor_machine);
|
||||
} else {
|
||||
this->set_selected_machine(userMachineList.begin()->second->get_dev_id());
|
||||
}
|
||||
}
|
||||
// Only reconnect the remembered cloud machine. Do not select an arbitrary
|
||||
// first machine: agent swaps intentionally leave the selection empty until
|
||||
// the new agent explicitly selects its configured printer.
|
||||
if (userMachineList.empty())
|
||||
return;
|
||||
|
||||
const auto& last_monitor_machine = get_user_last_machine();
|
||||
if (userMachineList.find(last_monitor_machine) != userMachineList.end())
|
||||
set_selected_machine(last_monitor_machine);
|
||||
}
|
||||
|
||||
void DeviceManager::OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state)
|
||||
|
||||
@@ -48,6 +48,10 @@ public:
|
||||
MachineObject* get_selected_machine();
|
||||
bool set_selected_machine(std::string dev_id);
|
||||
|
||||
// why: clears stale sidebar sync-status / AMS visuals. Public so the printer-agent
|
||||
// swap path can reuse it instead of duplicating the two sidebar calls.
|
||||
void OnSelectedMachineLost();
|
||||
|
||||
void record_user_last_machine(const std::string& dev_id);
|
||||
std::string get_user_last_machine() const;
|
||||
|
||||
@@ -70,6 +74,8 @@ public:
|
||||
void erase_user_machine(std::string dev_id) { userMachineList.erase(dev_id); }
|
||||
void clean_user_info(bool keep_local_selection = false);
|
||||
|
||||
void clear_other_devices();
|
||||
|
||||
void load_last_machine();
|
||||
void update_user_machine_list_info(const std::string& provider);
|
||||
void parse_user_print_info(std::string body);
|
||||
@@ -110,7 +116,6 @@ private:
|
||||
void check_pushing();
|
||||
|
||||
void OnMachineBindStateChanged(MachineObject* obj, const std::string& new_state);
|
||||
void OnSelectedMachineLost();
|
||||
void OnSelectedMachineChanged(const std::string& pre_dev_id, const std::string& new_dev_id);
|
||||
|
||||
|
||||
|
||||
@@ -4647,6 +4647,40 @@ void MachineObject::set_ctt_dlg( wxString text){
|
||||
}
|
||||
}
|
||||
|
||||
void MachineObject::show_unsupported_dlg(int code)
|
||||
{
|
||||
// why: a dead control invites repeat clicks, and the frame is modeless - without the guard
|
||||
// every click stacks another one. Same shape as set_ctt_dlg above, including the reset on
|
||||
// both hide and close so a dismissed dialog can reappear on the next attempt.
|
||||
if (m_unsupported_dlg_shown) {
|
||||
return;
|
||||
}
|
||||
m_unsupported_dlg_shown = true;
|
||||
|
||||
// why: two codes so the user learns which kind of dead end this is - the slicer having no
|
||||
// translation for the command, or the printer's own config lacking the hardware to run it.
|
||||
const wxString text = (code == ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE) ?
|
||||
_L("This printer is not configured with the hardware this control needs.") :
|
||||
_L("This control is not supported on this printer.");
|
||||
|
||||
// note: constructed directly rather than through CallAfter because every publish_json caller
|
||||
// is on the UI thread - clicks come from wx handlers, and the agent marshals its own push
|
||||
// callbacks back to main before parse_json runs. set_ctt_dlg relies on the same property.
|
||||
auto unsupported_dlg = new GUI::SecondaryCheckDialog(nullptr, wxID_ANY, _L("Warning"),
|
||||
GUI::SecondaryCheckDialog::VisibleButtons::ONLY_CONFIRM);
|
||||
unsupported_dlg->update_text(text);
|
||||
unsupported_dlg->Bind(wxEVT_SHOW, [this](auto& e) {
|
||||
if (!e.IsShown()) {
|
||||
m_unsupported_dlg_shown = false;
|
||||
}
|
||||
});
|
||||
unsupported_dlg->Bind(wxEVT_CLOSE_WINDOW, [this](auto& e) {
|
||||
e.Skip();
|
||||
m_unsupported_dlg_shown = false;
|
||||
});
|
||||
unsupported_dlg->on_show();
|
||||
}
|
||||
|
||||
int MachineObject::publish_gcode(std::string gcode_str)
|
||||
{
|
||||
json j;
|
||||
|
||||
@@ -272,9 +272,11 @@ public:
|
||||
bool m_is_online;
|
||||
bool m_lan_mode_connection_state{false};
|
||||
bool m_set_ctt_dlg{ false };
|
||||
bool m_unsupported_dlg_shown{ false };
|
||||
void set_lan_mode_connection_state(bool state) {m_lan_mode_connection_state = state;};
|
||||
bool get_lan_mode_connection_state() {return m_lan_mode_connection_state;};
|
||||
void set_ctt_dlg( wxString text);
|
||||
void show_unsupported_dlg(int code);
|
||||
int parse_msg_count = 0;
|
||||
int keep_alive_count = 0;
|
||||
std::chrono::system_clock::time_point last_update_time; /* last received print data from machine */
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiAmsHumidityPopup.cpp
|
||||
/**********************************************************
|
||||
* File: uiAmsHumidityPopup.cpp
|
||||
* Description: The popup with DevAms Humidity
|
||||
*
|
||||
* \n class uiAmsHumidityPopup
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#include "uiAmsHumidityPopup.h"
|
||||
|
||||
@@ -191,4 +191,4 @@ void uiAmsPercentHumidityDryPopup::msw_rescale()
|
||||
|
||||
} // namespace GUI
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiAmsHumidityPopup.h
|
||||
/**********************************************************
|
||||
* File: uiAmsHumidityPopup.h
|
||||
* Description: The popup with DevAms Humidity
|
||||
*
|
||||
* \n class uiAmsHumidityPopup
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#pragma once
|
||||
#include "slic3r/GUI/Widgets/AMSItem.hpp"
|
||||
@@ -68,7 +68,7 @@ private:
|
||||
|
||||
wxStaticBitmap* m_dry_state_img;
|
||||
Label* m_dry_state;
|
||||
|
||||
|
||||
Label* m_humidity_header;
|
||||
Label* m_humidity_label;
|
||||
|
||||
@@ -81,4 +81,4 @@ private:
|
||||
wxSizer* m_sizer;
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiDeviceUpdateVersion.cpp
|
||||
/**********************************************************
|
||||
* File: uiDeviceUpdateVersion.cpp
|
||||
* Description: The panel with firmware info
|
||||
*
|
||||
* \n class uiDeviceUpdateVersion
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#include "uiDeviceUpdateVersion.h"
|
||||
|
||||
@@ -114,4 +114,4 @@ void uiDeviceUpdateVersion::CreateWidgets()
|
||||
Layout();
|
||||
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//**********************************************************/
|
||||
/* File: uiDeviceUpdateVersion.h
|
||||
/**********************************************************
|
||||
* File: uiDeviceUpdateVersion.h
|
||||
* Description: The panel with firmware info
|
||||
*
|
||||
* \n class uiDeviceUpdateVersion
|
||||
//**********************************************************/
|
||||
**********************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <wx/panel.h>
|
||||
@@ -44,4 +44,4 @@ private:
|
||||
wxStaticText* m_dev_version;
|
||||
wxStaticBitmap* m_dev_upgrade_indicator;
|
||||
};
|
||||
};// end of namespace Slic3r::GUI
|
||||
};// end of namespace Slic3r::GUI
|
||||
|
||||
+176
-118
@@ -35,6 +35,7 @@
|
||||
#include "Widgets/TextCtrl.h"
|
||||
|
||||
#include "../Utils/ColorSpaceConvert.hpp"
|
||||
#include "../Utils/NetworkAgentFactory.hpp"
|
||||
#ifdef __WXOSX__
|
||||
#define wxOSX true
|
||||
#else
|
||||
@@ -1336,27 +1337,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());
|
||||
|
||||
@@ -1377,6 +1358,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))
|
||||
@@ -1421,39 +1406,6 @@ using choice_ctrl = ::ComboBox; // BBS
|
||||
|
||||
static std::map<std::string, DynamicList*> dynamic_lists;
|
||||
|
||||
static bool is_plugin_printer_agent_key(const std::string& value)
|
||||
{
|
||||
return value.rfind("plugin:", 0) == 0;
|
||||
}
|
||||
|
||||
static int printer_agent_item_for_enum_index(const choice_ctrl* field, int enum_index)
|
||||
{
|
||||
if (!field)
|
||||
return -1;
|
||||
|
||||
const unsigned int count = field->GetCount();
|
||||
for (unsigned int idx = 0; idx < count; ++idx) {
|
||||
if (void* data = field->GetClientData(idx)) {
|
||||
const int stored = static_cast<int>(reinterpret_cast<uintptr_t>(data)) - 1;
|
||||
if (stored == enum_index)
|
||||
return static_cast<int>(idx);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int printer_agent_enum_index_for_item(const choice_ctrl* field, int item_index, int fallback)
|
||||
{
|
||||
if (!field || item_index < 0)
|
||||
return fallback;
|
||||
|
||||
if (void* data = field->GetClientData(item_index))
|
||||
return static_cast<int>(reinterpret_cast<uintptr_t>(data)) - 1;
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void Choice::register_dynamic_list(std::string const &optname, DynamicList *list) { dynamic_lists.emplace(optname, list); }
|
||||
|
||||
void DynamicList::update()
|
||||
@@ -1536,33 +1488,7 @@ void Choice::BUILD()
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
||||
if (! m_opt.enum_labels.empty() || ! m_opt.enum_values.empty()) {
|
||||
if (m_opt_id == "printer_agent") {
|
||||
const bool has_builtin_agents = std::any_of(m_opt.enum_values.begin(), m_opt.enum_values.end(),
|
||||
[](const std::string& value) { return !is_plugin_printer_agent_key(value); });
|
||||
const bool has_plugin_agents = std::any_of(m_opt.enum_values.begin(), m_opt.enum_values.end(),
|
||||
[](const std::string& value) { return is_plugin_printer_agent_key(value); });
|
||||
|
||||
auto append_agent_rows = [this, temp](bool plugins) {
|
||||
for (size_t i = 0; i < m_opt.enum_values.size(); ++i) {
|
||||
const bool is_plugin = is_plugin_printer_agent_key(m_opt.enum_values[i]);
|
||||
if (is_plugin != plugins)
|
||||
continue;
|
||||
|
||||
const wxString label = i < m_opt.enum_labels.size() ? _(m_opt.enum_labels[i]) : wxString(m_opt.enum_values[i]);
|
||||
const int item = temp->Append(label);
|
||||
temp->SetClientData(item, reinterpret_cast<void*>(static_cast<uintptr_t>(i + 1)));
|
||||
}
|
||||
};
|
||||
|
||||
if (has_builtin_agents) {
|
||||
temp->Append(_L("System agents"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(false);
|
||||
}
|
||||
if (has_plugin_agents) {
|
||||
temp->Append(_L("Plugins"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(true);
|
||||
}
|
||||
} else if (m_opt.enum_labels.empty()) {
|
||||
if (m_opt.enum_labels.empty()) {
|
||||
// Append non-localized enum_values
|
||||
for (auto el : m_opt.enum_values)
|
||||
temp->Append(el);
|
||||
@@ -1669,7 +1595,7 @@ void Choice::set_selection()
|
||||
switch (m_opt.type) {
|
||||
case coEnum:{
|
||||
const int val = m_opt.default_value->getInt();
|
||||
field->SetSelection(m_opt_id == "printer_agent" ? printer_agent_item_for_enum_index(field, val) : val);
|
||||
field->SetSelection(val);
|
||||
break;
|
||||
}
|
||||
case coFloat:
|
||||
@@ -1719,12 +1645,7 @@ void Choice::set_value(const std::string& value, bool change_event) //! Redunda
|
||||
}
|
||||
|
||||
choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
|
||||
if (m_opt_id == "printer_agent") {
|
||||
const int enum_index = idx == m_opt.enum_values.size() ?
|
||||
(m_opt.default_value ? m_opt.default_value->getInt() : 0) :
|
||||
static_cast<int>(idx);
|
||||
field->SetSelection(printer_agent_item_for_enum_index(field, enum_index));
|
||||
} else if (idx == m_opt.enum_values.size())
|
||||
if (idx == m_opt.enum_values.size())
|
||||
field->SetValue(value);
|
||||
else
|
||||
field->SetSelection(idx);
|
||||
@@ -1790,33 +1711,11 @@ void Choice::set_value(const boost::any& value, bool change_event)
|
||||
case coEnum:
|
||||
// BBS
|
||||
case coEnums: {
|
||||
auto printer_agent_index_from_key = [this](const std::string& key) {
|
||||
auto it = std::find(m_opt.enum_values.begin(), m_opt.enum_values.end(), key);
|
||||
if (it != m_opt.enum_values.end())
|
||||
return static_cast<int>(it - m_opt.enum_values.begin());
|
||||
return m_opt.default_value ? m_opt.default_value->getInt() : 0;
|
||||
};
|
||||
|
||||
int val = 0;
|
||||
if (m_opt_id == "printer_agent") {
|
||||
if (const int* int_value = boost::any_cast<int>(&value))
|
||||
val = *int_value;
|
||||
else if (const wxString* wx_value = boost::any_cast<wxString>(&value))
|
||||
val = printer_agent_index_from_key(into_u8(*wx_value));
|
||||
else if (const std::string* string_value = boost::any_cast<std::string>(&value))
|
||||
val = printer_agent_index_from_key(*string_value);
|
||||
else {
|
||||
m_disable_change_event = false;
|
||||
return;
|
||||
}
|
||||
} else
|
||||
val = boost::any_cast<int>(value);
|
||||
int val = boost::any_cast<int>(value);
|
||||
|
||||
int selection = val;
|
||||
|
||||
if (m_opt_id == "printer_agent") {
|
||||
selection = printer_agent_item_for_enum_index(field, val);
|
||||
} else if (m_opt_id == "input_shaping_type") {
|
||||
if (m_opt_id == "input_shaping_type") {
|
||||
if (field != nullptr) {
|
||||
const unsigned int count = field->GetCount();
|
||||
int match_index = -1;
|
||||
@@ -1938,12 +1837,6 @@ boost::any& Choice::get_value()
|
||||
{
|
||||
if (m_opt.nullable && field->GetSelection() == -1)
|
||||
m_value = ConfigOptionEnumsGenericNullable::nil_value();
|
||||
else if (m_opt_id == "printer_agent")
|
||||
{
|
||||
const int selection = field->GetSelection();
|
||||
const int fallback = m_opt.default_value ? m_opt.default_value->getInt() : 0;
|
||||
m_value = printer_agent_enum_index_for_item(field, selection, fallback);
|
||||
}
|
||||
else if (m_opt_id == "input_shaping_type")
|
||||
{
|
||||
int selection = field->GetSelection();
|
||||
@@ -2085,6 +1978,171 @@ void Choice::msw_rescale()
|
||||
}
|
||||
|
||||
|
||||
// PrinterAgentChoice
|
||||
|
||||
void PrinterAgentChoice::reload_rows()
|
||||
{
|
||||
auto* combo = dynamic_cast<choice_ctrl*>(window); // wxWidgets ComboBox
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
// clear ComboBox
|
||||
combo->Clear();
|
||||
|
||||
// helpers
|
||||
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
const bool has_builtin_agents = std::any_of(agents.begin(), agents.end(),
|
||||
[](const PrinterAgentInfo& a) { return !a.is_plugin(); });
|
||||
const bool has_plugin_agents = std::any_of(agents.begin(), agents.end(),
|
||||
[](const PrinterAgentInfo& a) { return a.is_plugin(); });
|
||||
|
||||
auto append_agent_rows = [combo](bool is_plugin)
|
||||
{
|
||||
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
for (size_t i = 0; i < agents.size(); ++i)
|
||||
{
|
||||
if (agents[i].is_plugin() != is_plugin)
|
||||
continue;
|
||||
const int item = combo->Append(_(agents[i].display_name));
|
||||
// why: carry the agent-id string on the row. alias is an owned wxString (auto-freed, never rendered)
|
||||
combo->SetItemAlias(item, from_u8(agents[i].id));
|
||||
}
|
||||
};
|
||||
|
||||
// append rows
|
||||
if (has_builtin_agents)
|
||||
{
|
||||
combo->Append(_L("System agents"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(false); // append rows for agents that are not plugins
|
||||
}
|
||||
if (has_plugin_agents)
|
||||
{
|
||||
combo->Append(_L("Plugins"), wxNullBitmap, DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED);
|
||||
append_agent_rows(true); // append rows for agents that are plugins
|
||||
}
|
||||
}
|
||||
|
||||
void PrinterAgentChoice::BUILD()
|
||||
{
|
||||
wxSize size(def_width_wider() * m_em_unit, wxDefaultCoord);
|
||||
if (m_opt.height >= 0) size.SetHeight(m_opt.height * m_em_unit);
|
||||
if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
|
||||
|
||||
static Builder<choice_ctrl> builder;
|
||||
choice_ctrl* temp = builder.build(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr,
|
||||
wxCB_READONLY);
|
||||
temp->Clear();
|
||||
temp->GetDropDown().SetUseContentWidth(true);
|
||||
if (parent_is_custom_ctrl && m_opt.height < 0)
|
||||
opt_height = (double)temp->GetTextCtrl()->GetSize().GetHeight() / m_em_unit;
|
||||
temp->SetTextLabel(_L(m_opt.sidetext));
|
||||
m_combine_side_text = true;
|
||||
#ifdef __WXGTK3__
|
||||
wxSize best_sz = temp->GetBestSize();
|
||||
if (best_sz.x > size.x) temp->SetSize(best_sz);
|
||||
#endif
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
||||
reload_rows();
|
||||
|
||||
temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId());
|
||||
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
|
||||
}
|
||||
|
||||
// Resolve CONFIG id string to a matching row in the live REGISTRY. "" uses the vendor default.
|
||||
// An unregistered id clears selection and shows "<id> (missing)" as free text.
|
||||
void PrinterAgentChoice::set_value(const std::string& value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window);
|
||||
|
||||
// check if any row's corresponding id matches the agent id we are attempting to set
|
||||
const std::string effective_agent_id = wxGetApp().resolve_printer_agent_id(value);
|
||||
const unsigned int count = field->GetCount();
|
||||
int match = wxNOT_FOUND;
|
||||
for (unsigned int i = 0; i < count; ++i)
|
||||
{
|
||||
if (into_u8(field->GetItemAlias(i)) == effective_agent_id) // if alias == id
|
||||
{
|
||||
match = static_cast<int>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// based on match or not, set selection and value
|
||||
// - SetSelection and SetValue are UI to manipulate the display of the ComboBox
|
||||
// - SetSelection automatically calls SetValue for the same value
|
||||
// - we can also SetValue separately from SetSelection
|
||||
if (match == wxNOT_FOUND)
|
||||
{
|
||||
field->SetSelection(wxNOT_FOUND); // nothing shows as selected in the dropdown
|
||||
field->SetValue(from_u8(value + " (missing)")); // set a value not in the selection (upper display field)
|
||||
}
|
||||
else
|
||||
{
|
||||
// display name of agent shows both in upper display field and appears selected in dropdown
|
||||
field->SetSelection(match);
|
||||
}
|
||||
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
// Accept boost::any values from callers (usually to OptionsGroup/Field parent classes) and normalize them to an agent id.
|
||||
// Then use PrinterAgentChoice::set_value(std::string& value, ...)
|
||||
void PrinterAgentChoice::set_value(const boost::any& value, bool change_event)
|
||||
{
|
||||
m_disable_change_event = !change_event;
|
||||
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window);
|
||||
if (value.empty())
|
||||
{
|
||||
field->SetValue("");
|
||||
m_value = value;
|
||||
m_disable_change_event = false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string id;
|
||||
if (const std::string* s = boost::any_cast<std::string>(&value))
|
||||
id = *s;
|
||||
else if (const wxString* w = boost::any_cast<wxString>(&value))
|
||||
id = into_u8(*w);
|
||||
set_value(id, change_event);
|
||||
}
|
||||
|
||||
// A real row returns its alias, which is the agent id. Header rows, missing rows,
|
||||
// and no selection return empty boost::any so the custom writer leaves config unchanged.
|
||||
boost::any& PrinterAgentChoice::get_value()
|
||||
{
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window);
|
||||
const int sel = field->GetSelection();
|
||||
const std::string id = sel < 0 ? std::string{} : into_u8(field->GetItemAlias(sel));
|
||||
if (id.empty())
|
||||
m_value = boost::any{};
|
||||
else
|
||||
m_value = id;
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void PrinterAgentChoice::enable() { dynamic_cast<choice_ctrl*>(window)->Enable(); }
|
||||
void PrinterAgentChoice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); }
|
||||
|
||||
void PrinterAgentChoice::msw_rescale()
|
||||
{
|
||||
Field::msw_rescale();
|
||||
|
||||
auto* field = dynamic_cast<choice_ctrl*>(window)->GetTextCtrl();
|
||||
wxSize size(wxDefaultSize);
|
||||
size.SetWidth((m_opt.width > 0 ? m_opt.width : def_width_wider()) * m_em_unit);
|
||||
field->SetMinSize(wxSize(-1, int(1.5f * field->GetFont().GetPixelSize().y + 0.5f)));
|
||||
field->SetSize(size);
|
||||
|
||||
dynamic_cast<choice_ctrl*>(window)->Rescale();
|
||||
}
|
||||
|
||||
void PluginField::BUILD()
|
||||
{
|
||||
auto* panel = new wxPanel(m_parent, wxID_ANY);
|
||||
|
||||
@@ -469,6 +469,44 @@ public:
|
||||
void suppress_scroll();
|
||||
};
|
||||
|
||||
// printer_agent is a coString whose choices come from the live agent registry.
|
||||
// PrinterAgentChoice uses a ComboBox directly because Choice expects static config enums.
|
||||
// Real rows carry the stored agent id in the row alias (SetItemAlias/GetItemAlias).
|
||||
class PrinterAgentChoice : public Field
|
||||
{
|
||||
using Field::Field;
|
||||
|
||||
public:
|
||||
PrinterAgentChoice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id)
|
||||
{
|
||||
}
|
||||
|
||||
PrinterAgentChoice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(
|
||||
parent, opt, id)
|
||||
{
|
||||
}
|
||||
|
||||
~PrinterAgentChoice()
|
||||
{
|
||||
}
|
||||
|
||||
wxWindow* window{nullptr};
|
||||
|
||||
void BUILD() override;
|
||||
// Clear and repopulate rows from the live registry (grouped System agents / Plugins).
|
||||
// Does not change selection; the caller follows with set_value(stored id).
|
||||
void reload_rows();
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override;
|
||||
void disable() override;
|
||||
void msw_rescale() override;
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class PluginField : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1842,6 +1842,10 @@ void GLCanvas3D::enable_separator_toolbar(bool enable)
|
||||
m_separator_toolbar.set_enabled(enable);
|
||||
}
|
||||
|
||||
bool GLCanvas3D::has_mouse_capture() const {
|
||||
return m_canvas != nullptr && m_canvas->HasCapture();
|
||||
}
|
||||
|
||||
void GLCanvas3D::zoom_to_bed()
|
||||
{
|
||||
BoundingBoxf3 box = m_bed.build_volume().bounding_volume();
|
||||
@@ -2182,7 +2186,7 @@ void GLCanvas3D::render(bool only_init)
|
||||
|
||||
// Negative coordinate means out of the window, likely because the window was deactivated.
|
||||
// In that case the tooltip should be hidden.
|
||||
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) {
|
||||
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0. || has_mouse_capture()) { // ORCA continue to capture mouse pos mid drag
|
||||
if (tooltip.empty())
|
||||
tooltip = m_layers_editing.get_tooltip(*this);
|
||||
|
||||
@@ -4170,6 +4174,23 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// BBS: single snapshot
|
||||
Plater::SingleSnapshot single(wxGetApp().plater());
|
||||
|
||||
#ifdef __WXMAC__
|
||||
// On macOS, the mouse key state is only present for mouse btn related events such as wxEVT_LEFT_DOWN.
|
||||
// For other events, all buttons are reported as non-pressed, such as window leaving event. This causes
|
||||
// imgui stopped responding if cursor moved out of window, such as
|
||||
// https://github.com/OrcaSlicer/OrcaSlicer/pull/14999#issuecomment-5151344759
|
||||
// We solve this by correcting the state of the event from the actual mouse state querying with `wxGetMouseState()`
|
||||
// so it works like on other platforms.
|
||||
{
|
||||
const auto state = wxGetMouseState();
|
||||
evt.SetLeftDown(state.LeftIsDown());
|
||||
evt.SetMiddleDown(state.MiddleIsDown());
|
||||
evt.SetRightDown(state.RightIsDown());
|
||||
evt.SetAux1Down(state.Aux1IsDown());
|
||||
evt.SetAux2Down(state.Aux2IsDown());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
const float scale = m_retina_helper->get_scale_factor();
|
||||
evt.SetX(evt.GetX() * scale);
|
||||
@@ -4183,11 +4204,27 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// ignore left up events coming from imgui windows and not processed by them
|
||||
m_mouse.ignore_left_up = true;
|
||||
m_tooltip.set_in_imgui(false);
|
||||
if (imgui->update_mouse_data(evt)) {
|
||||
|
||||
// while a non-ImGui drag is already in progress (gizmo grabber, object move, rectangle selection, layer editing),
|
||||
// don't let ImGui/ImGuizmo claim the event just because the cursor is hovering something like the navigator cube
|
||||
// that incorrectly suppresses the active drag's tooltip and can interrupt its processing. The active drag always takes priority.
|
||||
const bool other_drag_active = m_gizmos.is_dragging() || m_mouse.dragging || m_rectangle_selection.is_dragging() || m_layers_editing.state == LayersEditing::Editing;
|
||||
|
||||
if (imgui->update_mouse_data(evt) && !other_drag_active) {
|
||||
if ((evt.LeftDown() || (evt.Moving() && (evt.AltDown() || evt.ShiftDown()))) && m_canvas != nullptr)
|
||||
m_canvas->SetFocus();
|
||||
m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast<double>();
|
||||
m_tooltip.set_in_imgui(true);
|
||||
|
||||
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
const bool imgui_dragging_active = (GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0) || m_navigator_dragging;
|
||||
if (!has_mouse_capture() && imgui_dragging_active)
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// release capture as soon as the button goes up
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
render();
|
||||
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
||||
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
|
||||
@@ -4284,6 +4321,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_main_toolbar.on_mouse(evt2, *this);
|
||||
}
|
||||
|
||||
// ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
if (!has_mouse_capture() && evt.LeftIsDown() && m_gizmos.is_dragging())
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
||||
@@ -4389,6 +4430,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
// Start editing the layer height.
|
||||
m_layers_editing.state = LayersEditing::Editing;
|
||||
_perform_layer_editing_action(&evt);
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -4402,6 +4446,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation
|
||||
&& m_gizmos.get_current_type() != GLGizmosManager::FuzzySkin) {
|
||||
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -4469,6 +4517,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_mouse.drag.start_position_3D = m_mouse.scene_position;
|
||||
m_sequential_print_clearance_first_displacement = true;
|
||||
m_moving = true;
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4477,6 +4528,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
}
|
||||
else if (evt.Dragging() && evt.LeftIsDown() && m_mouse.drag.move_volume_idx != -1 && m_layers_editing.state == LayersEditing::Unknown) {
|
||||
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
if (!m_mouse.drag.move_requires_threshold) {
|
||||
m_mouse.dragging = true;
|
||||
Vec3d cur_pos = m_mouse.drag.start_position_3D;
|
||||
@@ -4528,6 +4583,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
|
||||
//BBS not in assemble view
|
||||
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
m_rectangle_selection.dragging(pos.cast<double>());
|
||||
m_dirty = true;
|
||||
}
|
||||
@@ -4537,12 +4596,19 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
|
||||
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
|
||||
if (m_layers_editing.state == LayersEditing::Editing) {
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
_perform_layer_editing_action(&evt);
|
||||
m_mouse.position = pos.cast<double>();
|
||||
}
|
||||
}
|
||||
// do not process the dragging if the left mouse was set down in another canvas
|
||||
else if (is_camera_rotate(evt, button_mappings)) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// Orca: Sphere rotation for painting view
|
||||
// if dragging over blank area with left button or other button mapped to rotate, then rotate
|
||||
bool middle_or_right_button_used_as_rotate = (evt.MiddleIsDown() && button_mappings[MouseButton::Middle] == MouseAction::Rotation) ||
|
||||
@@ -4622,6 +4688,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
|
||||
}
|
||||
else if (is_camera_pan(evt, button_mappings)) {
|
||||
|
||||
if (!has_mouse_capture()) // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
m_canvas->CaptureMouse();
|
||||
|
||||
// if dragging with right button or if button functions swapped and dragging with left button over blank area then pan
|
||||
if (m_mouse.is_start_position_2D_defined()) {
|
||||
// get point in model space at Z = 0
|
||||
@@ -4686,7 +4756,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
deselect_all();
|
||||
}
|
||||
//BBS Select plate in this 3D canvas.
|
||||
else if (evt.LeftUp() && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
|
||||
// The left up may come from an ImGui window (e.g. a drag started on the gizmo floating window and released over the bed),
|
||||
// in which case it must not be treated as a click on the plate, otherwise the gizmo would be closed (see deselect_all below).
|
||||
else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
|
||||
{
|
||||
int hover_idx = m_hover_plate_idxs.front();
|
||||
wxGetApp().plater()->select_plate_by_hover_id(hover_idx);
|
||||
@@ -6033,9 +6105,18 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
{
|
||||
if (!wxGetApp().show_3d_navigator()) {
|
||||
m_canvas_toolbar_pos[0] = 0;
|
||||
m_navigator_dragging = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix stealing capture event from other drag events
|
||||
const bool other_drag_active = !m_navigator_dragging && (m_moving || m_rectangle_selection.is_dragging() || m_gizmos.is_dragging() || m_layers_editing.state == LayersEditing::Editing);
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const bool saved_mouse_down0 = io.MouseDown[0];
|
||||
if (other_drag_active)
|
||||
io.MouseDown[0] = false;
|
||||
|
||||
ImGuizmo::BeginFrame();
|
||||
|
||||
auto& style = ImGuizmo::GetStyle();
|
||||
@@ -6060,7 +6141,6 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
sc *= (float) dpi / (float) DPI_DEFAULT;
|
||||
#endif // WIN32
|
||||
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
const float viewManipulateLeft = 0;
|
||||
const float viewManipulateTop = io.DisplaySize.y;
|
||||
const float camDistance = 8.f;
|
||||
@@ -6084,6 +6164,10 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
camDistance, ImVec2(viewManipulateLeft, viewManipulateTop - size), ImVec2(size, size),
|
||||
0x00101010);
|
||||
|
||||
// Restore the real mouse-down state
|
||||
if (other_drag_active)
|
||||
io.MouseDown[0] = saved_mouse_down0;
|
||||
|
||||
if (result.changed) {
|
||||
for (unsigned int c = 0; c < 4; ++c) {
|
||||
for (unsigned int r = 0; r < 4; ++r) {
|
||||
@@ -6115,6 +6199,8 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
|
||||
request_extra_frame();
|
||||
}
|
||||
|
||||
m_navigator_dragging = result.dragging;
|
||||
}
|
||||
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
||||
@@ -9226,8 +9312,12 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
|
||||
|
||||
//ORCA ImGui::IsWindowHovered() returns false when left_down events on buttons that causes scrollbar disappears for a short time
|
||||
auto win_pos = ImGui::GetWindowPos();
|
||||
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll); // use non clipped rectangle to reserve clickable area for scrollbar track
|
||||
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered;
|
||||
bool is_win_hovered = ImGui::IsMouseHoveringRect(win_pos, win_pos + ImVec2(window_width + (show_scroll ? scrollbar_size : 0), window_height), !show_scroll);
|
||||
|
||||
// Also show scrollbar visible and continue to capture mouse position
|
||||
const bool is_scrollbar_active_drag = GImGui != nullptr && ImGui::GetIO().MouseDown[0] && GImGui->ActiveId != 0 && GImGui->ActiveIdWindow == ImGui::GetCurrentWindow();
|
||||
|
||||
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered || is_scrollbar_active_drag;
|
||||
|
||||
imgui.end();
|
||||
}
|
||||
|
||||
@@ -589,6 +589,7 @@ private:
|
||||
bool m_toolpath_outside{ false };
|
||||
ECursorType m_cursor_type;
|
||||
GLSelectionRectangle m_rectangle_selection;
|
||||
bool m_navigator_dragging{ false };
|
||||
|
||||
//BBS:add plate related logic
|
||||
mutable std::vector<int> m_hover_volume_idxs;
|
||||
@@ -916,6 +917,7 @@ public:
|
||||
void update_volumes_colors_by_extruder();
|
||||
|
||||
bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; }
|
||||
bool has_mouse_capture() const;
|
||||
|
||||
void render(bool only_init = false);
|
||||
bool is_rendering_enabled()
|
||||
@@ -1117,6 +1119,10 @@ public:
|
||||
|
||||
void set_mouse_as_dragging() { m_mouse.dragging = true; }
|
||||
bool is_mouse_dragging() const { return m_mouse.dragging; }
|
||||
// True when the current left up event comes from an ImGui window and was not processed by it
|
||||
// (e.g. a drag that started on a gizmo floating window and was released over the 3D scene).
|
||||
// Such a release is the end of an ImGui interaction, not a click on the scene.
|
||||
bool is_mouse_left_up_ignored() const { return m_mouse.ignore_left_up; }
|
||||
|
||||
double get_size_proportional_to_max_bed_size(double factor) const;
|
||||
|
||||
|
||||
@@ -256,18 +256,18 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
||||
}
|
||||
}
|
||||
|
||||
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font)
|
||||
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts)
|
||||
{
|
||||
wxGetApp().CallAfter([=] {
|
||||
ErrorDialog msg(parent, message, monospaced_font);
|
||||
ErrorDialog msg(parent, message, has_code_excerpts);
|
||||
msg.ShowModal();
|
||||
});
|
||||
}
|
||||
|
||||
void show_error(wxWindow* parent, const char* message, bool monospaced_font)
|
||||
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts)
|
||||
{
|
||||
assert(message);
|
||||
show_error(parent, wxString::FromUTF8(message), monospaced_font);
|
||||
show_error(parent, wxString::FromUTF8(message), has_code_excerpts);
|
||||
}
|
||||
|
||||
void show_error_id(int id, const std::string& message)
|
||||
|
||||
@@ -40,11 +40,11 @@ extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_
|
||||
// Change option value in config
|
||||
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
|
||||
|
||||
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
|
||||
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false);
|
||||
void show_error(wxWindow* parent, const char* message, bool monospaced_font = false);
|
||||
inline void show_error(wxWindow* parent, const std::string& message, bool monospaced_font = false) { show_error(parent, message.c_str(), monospaced_font); }
|
||||
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
|
||||
// monospaced so the caret aligns. Used for placeholder-parser errors.
|
||||
void show_error(wxWindow* parent, const wxString& message, bool has_code_excerpts = false);
|
||||
void show_error(wxWindow* parent, const char* message, bool has_code_excerpts = false);
|
||||
inline void show_error(wxWindow* parent, const std::string& message, bool has_code_excerpts = false) { show_error(parent, message.c_str(), has_code_excerpts); }
|
||||
void show_error_id(int id, const std::string& message); // For Perl
|
||||
void show_info(wxWindow* parent, const wxString& message, const wxString& title = wxString());
|
||||
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
|
||||
|
||||
+113
-19
@@ -307,6 +307,20 @@ public:
|
||||
#endif // !__APPLE__
|
||||
)
|
||||
{
|
||||
// Some desktop environments ignore splash screen typed window properties
|
||||
// when running the app through Wayland,resulting in the titlebar being shown
|
||||
// on the splash screen. The code below creates a client-side window decoration
|
||||
// when running on Wayland and then removes that decoration. This ensures every
|
||||
// environment correctly targets and removes the titlebar for this screen.
|
||||
#if defined(__WXGTK__)
|
||||
if (Slic3r::GUI::is_running_on_wayland()) {
|
||||
GtkWidget *empty = gtk_fixed_new();
|
||||
gtk_widget_set_size_request(empty, 0, 0);
|
||||
gtk_window_set_titlebar(GTK_WINDOW(GetHandle()), empty);
|
||||
gtk_window_set_decorated(GTK_WINDOW(GetHandle()), false);
|
||||
}
|
||||
#endif
|
||||
|
||||
this->SetPosition(pos);
|
||||
this->CenterOnScreen();
|
||||
|
||||
@@ -2795,6 +2809,45 @@ void GUI_App::init_plugin_gui_wiring()
|
||||
});
|
||||
};
|
||||
|
||||
// why: a newly loaded plugin only adds a selectable agent
|
||||
// refresh the dropdown and leave the live agent alone
|
||||
auto refresh_printer_agent_dropdown_after_load = [](const std::string&)
|
||||
{
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
|
||||
GUI_App* app = &GUI::wxGetApp();
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->CallAfter([app]
|
||||
{
|
||||
if (!app->is_closing())
|
||||
app->refresh_printer_agent_dropdown();
|
||||
});
|
||||
};
|
||||
|
||||
// why: the unloaded plugin may have been the provider of the live agent
|
||||
// re-run selection, where a now-missing agent will be cleared
|
||||
// refresh dropdown after
|
||||
auto switch_printer_agent_after_unload = [](const std::string&)
|
||||
{
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
|
||||
GUI_App* app = &GUI::wxGetApp();
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->CallAfter([app] {
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->switch_printer_agent();
|
||||
app->refresh_printer_agent_dropdown();
|
||||
});
|
||||
};
|
||||
|
||||
plugin_mgr.subscribe_on_unload_callback(PluginHostUi::close_windows_for_plugin);
|
||||
plugin_mgr.subscribe_on_load_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
@@ -2810,11 +2863,14 @@ void GUI_App::init_plugin_gui_wiring()
|
||||
return;
|
||||
wxGetApp().mainframe->plugin_pages().on_plugin_deregister(plugin_key);
|
||||
});
|
||||
plugin_mgr.subscribe_on_load_callback(refresh_printer_agent_dropdown_after_load);
|
||||
plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload);
|
||||
plugin_mgr.subscribe_on_capability_load_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
|
||||
[refresh_plugins_dialog, refresh_printer_agent_dropdown_after_load](const PluginCapabilityId& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::register_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
refresh_printer_agent_dropdown_after_load(capability.plugin_key);
|
||||
// A newly loaded capability may satisfy a missing-plugin notification; re-validate the
|
||||
// current plate (on the UI thread) so the notification clears once its plugin is available.
|
||||
if (wxTheApp && !wxGetApp().is_closing())
|
||||
@@ -2826,12 +2882,13 @@ void GUI_App::init_plugin_gui_wiring()
|
||||
wxGetApp().mainframe->plugin_pages().on_cap_register(capability);
|
||||
});
|
||||
plugin_mgr.subscribe_on_capability_unload_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
|
||||
[refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
|
||||
if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
|
||||
wxGetApp().mainframe->plugin_pages().on_cap_deregister(capability);
|
||||
refresh_plugins_dialog();
|
||||
switch_printer_agent_after_unload(capability.plugin_key);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3873,6 +3930,48 @@ unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
|
||||
));
|
||||
}
|
||||
|
||||
void GUI_App::refresh_printer_agent_dropdown()
|
||||
{
|
||||
if (Tab* tab = get_tab(Preset::TYPE_PRINTER))
|
||||
{
|
||||
if (auto* printer_tab = dynamic_cast<TabPrinter*>(tab))
|
||||
printer_tab->refresh_printer_agent_dropdown();
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent)
|
||||
{
|
||||
if (!m_agent)
|
||||
return;
|
||||
|
||||
// why: tearing down the old machine selection is only ever the prefix of setting the live
|
||||
// agent (to a new one, or to null when the selection is missing) - so it lives here, not as
|
||||
// a standalone helper. Pass nullptr to clear the selection.
|
||||
if (DeviceManager* dev = getDeviceManager())
|
||||
{
|
||||
dev->set_selected_machine(""); // why: empty id disconnects and deselects the current machine
|
||||
m_agent->set_user_selected_machine("");
|
||||
// note: belt-and-suspenders (precedent: DeviceManagerRefresher::on_timer)
|
||||
dev->OnSelectedMachineLost(); // why: clear stale sidebar sync-status / AMS
|
||||
dev->clear_other_devices(); // why: drop stale LAN discoveries; keep My Devices
|
||||
}
|
||||
|
||||
m_agent->set_printer_agent(agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
}
|
||||
|
||||
std::string GUI_App::resolve_printer_agent_id(const std::string& stored_id)
|
||||
{
|
||||
if (!stored_id.empty())
|
||||
return stored_id;
|
||||
return (preset_bundle && preset_bundle->is_bbl_vendor()) ? BBL_PRINTER_AGENT_ID : ORCA_PRINTER_AGENT_ID;
|
||||
}
|
||||
|
||||
std::string GUI_App::canonical_printer_agent_id(const std::string& picked_id)
|
||||
{
|
||||
return picked_id == resolve_printer_agent_id("") ? std::string() : picked_id;
|
||||
}
|
||||
|
||||
void GUI_App::switch_printer_agent()
|
||||
{
|
||||
if (!m_agent) {
|
||||
@@ -3880,24 +3979,17 @@ void GUI_App::switch_printer_agent()
|
||||
return;
|
||||
}
|
||||
|
||||
// Read printer_agent from config, falling back to default
|
||||
std::string effective_agent_id = ORCA_PRINTER_AGENT_ID;
|
||||
if (preset_bundle->is_bbl_vendor())
|
||||
effective_agent_id = BBL_PRINTER_AGENT_ID;
|
||||
|
||||
const DynamicPrintConfig& config = preset_bundle->printers.get_edited_preset().config;
|
||||
if (config.has("printer_agent")) {
|
||||
const std::string& value = config.option<ConfigOptionString>("printer_agent")->value;
|
||||
if (!value.empty())
|
||||
effective_agent_id = value;
|
||||
}
|
||||
const std::string effective_agent_id = resolve_printer_agent_id(config.opt_string("printer_agent"));
|
||||
|
||||
// Check if agent is registered
|
||||
const PrinterAgentInfo* agent_info_ptr = NetworkAgentFactory::get_printer_agent_info(effective_agent_id);
|
||||
if (!agent_info_ptr) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": unregistered agent ID '" << effective_agent_id
|
||||
<< "', keeping current agent";
|
||||
// Keep current agent, don't switch
|
||||
// why: the selected agent's provider is gone (e.g. plugin unloaded); leaving the old
|
||||
// live agent up would keep talking to a machine the user can no longer select.
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": agent ID '" << effective_agent_id
|
||||
<< "' is unregistered; clearing live printer agent";
|
||||
set_live_printer_agent(nullptr);
|
||||
return;
|
||||
}
|
||||
const PrinterAgentInfo agent_info = *agent_info_ptr;
|
||||
@@ -3911,7 +4003,9 @@ void GUI_App::switch_printer_agent()
|
||||
NetworkAgentFactory::create_printer_agent_by_id(effective_agent_id, cloud_agent, log_dir);
|
||||
|
||||
if (!new_printer_agent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id << "', keeping current agent";
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id
|
||||
<< "'; clearing live printer agent";
|
||||
set_live_printer_agent(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3934,9 +4028,9 @@ void GUI_App::switch_printer_agent()
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the agent
|
||||
m_agent->set_printer_agent(new_printer_agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
// Swap the agent; set_live_printer_agent resets the device selection so the new
|
||||
// agent starts clean (#124).
|
||||
set_live_printer_agent(new_printer_agent);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
|
||||
|
||||
|
||||
@@ -365,9 +365,14 @@ public:
|
||||
HMSQuery* get_hms_query() { return hms_query; }
|
||||
NetworkAgent* getAgent() { return m_agent; }
|
||||
|
||||
// Dynamic printer agent switching
|
||||
// Reconcile the live printer agent with the stored preset selection.
|
||||
void switch_printer_agent();
|
||||
|
||||
std::string resolve_printer_agent_id(const std::string& stored_id);
|
||||
// ORCA TODO: in the future, bbl presets should specify "bbl" printer agent id
|
||||
// then, all resolve and canonical would just be ORCA<->""
|
||||
std::string canonical_printer_agent_id(const std::string& picked_id);
|
||||
|
||||
FilamentColorCodeQuery* get_filament_color_code_query();
|
||||
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
|
||||
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
|
||||
@@ -798,6 +803,11 @@ private:
|
||||
void window_pos_center(wxTopLevelWindow *window);
|
||||
bool select_language();
|
||||
|
||||
// Dynamic printer agent selection - internal helpers for switch_printer_agent
|
||||
// and the plugin load/unload callbacks (init_plugin_gui_wiring).
|
||||
void refresh_printer_agent_dropdown();
|
||||
void set_live_printer_agent(std::shared_ptr<IPrinterAgent> agent); // null clears the selection
|
||||
|
||||
bool config_wizard_startup();
|
||||
void check_updates(const bool verbose);
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
|
||||
{"sparse_infill_density", "", 1},
|
||||
{"fill_multiline", "", 1},
|
||||
{"sparse_infill_pattern", "", 1},
|
||||
{"sparse_infill_smooth_factor", "", 1},
|
||||
{"lateral_lattice_angle_1", "", 1},
|
||||
{"lateral_lattice_angle_2", "", 1},
|
||||
{"infill_overhang_angle", "", 1},
|
||||
|
||||
@@ -3213,7 +3213,7 @@ void ObjectList::merge(bool to_multipart_object)
|
||||
//changed_object(obj_idx);
|
||||
//remove();
|
||||
}
|
||||
/* wxGetApp().plater()->load_model_objects(objects);
|
||||
// wxGetApp().plater()->load_model_objects(objects);
|
||||
|
||||
Selection& selection = p->view3D->get_canvas3d()->get_selection();
|
||||
size_t last_obj_idx = p->model.objects.size() - 1;
|
||||
|
||||
@@ -548,7 +548,7 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
const char* css =
|
||||
"button {"
|
||||
"button, button:hover, button:active, button:focus {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
@@ -589,6 +589,58 @@ void RemoveButtonBorder(wxWindow* win)
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoveInputBorder(wxWindow* win)
|
||||
{
|
||||
GtkWidget* widget = win->GetHandle();
|
||||
if (!widget) return;
|
||||
|
||||
#if GTK_CHECK_VERSION(3, 0, 0)
|
||||
// GTK3+: use CSS provider
|
||||
GtkCssProvider* provider = gtk_css_provider_new();
|
||||
|
||||
// Target 'entry' and its inner subnodes (like text selection areas)
|
||||
const char* css =
|
||||
"entry, entry text, entry undershoot {"
|
||||
" border: none;"
|
||||
" outline: none;"
|
||||
" box-shadow: none;"
|
||||
" padding: 0px;"
|
||||
" margin: 0px;"
|
||||
" min-height: 0px;"
|
||||
" min-width: 0px;"
|
||||
" background: none;"
|
||||
"}";
|
||||
|
||||
#if GTK_CHECK_VERSION(4, 0, 0)
|
||||
// GTK4
|
||||
gtk_css_provider_load_from_data(provider, css, -1);
|
||||
#else
|
||||
// GTK3
|
||||
gtk_css_provider_load_from_data(provider, css, -1, nullptr);
|
||||
#endif
|
||||
|
||||
GtkStyleContext* ctx = gtk_widget_get_style_context(widget);
|
||||
gtk_style_context_add_provider(
|
||||
ctx,
|
||||
GTK_STYLE_PROVIDER(provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER
|
||||
);
|
||||
g_object_unref(provider);
|
||||
|
||||
#else
|
||||
// GTK2: Target the x/y thickness of the entry widget
|
||||
gtk_rc_parse_string(
|
||||
"style \"no-padding-entry\" {"
|
||||
" xthickness = 0"
|
||||
" ythickness = 0"
|
||||
" GtkEntry::inner-border = { 0, 0, 0, 0 }"
|
||||
" GtkEntry::focus-line-width = 0"
|
||||
"}"
|
||||
"class \"GtkEntry\" style \"no-padding-entry\""
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#endif // __WXGTK__
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
@@ -113,14 +113,7 @@ public:
|
||||
update_dark_ui(this);
|
||||
#endif
|
||||
|
||||
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
|
||||
// So, calculate the m_em_unit value from the font size, as before
|
||||
#if !defined(__WXGTK__)
|
||||
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
|
||||
#else
|
||||
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
||||
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
|
||||
#endif // __WXGTK__
|
||||
update_em_unit();
|
||||
|
||||
// recalc_font();
|
||||
|
||||
@@ -235,6 +228,19 @@ private:
|
||||
// m_em_unit = metrics.averageWidth;
|
||||
// }
|
||||
|
||||
// update em_unit value for new window font
|
||||
void update_em_unit()
|
||||
{
|
||||
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
|
||||
// So, calculate the m_em_unit value from the font size, as before
|
||||
#if !defined(__WXGTK__)
|
||||
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
|
||||
#else
|
||||
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
||||
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
|
||||
#endif // __WXGTK__
|
||||
}
|
||||
|
||||
// check if new scale is differ from previous
|
||||
bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; }
|
||||
|
||||
@@ -247,8 +253,7 @@ private:
|
||||
// set normal application font as a current window font
|
||||
m_normal_font = this->GetFont();
|
||||
|
||||
// update em_unit value for new window font
|
||||
m_em_unit = std::max<int>(10, 10.0f * m_scale_factor);
|
||||
update_em_unit();
|
||||
|
||||
// rescale missed controls sizes and images
|
||||
on_dpi_changed(suggested_rect);
|
||||
@@ -472,8 +477,9 @@ void dataview_remove_insets(wxDataViewCtrl* dv);
|
||||
void staticbox_remove_margin(wxStaticBox* sb);
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
void RemoveButtonBorder(wxWindow* win);
|
||||
#ifdef __WXGTK__
|
||||
void RemoveButtonBorder(wxWindow* win); // for wxButton/wxBitmapToggleButton based controls (SwitchButton, CheckBox)
|
||||
void RemoveInputBorder(wxWindow* win); // for TextCtrl based controls (TextInput, ComboBox, SpinInput..)
|
||||
#endif
|
||||
|
||||
#if defined(__WXOSX__) || defined(__linux__)
|
||||
|
||||
@@ -442,7 +442,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
|
||||
}
|
||||
} else if (m_dragging) {
|
||||
// when mouse cursor leave window than finish actual dragging operation
|
||||
bool is_leaving = mouse_event.Leaving();
|
||||
bool is_leaving = mouse_event.Leaving() && !m_parent.has_mouse_capture(); // ORCA keep tracking mouse position while drag active and cursor not in window bounds
|
||||
if (mouse_event.Dragging()) {
|
||||
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
auto ray = m_parent.mouse_ray(mouse_coord);
|
||||
|
||||
@@ -15,6 +15,8 @@ static const ColorRGBA DEF_COLOR = {0.7f, 0.7f, 0.7f, 1.f};
|
||||
static const ColorRGBA SELECTED_COLOR = {0.0f, 0.5f, 0.5f, 1.0f};
|
||||
static const ColorRGBA ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f};
|
||||
static const ColorRGBA HOVER_COLOR = {0.7f, 0.7f, 0.7f, 0.5f};
|
||||
static constexpr float BRIM_EAR_RADIUS_MIN = 0.1f;
|
||||
static constexpr float BRIM_EAR_RADIUS_MAX = 100.f;
|
||||
|
||||
static ModelVolume *get_model_volume(const Selection &selection, Model &model)
|
||||
{
|
||||
@@ -41,14 +43,14 @@ GLGizmoBrimEars::GLGizmoBrimEars(GLCanvas3D &parent, const std::string &icon_fil
|
||||
|
||||
bool GLGizmoBrimEars::on_init()
|
||||
{
|
||||
m_new_point_head_diameter = get_brim_default_radius();
|
||||
m_new_point_head_radius = get_brim_default_radius();
|
||||
|
||||
m_shortcut_key = WXK_CONTROL_E;
|
||||
|
||||
const wxString ctrl = GUI::shortkey_ctrl_prefix();
|
||||
const wxString alt = GUI::shortkey_alt_prefix();
|
||||
|
||||
m_desc["head_diameter"] = _L("Head diameter");
|
||||
m_desc["brim_ear_radius"] = _L("Brim ear radius");
|
||||
m_desc["max_angle"] = _L("Max angle");
|
||||
m_desc["detection_radius"] = _L("Detection radius");
|
||||
m_desc["remove"] = _L("Remove");
|
||||
@@ -62,7 +64,7 @@ bool GLGizmoBrimEars::on_init()
|
||||
m_shortcuts = {
|
||||
{_L("Left mouse button"), _L("Add or Select")},
|
||||
{_L("Right mouse button"), _L("Remove")},
|
||||
{ctrl + _L("Mouse wheel"), m_desc["head_diameter"]},
|
||||
{ctrl + _L("Mouse wheel"), m_desc["brim_ear_radius"]},
|
||||
{alt + _L("Mouse wheel"), m_desc["section_view"]},
|
||||
};
|
||||
|
||||
@@ -358,7 +360,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
Transform3d inverse_trsf = volume->get_instance_transformation().get_matrix_no_offset().inverse();
|
||||
std::pair<Vec3f, Vec3f> pos_and_normal;
|
||||
if (unproject_on_mesh2(mouse_position, pos_and_normal)) {
|
||||
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_diameter / 2.f), false, (inverse_trsf * m_world_normal).cast<float>(), true);
|
||||
render_hover_point = CacheEntry(BrimPoint(pos_and_normal.first, m_new_point_head_radius), false, (inverse_trsf * m_world_normal).cast<float>(), true);
|
||||
} else {
|
||||
render_hover_point.reset();
|
||||
}
|
||||
@@ -397,7 +399,7 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
Vec3d object_pos = trsf.inverse() * world_pos;
|
||||
// brim ear always face up
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Add brim ear");
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2.f, false, (inverse_trsf * m_world_normal).cast<float>());
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, (inverse_trsf * m_world_normal).cast<float>());
|
||||
m_parent.set_as_dirty();
|
||||
m_wait_for_up_event = true;
|
||||
find_single();
|
||||
@@ -490,9 +492,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
// mouse wheel up
|
||||
if (action == SLAGizmoEventType::MouseWheelUp) {
|
||||
if (control_down) {
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
float initial_value = m_new_point_head_radius;
|
||||
begin_radius_change(initial_value);
|
||||
m_new_point_head_diameter = std::min(20., initial_value + 0.1);
|
||||
m_new_point_head_radius = std::min(BRIM_EAR_RADIUS_MAX, initial_value + 0.1f);
|
||||
update_cache_radius();
|
||||
return true;
|
||||
}
|
||||
@@ -502,9 +504,9 @@ bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_p
|
||||
|
||||
if (action == SLAGizmoEventType::MouseWheelDown) {
|
||||
if (control_down) {
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
float initial_value = m_new_point_head_radius;
|
||||
begin_radius_change(initial_value);
|
||||
m_new_point_head_diameter = std::max(5., initial_value - 0.1);
|
||||
m_new_point_head_radius = std::max(BRIM_EAR_RADIUS_MIN, initial_value - 0.1f);
|
||||
update_cache_radius();
|
||||
return true;
|
||||
}
|
||||
@@ -597,18 +599,18 @@ std::vector<const ConfigOption *> GLGizmoBrimEars::get_config_options(const std:
|
||||
|
||||
void GLGizmoBrimEars::begin_radius_change(float initial_value)
|
||||
{
|
||||
if (m_old_point_head_diameter == 0.f)
|
||||
m_old_point_head_diameter = initial_value;
|
||||
if (m_old_point_head_radius == 0.f)
|
||||
m_old_point_head_radius = initial_value;
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::update_cache_radius()
|
||||
{
|
||||
if (render_hover_point)
|
||||
render_hover_point->brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
|
||||
render_hover_point->brim_point.head_front_radius = m_new_point_head_radius;
|
||||
|
||||
for (auto &cache_entry : m_editing_cache)
|
||||
if (cache_entry.selected) {
|
||||
cache_entry.brim_point.head_front_radius = m_new_point_head_diameter / 2.f;
|
||||
cache_entry.brim_point.head_front_radius = m_new_point_head_radius;
|
||||
find_single();
|
||||
update_model_object();
|
||||
}
|
||||
@@ -617,18 +619,18 @@ void GLGizmoBrimEars::update_cache_radius()
|
||||
|
||||
void GLGizmoBrimEars::apply_radius_change()
|
||||
{
|
||||
if (m_old_point_head_diameter == 0.f) return;
|
||||
if (m_old_point_head_radius == 0.f) return;
|
||||
|
||||
// momentarily restore the old value to take snapshot
|
||||
for (auto& cache_entry : m_editing_cache)
|
||||
if (cache_entry.selected)
|
||||
cache_entry.brim_point.head_front_radius = m_old_point_head_diameter / 2.f;
|
||||
float backup = m_new_point_head_diameter;
|
||||
m_new_point_head_diameter = m_old_point_head_diameter;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change point head diameter");
|
||||
m_new_point_head_diameter = backup;
|
||||
cache_entry.brim_point.head_front_radius = m_old_point_head_radius;
|
||||
float backup = m_new_point_head_radius;
|
||||
m_new_point_head_radius = m_old_point_head_radius;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Change brim ear radius");
|
||||
m_new_point_head_radius = backup;
|
||||
update_cache_radius();
|
||||
m_old_point_head_diameter = 0.f;
|
||||
m_old_point_head_radius = 0.f;
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limit)
|
||||
@@ -653,7 +655,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
||||
|
||||
float space_size = m_imgui->get_style_scaling() * 8;
|
||||
std::vector<wxString> text_list = {m_desc["head_diameter"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
|
||||
std::vector<wxString> text_list = {m_desc["brim_ear_radius"], m_desc["max_angle"], m_desc["detection_radius"], m_desc["clipping_of_view"],
|
||||
m_desc["create"], m_desc["remove"]};
|
||||
float widest_text = m_imgui->find_widest_text(text_list);
|
||||
float caption_size = widest_text + space_size + ImGui::GetStyle().WindowPadding.x;
|
||||
@@ -680,11 +682,11 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
// - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
|
||||
// - take correct undo/redo snapshot after the user is done with moving the slider
|
||||
ImGui::AlignTextToFramePadding();
|
||||
float initial_value = m_new_point_head_diameter;
|
||||
m_imgui->text(m_desc["head_diameter"]);
|
||||
float initial_value = m_new_point_head_radius;
|
||||
m_imgui->text(m_desc["brim_ear_radius"]);
|
||||
ImGui::SameLine(caption_size);
|
||||
ImGui::PushItemWidth(slider_width);
|
||||
m_imgui->bbl_slider_float_style("##head_diameter", &m_new_point_head_diameter, 5, 20, "%.1f", 1.0f, true);
|
||||
m_imgui->bbl_slider_float_style("##brim_ear_radius", &m_new_point_head_radius, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f", 1.0f, true);
|
||||
if (m_imgui->get_last_slider_status().clicked) {
|
||||
begin_radius_change(initial_value);
|
||||
}
|
||||
@@ -695,7 +697,7 @@ void GLGizmoBrimEars::on_render_input_window(float x, float y, float bottom_limi
|
||||
}
|
||||
ImGui::SameLine(drag_left_width);
|
||||
ImGui::PushItemWidth(1.5 * slider_icon_width);
|
||||
ImGui::BBLDragFloat("##head_diameter_input", &m_new_point_head_diameter, 0.05f, 0.0f, 0.0f, "%.1f");
|
||||
ImGui::BBLDragFloat("##brim_ear_radius_input", &m_new_point_head_radius, 0.05f, BRIM_EAR_RADIUS_MIN, BRIM_EAR_RADIUS_MAX, "%.1f");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -910,9 +912,9 @@ void GLGizmoBrimEars::on_stop_dragging()
|
||||
m_point_before_drag = CacheEntry();
|
||||
}
|
||||
|
||||
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
|
||||
void GLGizmoBrimEars::on_load(cereal::BinaryInputArchive &ar) { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
|
||||
|
||||
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_diameter, m_editing_cache, m_selection_empty); }
|
||||
void GLGizmoBrimEars::on_save(cereal::BinaryOutputArchive &ar) const { ar(m_new_point_head_radius, m_editing_cache, m_selection_empty); }
|
||||
|
||||
void GLGizmoBrimEars::select_point(int i)
|
||||
{
|
||||
@@ -920,11 +922,11 @@ void GLGizmoBrimEars::select_point(int i)
|
||||
for (auto &point_and_selection : m_editing_cache) point_and_selection.selected = (i == AllPoints);
|
||||
m_selection_empty = (i == NoPoints);
|
||||
|
||||
if (i == AllPoints) m_new_point_head_diameter = m_editing_cache[0].brim_point.head_front_radius * 2.f;
|
||||
if (i == AllPoints) m_new_point_head_radius = m_editing_cache[0].brim_point.head_front_radius;
|
||||
} else {
|
||||
m_editing_cache[i].selected = true;
|
||||
m_selection_empty = false;
|
||||
m_new_point_head_diameter = m_editing_cache[i].brim_point.head_front_radius * 2.f;
|
||||
m_new_point_head_radius = m_editing_cache[i].brim_point.head_front_radius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1011,8 +1013,7 @@ void GLGizmoBrimEars::auto_generate()
|
||||
auto add_point = [this, &trsf, &normal](const Point &p) {
|
||||
Vec3d world_pos = {float(p.x() * SCALING_FACTOR), float(p.y() * SCALING_FACTOR), -0.0001};
|
||||
Vec3d object_pos = trsf.inverse() * world_pos;
|
||||
// m_editing_cache.emplace_back(BrimPoint(object_pos.cast<float>(), m_new_point_head_diameter / 2), false, normal);
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_diameter / 2, false, normal);
|
||||
add_point_to_cache(object_pos.cast<float>(), m_new_point_head_radius, false, normal);
|
||||
};
|
||||
for (const ExPolygon &ex_poly : m_first_layer) {
|
||||
Polygon out_poly = ex_poly.contour;
|
||||
@@ -1158,8 +1159,11 @@ void GLGizmoBrimEars::reset_all_pick() { std::map<GLVolume *, std::shared_ptr<Pi
|
||||
float GLGizmoBrimEars::get_brim_default_radius() const
|
||||
{
|
||||
const double nozzle_diameter = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
|
||||
const DynamicPrintConfig &pring_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
return pring_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 16.0f;
|
||||
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
return std::clamp(
|
||||
float(print_cfg.get_abs_value("initial_layer_line_width", nozzle_diameter) * 8.0),
|
||||
BRIM_EAR_RADIUS_MIN,
|
||||
BRIM_EAR_RADIUS_MAX);
|
||||
}
|
||||
|
||||
ExPolygon GLGizmoBrimEars::make_polygon(BrimPoint point, const Geometry::Transformation &trsf)
|
||||
|
||||
@@ -98,12 +98,12 @@ private:
|
||||
|
||||
void render_points(const Selection& selection);
|
||||
|
||||
float m_new_point_head_diameter; // Size of a new point.
|
||||
float m_new_point_head_radius; // Radius of a new point.
|
||||
float m_max_angle = 125.f;
|
||||
float m_detection_radius = 1.f;
|
||||
double m_detection_radius_max = .0f;
|
||||
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
|
||||
float m_old_point_head_diameter = 0.; // the same
|
||||
float m_old_point_head_radius = 0.; // the same
|
||||
mutable std::vector<CacheEntry> m_editing_cache; // a support point and whether it is currently selectedchanges or undo/redo
|
||||
std::map<int, CacheEntry> m_single_brim;
|
||||
ObjectID m_old_mo_id;
|
||||
|
||||
@@ -566,8 +566,11 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event)
|
||||
|
||||
void GLGizmoEmboss::on_mouse_change_selection(const wxMouseEvent &mouse_event)
|
||||
{
|
||||
static bool was_dragging = true;
|
||||
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging) {
|
||||
static bool was_dragging = true;
|
||||
// The left up may be the end of a drag that started on the gizmo floating window (e.g. selecting
|
||||
// text in the input field). Such a release is not a click on the scene and must not close the gizmo.
|
||||
// (The flag is only set for left up events, so right up behavior is unchanged.)
|
||||
if ((mouse_event.LeftUp() || mouse_event.RightUp()) && !was_dragging && !m_parent.is_mouse_left_up_ignored()) {
|
||||
// is hovered volume closest hovered?
|
||||
int hovered_idx = m_parent.get_first_hover_volume_idx();
|
||||
if (hovered_idx < 0)
|
||||
|
||||
@@ -1733,7 +1733,7 @@ std::string IMSlider::get_label(int tick, LabelType label_type)
|
||||
::sprintf(layer_height, "%.2f", m_values.empty() ? m_label_koef * value : m_values[value]);
|
||||
if (label_type == ltHeight) return std::string(layer_height);
|
||||
if (label_type == ltHeightWithLayer) {
|
||||
char buffer[64];
|
||||
char buffer[90];
|
||||
size_t layer_number;
|
||||
layer_number = m_draw_mode == dmSequentialFffPrint ? (m_values.empty() ? value : value + 1) : m_is_wipe_tower ? get_layer_number(value, label_type) + 1 : (m_values.empty() ? value : value + 1);
|
||||
::sprintf(buffer, "%5s\n%5s", std::to_string(layer_number).c_str(), layer_height);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
{
|
||||
|
||||
@@ -149,6 +149,46 @@ void OrientJob::prepare()
|
||||
}
|
||||
}
|
||||
|
||||
/// parameters to minimize support area
|
||||
static void setMinimalSupportAreaPrams(Slic3r::orientation::OrientParams &out)
|
||||
{
|
||||
out.TAR_A = 0.015f;
|
||||
out.TAR_B = 0.177f;
|
||||
out.RELATIVE_F = 20;
|
||||
out.CONTOUR_F = 0.5f;
|
||||
out.BOTTOM_F = 2.5f;
|
||||
out.BOTTOM_HULL_F = 0.1f;
|
||||
out.TAR_C = 0.1f;
|
||||
out.TAR_D = 1;
|
||||
out.TAR_E = 0.0115f;
|
||||
out.FIRST_LAY_H = 0.2f; // 0.0475;
|
||||
out.VECTOR_TOL = -0.00083f;
|
||||
out.NEGL_FACE_SIZE = 0.01f;
|
||||
out.ASCENT = -0.5f;
|
||||
out.PLAFOND_ADV = 0.0599f;
|
||||
out.CONTOUR_AMOUNT = 0.0182427f;
|
||||
out.OV_H = 2.574f;
|
||||
out.height_offset = 2.3728f;
|
||||
out.height_log = 0.041375f;
|
||||
out.height_log_k = 1.9325457f;
|
||||
out.LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face 0.9997f
|
||||
out.LAF_MIN = 0.97f; // cos(14\degree) 0.9703f
|
||||
out.TAR_LAF = 0.001f; // 0.01f
|
||||
out.TAR_PROJ_AREA = 0.1f;
|
||||
out.BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the object may be unstable
|
||||
out.BOTTOM_MAX = 2000; // max bottom area. If get to it the object is stable enough (further increase bottom area won't do more help)
|
||||
out.height_to_bottom_hull_ratio_MIN = 1,
|
||||
out.BOTTOM_HULL_MAX = 2000; // max bottom hull area
|
||||
out.APPERANCE_FACE_SUPP = 3; // penalty of generating supports on appearance face
|
||||
out.overhang_angle = 60.f;
|
||||
out.use_low_angle_face = true;
|
||||
out.min_volume = false;
|
||||
out.fun_dir = {};
|
||||
out.parallel = true;
|
||||
out.progressind = {};
|
||||
out.stopcondition = {};
|
||||
}
|
||||
|
||||
void OrientJob::process(Ctl &ctl)
|
||||
{
|
||||
static const auto arrangestr = _u8L("Orienting...");
|
||||
@@ -161,9 +201,8 @@ void OrientJob::process(Ctl &ctl)
|
||||
const GLCanvas3D::OrientSettings& settings = m_plater->canvas3D()->get_orient_settings();
|
||||
|
||||
orientation::OrientParams params;
|
||||
orientation::OrientParamsArea params_area;
|
||||
if (settings.min_area) {
|
||||
memcpy(¶ms, ¶ms_area, sizeof(params));
|
||||
setMinimalSupportAreaPrams(params);
|
||||
params.min_volume = false;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -707,7 +707,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
m_print_enable = get_enable_print_status();
|
||||
m_print_btn->Enable(m_print_enable);
|
||||
if (m_print_enable) {
|
||||
if (wxGetApp().preset_bundle->use_bbl_network())
|
||||
if (wxGetApp().preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_PRINT_PLATE));
|
||||
else
|
||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE));
|
||||
@@ -1377,9 +1377,88 @@ void MainFrame::init_tabpanel() {
|
||||
}
|
||||
|
||||
// SoftFever
|
||||
void MainFrame::show_device(bool bBBLPrinter) {
|
||||
void MainFrame::show_device(bool should_use_native) {
|
||||
auto idx = -1;
|
||||
if (bBBLPrinter) {
|
||||
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
|
||||
// The legacy page is appended when printer agents are enabled. Remove that
|
||||
// extra page before switching back to the normal native/legacy layout.
|
||||
if (!use_printer_agents) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND && idx != tpMonitor) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_printer_agents) {
|
||||
if (!m_monitor) {
|
||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
|
||||
if (m_tabpanel->FindPage(m_monitor) == wxNOT_FOUND) {
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->RemovePage(idx);
|
||||
}
|
||||
m_monitor->Show(false);
|
||||
m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"),
|
||||
std::string("tab_monitor_active"));
|
||||
}
|
||||
|
||||
if (m_printer_view == nullptr) {
|
||||
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent& evt) {
|
||||
wxString url = evt.GetString();
|
||||
wxString key = evt.GetAPIkey();
|
||||
// select_tab(MainFrame::tpMonitor);
|
||||
m_printer_view->load_url(url, key);
|
||||
});
|
||||
}
|
||||
|
||||
if (wxGetApp().is_enable_multi_machine()) {
|
||||
if (!m_multi_machine) {
|
||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
// TODO: change the bitmap
|
||||
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
|
||||
m_multi_machine->Show(false);
|
||||
m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
|
||||
std::string("tab_multi_active"), false);
|
||||
}
|
||||
}
|
||||
if (!m_calibration) {
|
||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||
}
|
||||
// Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
|
||||
// the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
|
||||
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
|
||||
m_calibration->Show(false);
|
||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
|
||||
std::string("tab_calibration_active"), false);
|
||||
}
|
||||
|
||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
||||
m_printer_view->Show(false);
|
||||
m_tabpanel->AddPage(m_printer_view, _L("Device (legacy)"), std::string("tab_monitor_active"),
|
||||
std::string("tab_monitor_active"), false);
|
||||
} else {
|
||||
m_tabpanel->SetPageText(idx, _L("Device (legacy)"));
|
||||
}
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxGetApp().UpdateDarkUIWin(this);
|
||||
#endif // _MSW_DARK_MODE
|
||||
|
||||
fit_tab_labels(); // ORCA on printer change
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_use_native) {
|
||||
if (m_tabpanel->FindPage(m_monitor) != wxNOT_FOUND) {
|
||||
fit_tab_labels(); // ORCA on printer change - same button layout
|
||||
return;
|
||||
@@ -2018,7 +2097,8 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
SidePopup* p = new SidePopup(this);
|
||||
|
||||
if (wxGetApp().preset_bundle
|
||||
&& !wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||
&& !wxGetApp().preset_bundle->is_bbl_vendor()
|
||||
&& !wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
// ThirdParty Buttons
|
||||
SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), "");
|
||||
export_gcode_btn->SetCornerRadius(0);
|
||||
@@ -2151,7 +2231,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
||||
|
||||
const auto preset_bundle = wxGetApp().preset_bundle;
|
||||
if (preset_bundle) {
|
||||
if (preset_bundle->use_bbl_network()) {
|
||||
if (preset_bundle->use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
// BBL network support everything
|
||||
} else {
|
||||
support_send = false; // All 3rd print hosts do not have the send options
|
||||
@@ -4281,7 +4361,7 @@ void MainFrame::load_printer_url(wxString url, wxString apikey)
|
||||
void MainFrame::load_printer_url()
|
||||
{
|
||||
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.use_bbl_device_tab() || NetworkAgentFactory::is_current_printer_agent_plugin())
|
||||
if (preset_bundle.use_bbl_device_tab() || wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
return;
|
||||
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
|
||||
@@ -356,7 +356,7 @@ public:
|
||||
void RunScript(wxString js);
|
||||
|
||||
//SoftFever
|
||||
void show_device(bool bBBLPrinter);
|
||||
void show_device(bool should_use_native);
|
||||
void fit_tab_labels(); // ORCA
|
||||
PluginPages& plugin_pages() { return m_plugin_pages; }
|
||||
|
||||
|
||||
+106
-18
@@ -9,8 +9,13 @@
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
#include <wx/html/winpars.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
@@ -229,12 +234,82 @@ void MsgDialog::finalize()
|
||||
}
|
||||
|
||||
|
||||
// A placeholder-parser caret line, pointing at the column where parsing failed.
|
||||
static bool is_caret_line(const std::string &line)
|
||||
{
|
||||
return std::count(line.begin(), line.end(), '^') == 1 &&
|
||||
std::all_of(line.begin(), line.end(), [](char c) { return c == ' ' || c == '^'; });
|
||||
}
|
||||
|
||||
// Tag each line as a code excerpt (a caret line or the source line above one) that must stay
|
||||
// monospaced for the '^' to align.
|
||||
static std::vector<std::pair<std::string, bool>> classify_code_lines(const std::string &msg)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, msg, boost::is_any_of("\n"));
|
||||
for (std::string &line : lines)
|
||||
if (!line.empty() && line.back() == '\r')
|
||||
line.pop_back();
|
||||
|
||||
std::vector<std::pair<std::string, bool>> tagged;
|
||||
tagged.reserve(lines.size());
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
bool is_code = is_caret_line(lines[i]) || (i + 1 < lines.size() && is_caret_line(lines[i + 1]));
|
||||
tagged.emplace_back(std::move(lines[i]), is_code);
|
||||
}
|
||||
return tagged;
|
||||
}
|
||||
|
||||
// Keeps whitespace literal so the caret's leading spaces survive.
|
||||
// Used inside <code>, which supplies the fixed face. <pre> does both but adds a blank line above it.
|
||||
class CodeExcerptTagHandler : public wxHtmlWinTagHandler
|
||||
{
|
||||
public:
|
||||
wxString GetSupportedTags() override { return wxT("EXCERPT"); }
|
||||
bool HandleTag(const wxHtmlTag &tag) override
|
||||
{
|
||||
const wxHtmlWinParser::WhitespaceMode ws = m_WParser->GetWhitespaceMode();
|
||||
m_WParser->SetWhitespaceMode(wxHtmlWinParser::Whitespace_Pre);
|
||||
ParseInner(tag);
|
||||
m_WParser->SetWhitespaceMode(ws);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Render the message as HTML, monospacing only the code excerpts.
|
||||
static std::string format_parser_error_html(const std::string &msg)
|
||||
{
|
||||
std::string out;
|
||||
for (const auto &[text, is_code] : classify_code_lines(msg)) {
|
||||
if (!out.empty()) out += "<br>"; // join, not trail; a trailing <br> forces a scrollbar
|
||||
std::string escaped = xml_escape(text);
|
||||
if (is_code)
|
||||
out += "<code><excerpt>" + escaped + "</excerpt></code>";
|
||||
else
|
||||
out += escaped;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Measure each line in the font it will render in, so the dialog fits the longest line without slack.
|
||||
static wxSize measure_mixed_text(wxWindow *parent, const std::string &msg, const wxFont &prose_font, const wxFont &code_font)
|
||||
{
|
||||
wxClientDC dc(parent);
|
||||
int width = 0, height = 0;
|
||||
for (const auto &[text, is_code] : classify_code_lines(msg)) {
|
||||
dc.SetFont(is_code ? code_font : prose_font);
|
||||
width = std::max(width, dc.GetTextExtent(wxString::FromUTF8(text.c_str())).GetWidth());
|
||||
height += dc.GetCharHeight();
|
||||
}
|
||||
return wxSize(width, height);
|
||||
}
|
||||
|
||||
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
|
||||
static void add_msg_content(wxWindow *parent,
|
||||
wxBoxSizer *content_sizer,
|
||||
wxString msg,
|
||||
bool monospaced_font = false,
|
||||
bool is_marked_msg = false,
|
||||
bool has_code_excerpts = false,
|
||||
bool is_marked_msg = false,
|
||||
const wxString &link_text = "",
|
||||
std::function<void(const wxString &)> link_callback = nullptr)
|
||||
{
|
||||
@@ -243,7 +318,7 @@ static void add_msg_content(wxWindow *parent,
|
||||
|
||||
// count lines in the message
|
||||
int msg_lines = 0;
|
||||
if (!monospaced_font) {
|
||||
if (!has_code_excerpts) {
|
||||
int line_len = 55;// count of symbols in one line
|
||||
int start_line = 0;
|
||||
for (auto i = msg.begin(); i != msg.end(); ++i) {
|
||||
@@ -300,13 +375,23 @@ static void add_msg_content(wxWindow *parent,
|
||||
page_size = wxSize(info_width, page_height);
|
||||
}
|
||||
else {
|
||||
wxClientDC dc(parent);
|
||||
dc.SetFont(font); // ORCA without this it calculates bigger size
|
||||
wxSize msg_sz = dc.GetMultiLineTextExtent(msg) + parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
|
||||
wxSize msg_sz;
|
||||
if (has_code_excerpts) {
|
||||
msg_sz = measure_mixed_text(parent, msg.ToUTF8().data(), font, monospace);
|
||||
} else {
|
||||
wxClientDC dc(parent);
|
||||
dc.SetFont(font); // ORCA without this it calculates bigger size
|
||||
msg_sz = dc.GetMultiLineTextExtent(msg);
|
||||
}
|
||||
msg_sz += parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
|
||||
|
||||
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(msg_sz.GetY(), info_width));
|
||||
int page_height = msg_sz.GetY();
|
||||
// Reserve the horizontal scrollbar's height, or it clips the last line.
|
||||
if (msg_sz.GetX() > info_width)
|
||||
page_height += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, parent);
|
||||
page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(page_height, info_width));
|
||||
// Extra line breaks in message dialog
|
||||
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false) {//for common text
|
||||
if (link_text.IsEmpty() && !link_callback && is_marked_msg == false && !has_code_excerpts) {//for common text
|
||||
html->Destroy();
|
||||
if (msg_sz.GetX() < info_width) {//No need for line breaks
|
||||
info_width = msg_sz.GetX();
|
||||
@@ -337,12 +422,15 @@ static void add_msg_content(wxWindow *parent,
|
||||
}
|
||||
html->SetMinSize(page_size);
|
||||
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
if (monospaced_font)
|
||||
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
|
||||
std::string msg_escaped;
|
||||
if (has_code_excerpts) {
|
||||
html->GetParser()->AddTagHandler(new CodeExcerptTagHandler());
|
||||
msg_escaped = format_parser_error_html(msg.ToUTF8().data());
|
||||
} else {
|
||||
msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
}
|
||||
|
||||
if (!link_text.IsEmpty() && link_callback) {
|
||||
msg_escaped += "<span><a href=\"#\" style=\"color:rgb(0, 150, 136); text-decoration:underline;\">" + std::string(link_text.ToUTF8().data()) + "</a></span>";
|
||||
@@ -360,15 +448,15 @@ static void add_msg_content(wxWindow *parent,
|
||||
|
||||
// ErrorDialog
|
||||
|
||||
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool monospaced_font)
|
||||
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts)
|
||||
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_FULL_NAME),
|
||||
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_FULL_NAME), wxOK)
|
||||
, msg(temp_msg)
|
||||
{
|
||||
add_msg_content(this, content_sizer, msg, monospaced_font);
|
||||
add_msg_content(this, content_sizer, msg, has_code_excerpts);
|
||||
|
||||
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
|
||||
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/64));
|
||||
// Use a small bitmap for code excerpts, which cannot wrap and so need the width.
|
||||
logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, has_code_excerpts ? 48 : /*1*/64));
|
||||
|
||||
SetMaxSize(MSG_DLG_MAX_SIZE);
|
||||
|
||||
|
||||
@@ -106,9 +106,9 @@ protected:
|
||||
class ErrorDialog : public MsgDialog
|
||||
{
|
||||
public:
|
||||
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
|
||||
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool courier_font);
|
||||
// If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
|
||||
// monospaced so the caret aligns. Used for placeholder-parser errors.
|
||||
ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts);
|
||||
ErrorDialog(ErrorDialog &&) = delete;
|
||||
ErrorDialog(const ErrorDialog &) = delete;
|
||||
ErrorDialog &operator=(ErrorDialog &&) = delete;
|
||||
|
||||
@@ -54,6 +54,9 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
|
||||
case ConfigOptionDef::GUIType::one_string: m_fields.emplace(id, TextCtrl::Create<TextCtrl>(this->ctrl_parent(), opt, id)); break;
|
||||
case ConfigOptionDef::GUIType::plugin_picker: m_fields.emplace(id, PluginField::Create<PluginField>(this->ctrl_parent(), opt, id)); break;
|
||||
case ConfigOptionDef::GUIType::plugin_config: m_fields.emplace(id, PluginConfigField::Create<PluginConfigField>(this->ctrl_parent(), opt, id)); break;
|
||||
case ConfigOptionDef::GUIType::printer_agent_select: m_fields.emplace(
|
||||
id, PrinterAgentChoice::Create<PrinterAgentChoice>(this->ctrl_parent(), opt, id));
|
||||
break;
|
||||
default:
|
||||
switch (opt.type) {
|
||||
case coFloatOrPercent:
|
||||
@@ -386,6 +389,7 @@ void OptionsGroup::activate_line(Line& line)
|
||||
}
|
||||
if (label != nullptr && line.label_tooltip != "")
|
||||
label->SetToolTip(line.label_tooltip);
|
||||
line.label_widget = label;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,6 +578,7 @@ void OptionsGroup::clear(bool destroy_custom_ctrl)
|
||||
for (Line& line : m_lines) {
|
||||
if (line.near_label_widget_win)
|
||||
line.near_label_widget_win = nullptr;
|
||||
line.label_widget = nullptr;
|
||||
|
||||
if (line.widget_sizer) {
|
||||
line.widget_sizer->Clear(true);
|
||||
@@ -652,6 +657,16 @@ Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index
|
||||
|
||||
void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value)
|
||||
{
|
||||
if (opt_id == "printer_agent") {
|
||||
// TODO: Replace this option-specific branch with a generic value adapter if
|
||||
// more fields need custom field-value to config-value conversion.
|
||||
if (const std::string* id = boost::any_cast<std::string>(&value))
|
||||
this->change_opt_value("printer_agent", wxGetApp().canonical_printer_agent_id(*id));
|
||||
|
||||
OptionsGroup::on_change_OG(opt_id, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_opt_map.empty()) {
|
||||
auto it = m_opt_map.find(opt_id);
|
||||
if (it == m_opt_map.end()) {
|
||||
@@ -770,6 +785,19 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (opt_key == "printer_agent")
|
||||
{
|
||||
// why: printer_agent is a coString kept out of m_opt_map. The generic non-opt_map revert
|
||||
// below restores the edited config from get_value(), but a deregistered/"(missing)" saved
|
||||
// id has no selectable row, so the field yields no value and the edited config keeps the
|
||||
// user's interim pick -> stuck dirty. Restore the SAVED id straight into the edited config
|
||||
// (displayable or not; config is the saved or system baseline), then repaint and notify.
|
||||
const std::string saved_id = config.opt_string("printer_agent");
|
||||
set_value(opt_key, saved_id);
|
||||
this->change_opt_value(opt_key, saved_id);
|
||||
OptionsGroup::on_change_OG(opt_key, saved_id);
|
||||
return;
|
||||
}
|
||||
else if (m_opt_map.find(opt_key) == m_opt_map.end() ||
|
||||
// This option don't have corresponded field
|
||||
opt_key == "printable_area" || opt_key == "compatible_printers" || opt_key == "compatible_prints" || opt_key == "thumbnails" ||
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
widget_t widget {nullptr};
|
||||
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
|
||||
wxWindow* near_label_widget_win {nullptr};
|
||||
wxStaticText* label_widget {nullptr};
|
||||
wxSizer* widget_sizer {nullptr};
|
||||
wxSizer* extra_widget_sizer {nullptr};
|
||||
//BBS: export the extra colume widget
|
||||
@@ -81,6 +82,14 @@ public:
|
||||
label(_(label)), label_tooltip(_(tooltip)) {}
|
||||
Line() : m_is_separator(true) {}
|
||||
|
||||
void set_label(const wxString& new_label) {
|
||||
label = new_label;
|
||||
if (label_widget != nullptr) {
|
||||
label_widget->SetLabel(label + (label.IsEmpty() ? "" : ": "));
|
||||
label_widget->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_separator() const { return m_is_separator; }
|
||||
bool has_only_option(const std::string& opt_key) const { return m_options.size() == 1 && m_options[0].opt_id == opt_key; }
|
||||
|
||||
|
||||
@@ -2298,6 +2298,7 @@ arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const Dynamic
|
||||
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
|
||||
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
|
||||
int plate_width=m_width, plate_depth=m_depth;
|
||||
w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
|
||||
float depth = wt_size(1);
|
||||
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
|
||||
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "format.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
@@ -128,22 +127,8 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog()
|
||||
void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup)
|
||||
{
|
||||
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
// Special handling for printer_agent: convert fake enum index to string agent ID
|
||||
if (opt_key == "printer_agent") {
|
||||
try {
|
||||
int selected_idx = boost::any_cast<int>(value);
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
if (selected_idx >= 0 && selected_idx < static_cast<int>(agents.size())) {
|
||||
m_config->set_key_value("printer_agent",
|
||||
new ConfigOptionString(agents[selected_idx].id));
|
||||
}
|
||||
} catch (const boost::bad_any_cast&) {
|
||||
// If value is not an int, ignore
|
||||
}
|
||||
if (opt_key == "host_type" || opt_key == "printhost_authorization_type")
|
||||
this->update();
|
||||
} else if (opt_key == "host_type" || opt_key == "printhost_authorization_type") {
|
||||
this->update();
|
||||
}
|
||||
if (opt_key == "print_host")
|
||||
this->update_printhost_buttons();
|
||||
if (opt_key == "printhost_port")
|
||||
@@ -154,47 +139,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||
|
||||
m_optgroup->append_single_option_line("host_type");
|
||||
|
||||
// Build printer agent dropdown from registry (only if network agent is available)
|
||||
if (wxGetApp().getAgent() != nullptr) {
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
|
||||
if (!agents.empty()) {
|
||||
// Create a fake enum option to force a Choice widget instead of TextCtrl
|
||||
// (printer_agent is coString in config, but we need a dropdown)
|
||||
ConfigOptionDef def;
|
||||
def.type = coEnum;
|
||||
def.width = Field::def_width_wider();
|
||||
def.label = L("Printer Agent");
|
||||
def.tooltip = L("Select the network agent implementation for printer communication. "
|
||||
"Available agents are registered at startup.");
|
||||
def.mode = comAdvanced;
|
||||
|
||||
// Populate enum values and labels from registered agents
|
||||
for (const auto& agent : agents) {
|
||||
def.enum_values.push_back(agent.id);
|
||||
def.enum_labels.push_back(agent.display_name);
|
||||
}
|
||||
|
||||
// Resolve selected agent: use config value if valid, otherwise fall back to default
|
||||
std::string selected_agent = m_config->opt_string("printer_agent");
|
||||
auto it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
|
||||
if (it == agents.end()) {
|
||||
selected_agent = ORCA_PRINTER_AGENT_ID;
|
||||
it = std::find_if(agents.begin(), agents.end(), [&selected_agent](const auto& a) { return a.id == selected_agent; });
|
||||
}
|
||||
|
||||
if (it != agents.end()) {
|
||||
size_t default_idx = std::distance(agents.begin(), it);
|
||||
def.set_default_value(new ConfigOptionInt(static_cast<int>(default_idx)));
|
||||
}
|
||||
|
||||
// Create and append the option line
|
||||
auto agent_option = Option(def, "printer_agent");
|
||||
Line agent_line = m_optgroup->create_single_option_line(agent_option);
|
||||
m_optgroup->append_line(agent_line);
|
||||
}
|
||||
}
|
||||
|
||||
auto create_sizer_with_btn = [](wxWindow* parent, Button** btn, const std::string& icon_name, const wxString& label) {
|
||||
*btn = new Button(parent, label);
|
||||
(*btn)->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
|
||||
@@ -725,7 +669,7 @@ void PhysicalPrinterDialog::update(bool printer_change)
|
||||
}
|
||||
|
||||
// For bbl printers, show option to control the device tab
|
||||
if (wxGetApp().preset_bundle->is_bbl_vendor()) {
|
||||
if (wxGetApp().preset_bundle->is_bbl_vendor() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
m_optgroup->show_field("bbl_use_print_host_webui");
|
||||
const bool use_print_host_webui = !current_webui.empty();
|
||||
if (Field* printhost_webui_field = m_optgroup->get_field("bbl_use_print_host_webui"); printhost_webui_field) {
|
||||
@@ -816,31 +760,6 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change)
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update_printer_agent_type()
|
||||
{
|
||||
if (m_config == nullptr)
|
||||
return;
|
||||
|
||||
Field* agent_field = m_optgroup->get_field("printer_agent");
|
||||
if (!agent_field)
|
||||
return;
|
||||
|
||||
Choice* agent_choice = dynamic_cast<Choice*>(agent_field);
|
||||
if (!agent_choice)
|
||||
return;
|
||||
|
||||
// Sync selection with current config value
|
||||
const std::string current_agent = m_config->opt_string("printer_agent");
|
||||
|
||||
auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
for (size_t i = 0; i < agents.size(); ++i) {
|
||||
if (agents[i].id == current_agent) {
|
||||
agent_choice->set_value(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update_printers()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
@@ -894,11 +813,6 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event)
|
||||
{
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name);
|
||||
event.Skip();
|
||||
|
||||
// Defer printer agent switch to ensure preset save completes first
|
||||
wxGetApp().CallAfter([] {
|
||||
wxGetApp().switch_printer_agent();
|
||||
});
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -60,7 +60,6 @@ public:
|
||||
|
||||
void update(bool printer_change = false);
|
||||
void update_host_type(bool printer_change);
|
||||
void update_printer_agent_type();
|
||||
void update_preset_input();
|
||||
void update_printhost_buttons();
|
||||
void update_printers();
|
||||
|
||||
+31
-26
@@ -3246,7 +3246,8 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
|
||||
auto p_mainframe = wxGetApp().mainframe;
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
const bool use_native_device_tab = preset_bundle.use_bbl_device_tab() || NetworkAgentFactory::is_current_printer_agent_plugin();
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
const bool use_native_device_tab = preset_bundle.use_bbl_device_tab() || use_printer_agents;
|
||||
|
||||
if (preset_bundle.use_bbl_network()) {
|
||||
//only show connection button for not-BBL printer
|
||||
@@ -3258,7 +3259,8 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
p_mainframe->set_print_button_to_default(MainFrame::PrintSelectType::ePrintPlate);
|
||||
} else {
|
||||
//p->btn_connect_printer->Show();
|
||||
p->m_printer_connect->Show();
|
||||
// ORCA: hide the physical-printer connection button when printer agents are enabled
|
||||
p->m_printer_connect->Show(!use_printer_agents);
|
||||
|
||||
// ORCA: show/hide sync-ams button based on filament sync mode
|
||||
auto agent = wxGetApp().getAgent();
|
||||
@@ -3280,10 +3282,12 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
||||
apikey = cfg.opt_string("printhost_apikey");
|
||||
print_btn_type = preset_bundle.is_bbl_vendor() ? MainFrame::PrintSelectType::ePrintPlate : MainFrame::PrintSelectType::eSendGcode;
|
||||
print_btn_type = (preset_bundle.is_bbl_vendor() || wxGetApp().app_config->get_bool("use_printer_agents"))
|
||||
? MainFrame::PrintSelectType::ePrintPlate
|
||||
: MainFrame::PrintSelectType::eSendGcode;
|
||||
}
|
||||
|
||||
if (!use_native_device_tab)
|
||||
if (!use_native_device_tab || use_printer_agents)
|
||||
p_mainframe->load_printer_url(url, apikey);
|
||||
|
||||
|
||||
@@ -3439,7 +3443,10 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
|
||||
bool isBBL = preset_bundle.is_bbl_vendor();
|
||||
bool is_dual_extruder = extruder_variants->size() == 2;
|
||||
p->layout_printer(preset_bundle.use_bbl_network(), isBBL && is_dual_extruder);
|
||||
// why: agent mode drives the native device tab, so the sidebar lays out like BBL
|
||||
// (no physical-printer connect button).
|
||||
p->layout_printer(preset_bundle.use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents"),
|
||||
isBBL && is_dual_extruder);
|
||||
|
||||
// Update nozzle titles from printer config (e.g. "Main Nozzle" / "Auxiliary Nozzle" for N6)
|
||||
// UI left = DEPUTY_EXTRUDER_ID(1), UI right = MAIN_EXTRUDER_ID(0)
|
||||
@@ -5625,6 +5632,7 @@ struct Plater::priv
|
||||
void on_action_slice_all(SimpleEvent&);
|
||||
void on_action_publish(wxCommandEvent &evt);
|
||||
void on_action_print_plate(SimpleEvent&);
|
||||
void open_machine_select_dialog(int plate_idx, PrintFromType print_type = PrintFromType::FROM_NORMAL);
|
||||
void on_action_print_all(SimpleEvent&);
|
||||
void on_action_export_gcode(SimpleEvent&);
|
||||
void on_action_send_gcode(SimpleEvent&);
|
||||
@@ -11166,18 +11174,23 @@ void Plater::priv::on_action_print_plate(SimpleEvent&)
|
||||
}
|
||||
|
||||
PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.use_bbl_network()) {
|
||||
// BBS
|
||||
if (!m_select_machine_dlg)
|
||||
m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL);
|
||||
m_select_machine_dlg->prepare(partplate_list.get_curr_plate_index());
|
||||
m_select_machine_dlg->ShowModal();
|
||||
if (preset_bundle.use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
open_machine_select_dialog(partplate_list.get_curr_plate_index());
|
||||
} else {
|
||||
q->send_gcode_legacy(PLATE_CURRENT_IDX, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::open_machine_select_dialog(int plate_idx, PrintFromType print_type)
|
||||
{
|
||||
// BBS
|
||||
if (!m_select_machine_dlg)
|
||||
m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(print_type);
|
||||
m_select_machine_dlg->prepare(plate_idx);
|
||||
m_select_machine_dlg->ShowModal();
|
||||
}
|
||||
|
||||
void Plater::priv::on_action_send_to_multi_machine(SimpleEvent&)
|
||||
{
|
||||
if (!m_send_multi_dlg)
|
||||
@@ -11193,10 +11206,7 @@ void Plater::priv::on_action_print_plate_from_sdcard(SimpleEvent&)
|
||||
}
|
||||
|
||||
//BBS
|
||||
if (!m_select_machine_dlg) m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(PrintFromType::FROM_SDCARD_VIEW);
|
||||
m_select_machine_dlg->prepare(0);
|
||||
m_select_machine_dlg->ShowModal();
|
||||
open_machine_select_dialog(0, PrintFromType::FROM_SDCARD_VIEW);
|
||||
}
|
||||
|
||||
void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
@@ -11218,13 +11228,13 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
||||
new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_PREVIEW);
|
||||
update_sidebar();
|
||||
int old_sel = e.GetOldSelection();
|
||||
const bool is_printer_agent_plugin = NetworkAgentFactory::is_current_printer_agent_plugin();
|
||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||
const bool use_native_device_tab = wxGetApp().preset_bundle &&
|
||||
(wxGetApp().preset_bundle->use_bbl_device_tab() || is_printer_agent_plugin);
|
||||
(wxGetApp().preset_bundle->use_bbl_device_tab() || use_printer_agents);
|
||||
if (use_native_device_tab && new_sel == main_frame->m_tabpanel->FindPageByName(TAB_ID_MONITOR)) {
|
||||
// BBL network module is only required for BBL-vendor printers.
|
||||
// Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it.
|
||||
if (!is_printer_agent_plugin && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
if (!use_printer_agents && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||
e.Veto();
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2%, lack of network plugins") % old_sel % new_sel;
|
||||
if (q) {
|
||||
@@ -11280,13 +11290,8 @@ void Plater::priv::on_action_print_all(SimpleEvent&)
|
||||
}
|
||||
|
||||
PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
|
||||
if (preset_bundle.use_bbl_network()) {
|
||||
// BBS
|
||||
if (!m_select_machine_dlg)
|
||||
m_select_machine_dlg = new SelectMachineDialog(q);
|
||||
m_select_machine_dlg->set_print_type(PrintFromType::FROM_NORMAL);
|
||||
m_select_machine_dlg->prepare(PLATE_ALL_IDX);
|
||||
m_select_machine_dlg->ShowModal();
|
||||
if (preset_bundle.use_bbl_network() || wxGetApp().app_config->get_bool("use_printer_agents")) {
|
||||
open_machine_select_dialog(PLATE_ALL_IDX);
|
||||
} else {
|
||||
q->send_gcode_legacy(PLATE_ALL_IDX, nullptr);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Slic3r
|
||||
// IMPORTANT: ordinal order is the Plugins dialog Source sort priority.
|
||||
Mine,
|
||||
Subscribed,
|
||||
Orphaned,
|
||||
Local
|
||||
};
|
||||
|
||||
@@ -20,6 +21,7 @@ namespace Slic3r
|
||||
{
|
||||
case PluginSource::Mine: return "mine";
|
||||
case PluginSource::Subscribed: return "subscribed";
|
||||
case PluginSource::Orphaned: return "orphaned";
|
||||
case PluginSource::Local: return "local";
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,7 @@ struct PluginDialogItem
|
||||
bool loading = false;
|
||||
|
||||
bool is_cloud_plugin = false;
|
||||
bool orphaned = false;
|
||||
bool has_local_package = false;
|
||||
bool unauthorized = false;
|
||||
bool has_script_capability = false;
|
||||
@@ -246,6 +247,7 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
|
||||
payload_item["sharing_token"] = dialog_item.sharing_token;
|
||||
payload_item["thumbnail_url"] = dialog_item.thumbnail_url;
|
||||
payload_item["installed"] = dialog_item.has_local_package;
|
||||
payload_item["orphaned"] = dialog_item.orphaned;
|
||||
payload_item["installed_version"] = dialog_item.installed_version;
|
||||
payload_item["latest_version"] = dialog_item.latest_version;
|
||||
return payload_item;
|
||||
@@ -268,7 +270,8 @@ PluginSource derive_plugin_source(const PluginDescriptor& descriptor)
|
||||
const bool is_cloud = descriptor.is_cloud_plugin();
|
||||
const bool is_mine = is_cloud && has_cloud_meta && descriptor.cloud->is_mine;
|
||||
|
||||
// Source is ownership/locality only; issue states never replace this badge.
|
||||
if (is_cloud && has_cloud_meta && descriptor.cloud->orphaned)
|
||||
return PluginSource::Orphaned;
|
||||
if (is_mine)
|
||||
return PluginSource::Mine;
|
||||
if (is_cloud)
|
||||
@@ -281,11 +284,12 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
||||
PluginAvailableActions available_actions;
|
||||
const bool is_loading = item.status == PluginStatus::Loading;
|
||||
const bool is_cloud = item.is_cloud_plugin;
|
||||
const bool is_orphaned = item.orphaned;
|
||||
const bool is_mine = item.source == PluginSource::Mine;
|
||||
const bool has_local = item.has_local_package;
|
||||
const bool authorized_for_install = !item.unauthorized;
|
||||
|
||||
available_actions.toggle_installs_cloud_plugin = is_cloud && !has_local && authorized_for_install;
|
||||
available_actions.toggle_installs_cloud_plugin = is_cloud && !is_orphaned && !has_local && authorized_for_install;
|
||||
available_actions.can_toggle = !is_loading && (has_local || available_actions.toggle_installs_cloud_plugin);
|
||||
|
||||
auto add_action = [&available_actions](const char* id, const char* label, bool enabled = true, bool danger = false) {
|
||||
@@ -294,7 +298,7 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
||||
|
||||
// Owned cloud plugins fall through to the local delete: it removes the installed package only.
|
||||
// Deleting a plugin from the cloud is a plugin hub operation and is never offered here.
|
||||
if (is_cloud && !is_mine) {
|
||||
if (is_cloud && !is_orphaned && !is_mine) {
|
||||
add_action("unsubscribe_plugin", "Unsubscribe", true, true);
|
||||
} else if (has_local) {
|
||||
add_action("delete_plugin", "Delete", true, true);
|
||||
@@ -302,11 +306,13 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
|
||||
|
||||
add_action("open_folder", "Show in folder", has_local);
|
||||
|
||||
if (is_cloud) {
|
||||
add_action("reinstall_plugin", "Reinstall");
|
||||
} else {
|
||||
add_action("reload_plugin", "Reload");
|
||||
add_action("clear_cache_reload_plugin", "Delete cache and reload");
|
||||
if (!is_orphaned) {
|
||||
if (is_cloud) {
|
||||
add_action("reinstall_plugin", "Reinstall");
|
||||
} else {
|
||||
add_action("reload_plugin", "Reload");
|
||||
add_action("clear_cache_reload_plugin", "Delete cache and reload");
|
||||
}
|
||||
}
|
||||
|
||||
return available_actions;
|
||||
@@ -360,6 +366,7 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
|
||||
item.error_text = descriptor.normalized_error();
|
||||
item.has_error = descriptor.has_error();
|
||||
item.is_cloud_plugin = descriptor.is_cloud_plugin();
|
||||
item.orphaned = descriptor.cloud.has_value() && descriptor.cloud->orphaned;
|
||||
item.has_local_package = descriptor.has_local_package();
|
||||
item.unauthorized = descriptor.is_unauthorized();
|
||||
item.is_loaded = manager.is_plugin_loaded(descriptor.plugin_key);
|
||||
@@ -592,7 +599,35 @@ bool PluginsDialog::get_descriptor(const std::string& plugin_key, PluginDescript
|
||||
|
||||
void PluginsDialog::refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud)
|
||||
{
|
||||
run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() { send_plugins(); }, title, message);
|
||||
run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() {
|
||||
prompt_for_missing_plugins();
|
||||
send_plugins();
|
||||
}, title, message);
|
||||
}
|
||||
|
||||
void PluginsDialog::prompt_for_missing_plugins()
|
||||
{
|
||||
PluginManager& manager = PluginManager::instance();
|
||||
const std::vector<PluginDescriptor> missing = manager.get_missing_plugin_descriptors();
|
||||
if (missing.empty())
|
||||
return;
|
||||
|
||||
wxString names;
|
||||
std::vector<std::string> keys;
|
||||
keys.reserve(missing.size());
|
||||
for (const PluginDescriptor& plugin : missing) {
|
||||
keys.push_back(plugin.plugin_key);
|
||||
names += "\n- ";
|
||||
names += plugin_display_name(plugin.plugin_key);
|
||||
}
|
||||
|
||||
const int result = wxMessageBox(
|
||||
wxString::Format(_L("The following installed plugins were not found on disk:\n%s\n\nRemove them from OrcaSlicer?"), names),
|
||||
_L("Missing Plugins"), wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this);
|
||||
restore_z_order();
|
||||
|
||||
if (result == wxYES)
|
||||
manager.remove_missing_plugins(keys);
|
||||
}
|
||||
|
||||
void PluginsDialog::refresh_plugins()
|
||||
|
||||
@@ -68,6 +68,7 @@ private:
|
||||
bool get_descriptor(const std::string& plugin_key, Slic3r::PluginDescriptor& descriptor) const;
|
||||
|
||||
void refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud);
|
||||
void prompt_for_missing_plugins();
|
||||
void refresh_plugins();
|
||||
void toggle_plugin(const std::string& plugin_key, bool enabled);
|
||||
void toggle_plugin_capability(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name, bool enabled);
|
||||
|
||||
@@ -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));
|
||||
@@ -1127,6 +1135,14 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
wxGetApp().plater()->sidebar().update_presets(Preset::TYPE_FILAMENT);
|
||||
}
|
||||
|
||||
if (param == "use_printer_agents")
|
||||
{
|
||||
// Rebuild the Device tab so the native/web-UI choice reflects the new flag
|
||||
// immediately, instead of only on the next printer-preset change or restart.
|
||||
if (wxGetApp().plater())
|
||||
wxGetApp().plater()->sidebar().update_all_preset_comboboxes();
|
||||
}
|
||||
|
||||
if (param == "enable_high_low_temp_mixed_printing") {
|
||||
if (checkbox->GetValue()) {
|
||||
const wxString warning_title = _L("Bed Temperature Difference Warning");
|
||||
@@ -1914,6 +1930,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);
|
||||
|
||||
@@ -2071,6 +2109,12 @@ void PreferencesDialog::create_items()
|
||||
auto item_show_unsupported = create_item_checkbox(_L("Show unsupported presets"), _L("Show incompatible/unsupported presets in the printer and filament dropdown lists. These presets cannot be selected."), "show_unsupported_presets");
|
||||
g_sizer->Add(item_show_unsupported);
|
||||
|
||||
auto item_plugin_printer_agents = create_item_checkbox(
|
||||
_L("(Experimental) Use printer agents instead of print hosts"), _L(
|
||||
"Route print jobs for non-Bambu printers through printer plug-in agents instead of the classic print-host upload flow.\nWhen disabled, OrcaSlicer uses the legacy print-host behavior."),
|
||||
"use_printer_agents");
|
||||
g_sizer->Add(item_plugin_printer_agents);
|
||||
|
||||
//// DEVELOPER > Experimental Features
|
||||
g_sizer->Add(create_item_title(_L("Experimental Features")), 1, 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;
|
||||
|
||||
|
||||
@@ -865,6 +865,9 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
|
||||
clr_picker = new wxBitmapButton(parent, wxID_ANY, {}, wxDefaultPosition, wxSize(FromDIP(20), FromDIP(20)), wxBU_EXACTFIT | wxBU_AUTODRAW | wxBORDER_NONE);
|
||||
clr_picker->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
clr_picker->SetToolTip(_L("Click to select filament color"));
|
||||
#ifdef __WXGTK__
|
||||
RemoveButtonBorder(clr_picker);
|
||||
#endif
|
||||
clr_picker->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||
// Check if it's an official filament
|
||||
auto fila_type = Preset::remove_suffix_modified(GetValue().ToUTF8().data());
|
||||
|
||||
@@ -3629,7 +3629,7 @@ void SelectMachineDialog::on_send_print()
|
||||
BOOST_LOG_TRIVIAL(error) << "build_nozzle_info errors";
|
||||
}
|
||||
|
||||
m_print_job->sdcard_state = obj_->GetStorage()->get_sdcard_state();
|
||||
m_print_job->sdcard_state = obj_->GetStorage()->get_sdcard_state();
|
||||
m_print_job->has_sdcard = wxGetApp().app_config->get("allow_abnormal_storage") == "true"
|
||||
? (m_print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_NORMAL
|
||||
|| m_print_job->sdcard_state == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL)
|
||||
@@ -3868,12 +3868,11 @@ _compare_obj_names(MachineObject* obj1, MachineObject* obj2)
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*@note _collect_machine_list
|
||||
*@param dev_manager -- the device manager
|
||||
*@param sorted_machine_objs -- return the sorted machine objects
|
||||
*@param best_one -- return the best one
|
||||
*/
|
||||
/*******************************************************************/
|
||||
* @note _collect_machine_list
|
||||
* @param dev_manager -- the device manager
|
||||
* @param sorted_machine_objs -- return the sorted machine objects
|
||||
* @param best_one -- return the best one
|
||||
*******************************************************************/
|
||||
static void
|
||||
_collect_sorted_machines(Slic3r::DeviceManager* dev_manager,
|
||||
std::vector<MachineObject*>& sorted_machine_objs)
|
||||
|
||||
+104
-8
@@ -33,6 +33,7 @@
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "slic3r/Utils/NetworkAgentFactory.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
#include "slic3r/plugin/PluginConfig.hpp"
|
||||
#include "Plater.hpp"
|
||||
@@ -1738,6 +1739,13 @@ void Tab::toggle_line(const std::string &opt_key, bool toggle, int opt_index)
|
||||
if (line) line->toggle_visible = toggle;
|
||||
};
|
||||
|
||||
void Tab::set_option_label(const std::string &opt_key, const wxString &label, int opt_index)
|
||||
{
|
||||
if (!m_active_page) return;
|
||||
Line *line = m_active_page->get_line(opt_key, opt_index);
|
||||
if (line) line->set_label(label);
|
||||
}
|
||||
|
||||
// To be called by custom widgets, load a value into a config,
|
||||
// update the preset selection boxes (the dirty flags)
|
||||
// If value is saved before calling this function, put saved_value = true,
|
||||
@@ -2816,6 +2824,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
||||
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
||||
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_patterns#sparse-infill-smooth-factor");
|
||||
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
||||
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
|
||||
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
|
||||
@@ -3069,6 +3078,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("combine_brims", "others_settings_brim#combine-brims");
|
||||
optgroup->append_single_option_line("brim_ears_max_angle", "others_settings_brim#ear-max-angle");
|
||||
optgroup->append_single_option_line("brim_ears_detection_length", "others_settings_brim#ear-detection-radius");
|
||||
optgroup->append_single_option_line("brim_ears_outer_only", "others_settings_brim#brim-ears-outer-only");
|
||||
|
||||
optgroup = page->new_optgroup(L("Special mode"), L"param_special");
|
||||
optgroup->append_single_option_line("slicing_mode", "others_settings_special_mode#slicing-mode");
|
||||
@@ -3997,13 +4007,12 @@ void TabFilament::add_filament_overrides_page()
|
||||
|
||||
const int extruder_idx = 0; // #ys_FIXME
|
||||
|
||||
ConfigOptionsGroupShp retraction_optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
|
||||
auto append_retraction_option = [this, retraction_optgroup](const std::string& opt_key, int opt_index)
|
||||
auto append_retraction_option = [this](ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index)
|
||||
{
|
||||
Line line {"",""};
|
||||
line = retraction_optgroup->create_single_option_line(retraction_optgroup->get_option(opt_key, opt_index));
|
||||
line = optgroup->create_single_option_line(optgroup->get_option(opt_key, opt_index));
|
||||
|
||||
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(retraction_optgroup), opt_key, opt_index](wxWindow* parent) {
|
||||
line.near_label_widget = [this, optgroup_wk = ConfigOptionsGroupWkp(optgroup), opt_key, opt_index](wxWindow* parent) {
|
||||
auto check_box = new ::CheckBox(parent); // ORCA modernize checkboxes
|
||||
check_box->Bind(wxEVT_TOGGLEBUTTON, [this, optgroup_wk, opt_key, opt_index](wxCommandEvent& evt) {
|
||||
const bool is_checked = evt.IsChecked();
|
||||
@@ -4030,9 +4039,10 @@ void TabFilament::add_filament_overrides_page()
|
||||
return check_box;
|
||||
};
|
||||
|
||||
retraction_optgroup->append_line(line);
|
||||
optgroup->append_line(line);
|
||||
};
|
||||
|
||||
ConfigOptionsGroupShp retraction_optgroup = page->new_optgroup(L("Retraction"), L"param_retraction");
|
||||
for (const std::string opt_key : { "filament_retraction_length",
|
||||
"filament_z_hop",
|
||||
"filament_z_hop_types",
|
||||
@@ -4056,7 +4066,13 @@ void TabFilament::add_filament_overrides_page()
|
||||
//SoftFever
|
||||
// "filament_seam_gap"
|
||||
})
|
||||
append_retraction_option(opt_key, extruder_idx);
|
||||
append_retraction_option(retraction_optgroup, opt_key, extruder_idx);
|
||||
|
||||
ConfigOptionsGroupShp toolchange_optgroup = page->new_optgroup(L("Retraction when switching material"), L"param_retraction_material_change");
|
||||
for (const std::string opt_key : { "filament_retract_length_toolchange",
|
||||
"filament_retract_restart_extra_toolchange"
|
||||
})
|
||||
append_retraction_option(toolchange_optgroup, opt_key, extruder_idx);
|
||||
|
||||
ConfigOptionsGroupShp ironing_optgroup = page->new_optgroup(L("Ironing"), L"param_ironing");
|
||||
auto append_ironing_option = [this, ironing_optgroup](const std::string& opt_key, int opt_index)
|
||||
@@ -4171,6 +4187,8 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
|
||||
"filament_retraction_speed",
|
||||
"filament_deretraction_speed",
|
||||
"filament_retract_restart_extra",
|
||||
"filament_retract_length_toolchange",
|
||||
"filament_retract_restart_extra_toolchange",
|
||||
"filament_retraction_minimum_travel",
|
||||
"filament_retract_when_changing_layer",
|
||||
"filament_wipe",
|
||||
@@ -4201,7 +4219,8 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
|
||||
is_checked &= !dynamic_cast<ConfigOptionVectorBase*>(m_config->option(opt_key))->is_nil(extruder_idx);
|
||||
m_overrides_options[opt_key]->SetValue(is_checked);
|
||||
|
||||
Field* field = optgroup->get_fieldc(opt_key, 0);
|
||||
// the toolchange overrides live in their own optgroup, so search the whole page
|
||||
Field* field = page->get_field(opt_key, 0);
|
||||
if (field == nullptr) continue;
|
||||
|
||||
if (opt_key == "filament_long_retractions_when_cut") {
|
||||
@@ -5007,8 +5026,43 @@ void TabPrinter::build_fff()
|
||||
|
||||
optgroup->append_single_option_line("printer_structure", "printer_basic_information_advanced#printer-structure");
|
||||
optgroup->append_single_option_line("gcode_flavor", "printer_basic_information_advanced#g-code-flavor");
|
||||
optgroup->append_single_option_line("gcode_skip_config_block", "printer_basic_information_advanced#skip-g-code-config-block");
|
||||
optgroup->append_single_option_line("pellet_modded_printer", "printer_basic_information_advanced#pellet-modded-printer");
|
||||
optgroup->append_single_option_line("bbl_use_printhost", "printer_basic_information_advanced#use-3rd-party-print-host");
|
||||
|
||||
// "Printer Agent" dropdown - printer_agent is a coString; gui_type routes it to
|
||||
// PrinterAgentChoice instead of a TextCtrl. Rows and values come from the live agent
|
||||
// registry, and the value is stored as the agent-id string.
|
||||
if (wxGetApp().getAgent() != nullptr)
|
||||
{
|
||||
auto registered_printer_agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
if (!registered_printer_agents.empty())
|
||||
{
|
||||
ConfigOptionDef def;
|
||||
def.type = coString;
|
||||
def.gui_type = ConfigOptionDef::GUIType::printer_agent_select;
|
||||
def.width = 3 * Field::def_width_wider() / 2;
|
||||
def.label = L("Printer Agent");
|
||||
def.tooltip = L("Select the network agent implementation for printer communication. "
|
||||
"Available agents are registered at startup.");
|
||||
def.mode = comAdvanced;
|
||||
|
||||
// Create the field without get_option() so it is not registered in m_opt_map.
|
||||
// ConfigOptionsGroup handles printer_agent before the generic mapped write path.
|
||||
Line agent_line = optgroup->create_single_option_line(Option(def, "printer_agent"));
|
||||
optgroup->append_line(agent_line);
|
||||
if (Field* agent_field = get_field("printer_agent"))
|
||||
{
|
||||
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
|
||||
choice->set_value(m_config->opt_string("printer_agent"), false);
|
||||
}
|
||||
|
||||
// Register by hand so the UnsavedChanges dialog can render a row for it.
|
||||
wxGetApp().sidebar().get_searcher().add_key("printer_agent", m_type, optgroup->title,
|
||||
optgroup->config_category());
|
||||
}
|
||||
}
|
||||
|
||||
optgroup->append_single_option_line("use_3mf");
|
||||
optgroup->append_single_option_line("scan_first_layer" , "printer_basic_information_advanced#scan-first-layer");
|
||||
optgroup->append_single_option_line("enable_power_loss_recovery", "printer_basic_information_advanced#power-loss-recovery");
|
||||
@@ -5875,6 +5929,16 @@ void TabPrinter::reload_config()
|
||||
// so update it implicitly
|
||||
if (m_active_page && m_active_page->title() == "Multimaterial")
|
||||
m_active_page->set_value("extruders_count", int(m_extruders_count));
|
||||
|
||||
// m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly.
|
||||
if (Field* agent_field = get_field("printer_agent"))
|
||||
{
|
||||
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
|
||||
{
|
||||
const std::string selected_agent = m_config->opt_string("printer_agent");
|
||||
choice->set_value(selected_agent, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabPrinter::activate_selected_page(std::function<void()> throw_if_canceled)
|
||||
@@ -5885,6 +5949,16 @@ void TabPrinter::activate_selected_page(std::function<void()> throw_if_canceled)
|
||||
// so update it implicitly
|
||||
if (m_active_page && m_active_page->title() == "Multimaterial")
|
||||
m_active_page->set_value("extruders_count", int(m_extruders_count));
|
||||
|
||||
// m_opt_map-driven reload does not cover printer_agent, so sync this custom field explicitly.
|
||||
if (Field* agent_field = get_field("printer_agent"))
|
||||
{
|
||||
if (auto* choice = dynamic_cast<PrinterAgentChoice*>(agent_field); choice && choice->getWindow())
|
||||
{
|
||||
const std::string selected_agent = m_config->opt_string("printer_agent");
|
||||
choice->set_value(selected_agent, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabPrinter::clear_pages()
|
||||
@@ -7843,6 +7917,24 @@ bool TabPrinter::apply_extruder_cnt_from_cache()
|
||||
return false;
|
||||
}
|
||||
|
||||
void TabPrinter::refresh_printer_agent_dropdown() const
|
||||
{
|
||||
auto* choice = dynamic_cast<PrinterAgentChoice*>(get_field("printer_agent"));
|
||||
if (!choice || !choice->getWindow())
|
||||
return;
|
||||
|
||||
const auto agents = NetworkAgentFactory::get_registered_printer_agents();
|
||||
if (agents.empty())
|
||||
return;
|
||||
|
||||
// why: rows live on PrinterAgentChoice now; rebuild them from the live registry and re-select the stored id.
|
||||
const std::string selected_agent = wxGetApp().preset_bundle->printers.get_edited_preset()
|
||||
.config.opt_string("printer_agent");
|
||||
choice->reload_rows();
|
||||
choice->set_value(selected_agent, false);
|
||||
this->GetParent()->Layout();
|
||||
}
|
||||
|
||||
bool Tab::validate_custom_gcodes()
|
||||
{
|
||||
if (m_type != Preset::TYPE_FILAMENT &&
|
||||
@@ -8938,11 +9030,15 @@ ConfigManipulation Tab::get_config_manipulation()
|
||||
return toggle_line(opt_key, toggle, opt_index >= 0 ? opt_index + 256 : opt_index);
|
||||
};
|
||||
|
||||
auto cb_set_option_label = [this](const t_config_option_key &opt_key, const wxString &label, int opt_index) {
|
||||
return set_option_label(opt_key, label, opt_index >= 0 ? opt_index + 256 : opt_index);
|
||||
};
|
||||
|
||||
auto cb_value_change = [this](const std::string& opt_key, const boost::any& value) {
|
||||
return on_value_change(opt_key, value);
|
||||
};
|
||||
|
||||
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this);
|
||||
return ConfigManipulation(load_config, cb_toggle_field, cb_toggle_line, cb_value_change, nullptr, this, cb_set_option_label);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -402,6 +402,7 @@ public:
|
||||
Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1);
|
||||
void toggle_option(const std::string &opt_key, bool toggle, int opt_index = -1);
|
||||
void toggle_line(const std::string &opt_key, bool toggle, int opt_index = -1); // BBS: hide some line
|
||||
void set_option_label(const std::string &opt_key, const wxString &label, int opt_index = -1);
|
||||
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString);
|
||||
bool current_preset_is_dirty() const;
|
||||
bool saved_preset_is_dirty() const;
|
||||
@@ -674,6 +675,7 @@ public:
|
||||
wxSizer* create_bed_shape_widget(wxWindow* parent);
|
||||
void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr);
|
||||
bool apply_extruder_cnt_from_cache();
|
||||
void refresh_printer_agent_dropdown() const;
|
||||
};
|
||||
|
||||
class TabSLAMaterial : public Tab
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "../wxExtensions.hpp"
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
@@ -29,7 +29,7 @@ CheckBox::CheckBox(wxWindow *parent, int id)
|
||||
Bind(wxEVT_LEAVE_WINDOW, &CheckBox::updateBitmap, this);
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include "../wxExtensions.hpp"
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
RadioBox::RadioBox(wxWindow *parent)
|
||||
@@ -15,6 +19,7 @@ RadioBox::RadioBox(wxWindow *parent)
|
||||
// Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); });
|
||||
update();
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
wxSize bestSize = GetBestSize();
|
||||
bestSize.IncTo(m_on.GetBmpSize());
|
||||
SetSize(bestSize);
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
#include <wx/dcgraph.h>
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
BEGIN_EVENT_TABLE(SpinInput, StaticBox)
|
||||
|
||||
EVT_KEY_DOWN(SpinInput::keyPressed)
|
||||
@@ -58,6 +62,11 @@ void SpinInput::Create(wxWindow *parent,
|
||||
state_handler.attach({&label_color, &text_color});
|
||||
state_handler.update_binds();
|
||||
text_ctrl = new TextCtrl(this, wxID_ANY, text, {20, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER, wxTextValidator(wxFILTER_DIGITS));
|
||||
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveInputBorder(text_ctrl);
|
||||
#endif
|
||||
|
||||
text_ctrl->SetFont(Label::Body_14);
|
||||
text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states()));
|
||||
text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states()));
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "libslic3r/MacUtils.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
@@ -37,7 +37,7 @@ SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id)
|
||||
Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); });
|
||||
SetFont(Label::Body_12);
|
||||
|
||||
#ifdef __WXGTK3__
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dcgraph.h>
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include "../GUI_Utils.hpp"
|
||||
#endif
|
||||
|
||||
BEGIN_EVENT_TABLE(TextInput, StaticBox)
|
||||
|
||||
EVT_PAINT(TextInput::paintEvent)
|
||||
@@ -60,6 +64,11 @@ void TextInput::Create(wxWindow * parent,
|
||||
state_handler.attach({&label_color, & text_color});
|
||||
state_handler.update_binds();
|
||||
text_ctrl = new TextCtrl(this, wxID_ANY, text, {4, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER);
|
||||
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveInputBorder(text_ctrl);
|
||||
#endif
|
||||
|
||||
text_ctrl->SetFont(Label::Body_14);
|
||||
text_ctrl->SetInitialSize(text_ctrl->GetBestSize());
|
||||
text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states()));
|
||||
|
||||
@@ -1022,6 +1022,10 @@ ScalableButton::ScalableButton( wxWindow * parent,
|
||||
m_width = size.x * 10 / em;
|
||||
m_height= size.y * 10 / em;
|
||||
}
|
||||
|
||||
#ifdef __WXGTK__
|
||||
Slic3r::GUI::RemoveButtonBorder(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user