Merge upstream main: prime tower on non-rectangular beds, toolchange temperature wait, ironing speed override, preset dialog QOL

# Conflicts:
#	src/libslic3r/GCode.cpp
#	src/slic3r/GUI/Tab.cpp
This commit is contained in:
Clifford Garwood
2026-08-06 17:07:02 -04:00
321 changed files with 12723 additions and 1995 deletions
+1 -1
View File
@@ -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();
+7 -8
View File
@@ -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;
}
+2 -1
View File
@@ -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>();
}
};
+7 -1
View File
@@ -626,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");
@@ -877,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();
+2
View File
@@ -2273,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.
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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;
}
+110 -72
View File
@@ -770,30 +770,31 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
return changes;
}
// Clearance the tower-approach router keeps around the tower: the avoid box is
// inflated by this much before routing, and the inflated corners must stay on the
// bed for a route to be generated at all.
static constexpr float wipe_tower_routing_clearance = 2.f;
// BBS
// start_pos refers to the last position before the wipe_tower.
// end_pos refers to the wipe tower's start_pos.
// using the print coordinate system
Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point& start_pos,const Point &end_pos , const BoundingBox& avoid_polygon , const BoundingBox& printer_bbx) const
Polyline WipeTowerIntegration::generate_path_to_wipe_tower(const Point& start_pos,const Point &end_pos , const BoundingBox& avoid_polygon , const Polygons& bed_polygons) const
{
Polyline res;
coord_t alpha = scaled(2.f); // offset distance
coord_t alpha = scaled(wipe_tower_routing_clearance); // offset distance
BoundingBox avoid_polygon_inner = avoid_polygon;
avoid_polygon_inner.offset(alpha);
coord_t width = avoid_polygon_inner.max[0] - avoid_polygon_inner.min[0];
Polygon bed_polygon = printer_bbx.polygon();
Vec2f v(1, 0); // the first print direction of end_pos.
if (abs(end_pos[0] - avoid_polygon_inner.min[0]) < width / 2) v = -v; // judge whether the wipe tower's infill goes to the left or right.
// Judge whether the avoid_polygon_inner is outside the printer_bbx.
// Judge whether the avoid_polygon_inner is outside the bed. The real printable
// outline is tested (not its bounding box), so on circular/custom beds corners
// hanging off the bed are rejected.
// If so, do nothing and just go directly to the end_pos.
bool is_bbx_in_bed = true;
Points avoid_points = avoid_polygon_inner.polygon().points;
for (auto &wipe_tower_bbx_p : avoid_points) {
if (ClipperLib::PointInPolygon(wipe_tower_bbx_p, bed_polygon.points) != 1) {
is_bbx_in_bed = false;
break;
}
}
const bool is_bbx_in_bed = std::all_of(avoid_points.begin(), avoid_points.end(),
[&bed_polygons](const Point &pt) { return contains(bed_polygons, pt, /*border_result=*/false); });
if (!is_bbx_in_bed) {
res.points.push_back(end_pos);
return res;
@@ -900,27 +901,17 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
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
// Bed outline the tower-approach router plans against, in object coordinates. The real
// outline is returned, not its bounding box, so the router's containment tests fail off
// the bed on circular/custom shapes; the multi-nozzle narrowing lives in the accessor.
Polygons WipeTowerIntegration::shared_printable_area(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;
// The frame change is a pure translation, so transform the origin once.
const Point offset = wipe_tower_point_to_object_point(gcodegen, Vec2f(m_plate_origin(0), m_plate_origin(1)));
Polygons bed_polygons = gcodegen.m_print->get_extruder_shared_printable_polygon();
for (Polygon &poly : bed_polygons)
poly.translate(offset);
return bed_polygons;
}
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
@@ -935,15 +926,37 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
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))
// Transform tower-local corners exactly like the tcr points; a rotated tower gets a
// conservative axis-aligned envelope from the result.
auto tower_polygon = [&](const BoundingBoxf &bbx) {
Polygon poly = scaled(bbx).polygon();
for (Point &p : poly.points)
p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
return poly;
};
// The avoid envelope covers the first-layer brim (and rib flare), which a travel may
// cross freely: early-out only when the approach already starts over the tower body
// itself, so a start between the wall and the brim edge still gets routed in through
// the wall opening. Test the rotated polygon, not its bounding box — at angles off the
// axes the box's corner triangles cover most of the brim ring.
const float body_width = gcodegen.m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib ? m_wipe_tower_depth : m_right;
if (tower_polygon(BoundingBoxf(Vec2d(0., 0.), Vec2d(body_width, m_wipe_tower_depth))).contains(route_start))
return {};
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_travel_bounds(gcodegen));
const Polygons bed = shared_printable_area(gcodegen);
BoundingBox avoid_bbx = get_extents(tower_polygon(m_wipe_tower_bbx));
// The inflated corners must stay on the bed for the router to generate a route at all:
// clamp the box against the bed shrunk by the clearance the router adds, so a tower
// parked near the bed edge is still routed along the clamped side instead of always
// travelling straight across the tower.
BoundingBox clamp_bbx = get_extents(bed);
clamp_bbx.offset(-(scaled(wipe_tower_routing_clearance) + SCALED_EPSILON));
avoid_bbx.min = avoid_bbx.min.cwiseMax(clamp_bbx.min);
avoid_bbx.max = avoid_bbx.max.cwiseMin(clamp_bbx.max);
if (avoid_bbx.min.x() >= avoid_bbx.max.x() || avoid_bbx.min.y() >= avoid_bbx.max.y())
return {};
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, bed);
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)
@@ -1139,8 +1152,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();
@@ -1324,7 +1337,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 = printer_travel_bounds(gcodegen);
BoundingBox avoid_bbx;
{
// set avoid_bbx
avoid_bbx = scaled(m_wipe_tower_bbx);
@@ -1336,7 +1349,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
avoid_bbx = BoundingBox(avoid_points.points);
}
std::string travel_to_wipe_tower_gcode;
Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, printer_bbx);
Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, shared_printable_area(gcodegen));
for (size_t i = 0; i < travel_polyline.points.size(); ++i) {
const auto &p = travel_polyline.points[i];
@@ -1556,7 +1569,8 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(new_extruder_id);
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
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override,
WipeTower2::wait_for_temp_enabled(gcodegen.m_config)); // 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
@@ -1707,7 +1721,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
std::string trimmed = line;
trimmed.erase(0, trimmed.find_first_not_of(" \t"));
bool skip_line = false;
if (boost::starts_with(trimmed, "M109")) {
if (boost::starts_with(trimmed, "M109") && trimmed.find(WipeTower2::wait_for_temp_tag()) == std::string::npos) {
bool matches_extruder = true;
if (trimmed.find('T') != std::string::npos)
matches_extruder = trimmed.find(t_token) != std::string::npos;
@@ -2921,6 +2935,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
@@ -3096,7 +3111,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);
@@ -4223,23 +4238,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");
@@ -5646,7 +5663,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
@@ -7801,8 +7818,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
if (sloped) {
speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(speed));
}
}
else if(path.role() == erInternalBridgeInfill) {
} else if(path.role() == erInternalBridgeInfill) {
speed = m_config.get_abs_value_at("internal_bridge_speed", get_nozzle_config_index(m_writer.filament()->id()));
} else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) {
speed = NOZZLE_CONFIG(bridge_speed);
@@ -7813,7 +7829,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} else if (path.role() == erTopSolidInfill) {
speed = NOZZLE_CONFIG(top_surface_speed);
} else if (path.role() == erIroning) {
speed = m_config.get_abs_value("ironing_speed");
const size_t filament_idx = get_filament_config_index(m_writer.filament()->id());
speed = m_config.filament_ironing_speed.is_nil(filament_idx)
? m_config.get_abs_value("ironing_speed")
: m_config.filament_ironing_speed.get_at(filament_idx);
} else if (path.role() == erBottomSurface) {
speed = NOZZLE_CONFIG(initial_layer_infill_speed);
} else if (path.role() == erGapFill) {
@@ -9104,7 +9123,7 @@ void GCode::update_placeholder_parser_with_variant_params()
}
}
std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override)
std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override, bool defer_temp_wait)
{
int new_extruder_id = get_extruder_id(new_filament_id);
if (!m_writer.need_toolchange(new_filament_id))
@@ -9232,7 +9251,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)
@@ -9240,6 +9259,24 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
if (toolchange_temp_override > 0)
new_filament_temp = toolchange_temp_override;
// With wait_for_temp_on_wipe_tower the blocking M109 is deferred to the wipe tower, so raise
// the incoming filament's target here — ahead of the tool change rather than after it — and
// let the heat-up overlap the change itself as well as the travel to the tower. The command
// always carries an explicit tool index (the option is off for single extruder MM, so the
// writer emits one), leaving the outgoing filament that pre_toolchange just dropped to its
// standby temperature alone. nozzle_temperature == 0 means "use the first layer temperature".
if (defer_temp_wait) {
// Target what the tower will wait on. It waits on the first layer temperature not only on
// the first layer but also while priming, which runs before any layer is set: there
// on_first_layer() is false and print_z is the initial layer height, so neither test above
// catches it. nozzle_temperature == 0 means "use the first layer temperature" as well.
int preheat_temp = new_filament_temp;
if (toolchange_temp_override <= 0 && (m_layer == nullptr || preheat_temp <= 0))
preheat_temp = m_config.nozzle_temperature_initial_layer.get_at(new_fi);
if (preheat_temp > 0)
gcode += m_writer.set_temperature(preheat_temp, false, new_filament_id);
}
Vec3d nozzle_pos = m_writer.get_position();
float old_retract_length, old_retract_length_toolchange, wipe_volume;
int old_filament_temp, old_filament_e_feedrate;
@@ -9263,7 +9300,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.
@@ -9549,9 +9586,10 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
}
check_add_eol(gcode);
}
// Set the new extruder to the operating temperature.
if (m_ooze_prevention.enable)
// Set the new extruder to the operating temperature. With defer_temp_wait the target was
// already raised before the tool change and the blocking wait belongs to the wipe tower
// generator, so there is nothing left to restore here.
if (m_ooze_prevention.enable && !defer_temp_wait)
gcode += m_ooze_prevention.post_toolchange(*this);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
+3 -3
View File
@@ -130,11 +130,11 @@ public:
private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
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;
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const Polygons &bed_polygons) 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;
Polygons shared_printable_area(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;
@@ -271,7 +271,7 @@ public:
std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone);
// extra_retract forwards a PETG pre-extrusion over-extrusion; default 0 -> identical to the plain deretract.
std::string unretract(float extra_retract = 0.f) { return m_writer.unlift() + m_writer.unretract(extra_retract); }
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1);
std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1, bool defer_temp_wait = false);
bool is_BBL_Printer();
WipeTowerType wipe_tower_type();
+61 -16
View File
@@ -617,6 +617,18 @@ Polygon generate_rectange_polygon(const Vec2f &wt_box_min ,const Vec2f & wt_box_
return res;
}
const char* flush_planner_queue_command(GCodeFlavor flavor)
{
return flavor == gcfKlipper ? "M400\n" : "G4 S0\n";
}
std::string wait_command(GCodeFlavor flavor, float seconds)
{
if (flavor == gcfKlipper)
return "G4 P" + std::to_string(std::lround(seconds * 1000.f)) + "\n";
return "G4 S" + Slic3r::float_to_string_decimal_point(seconds, 3) + "\n";
}
class WipeTowerWriter
{
public:
@@ -1145,7 +1157,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;
}
@@ -1190,7 +1202,7 @@ public:
WipeTowerWriter& flush_planner_queue()
{
m_gcode += "G4 S0\n";
m_gcode += flush_planner_queue_command(m_gcode_flavor);
return *this;
}
@@ -1333,6 +1345,8 @@ public:
{
std::string buffer;
if (wait_for_moves)
// Not flush_planner_queue_command(): this BBL precool path wants M400, which every
// flavor it reaches understands, not the zero dwell the other flavors flush with.
buffer += "M400\n";
buffer += "M104";
if (target_extruder != -1)
@@ -1616,25 +1630,56 @@ float WipeTower::get_auto_brim_by_height(float max_height) {
return 8.f;
}
Vec2f WipeTower::move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2,int scaled_offset)
Vec2f WipeTower::move_box_inside_polygon(const BoundingBox &box, const Polygons &polygons, coord_t offset)
{
Vec2f res{0, 0};
if (box1.size()[0] >= box2.size()[0]- 2*scaled_offset || box1.size()[1] >= box2.size()[1]-2*scaled_offset) return res;
if (polygons.empty()) return Vec2f{0.f, 0.f};
if (box1.max[0] > box2.max[0] - scaled_offset) {
res[0] = unscaled<float>((box2.max[0] - scaled_offset) - box1.max[0]);
}
else if (box1.min[0] < box2.min[0] + scaled_offset) {
res[0] = unscaled<float>((box2.min[0] + scaled_offset) - box1.min[0]);
const BoundingBox bed = get_extents(polygons);
// No position fits the footprint.
if (box.size().x() >= bed.size().x() - 2 * offset || box.size().y() >= bed.size().y() - 2 * offset)
return Vec2f{0.f, 0.f};
// Clamp against the bounding box first, moving only along the axis that is violated so a dragged
// prime tower slides along the bed edge instead of jumping inwards.
Point shift(0, 0);
for (int axis = 0; axis < 2; ++axis) {
if (box.max[axis] > bed.max[axis] - offset)
shift[axis] = (bed.max[axis] - offset) - box.max[axis];
else if (box.min[axis] < bed.min[axis] + offset)
shift[axis] = (bed.min[axis] + offset) - box.min[axis];
}
if (box1.max[1] > box2.max[1] - scaled_offset) {
res[1] = unscaled<float>((box2.max[1] - scaled_offset) - box1.max[1]);
// A bed that fills its own bounding box is fully clamped by that, so every rectangular bed — all
// but the delta-style profiles — stops here and keeps its historic placement, including when a
// negative margin lets the footprint hang over the edge. The tolerance is relative because an
// exact rectangle loses a few ulps once the areas are squared world coordinates.
double area = 0.;
for (const Polygon &poly : polygons) area += std::abs(poly.area());
const double bed_area = double(bed.size().x()) * double(bed.size().y());
if (area >= bed_area * (1. - EPSILON)) return unscaled<float>(shift);
// Clamp a negative margin (an auto brim width that has not been resolved yet) to zero: padding by
// it would shrink the footprint and hand back a position the validation still rejects. The
// epsilon lets the move's round trip through millimeters land on the outline without counting as
// a violation.
BoundingBox padded = box.inflated(std::max<coord_t>(offset, 0) - SCALED_EPSILON);
padded.translate(shift);
auto fits = [&padded, &polygons](const Point &move) {
BoundingBox moved = padded;
moved.translate(move);
return diff(Polygons{moved.polygon()}, polygons).empty();
};
if (fits(Point(0, 0))) return unscaled<float>(shift);
// Walk towards the middle of the bed. On every non-rectangular bed we ship, the fitting positions
// form a convex region around it, so bisecting stops just inside the outline.
Point lo(0, 0), hi = bed.center() - padded.center();
if (!fits(hi)) return unscaled<float>(shift);
for (int i = 0; i < 12; ++i) {
const Point mid = (lo + hi) / 2;
if (fits(mid)) hi = mid; else lo = mid;
}
else if (box1.min[1] < box2.min[1] + scaled_offset) {
res[1] = unscaled<float>((box2.min[1] + scaled_offset) - box1.min[1]);
}
return res;
return unscaled<float>(Point(shift + hi));
}
Polygon WipeTower::rib_section(float width, float depth, float rib_length, float rib_width,bool fillet_wall)
+11 -1
View File
@@ -26,6 +26,12 @@ enum GCodeFlavor : unsigned char;
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
{
public:
@@ -39,7 +45,11 @@ public:
static TriangleMesh its_make_rib_tower(float width, float depth, float height, float rib_length, float rib_width, bool fillet_wall);
static TriangleMesh its_make_rib_brim(const Polygon& brim, float layer_height);
static Polygon rib_section(float width, float depth, float rib_length, float rib_width, bool fillet_wall);
static Vec2f move_box_inside_box(const BoundingBox &box1, const BoundingBox &box2, int offset = 0);
// Translation that brings a footprint inside the printable outline, padded by offset. The prime
// tower is validated against the real outline (see layered_print_cleareance_valid), so clamping
// against the bounding box alone would leave it off a delta or hexagonal bed. box and polygons
// must share one scaled coordinate frame; the translation comes back in millimeters.
static Vec2f move_box_inside_polygon(const BoundingBox &box, const Polygons &polygons, coord_t offset = 0);
static Polygon rounding_polygon(Polygon &polygon, double rounding = 2., double angle_tol = 30. / 180. * PI);
struct Extrusion
{
+171 -24
View File
@@ -386,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;
}
@@ -412,6 +413,7 @@ public:
const Vec2f& pos() const { return m_current_pos; }
const Vec2f start_pos_rotated() const { return m_start_pos; }
const Vec2f pos_rotated() const { return this->rotate(m_current_pos); }
const Vec2f rotated(const Vec2f &pt) const { return this->rotate(pt); }
float elapsed_time() const { return m_elapsed_time; }
float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
@@ -623,10 +625,13 @@ public:
}
// Set extruder temperature, don't wait by default.
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false)
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false, const std::string& comment = std::string())
{
m_gcode += "G4 S0\n"; // to flush planner queue
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
flush_planner_queue();
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature);
if (!comment.empty())
m_gcode += " " + comment;
m_gcode += "\n";
return *this;
}
@@ -635,7 +640,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;
}
@@ -677,8 +682,8 @@ public:
}
WipeTowerWriter2& flush_planner_queue()
{
m_gcode += "G4 S0\n";
{
m_gcode += flush_planner_queue_command(m_gcode_flavor);
return *this;
}
@@ -1005,6 +1010,13 @@ bool WipeTower2::use_gap_wall(const PrintConfig& config)
return config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone;
}
bool WipeTower2::wait_for_temp_enabled(const PrintConfig& config)
{
// SEMM runs its own unload/load temperature sequence; the GUI hides the option
// there but a profile may still carry it set.
return config.wait_for_temp_on_wipe_tower.value && !config.single_extruder_multi_material.value;
}
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),
@@ -1034,7 +1046,8 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
m_wall_type((int)config.wipe_tower_wall_type),
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)
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value),
m_wait_for_temp_on_wipe_tower(wait_for_temp_enabled(config))
{
// Read absolute value of first layer speed, if given as percentage,
// it is taken over following default. Speeds from config are not
@@ -1084,6 +1097,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
m_bed_bottom_left = m_bed_shape == RectangularBed
? Vec2f(bed_points.front().x(), bed_points.front().y())
: Vec2f::Zero();
m_bed_polygon = Polygon::new_scale(bed_points);
}
@@ -1235,7 +1249,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
unsigned int tool = tools[idx_tool];
m_left_to_right = true;
toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials.
toolchange_Change(writer, tool, m_filpar[tool].material, m_filpar[tool].first_layer_temperature, false); // Select the tool, set a speed override for soluble and flex materials.
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.
@@ -1354,13 +1368,19 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
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),
new_tool_temp);
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
// Wait-at-tower target: the interface temp when an interface boost applies on this layer,
// otherwise the print temp (nozzle_temperature == 0 means "use the first layer temp").
int wait_for_temp = interface_layer && m_filpar[tool].interface_print_temperature > 0 ?
m_filpar[tool].interface_print_temperature :
(is_first_layer() || m_filpar[tool].temperature == 0 ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
toolchange_Change(writer, tool, m_filpar[tool].material, wait_for_temp, true); // Change the tool, set a speed override for soluble and flex materials.
toolchange_Load(writer, cleaning_box);
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
int base_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature;
if (interface_layer) {
int interface_temp = m_filpar[tool].interface_print_temperature;
if (interface_temp > 0 && interface_temp != base_temp)
// With wait-for-temp-on-wipe-tower the toolchange already blocked for the interface temp.
if (interface_temp > 0 && interface_temp != base_temp && !m_wait_for_temp_on_wipe_tower)
writer.set_extruder_temp(interface_temp, true);
if (m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp)
writer.set_extruder_temp(base_temp, false);
@@ -1670,7 +1690,9 @@ void WipeTower2::toolchange_Unload(
void WipeTower2::toolchange_Change(
WipeTowerWriter2 &writer,
const size_t new_tool,
const std::string& new_material)
const std::string& new_material,
const int wait_for_temp,
const bool wait_beside_tower)
{
// Ask the writer about how much of the old filament we consumed:
if (m_current_tool < m_used_filament_length.size())
@@ -1684,6 +1706,90 @@ void WipeTower2::toolchange_Change(
if (m_is_mk4mmu3)
writer.switch_filament_monitoring(true);
const bool wait_for_temp_here = m_wait_for_temp_on_wipe_tower && wait_for_temp > 0;
// The Tn above was issued without a blocking temperature wait (GCode::set_extruder only raises
// the target, ahead of the Tn); block here, before the deretraction below, which must not
// extrude on a cold nozzle. Like the Bambu H2C, park beside the tower for the heat-up so drool lands
// next to it instead of on its top surface — nearest x side first, then that side clamped
// toward the bed edge, then the far side, in place if all would leave the bed. Raw
// pre-rotated moves (see the repositioning move below) keep the writer's tracked position
// at the tower entry. The tag keeps the
// interface-temp deduplication pass in append_tcr2 from stripping the M109.
if (wait_for_temp_here && wait_beside_tower) {
// The rib wall and the stabilization cone bulge past the nominal width rectangle
// (widest near the bottom), and the first-layer brim is printed around the wall later
// in the layer — clear the widest of them, not just the rectangle, so the park point
// and its drool stay off the tower.
float min_x = 0.f, max_x = m_wipe_tower_width;
if (m_wall_type == (int)wtwRib) {
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
const BoundingBox rib_bbox = get_extents(generate_rib_polygon(wt_box)); // the fillet stays within this bbox
min_x = std::min(min_x, unscaled<float>(rib_bbox.min.x()));
max_x = std::max(max_x, unscaled<float>(rib_bbox.max.x()));
} else if (m_wall_type == (int)wtwCone) {
const double support_scale = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth,
m_wipe_tower_cone_angle).second;
const double z = m_no_sparse_layers ? (m_current_height + m_layer_info->height) : m_layer_info->z;
const double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle / 2.f)) * (m_wipe_tower_height - z);
const double w = m_layer_info->depth + m_perimeter_width;
if (r > 0.5 * w + 0.01) { // same guard as generate_support_cone_wall
const float bulge = float(std::sqrt(r * r - 0.25 * w * w) / support_scale);
min_x = std::min(min_x, m_wipe_tower_width / 2.f - bulge);
max_x = std::max(max_x, m_wipe_tower_width / 2.f + bulge);
}
}
if (is_first_layer()) {
const float brim = m_wipe_tower_brim_width < 0.f ? WipeTower::get_auto_brim_by_height(m_wipe_tower_height) :
m_wipe_tower_brim_width;
min_x -= brim;
max_x += brim;
}
constexpr float gap = 2.f;
constexpr float min_gap = 0.5f;
const bool on_left = writer.x() < m_wipe_tower_width / 2.f;
const float near_x = on_left ? min_x - gap : max_x + gap;
const float far_x = on_left ? max_x + gap : min_x - gap;
const Eigen::Rotation2Df to_bed(float(Geometry::deg2rad(m_wipe_tower_rotation_angle)));
auto park_pt_on_bed = [this, &writer, to_bed](float side_x) {
const Vec2f bed_pt = to_bed * (writer.rotated(Vec2f(side_x, writer.y())) + m_rib_offset) + m_wipe_tower_pos;
return m_bed_polygon.contains(Point::new_scale(bed_pt.x(), bed_pt.y()));
};
float park_x = near_x;
bool have_park = park_pt_on_bed(near_x);
if (!have_park) {
// The ideal near point hangs off the bed: pull it back to the bed edge as long
// as that still clears the tower envelope by min_gap (the BBL tower clamps its
// stop_pos against the bed the same way in append_tcr). Bisection, because with
// tower rotation and non-rectangular beds the bed edge is not axis-aligned.
const float limit_x = on_left ? min_x - min_gap : max_x + min_gap;
if (park_pt_on_bed(limit_x)) {
float on = limit_x, off = near_x;
for (int i = 0; i < 8; ++i) {
const float mid = 0.5f * (on + off);
if (park_pt_on_bed(mid))
on = mid;
else
off = mid;
}
park_x = on;
have_park = true;
}
}
if (!have_park && park_pt_on_bed(far_x)) {
park_x = far_x;
have_park = true;
}
if (have_park) {
const Vec2f stop = writer.rotated(Vec2f(park_x, writer.y()));
writer.feedrate(m_travel_speed * 60.f)
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(stop.x())
+ " Y" + Slic3r::float_to_string_decimal_point(stop.y())
+ never_skip_tag() + "\n");
}
writer.set_extruder_temp(wait_for_temp, true, wait_for_temp_tag());
}
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
// postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
@@ -1694,6 +1800,10 @@ void WipeTower2::toolchange_Change(
+ never_skip_tag() + "\n"
);
// Priming has no tower to park beside — wait right at the priming line instead.
if (wait_for_temp_here && !wait_beside_tower)
writer.set_extruder_temp(wait_for_temp, true, wait_for_temp_tag());
writer.append("[deretraction_from_wipe_tower_generator]");
// The toolchange Tn command will be inserted later, only in case that the user does
@@ -2020,31 +2130,68 @@ std::pair<double, double> WipeTower2::get_wipe_tower_cone_base(double width, dou
}
// Static method to extract wipe_volumes[from][to] from the configuration.
std::vector<std::vector<float>> WipeTower2::extract_wipe_volumes(const PrintConfig& config)
// Takes a ConfigBase so the GUI's wipe tower size estimate can pass the plate's
// DynamicPrintConfig directly instead of materializing a full PrintConfig per call.
std::vector<std::vector<float>> WipeTower2::extract_wipe_volumes(const ConfigBase& config)
{
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
std::vector<float> wiping_matrix(cast<float>(config.flush_volumes_matrix.values));
auto scale = config.flush_multiplier.get_at(0);
// flush_volumes_matrix holds one filaments x filaments block per nozzle (written by
// PresetBundle::update_multi_material_filament_presets), so the filament count is
// sqrt(size / nozzles). One tower serves every nozzle and the filament to nozzle assignment is
// only decided later by ToolOrdering, so fold the blocks with std::max: the depth reserved here
// has to cover the worst nozzle. With a single nozzle the fold has one term.
const std::vector<double> &raw_matrix = config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
const auto *nozzle_diameter = config.option<ConfigOptionFloats>("nozzle_diameter");
size_t nozzle_nums = (nozzle_diameter == nullptr || nozzle_diameter->values.empty()) ? 1 : nozzle_diameter->values.size();
unsigned int number_of_extruders = (unsigned int)(sqrt(raw_matrix.size() / nozzle_nums) + EPSILON);
if (size_t(number_of_extruders) * number_of_extruders * nozzle_nums != raw_matrix.size()) {
// Saved for a different nozzle count (older project, or the printer was just switched):
// fall back to reading the whole option as one block, as this did before.
nozzle_nums = 1;
number_of_extruders = (unsigned int)(sqrt(raw_matrix.size()) + EPSILON);
}
// The values shall only be used when SEMM is enabled. The purging for other printers
// is determined by filament_minimal_purge_on_wipe_tower.
if (! config.purge_in_prime_tower.value || ! config.single_extruder_multi_material.value)
std::fill(wiping_matrix.begin(), wiping_matrix.end(), 0.f);
const bool purge = config.option<ConfigOptionBool>("purge_in_prime_tower")->value
&& config.option<ConfigOptionBool>("single_extruder_multi_material")->value;
// Extract purging volumes for each extruder pair:
std::vector<std::vector<float>> wipe_volumes;
const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON);
for (size_t i = 0; i<number_of_extruders; ++i)
wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
// Extract purging volumes for each extruder pair, each nozzle's block scaled by its own multiplier:
std::vector<std::vector<float>> wipe_volumes(number_of_extruders, std::vector<float>(number_of_extruders, 0.f));
if (purge) {
const auto *multiplier = config.option<ConfigOptionFloats>("flush_multiplier");
for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) {
const std::vector<double> block = get_flush_volumes_matrix(raw_matrix, nozzle_id, nozzle_nums);
const double scale = multiplier->get_at(nozzle_id);
for (unsigned int i = 0; i<number_of_extruders; ++i)
for (unsigned int j = 0; j<number_of_extruders; ++j)
wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j], float(block[size_t(i) * number_of_extruders + j]) * scale);
}
}
// Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview.
const auto *minimal_purge = config.option<ConfigOptionFloats>("filament_minimal_purge_on_wipe_tower");
for (unsigned int i = 0; i<number_of_extruders; ++i)
for (unsigned int j = 0; j<number_of_extruders; ++j)
wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j] * scale, config.filament_minimal_purge_on_wipe_tower.get_at(j));
wipe_volumes[i][j] = std::max<float>(wipe_volumes[i][j], minimal_purge->get_at(j));
return wipe_volumes;
}
float WipeTower2::estimate_semm_flush_volume(const ConfigBase& config, size_t filaments_cnt)
{
const std::vector<std::vector<float>> wipe_volumes = extract_wipe_volumes(config);
if (wipe_volumes.empty()) // an empty flush matrix would make the average below 0/0
return 0.f;
float maximum = 0.f;
for (const std::vector<float> &v : wipe_volumes)
maximum += *std::max_element(v.begin(), v.end());
maximum = maximum * filaments_cnt / wipe_volumes.size();
// Orca: it's overshooting a bit, so let's reduce it a bit
maximum *= 0.6;
return maximum;
}
static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width)
{
float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow;
+18 -2
View File
@@ -17,13 +17,20 @@ namespace Slic3r
class WipeTowerWriter2;
class PrintRegionConfig;
class ConfigBase;
class WipeTower2
{
public:
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
// Marks the wait-for-temp-on-wipe-tower M109 so the interface-temp deduplication pass
// in WipeTowerIntegration::append_tcr2 does not strip it.
static const std::string wait_for_temp_tag() { return ";_WAIT_FOR_TEMP_ON_WIPE_TOWER"; }
static std::pair<double, double> get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg);
static std::vector<std::vector<float>> extract_wipe_volumes(const PrintConfig& config);
static std::vector<std::vector<float>> extract_wipe_volumes(const ConfigBase& config);
// Estimated total flush volume of a SEMM print with the given number of filaments,
// used to reserve wipe tower space before the tower is generated.
static float estimate_semm_flush_volume(const ConfigBase& config, size_t filaments_cnt);
// Construct ToolChangeResult from current state of WipeTower2 and WipeTowerWriter2.
@@ -38,6 +45,11 @@ public:
// Shared with the entry routing in GCode.cpp so the router and the tower agree.
static bool use_gap_wall(const PrintConfig& config);
// Whether the blocking toolchange temperature wait moves onto the wipe tower.
// Shared with the defer flag in GCode.cpp append_tcr2 so the deferral and the
// tower's tagged M109 can never disagree.
static bool wait_for_temp_enabled(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 )
@@ -227,6 +239,7 @@ private:
size_t m_first_layer_idx = size_t(-1);
bool m_enable_tower_interface_features = false;
bool m_enable_tower_interface_cooldown_during_tower = false;
bool m_wait_for_temp_on_wipe_tower = false;
bool m_prev_layer_had_interface = false;
bool m_current_layer_has_interface = false;
@@ -263,6 +276,7 @@ private:
} m_bed_shape;
float m_bed_width; // width of the bed bounding box
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
Polygon m_bed_polygon; // printable_area contour (scaled)
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
@@ -385,7 +399,9 @@ private:
void toolchange_Change(
WipeTowerWriter2 &writer,
const size_t new_tool,
const std::string& new_material);
const std::string& new_material,
const int wait_for_temp,
const bool wait_beside_tower);
void toolchange_Load(
WipeTowerWriter2 &writer,
+28 -24
View File
@@ -3,6 +3,7 @@
#include "I18N.hpp"
#include "PrintConfig.hpp"
#include "ClipperUtils.hpp"
#include "Geometry/ArcWelder.hpp"
#include "Line.hpp"
#include <algorithm>
#include <iomanip>
@@ -1035,45 +1036,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");
+1 -1
View File
@@ -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
View File
@@ -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;
};
+17 -12
View File
@@ -1977,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());
@@ -2090,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
}
+2
View File
@@ -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";
+4 -2
View File
@@ -1360,6 +1360,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",
@@ -1413,7 +1415,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",
@@ -1430,7 +1432,7 @@ static std::vector<std::string> s_Preset_printer_options {
"use_relative_e_distances", "extruder_type", "use_firmware_retraction", "printer_notes",
"grab_length", "support_object_skip_flush", "physical_extruder_map",
"cooling_tube_retraction",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower",
"cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "wipe_tower_type", "purge_in_prime_tower", "enable_filament_ramming", "tool_change_on_wipe_tower", "wait_for_temp_on_wipe_tower",
"z_offset",
"disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "use_3mf", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut",
"bed_temperature_formula", "nozzle_flush_dataset",
+39 -21
View File
@@ -283,6 +283,14 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_x"
|| opt_key == "wipe_tower_y"
|| opt_key == "wipe_tower_rotation_angle") {
// The tower gcode itself is position-independent (position and rotation are applied
// at export), except that the wait_for_temp_on_wipe_tower park bakes a bed-relative
// side choice into it (WipeTower2::toolchange_Change) — regenerate it when the tower
// moves. Gating on the old config is safe: both inputs of wait_for_temp_enabled
// invalidate psWipeTower themselves when they are part of the same diff.
if ((opt_key == "wipe_tower_x" || opt_key == "wipe_tower_y" || opt_key == "wipe_tower_rotation_angle")
&& WipeTower2::wait_for_temp_enabled(m_config))
steps.emplace_back(psWipeTower);
steps.emplace_back(psSkirtBrim);
} else if (
opt_key == "slicing_pipeline_plugin"
@@ -383,6 +391,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wiping_volumes_extruders"
|| opt_key == "enable_filament_ramming"
|| opt_key == "tool_change_on_wipe_tower"
|| opt_key == "wait_for_temp_on_wipe_tower"
|| opt_key == "purge_in_prime_tower"
|| opt_key == "z_offset"
|| opt_key == "support_multi_bed_types"
@@ -1040,13 +1049,14 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y));
wipe_tower_convex_hull.points.emplace_back(scale_(x + width), scale_(y + depth));
wipe_tower_convex_hull.points.emplace_back(scale_(x), scale_(y + depth));
wipe_tower_convex_hull.rotate(a);
wipe_tower_convex_hull.rotate(Geometry::deg2rad(a), Point(scale_(x), scale_(y)));
convex_hulls_temp.push_back(wipe_tower_convex_hull);
} else {
//here, wipe_tower_polygon is not always convex.
Polygon wipe_tower_polygon;
if (print.wipe_tower_data().wipe_tower_mesh_data)
wipe_tower_polygon = print.wipe_tower_data().wipe_tower_mesh_data->bottom;
wipe_tower_polygon.rotate(Geometry::deg2rad(a));
wipe_tower_polygon.translate(Point(scale_(x), scale_(y)));
convex_hulls_temp.push_back(wipe_tower_polygon);
}
@@ -1065,6 +1075,22 @@ static StringObjectException layered_print_cleareance_valid(const Print &print,
if (print_config.enable_wrapping_detection.value && !intersection({wrapping_poly}, convex_hulls_temp).empty()) {
return {L("Prime Tower") + L(" is too close to clumping detection area, and collisions will be caused.\n")};
}
// Skip the containment check for towers that will never be printed (single-filament
// prints without smooth timelapse keep the config's tower position but emit nothing).
// Pre-generation only the body square is tested — the auto-brim estimate can overshoot
// the generated brim by several mm and must not hard-fail a print that physically fits.
// Post-generation the mesh bottom already includes the real brim, so the exact
// footprint is tested.
if (filaments_count > 1 || print.enable_timelapse_print()) {
// The shared printable polygon is plate-local, while the tower polygons above are
// already shifted by the plate origin.
Polygons printable_polys = print.get_extruder_shared_printable_polygon();
const Point plate_shift(scale_(plate_origin.x()), scale_(plate_origin.y()));
for (Polygon &p : printable_polys)
p.translate(plate_shift);
if (!diff(convex_hulls_temp, printable_polys).empty())
return {L("Prime Tower") + L(" is partially outside the printable area, and it cannot be printed.\n")};
}
return {};
}
@@ -3962,6 +3988,12 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
double volume = wipe_volume * filament_depth_count;
if (m_config.nozzle_diameter.values.size() == 2) volume += filament_change_volume * (int) (filaments_cnt / 2);
// Sizing should take into account currently set wiping volumes.
// For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower)
// and it worked well enough. Let's try to do slightly better by accounting for the purging volumes.
const bool semm_flush = m_config.purge_in_prime_tower && m_config.single_extruder_multi_material;
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(m_config, filaments_cnt);
if (m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib) {
double depth = std::sqrt(volume / layer_height * extra_spacing);
if (need_wipe_tower || filaments_cnt > 1) {
@@ -3973,30 +4005,16 @@ const WipeTowerData &Print::wipe_tower_data(size_t filaments_cnt) const
}
}
else {
double width = m_config.prime_tower_width;
if (m_config.purge_in_prime_tower && m_config.single_extruder_multi_material) {
// Calculating depth should take into account currently set wiping volumes.
// For a long time, the initial preview would just use 900/width per toolchange (15mm on a 60mm wide tower)
// and it worked well enough. Let's try to do slightly better by accounting for the purging volumes.
std::vector<std::vector<float>> wipe_volumes = WipeTower2::extract_wipe_volumes(m_config);
std::vector<float> max_wipe_volumes;
for (const std::vector<float> &v : wipe_volumes)
max_wipe_volumes.emplace_back(*std::max_element(v.begin(), v.end()));
float maximum = std::accumulate(max_wipe_volumes.begin(), max_wipe_volumes.end(), 0.f);
maximum = maximum * filaments_cnt / max_wipe_volumes.size();
// Orca: it's overshooting a bit, so let's reduce it a bit
maximum *= 0.6;
const_cast<Print *>(this)->m_wipe_tower_data.depth = maximum / (layer_height * width);
} else {
double depth = volume / (layer_height * width) * extra_spacing;
if (need_wipe_tower || m_wipe_tower_data.depth > EPSILON) {
double width = m_config.prime_tower_width;
double depth = volume / (layer_height * width);
// The flush volumes already hold the spacing between wipes.
if (!semm_flush) depth *= extra_spacing;
if (need_wipe_tower || depth > EPSILON) {
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
depth = std::max((double) min_wipe_tower_depth, depth);
}
const_cast<Print *>(this)->m_wipe_tower_data.depth = depth;
}
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
const_cast<Print *>(this)->m_wipe_tower_data.brim_width = m_config.prime_tower_brim_width;
}
if (m_config.prime_tower_brim_width < 0) const_cast<Print *>(this)->m_wipe_tower_data.brim_width = WipeTower::get_auto_brim_by_height(max_height);
}
+32 -8
View File
@@ -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
@@ -4278,6 +4280,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.");
@@ -4311,7 +4322,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");
@@ -5744,12 +5755,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. });
@@ -6003,7 +6012,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
@@ -6708,6 +6717,17 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("wait_for_temp_on_wipe_tower", coBool);
def->label = L("Wait for temperature on wipe tower");
def->tooltip = L("Pick up the new tool without waiting for it to reach printing temperature, travel to the wipe "
"tower, and wait for the temperature there, right before purging. Ooze from the heat-up lands on "
"the tower instead of the model, and the travel overlaps with the heating. "
"Only relevant for multi-extruder (multi-toolhead) printers using a Type 2 wipe tower. "
"The firmware or tool change macro must not wait for the temperature itself. "
"When disabled, the temperature wait is issued right after the tool change command.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("wipe_tower_no_sparse_layers", coBool);
def->label = L("No sparse layers (beta)");
@@ -8301,10 +8321,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",
@@ -9392,6 +9414,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",
+2 -1
View File
@@ -1574,7 +1574,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))
@@ -1697,6 +1697,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, purge_in_prime_tower))
((ConfigOptionBool, enable_filament_ramming))
((ConfigOptionBool, tool_change_on_wipe_tower))
((ConfigOptionBool, wait_for_temp_on_wipe_tower))
((ConfigOptionBool, support_multi_bed_types))
((ConfigOptionBool, use_3mf))
+4 -4
View File
@@ -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;
+32 -13
View File
@@ -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)
+6 -1
View File
@@ -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);
+34
View File
@@ -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;
+2
View File
@@ -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
+171 -97
View File
@@ -35,6 +35,7 @@
#include "Widgets/TextCtrl.h"
#include "../Utils/ColorSpaceConvert.hpp"
#include "../Utils/NetworkAgentFactory.hpp"
#ifdef __WXOSX__
#define wxOSX true
#else
@@ -1403,39 +1404,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()
@@ -1518,33 +1486,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);
@@ -1651,7 +1593,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:
@@ -1701,12 +1643,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);
@@ -1772,33 +1709,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;
@@ -1920,12 +1835,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();
@@ -2067,6 +1976,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);
+38
View File
@@ -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:
+21 -43
View File
@@ -2879,6 +2879,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
}
if (wt && (need_wipe_tower || filaments_count > 1) && !wxGetApp().plater()->only_gcode_mode() && !wxGetApp().plater()->is_gcode_3mf()) {
// The tower size estimate reads printer- and filament-scope keys, which the print preset
// does not carry; built once here rather than per plate.
const DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config();
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
// If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
PartPlate* part_plate = ppl.get_plate(plate_id);
@@ -2903,51 +2906,26 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
if (part_plate->get_objects_on_this_plate().empty()) continue;
float brim_width = print->wipe_tower_data(filaments_count).brim_width;
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
{
const float margin = WIPE_TOWER_MARGIN + brim_width;
BoundingBoxf3 plate_bbox = part_plate->get_bounding_box();
BoundingBoxf plate_bbox_2d(Vec2d(plate_bbox.min(0), plate_bbox.min(1)), Vec2d(plate_bbox.max(0), plate_bbox.max(1)));
const std::vector<Pointfs> &extruder_areas = part_plate->get_extruder_areas();
for (Pointfs points : extruder_areas) {
BoundingBoxf bboxf(points);
plate_bbox_2d.min = plate_bbox_2d.min(0) >= bboxf.min(0) ? plate_bbox_2d.min : bboxf.min;
plate_bbox_2d.max = plate_bbox_2d.max(0) <= bboxf.max(0) ? plate_bbox_2d.max : bboxf.max;
}
coordf_t plate_bbox_x_min_local_coord = plate_bbox_2d.min(0) - plate_origin(0);
coordf_t plate_bbox_x_max_local_coord = plate_bbox_2d.max(0) - plate_origin(0);
coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1);
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
// update for wipe tower position
{
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
(float) wipe_tower_size(0), (float) wipe_tower_size(1), (float) wipe_tower_size(2),
a,
/*!print->is_step_done(psWipeTower)*/ true, brim_width);
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
}
} else {
const float margin = 2.f;
auto tower_bottom = current_print->wipe_tower_data().wipe_tower_mesh_data->bottom;
tower_bottom.translate(scaled(Vec2d{x, y}));
tower_bottom.translate(scaled(Vec2d{plate_origin[0], plate_origin[1]}));
auto tower_bottom_bbox = get_extents(tower_bottom);
BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_id)->get_build_volume(true);
BoundingBox plate_bbox2d = BoundingBox(scaled(Vec2f(plate_bbox.min[0], plate_bbox.min[1])), scaled(Vec2f(plate_bbox.max[0], plate_bbox.max[1])));
Vec2f offset = WipeTower::move_box_inside_box(tower_bottom_bbox, plate_bbox2d, scaled(margin));
int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
current_print->wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
current_print->wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh,
true,a,/*!print->is_step_done(psWipeTower)*/ true, m_initialized);
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
}
// The stored position is already clamped onto the bed, by
// set_default_wipe_tower_pos_for_plate and again on every drag.
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
// update for wipe tower position
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
(float) wipe_tower_size(0), (float) wipe_tower_size(1), (float) wipe_tower_size(2),
a,
/*!print->is_step_done(psWipeTower)*/ true, brim_width);
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
} else {
int volume_idx_wipe_tower_new = m_volumes.load_real_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
current_print->wipe_tower_data().wipe_tower_mesh_data->real_wipe_tower_mesh,
current_print->wipe_tower_data().wipe_tower_mesh_data->real_brim_mesh,
true,a,/*!print->is_step_done(psWipeTower)*/ true, m_initialized);
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
}
}
}
+4 -4
View File
@@ -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)
+5 -5
View File
@@ -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);
+99 -19
View File
@@ -2809,16 +2809,58 @@ 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(); });
plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
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())
@@ -2828,10 +2870,11 @@ void GUI_App::init_plugin_gui_wiring()
});
});
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);
refresh_plugins_dialog();
switch_printer_agent_after_unload(capability.plugin_key);
});
}
@@ -3873,6 +3916,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 +3965,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 +3989,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 +4014,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;
+11 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
+15 -10
View File
@@ -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);
+1 -1
View File
@@ -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);
+41 -2
View File
@@ -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(&params, &params_area, sizeof(params));
setMinimalSupportAreaPrams(params);
params.min_volume = false;
}
else {
+86 -6
View File
@@ -708,7 +708,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));
@@ -1368,9 +1368,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;
@@ -1999,7 +2078,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);
@@ -2132,7 +2212,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
@@ -4277,7 +4357,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;
+2 -2
View File
@@ -358,7 +358,7 @@ public:
void RunScript(wxString js);
//SoftFever
void show_device(bool bBBLPrinter);
void show_device(bool should_use_native);
void fit_tab_labels(); // ORCA
PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr };
@@ -385,7 +385,7 @@ public:
CalibrationPanel* m_calibration{ nullptr };
WebViewPanel* m_webview { nullptr };
PrinterWebView* m_printer_view{nullptr};
wxLogWindow* m_log_window { nullptr };
wxLogWindow* m_log_window { nullptr };
// BBS
//wxBookCtrlBase* m_tabpanel { nullptr };
Notebook* m_tabpanel{ nullptr };
+106 -18
View File
@@ -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);
+3 -3
View File
@@ -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;
+26
View File
@@ -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:
@@ -654,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()) {
@@ -772,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" ||
+30 -6
View File
@@ -3432,6 +3432,12 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con
}
double volume = wipe_volume * (extruder_count == 2 ? plate_extruder_size : (plate_extruder_size - 1));
if (extruder_count == 2) volume += filament_change_volume * (int) (plate_extruder_size / 2);
// Read from the passed plate config — m_print may not have been applied yet
// (fresh plates, CLI), in which case its PrintConfig still holds defaults.
const auto *purge_opt = config.option<ConfigOptionBool>("purge_in_prime_tower");
const auto *semm_opt = config.option<ConfigOptionBool>("single_extruder_multi_material");
const bool semm_flush = purge_opt && purge_opt->value && semm_opt && semm_opt->value;
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(config, plate_extruder_size);
if (use_rib_wall) {
depth = std::sqrt(volume / layer_height * extra_spacing);
if (need_wipe_tower || plate_extruder_size > 1) {
@@ -3444,7 +3450,9 @@ Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, con
}
}
else {
depth = volume/ (layer_height * w) *extra_spacing;
depth = volume / (layer_height * w);
// The flush volumes already hold the spacing between wipes.
if (!semm_flush) depth *= extra_spacing;
if (need_wipe_tower || depth > EPSILON) {
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
depth = std::max((double)min_wipe_tower_depth, depth);
@@ -4530,6 +4538,11 @@ BoundingBoxf3 PartPlate::get_build_volume(bool use_share)
return plate_box;
}
Polygon PartPlate::get_shared_printable_polygon() const
{
return m_extruder_areas.empty() ? Polygon::new_scale(m_shape) : get_shared_poly(m_extruder_areas);
}
bool PartPlate::contains(const Vec3d& point) const
{
return m_bounding_box.contains(point);
@@ -5632,22 +5645,21 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
}
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
float w = dynamic_cast<const ConfigOptionFloat *>(print_cfg.option("prime_tower_width"))->value;
float w = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_tower_width"))->value;
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
bool enable_wrapping = false;
const ConfigOptionBool *wrapping_opt = dynamic_cast<const ConfigOptionBool *>(full_config.option("enable_wrapping_detection"));
if (wrapping_opt) enable_wrapping = wrapping_opt->value;
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) {
wipe_tower_size = part_plate->estimate_wipe_tower_size(print_cfg, w, v, nozzle_nums, 2, false, enable_wrapping);
wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 2, false, enable_wrapping);
}
// Compute brim-aware margin: brim extends outward from tower position
float brim_width = 0.f;
const ConfigOptionFloat *brim_opt = print_cfg.option<ConfigOptionFloat>("prime_tower_brim_width");
const ConfigOptionFloat *brim_opt = full_config.option<ConfigOptionFloat>("prime_tower_brim_width");
if (brim_opt) {
brim_width = brim_opt->value;
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
@@ -5669,6 +5681,18 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
}
}
// The bounding box above still allows a corner a delta or hexagonal bed does not have, and the
// prime tower is validated against the real outline — pull it onto the bed before storing.
{
Polygons bed{part_plate->get_shared_printable_polygon()};
bed.front().translate(Point(-scaled(plate_origin.x()), -scaled(plate_origin.y()))); // into the frame x/y live in
const BoundingBox tower(Point::new_scale(x, y),
Point::new_scale(x + wipe_tower_size(0), y + wipe_tower_size(1)));
const Vec2f move = WipeTower::move_box_inside_polygon(tower, bed, scaled<coord_t>(margin));
x += move.x();
y += move.y();
}
ConfigOptionFloat wt_x_opt(x);
ConfigOptionFloat wt_y_opt(y);
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_idx, 0);
+3
View File
@@ -506,6 +506,9 @@ public:
const BoundingBox get_bounding_box_crd();
BoundingBoxf3 get_plate_box() {return get_build_volume();}
BoundingBoxf3 get_build_volume(bool use_share = false);
// Polygon counterpart of get_build_volume(true), in scaled world coordinates. The bounding box
// that one returns hides the corners a non-rectangular bed does not have.
Polygon get_shared_printable_polygon() const;
const std::vector<BoundingBoxf3>& get_exclude_areas() { return m_exclude_bounding_box; }
+2 -88
View File
@@ -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
-1
View File
@@ -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
View File
@@ -3249,7 +3249,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
@@ -3261,7 +3262,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();
@@ -3283,10 +3285,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);
@@ -3442,7 +3446,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)
@@ -5628,6 +5635,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&);
@@ -11398,18 +11406,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)
@@ -11425,10 +11438,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)
@@ -11443,13 +11453,13 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
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 == MainFrame::tpMonitor) {
// 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) {
@@ -11505,13 +11515,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);
}
+14
View File
@@ -1135,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");
@@ -2101,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);
+38 -5
View File
@@ -111,13 +111,46 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string &suffix, wxBox
sizer->Add(m_radio_group, 0, wxEXPAND | wxTOP | wxLEFT, BORDER_W);
if (parent->m_mode == comDevelop) {
m_detach_checkbox = new wxCheckBox(parent, wxID_ANY, _L("Detach from parent"));
sizer->Add(m_detach_checkbox, 0, wxALIGN_LEFT | wxALL, BORDER_W);
std::string inherits_str = sel_preset.inherits();
if (parent->m_mode == comDevelop && !inherits_str.empty()) {
wxBoxSizer *detach_sizer = new wxBoxSizer(wxHORIZONTAL);
auto detach_tooltip = _L("Copies all inherited values from the parent preset into this preset and removes the connection with the parent preset.");
auto detach_checkbox = new ::CheckBox(parent);
detach_checkbox->SetToolTip(detach_tooltip);
auto detach_label = new wxStaticText(parent, wxID_ANY, _L("Detach from parent"));
detach_label->SetFont(::Label::Body_14);
detach_label->SetForegroundColour(wxColour("#363636"));
detach_label->SetToolTip(detach_tooltip);
detach_sizer->Add(detach_checkbox, 0, wxALIGN_LEFT | wxLEFT, BORDER_W);
detach_sizer->Add(detach_label , 0, wxALIGN_CENTRE_VERTICAL | wxLEFT, FromDIP(5));
sizer->Add(detach_sizer, 0, wxEXPAND | wxTOP, BORDER_W);
sizer->AddSpacer(FromDIP(5));
auto parent_label = new wxStaticText(parent, wxID_ANY, inherits_str);
parent_label->SetFont(::Label::Body_12);
parent_label->SetForegroundColour(wxColour("#6B6B6B"));
parent_label->SetToolTip(_L("Parent preset"));
sizer->Add(parent_label, 0, wxEXPAND | wxLEFT, BORDER_W + FromDIP(24));
sizer->AddSpacer(FromDIP(5));
// Set initial state (unchecked by default)
m_detach_checkbox->SetValue(m_detach);
detach_checkbox->SetValue(m_detach);
// Bind the checkbox event to update the detach state for this item
m_detach_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_detach = m_detach_checkbox->GetValue(); });
detach_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, detach_checkbox](wxCommandEvent&) { m_detach = detach_checkbox->GetValue(); });
auto on_toggle = [this, detach_checkbox]() {
detach_checkbox->SetValue(!detach_checkbox->GetValue());
wxCommandEvent ev(wxEVT_TOGGLEBUTTON, detach_checkbox->GetId());
ev.SetEventObject(detach_checkbox);
detach_checkbox->GetEventHandler()->ProcessEvent(ev);
};
detach_label->Bind(wxEVT_LEFT_DOWN, [on_toggle](wxMouseEvent& e) {if(!e.LeftDClick()) on_toggle();});
detach_label->Bind(wxEVT_LEFT_DCLICK, [on_toggle](wxMouseEvent& e) {on_toggle();});
}
m_radio_group->Bind(wxEVT_COMMAND_RADIOBOX_SELECTED, [this](wxCommandEvent &e) {
-1
View File
@@ -75,7 +75,6 @@ class SavePresetDialog : public DPIDialog
bool m_save_to_project {false};
RadioGroup* m_radio_group; // ORCA
bool m_detach{false};
wxCheckBox* m_detach_checkbox{nullptr};
void update();
};
+6 -7
View File
@@ -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)
+2 -15
View File
@@ -1270,9 +1270,7 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor
} else {
if (v.is_wipe_tower) {//in world cs
int plate_idx = v.object_idx() - 1000;
BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_build_volume(true);
BoundingBox plate_bbox2d = BoundingBox(scaled(Vec2f(plate_bbox.min[0], plate_bbox.min[1])), scaled(Vec2f(plate_bbox.max[0], plate_bbox.max[1])));
Vec3d tower_size = v.bounding_box().size();
const Polygons bed_polys{wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_shared_printable_polygon()};
Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position();
Vec3d actual_displacement = displacement;
bool show_read_wipe_tower = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->fff_print()->is_step_done(psWipeTower);
@@ -1287,18 +1285,7 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor
BoundingBoxf3 tower_bbox = v.bounding_box();
tower_bbox.translate(actual_displacement + tower_origin);
BoundingBox tower_bbox2d = BoundingBox(scaled(Vec2f(tower_bbox.min[0], tower_bbox.min[1])), scaled(Vec2f(tower_bbox.max[0], tower_bbox.max[1])));
Vec2f offset = WipeTower::move_box_inside_box(tower_bbox2d, plate_bbox2d,scaled(margin));
//if (tower_origin(0) + actual_displacement(0) - margin < plate_bbox.min(0)) {
// actual_displacement(0) = plate_bbox.min(0) - tower_origin(0) + margin;
//} else if (tower_origin(0) + actual_displacement(0) + tower_size(0) + margin > plate_bbox.max(0)) {
// actual_displacement(0) = plate_bbox.max(0) - tower_origin(0) - tower_size(0) - margin;
//}
//if (tower_origin(1) + actual_displacement(1) - margin < plate_bbox.min(1)) {
// actual_displacement(1) = plate_bbox.min(1) - tower_origin(1) + margin;
//} else if (tower_origin(1) + actual_displacement(1) + tower_size(1) + margin > plate_bbox.max(1)) {
// actual_displacement(1) = plate_bbox.max(1) - tower_origin(1) - tower_size(1) - margin;
//}
const Vec2f offset = WipeTower::move_box_inside_polygon(tower_bbox2d, bed_polys, scaled(margin));
actual_displacement += Vec3d(offset[0], offset[1],0);
v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + actual_displacement);
}
+93 -8
View File
@@ -34,6 +34,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"
@@ -3079,7 +3080,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");
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");
@@ -4008,13 +4009,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();
@@ -4041,9 +4041,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",
@@ -4067,7 +4068,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)
@@ -4182,6 +4189,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",
@@ -4212,7 +4221,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") {
@@ -5608,8 +5618,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");
@@ -6174,6 +6219,7 @@ if (is_marlin_flavor)
optgroup->append_single_option_line("purge_in_prime_tower", "printer_multimaterial_wipe_tower#purge-in-prime-tower");
optgroup->append_single_option_line("enable_filament_ramming", "printer_multimaterial_wipe_tower#enable-filament-ramming");
optgroup->append_single_option_line("tool_change_on_wipe_tower", "printer_multimaterial_wipe_tower#tool-change-on-wipe-tower");
optgroup->append_single_option_line("wait_for_temp_on_wipe_tower", "printer_multimaterial_wipe_tower#wait-for-temperature-on-wipe-tower");
optgroup = page->new_optgroup(L("Single extruder multi-material parameters"), "param_settings");
@@ -6566,6 +6612,16 @@ void TabPrinter::reload_config()
m_imex_modes_ctrl->load_from_config(*m_config);
}
}
// 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)
@@ -6576,6 +6632,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()
@@ -6832,6 +6898,7 @@ void TabPrinter::toggle_options()
// so the option is irrelevant there.
const size_t extruders_count = m_config->option<ConfigOptionFloats>("nozzle_diameter")->size();
toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
toggle_option("wait_for_temp_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
}
wxString extruder_number;
long val = 1;
@@ -8607,6 +8674,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 &&
+1
View File
@@ -679,6 +679,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
+7
View File
@@ -2,6 +2,13 @@
#define __I_PRINTER_AGENT_HPP__
#include "bambu_networking.hpp"
// why: these extend the BAMBU_NETWORK_* return space rather than opening a new one - the value
// flows through the same int domain callers already compare against BAMBU_NETWORK_SUCCESS.
// They live here and not in bambu_networking.hpp because that file is a vendor header replaced
// wholesale by header-sync commits (see c09252ce11), which would silently clobber them.
// -70xx is free: the vendor occupies -1..-25 and -10xx through -60xx.
#define ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED -7010 // no translation exists for this command
#define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability
#include <string>
#include <memory>
-20
View File
@@ -465,25 +465,5 @@ void NetworkAgentFactory::deregister_python_printer_agent(const std::string& plu
<< plugin_key << "' with agent ID '" << agent_id << "'";
}
bool NetworkAgentFactory::is_current_printer_agent_plugin()
{
auto* preset_bundle = GUI::wxGetApp().preset_bundle;
if (!preset_bundle)
return false;
std::string agent_key = ORCA_PRINTER_AGENT_ID;
if (preset_bundle->is_bbl_vendor())
agent_key = BBL_PRINTER_AGENT_ID;
const auto& cfg = preset_bundle->printers.get_edited_preset().config;
if (cfg.has("printer_agent")) {
const std::string& value = cfg.option<ConfigOptionString>("printer_agent")->value;
if (!value.empty())
agent_key = value;
}
const PrinterAgentInfo* info = get_printer_agent_info(agent_key);
return info && info->is_plugin();
}
} // namespace Slic3r
-2
View File
@@ -166,8 +166,6 @@ public:
static void register_python_printer_agent(const std::string& plugin_key, const std::string& capability_name);
static void deregister_python_printer_agent(const std::string& plugin_key, const std::string& capability_name);
static bool is_current_printer_agent_plugin();
private:
// Factory is not instantiable
NetworkAgentFactory() = delete;
+1 -1
View File
@@ -107,7 +107,7 @@ std::optional<RaycastManager::Hit> RaycastManager::first_hit(const Vec3d& point,
const AABBMesh *hit_mesh = nullptr;
double hit_squared_distance = 0.;
int hit_face = -1;
Vec3d hit_world;
Vec3d hit_world { Vec3d::Zero() };
const Transform3d *hit_tramsformation = nullptr;
const TrKey *hit_key = nullptr;