mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Merge branch 'main' into feat/printer-agent-isolation
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -887,7 +887,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();
|
||||
|
||||
@@ -23,14 +23,14 @@ inline coord_t meshfix_maximum_extrusion_area_deviation() { return scaled<coo
|
||||
class WallToolPathsParams
|
||||
{
|
||||
public:
|
||||
float min_bead_width;
|
||||
float min_feature_size;
|
||||
float min_length_factor;
|
||||
float wall_transition_length;
|
||||
float wall_transition_angle;
|
||||
float wall_transition_filter_deviation;
|
||||
int wall_distribution_count;
|
||||
bool is_top_or_bottom_layer;
|
||||
float min_bead_width = 0.f;
|
||||
float min_feature_size = 0.f;
|
||||
float min_length_factor = 0.5f;
|
||||
float wall_transition_length = 0.f;
|
||||
float wall_transition_angle = 10.f;
|
||||
float wall_transition_filter_deviation = 0.f;
|
||||
int wall_distribution_count = 1;
|
||||
bool is_top_or_bottom_layer = false;
|
||||
|
||||
coord_t wall_maximum_resolution = meshfix_maximum_resolution();
|
||||
coord_t wall_maximum_deviation = meshfix_maximum_deviation();
|
||||
|
||||
@@ -3576,7 +3576,7 @@ Polylines FillLateralHoneycomb::fill_surface(const Surface *surface, const FillP
|
||||
// |
|
||||
// |
|
||||
// 0 --+--
|
||||
// / \
|
||||
// ⟋ ⟍
|
||||
// why inverted?
|
||||
// it makes determining some of the properties easier
|
||||
// and the two angled legs provide additional horizontal stiffness
|
||||
|
||||
@@ -712,7 +712,7 @@ unsigned int Step::get_triangle_num(double linear_deflection, double angle_defle
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
} catch(const Exception &e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+86
-50
@@ -768,30 +768,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;
|
||||
@@ -898,27 +899,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
|
||||
@@ -933,15 +924,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)
|
||||
@@ -1322,7 +1335,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);
|
||||
@@ -1334,7 +1347,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];
|
||||
@@ -1554,7 +1567,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
|
||||
@@ -1705,7 +1719,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;
|
||||
@@ -5511,7 +5525,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
|
||||
@@ -7636,8 +7650,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);
|
||||
@@ -7648,7 +7661,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) {
|
||||
@@ -8939,7 +8955,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))
|
||||
@@ -9046,6 +9062,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;
|
||||
@@ -9349,8 +9383,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)) {
|
||||
|
||||
@@ -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;
|
||||
@@ -262,7 +262,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();
|
||||
|
||||
|
||||
@@ -1630,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)
|
||||
|
||||
@@ -45,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
|
||||
{
|
||||
|
||||
@@ -413,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; }
|
||||
|
||||
@@ -624,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())
|
||||
{
|
||||
flush_planner_queue();
|
||||
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1006,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),
|
||||
@@ -1035,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
|
||||
@@ -1085,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1236,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.
|
||||
@@ -1355,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);
|
||||
@@ -1671,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())
|
||||
@@ -1685,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.
|
||||
@@ -1695,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
|
||||
@@ -2021,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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -94,7 +94,7 @@ public:
|
||||
|
||||
void* volume{nullptr};
|
||||
std::vector<int>* plane_indices{nullptr};
|
||||
Transform3d world_tran;
|
||||
Transform3d world_tran = Transform3d::Identity();
|
||||
std::shared_ptr<std::vector<SurfaceFeature>> world_plane_features{nullptr};
|
||||
std::shared_ptr<SurfaceFeature> origin_surface_feature{nullptr};
|
||||
|
||||
|
||||
+35
-85
@@ -48,100 +48,50 @@ struct OrientMesh {
|
||||
|
||||
};
|
||||
|
||||
// params for minimizing support area
|
||||
struct OrientParamsArea {
|
||||
float TAR_A = 0.015f;
|
||||
float TAR_B = 0.177f;
|
||||
float RELATIVE_F = 20;
|
||||
float CONTOUR_F = 0.5f;
|
||||
float BOTTOM_F = 2.5f;
|
||||
float BOTTOM_HULL_F = 0.1f;
|
||||
float TAR_C = 0.1f;
|
||||
float TAR_D = 1;
|
||||
float TAR_E = 0.0115f;
|
||||
float FIRST_LAY_H = 0.2f;//0.0475;
|
||||
float VECTOR_TOL = -0.00083f;
|
||||
float NEGL_FACE_SIZE = 0.01f;
|
||||
float ASCENT = -0.5f;
|
||||
float PLAFOND_ADV = 0.0599f;
|
||||
float CONTOUR_AMOUNT = 0.0182427f;
|
||||
float OV_H = 2.574f;
|
||||
float height_offset = 2.3728f;
|
||||
float height_log = 0.041375f;
|
||||
float height_log_k = 1.9325457f;
|
||||
float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face 0.9997f
|
||||
float LAF_MIN = 0.97f; // cos(14\degree) 0.9703f
|
||||
float TAR_LAF = 0.001f; //0.01f
|
||||
float TAR_PROJ_AREA = 0.1f;
|
||||
float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the object may be unstable
|
||||
float BOTTOM_MAX = 2000; // max bottom area. If get to it the object is stable enough (further increase bottom area won't do more help)
|
||||
float height_to_bottom_hull_ratio_MIN = 1;
|
||||
float BOTTOM_HULL_MAX = 2000;// max bottom hull area
|
||||
float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle = 60.f;
|
||||
bool use_low_angle_face = true;
|
||||
bool min_volume = false;
|
||||
Eigen::Vector3f fun_dir;
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = true;
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = {};
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition = {};
|
||||
|
||||
OrientParamsArea() = default;
|
||||
};
|
||||
|
||||
struct OrientParams {
|
||||
float TAR_A = 0.01f;//0.128f;
|
||||
float TAR_B = 0.177f;
|
||||
float RELATIVE_F= 6.610621027964314f;
|
||||
float CONTOUR_F = 0.23228623269775997f;
|
||||
float BOTTOM_F = 1.167152017941474f;
|
||||
float BOTTOM_HULL_F = 0.1f;
|
||||
float TAR_C = 0.24308070476924726f;
|
||||
float TAR_D = 0.6284515508160871f;
|
||||
float TAR_E = 0;//0.032157292647062234;
|
||||
float FIRST_LAY_H = 0.2f;//0.029;
|
||||
float VECTOR_TOL = -0.0011163303070972383f;
|
||||
float NEGL_FACE_SIZE = 0.1f;
|
||||
float ASCENT= -0.5f;
|
||||
float PLAFOND_ADV = 0.04079208948120519f;
|
||||
float CONTOUR_AMOUNT = 0.0101472219892684f;
|
||||
float OV_H = 1.0370178217794535f;
|
||||
float height_offset = 2.7417608343142073f;
|
||||
float height_log = 0.06442030687034085f;
|
||||
float height_log_k = 0.3933594673063997f;
|
||||
float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face //0.9997f;
|
||||
float LAF_MIN= 0.9703f; // cos(14\degree) 0.9703f;
|
||||
float TAR_LAF = 0.01f; //0.1f
|
||||
float TAR_PROJ_AREA = 0.1f;
|
||||
float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the objects may be unstable
|
||||
float BOTTOM_MAX = 2000; //400
|
||||
float height_to_bottom_hull_ratio_MIN = 1;
|
||||
float BOTTOM_HULL_MAX = 2000;// max bottom hull area to clip //600
|
||||
float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle = 60.f;
|
||||
bool use_low_angle_face = true;
|
||||
bool min_volume = false;
|
||||
Eigen::Vector3f fun_dir;
|
||||
float TAR_A { 0.01f }; // 0.128f;
|
||||
float TAR_B { 0.177f };
|
||||
float RELATIVE_F { 6.610621027964314f };
|
||||
float CONTOUR_F { 0.23228623269775997f };
|
||||
float BOTTOM_F { 1.167152017941474f };
|
||||
float BOTTOM_HULL_F { 0.1f };
|
||||
float TAR_C { 0.24308070476924726f };
|
||||
float TAR_D { 0.6284515508160871f };
|
||||
float TAR_E { 0}; // 0.032157292647062234;
|
||||
float FIRST_LAY_H { 0.2f}; // 0.029;
|
||||
float VECTOR_TOL { -0.0011163303070972383f };
|
||||
float NEGL_FACE_SIZE { 0.1f };
|
||||
float ASCENT { -0.5f };
|
||||
float PLAFOND_ADV { 0.04079208948120519f };
|
||||
float CONTOUR_AMOUNT { 0.0101472219892684f };
|
||||
float OV_H { 1.0370178217794535f };
|
||||
float height_offset { 2.7417608343142073f };
|
||||
float height_log { 0.06442030687034085f };
|
||||
float height_log_k { 0.3933594673063997f };
|
||||
float LAF_MAX { 0.999f }; // cos(1.4\degree) for low angle face //0.9997f;
|
||||
float LAF_MIN { 0.9703f }; // cos(14\degree) 0.9703f;
|
||||
float TAR_LAF { 0.01f }; // 0.1f
|
||||
float TAR_PROJ_AREA { 0.1f };
|
||||
float BOTTOM_MIN { 0.1f }; // min bottom area. If lower than it the objects may be unstable
|
||||
float BOTTOM_MAX { 2000 }; // 400
|
||||
float height_to_bottom_hull_ratio_MIN { 1 };
|
||||
float BOTTOM_HULL_MAX { 2000 }; // max bottom hull area to clip //600
|
||||
float APPERANCE_FACE_SUPP { 3 }; // penalty of generating supports on appearance face
|
||||
|
||||
float overhang_angle { 60.f };
|
||||
bool use_low_angle_face { true };
|
||||
bool min_volume { false };
|
||||
Eigen::Vector3f fun_dir {};
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = false;
|
||||
bool parallel { false };
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = {};
|
||||
std::function<void(unsigned, std::string)> progressind {};
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition = {};
|
||||
std::function<bool(void)> stopcondition {};
|
||||
|
||||
OrientParams() = default;
|
||||
};
|
||||
|
||||
@@ -1423,7 +1423,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
@@ -282,6 +282,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"
|
||||
@@ -382,6 +390,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"
|
||||
@@ -1039,13 +1048,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);
|
||||
}
|
||||
@@ -1064,6 +1074,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 {};
|
||||
}
|
||||
|
||||
@@ -3918,6 +3944,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) {
|
||||
@@ -3929,30 +3961,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);
|
||||
}
|
||||
|
||||
@@ -6570,6 +6570,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)");
|
||||
|
||||
@@ -1660,6 +1660,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))
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user