OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::WipeTowerIntegration Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/GCode.hpp>

Collaboration diagram for Slic3r::WipeTowerIntegration:

Public Member Functions

 WipeTowerIntegration (const PrintConfig &print_config, const int plate_idx, const Vec3d plate_origin, const std::vector< WipeTower::ToolChangeResult > &priming, const std::vector< std::vector< WipeTower::ToolChangeResult > > &tool_changes, const WipeTower::ToolChangeResult &final_purge, const std::vector< unsigned int > &slice_used_filaments)
std::string prime (GCode &gcodegen)
void next_layer ()
std::string tool_change (GCode &gcodegen, int extruder_id, bool finish_layer)
bool is_empty_wipe_tower_gcode (GCode &gcodegen, int extruder_id, bool finish_layer)
std::string finalize (GCode &gcodegen)
std::vector< float > used_filament_length () const
bool is_first_print () const
void set_is_first_print (bool is)
bool enable_timelapse_print () const
void set_wipe_tower_depth (float depth)
void set_wipe_tower_bbx (const BoundingBoxf &bbx)
void set_rib_offset (const Vec2f &rib_offset)

Private Member Functions

WipeTowerIntegration & operator= (const WipeTowerIntegration &)
std::string append_tcr (GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z=-1.) const
std::string tower_height_tag (GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, const std::string &tcr_gcode) 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
Polygons shared_printable_area (GCode &gcodegen) const
std::string post_process_wipe_tower_moves (const WipeTower::ToolChangeResult &tcr, const Vec2f &translation, float angle) const

Private Attributes

const float m_left
const float m_right
const Vec2f m_wipe_tower_pos
const float m_wipe_tower_rotation
std::vector< Vec2d > m_extruder_offsets
const std::vector< WipeTower::ToolChangeResult > & m_priming
const std::vector< std::vector< WipeTower::ToolChangeResult > > & m_tool_changes
const WipeTower::ToolChangeResult & m_final_purge
int m_layer_idx
int m_tool_change_idx
double m_last_wipe_tower_print_z
Vec3d m_plate_origin
bool m_single_extruder_multi_material
bool m_enable_timelapse_print
bool m_enable_wrapping_detection
bool m_is_first_print
const PrintConfig * m_print_config
float m_wipe_tower_depth
BoundingBoxf m_wipe_tower_bbx
Vec2f m_rib_offset {Vec2f(0, 0)}
const bool m_sparse_layers_skipped
const bool m_sparse_layers_combined
std::vector< float > m_compacted_tower_z

Detailed Description

Constructor & Destructor Documentation

◆ WipeTowerIntegration()

Slic3r::WipeTowerIntegration::WipeTowerIntegration ( const PrintConfig & print_config,
const int plate_idx,
const Vec3d plate_origin,
const std::vector< WipeTower::ToolChangeResult > & priming,
const std::vector< std::vector< WipeTower::ToolChangeResult > > & tool_changes,
const WipeTower::ToolChangeResult & final_purge,
const std::vector< unsigned int > & slice_used_filaments )
inline
113 :
114 m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
115 m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.prime_tower_width.value)),
116 m_wipe_tower_pos(float(print_config.wipe_tower_x.get_at(plate_idx)), float(print_config.wipe_tower_y.get_at(plate_idx))),
117 m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
118 m_priming(priming),
119 m_tool_changes(tool_changes),
120 m_final_purge(final_purge),
121 m_layer_idx(-1),
123 m_plate_origin(plate_origin),
124 m_single_extruder_multi_material(print_config.single_extruder_multi_material),
125 m_enable_timelapse_print(print_config.timelapse_type.value == TimelapseType::tlSmooth),
126 m_enable_wrapping_detection(print_config.enable_wrapping_detection && (print_config.wrapping_exclude_area.values.size() > 2) && (slice_used_filaments.size() <= 1)),
127 m_is_first_print(true),
128 m_print_config(&print_config),
129 m_last_wipe_tower_print_z(print_config.z_offset.value),
132 {
133 // Precomputed rather than accumulated while emitting, so that the clearance validator and
134 // the emitter cannot disagree about where the compacted tower sits on any given layer.
136 m_compacted_tower_z = compute_compacted_wipe_tower_z(tool_changes, float(print_config.z_offset.value));
137 // initialize with the extruder offset of master extruder id
138 m_extruder_offsets.resize(print_config.filament_map.size(), print_config.extruder_offset.get_at(print_config.master_extruder_id.value - 1));
139 const auto& filament_map = print_config.filament_map.values; // 1 based idx
140 for (size_t idx = 0; idx < filament_map.size(); ++idx)
141 m_extruder_offsets[idx] = print_config.extruder_offset.get_at(filament_map[idx] - 1);
142 }
const float m_right
Definition GCode.hpp:173
bool m_enable_timelapse_print
Definition GCode.hpp:190
int m_layer_idx
Definition GCode.hpp:183
const float m_left
Definition GCode.hpp:172
std::vector< float > m_compacted_tower_z
Definition GCode.hpp:204
const bool m_sparse_layers_combined
Definition GCode.hpp:202
const PrintConfig * m_print_config
Definition GCode.hpp:193
const bool m_sparse_layers_skipped
Definition GCode.hpp:199
const float m_wipe_tower_rotation
Definition GCode.hpp:175
int m_tool_change_idx
Definition GCode.hpp:184
const Vec2f m_wipe_tower_pos
Definition GCode.hpp:174
std::vector< Vec2d > m_extruder_offsets
Definition GCode.hpp:176
double m_last_wipe_tower_print_z
Definition GCode.hpp:185
const WipeTower::ToolChangeResult & m_final_purge
Definition GCode.hpp:181
const std::vector< WipeTower::ToolChangeResult > & m_priming
Definition GCode.hpp:179
bool m_is_first_print
Definition GCode.hpp:192
bool m_enable_wrapping_detection
Definition GCode.hpp:191
Vec3d m_plate_origin
Definition GCode.hpp:188
bool m_single_extruder_multi_material
Definition GCode.hpp:189
const std::vector< std::vector< WipeTower::ToolChangeResult > > & m_tool_changes
Definition GCode.hpp:180
@ tlSmooth
Definition PrintConfig.hpp:470
std::vector< float > compute_compacted_wipe_tower_z(const std::vector< std::vector< WipeTower::ToolChangeResult > > &tool_changes, float base_z)
Definition WipeTower.cpp:64
zaa_minimize_perimeter_height filament_map((ConfigOptionInts, filament_volume_map))((ConfigOptionInts
bool wipe_tower_sparse_layers_skipped(const PrintConfig &config)
Definition WipeTower.cpp:53
bool wipe_tower_sparse_layers_combined(const PrintConfig &config)
Definition WipeTower.cpp:77

References Slic3r::compute_compacted_wipe_tower_z(), Slic3r::filament_map(), m_compacted_tower_z, m_enable_timelapse_print, m_enable_wrapping_detection, m_extruder_offsets, m_final_purge, m_is_first_print, m_last_wipe_tower_print_z, m_layer_idx, m_left, m_plate_origin, m_priming, m_print_config, m_right, m_single_extruder_multi_material, m_sparse_layers_combined, m_sparse_layers_skipped, m_tool_change_idx, m_tool_changes, m_wipe_tower_pos, m_wipe_tower_rotation, Slic3r::single_extruder_multi_material(), Slic3r::tlSmooth, Slic3r::wipe_tower_sparse_layers_combined(), Slic3r::wipe_tower_sparse_layers_skipped(), and Slic3r::wipe_tower_y().

Referenced by operator=().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Function Documentation

◆ append_tcr()

std::string Slic3r::WipeTowerIntegration::append_tcr ( GCode & gcodegen,
const WipeTower::ToolChangeResult & tcr,
int new_extruder_id,
double z = -1. ) const
private
1103 {
1104 if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
1105 throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
1106
1107 const std::optional<GCode::ToolchangePositionState> position_before_toolchange =
1108 gcodegen.capture_toolchange_position();
1109
1110 int new_extruder_id = get_extruder_index(*m_print_config, new_filament_id);
1111
1112 // Logical nozzle grouping for this print (null on paths that don't populate it).
1113 auto group_result = gcodegen.m_print->get_layered_nozzle_group_result();
1114
1115 bool is_nozzle_change = !tcr.nozzle_change_result.gcode.empty() && (gcodegen.config().nozzle_diameter.size() > 1);
1116
1117 std::string gcode;
1118
1119 // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
1120 // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
1121 float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
1122
1123 auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
1124 Vec2f out = Eigen::Rotation2Df(alpha) * pt;
1126 return out;
1127 };
1128
1129 Vec2f start_pos = tcr.start_pos;
1130 Vec2f end_pos = tcr.end_pos;
1131 Vec2f tool_change_start_pos = start_pos;
1132 if (tcr.is_tool_change)
1133 tool_change_start_pos = tcr.tool_change_start_pos;
1134 if (!tcr.priming) {
1135 start_pos = transform_wt_pt(start_pos);
1136 end_pos = transform_wt_pt(end_pos);
1137 tool_change_start_pos = transform_wt_pt(tool_change_start_pos);
1138 }
1139
1140 Vec2f wipe_tower_offset = (tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos) + m_rib_offset;
1141 float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
1142
1143 std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
1144
1145 // BBS: add partplate logic
1146 Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
1147
1148 // BBS: toolchange gcode will move to start_pos,
1149 // so only perform movement when printing sparse partition to support upper layer.
1150 // start_pos is the position in plate coordinate.
1151 if (!tcr.priming && tcr.is_finish_first) {
1152 // Move over the wipe tower.
1153 gcode += gcodegen.retract();
1154 gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
1155 gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed,
1156 "Travel to a Wipe Tower");
1157 gcode += gcodegen.unretract();
1158 }
1159
1160
1161 double current_z = gcodegen.writer().get_position().z();
1162 if (z == -1.) // in case no specific z was provided, print at current_z pos
1163 z = current_z;
1164 // Orca: wipe_tower_no_sparse_layers crash guard. With sparse layers skipped the tower is
1165 // compacted far below the object, so descending to it is only safe once the nozzle is parked
1166 // over the tower - which is what the is_finish_first travel above does. Otherwise the nozzle
1167 // is still over the model and this descent would drive it into the print, so defer it to the
1168 // re-descents below, which run after the travel to the tower.
1169 const bool defer_compacted_descend = m_sparse_layers_skipped
1170 && !tcr.priming && !tcr.is_finish_first && (current_z - z) > EPSILON;
1171 if (!is_approx(z, current_z) && !defer_compacted_descend) {
1172 gcode += gcodegen.writer().retract();
1173 gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
1174 gcode += gcodegen.writer().unretract();
1175 }
1176 // Tower compacted below the object, so any extrusion emitted without an explicit z has to be
1177 // pulled back down to it first.
1178 const bool compacted_below_object = m_sparse_layers_skipped && z >= 0. && (tcr.print_z - z) > EPSILON;
1179
1180 // Process the end filament gcode.
1181 bool add_change_filament_624 = false;
1182 std::string end_filament_gcode_str;
1183 if (gcodegen.writer().filament() != nullptr) {
1184 // Process the custom filament_end_gcode in case of single_extruder_multi_material.
1185 unsigned int old_filament_id = gcodegen.writer().filament()->id();
1186 const std::string& filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_filament_id);
1187 if (gcodegen.writer().filament() != nullptr && !filament_end_gcode.empty()) {
1188 DynamicConfig config;
1189 config.set_key_value("filament_extruder_id", new ConfigOptionInt((int) old_filament_id));
1190 config.set_key_value("current_filament_id", new ConfigOptionInt((int) old_filament_id));
1191 config.set_key_value("current_nozzle_id", new ConfigOptionInt(nozzle_id_for_gcode_placeholder(group_result, (int) old_filament_id, (int) gcodegen.writer().filament()->extruder_id(), m_layer_idx)));
1192 config.set_key_value("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get())));
1193 config.set_key_value("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get())));
1194 config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
1195 config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
1196 config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
1197 if (!gcodegen.m_filament_instances_code.empty()) {
1198 end_filament_gcode_str += ("M624 " + gcodegen.m_filament_instances_code + "\n");
1199 gcodegen.m_filament_instances_code = "";
1200 add_change_filament_624 = true;
1201 }
1202 end_filament_gcode_str += gcodegen.placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_filament_id, &config);
1203 check_add_eol(end_filament_gcode_str);
1204 }
1205 }
1206
1207 std::string toolchange_gcode_str;
1208
1209 ZHopType z_hope_type = ZHopType(gcodegen.config().z_hop_types.get_at(gcodegen.get_filament_config_index((int)gcodegen.writer().filament()->id())));
1210 LiftType auto_lift_type = LiftType::NormalLift;
1211 if (z_hope_type == ZHopType::zhtAuto || z_hope_type == ZHopType::zhtSpiral || z_hope_type == ZHopType::zhtSlope)
1212 auto_lift_type = LiftType::SpiralLift;
1213
1214 // BBS: should be placed before toolchange parsing
1215 std::string toolchange_retract_str = gcodegen.retract(tcr.is_tool_change && !is_nozzle_change, false, auto_lift_type, true);
1216 check_add_eol(toolchange_retract_str);
1217
1218 //BBS: if needed, write the gcode_label_objects_end then priming tower, if the retract, didn't did it.
1219 std::string object_end_label_temp;
1220 gcodegen.m_writer.add_object_end_labels(object_end_label_temp);
1221
1222 // Process the custom change_filament_gcode. If it is empty, provide a simple Tn command to change the filament.
1223 // Otherwise, leave control to the user completely.
1224 std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value;
1225
1226 bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value;
1227 if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) is_used_travel_avoid_perimeter = false;
1228
1229 // add nozzle change gcode into change filament gcode
1230 std::string nozzle_change_gcode_trans;
1231 if (is_nozzle_change) {
1232 // move to start_pos before nozzle change
1233 // Orca: travel_to() lifts to the object layer height to clear the print. That lift is
1234 // needed when arriving from the model, but is a wasted full-height Z bounce when the
1235 // nozzle already sits on the compacted tower, so travel at the compacted z instead.
1236 const bool compact_intower_nc_travel = compacted_below_object
1237 && (tcr.print_z - gcodegen.writer().get_position().z()) > EPSILON;
1238 std::string start_pos_str;
1239 start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(tcr.nozzle_change_result.start_pos) + plate_origin_2d), erMixed,
1240 "Move to nozzle change start pos", compact_intower_nc_travel ? z : DBL_MAX);
1241 check_add_eol(start_pos_str);
1242 nozzle_change_gcode_trans += start_pos_str;
1243 // The nozzle-change wipe below carries no explicit z, so it would extrude at the object
1244 // layer height and float above the compacted tower. Descend unless the travel stayed down.
1245 if (!compact_intower_nc_travel && compacted_below_object) {
1246 std::string nc_z_descend = gcodegen.writer().travel_to_z(z, "Descend to compacted wipe tower z (no sparse layers)");
1247 check_add_eol(nc_z_descend);
1248 nozzle_change_gcode_trans += nc_z_descend;
1249 }
1250 nozzle_change_gcode_trans += gcodegen.unretract();
1251 nozzle_change_gcode_trans += transform_gcode(tcr.nozzle_change_result.gcode, tcr.nozzle_change_result.start_pos, wipe_tower_offset, wipe_tower_rotation);
1252 gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(tcr.nozzle_change_result.end_pos) + plate_origin_2d));
1253 gcodegen.m_wipe.reset_path();
1254 for (const Vec2f& wipe_pt : tcr.nozzle_change_result.wipe_path)
1255 gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt) + plate_origin_2d));
1256 nozzle_change_gcode_trans += gcodegen.retract(tcr.is_tool_change, false, auto_lift_type, true);
1257 end_filament_gcode_str = nozzle_change_gcode_trans + end_filament_gcode_str;
1258 }
1259
1260 end_filament_gcode_str = toolchange_retract_str + object_end_label_temp + end_filament_gcode_str;
1261
1262 std::string wipe_next_start_point_str;
1263 bool need_travel_after_change_filament_gcode = false; // travel need be after the filament changed to get the correct "m_curr_extruder_id"
1264 if (! change_filament_gcode.empty()) {
1265 DynamicConfig config;
1266 int old_filament_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->id() : -1;
1267 int old_extruder_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->extruder_id() : -1;
1268 // Logical nozzle ids for old/new filament (null-safe -> extruder id).
1269 int old_nozzle_id = nozzle_id_for_gcode_placeholder(group_result, old_filament_id, old_extruder_id, m_layer_idx);
1270 int next_nozzle_id = nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx);
1271
1272 config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id));
1273 config.set_key_value("next_extruder", new ConfigOptionInt(new_filament_id));
1274 const bool is_bbl_printer = gcodegen.m_print->is_BBL_printer();
1275 config.set_key_value("current_hotend", new ConfigOptionInt(
1276 hotend_id_for_gcode_placeholder(group_result, is_bbl_printer, old_filament_id, old_extruder_id, m_layer_idx)));
1277 config.set_key_value("next_hotend", new ConfigOptionInt(
1278 hotend_id_for_gcode_placeholder(group_result, is_bbl_printer, new_filament_id, (int) gcodegen.get_extruder_id(new_filament_id), m_layer_idx)));
1279 config.set_key_value("current_nozzle_id", new ConfigOptionInt(old_nozzle_id));
1280 config.set_key_value("next_nozzle_id", new ConfigOptionInt(next_nozzle_id));
1281 config.set_key_value("current_filament_id", new ConfigOptionInt(old_filament_id));
1282 config.set_key_value("next_filament_id", new ConfigOptionInt(new_filament_id));
1283 // Orca: nozzle-volume variant of the old/new extruder (e.g. "Direct Drive TPU High Flow"),
1284 // consumed by H2D's variant-aware change_filament_gcode. Null-safe: old_extruder_id may be -1.
1285 {
1286 const auto &extruder_variants = m_print_config->printer_extruder_variant.values;
1287 config.set_key_value("old_extruder_variant", new ConfigOptionString(
1288 (old_extruder_id >= 0 && old_extruder_id < (int) extruder_variants.size()) ? extruder_variants[old_extruder_id] : std::string()));
1289 config.set_key_value("new_extruder_variant", new ConfigOptionString(
1290 (new_extruder_id >= 0 && new_extruder_id < (int) extruder_variants.size()) ? extruder_variants[new_extruder_id] : std::string()));
1291 }
1292 config.set_key_value("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get())));
1293 config.set_key_value("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get())));
1294 config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
1295 config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
1296 config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
1297 // config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
1298 // BBS
1299 {
1300 GCodeWriter& gcode_writer = gcodegen.m_writer;
1301 FullPrintConfig& full_config = gcodegen.m_config;
1302
1303 // Per-variant filament arrays can hold one column per variant a filament uses
1304 // under a per-layer nozzle grouping; resolve the column instead of the raw id.
1305 // The old filament resolves at the current layer, which is safe because the
1306 // per-layer maps are gap-filled carry-forward.
1307 size_t old_fi = (old_filament_id != -1) ? gcodegen.get_filament_config_index(old_filament_id) : 0;
1308 size_t new_fi = gcodegen.get_filament_config_index(new_filament_id);
1309
1310 // set volumetric speed of outer wall ,ignore per obejct,just use default setting
1311 float outer_wall_volumetric_speed = get_outer_wall_volumetric_speed(full_config, *gcodegen.m_print, new_filament_id, (int)new_fi, gcodegen.get_extruder_id(new_filament_id));
1312 config.set_key_value("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed));
1313
1314 float old_retract_length = (old_filament_id != -1) ? full_config.retraction_length.get_at(old_fi) : 0;
1315 float new_retract_length = full_config.retraction_length.get_at(new_fi);
1316 float old_retract_length_toolchange = (old_filament_id != -1) ? full_config.retract_length_toolchange.get_at(old_fi) : 0;
1317 float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_fi);
1318 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;
1319 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);
1320 Vec3d nozzle_pos = gcode_writer.get_position();
1321
1322 float purge_volume = tcr.purge_volume < EPSILON ? 0 : std::max(tcr.purge_volume, g_min_purge_volume);
1323 float filament_area = float((M_PI / 4.f) * pow(full_config.filament_diameter.get_at(new_filament_id), 2));
1324 float purge_length = purge_volume / filament_area;
1325
1326 int old_filament_e_feedrate = (old_filament_id != -1) ? (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(old_fi) / filament_area) : 200;
1327 old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate;
1328 int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_fi) / filament_area);
1329 new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate;
1330 float wipe_avoid_pos_x = 0.f;
1331 {
1332 //set wipe_avoid_pos_x
1333 Vec2f box_min = transform_wt_pt(m_wipe_tower_bbx.min.cast<float>());
1334 Vec2f box_max = transform_wt_pt(m_wipe_tower_bbx.max.cast<float>());
1335 wipe_avoid_pos_x = get_wipe_avoid_pos_x(box_min, box_max, 3.f);
1336 }
1337
1338 // Position where the nozzle waits to heat, just outside the wipe tower. Clamp X to the
1339 // region every extruder can reach (shared printable polygon), which equals the full
1340 // printable_area for all current single/dual printers, so existing output is unchanged.
1341 Vec2f stop_pos = tool_change_start_pos;
1342 {
1343 BoundingBoxf bbx = m_wipe_tower_bbx;
1344 bbx.translate((m_wipe_tower_pos + m_rib_offset).cast<double>());
1345 stop_pos.x() += (stop_pos.x() < bbx.center().x()) ? -2.f : 2.f;
1346 auto printer_bbx = unscaled(get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon()));
1347 if (stop_pos.x() < printer_bbx.min[0]) stop_pos.x() = float(printer_bbx.min[0]);
1348 if (stop_pos.x() > printer_bbx.max[0]) stop_pos.x() = float(printer_bbx.max[0]);
1349 }
1350 config.set_key_value("wipe_tower_wait_pos_x", new ConfigOptionFloat(stop_pos.x()));
1351 config.set_key_value("wipe_tower_wait_pos_y", new ConfigOptionFloat(stop_pos.y()));
1352 config.set_key_value("wipe_tower_wait_pos_valid", new ConfigOptionBool(true));
1353 // Bambu Studio's names for the wait position, used by its profiles.
1354 config.set_key_value("wipe_tower_center_pos_x", new ConfigOptionFloat(stop_pos.x()));
1355 config.set_key_value("wipe_tower_center_pos_y", new ConfigOptionFloat(stop_pos.y()));
1356 config.set_key_value("wipe_tower_center_pos_valid", new ConfigOptionBool(true));
1357
1358 config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
1359 config.set_key_value("relative_e_axis", new ConfigOptionBool(full_config.use_relative_e_distances));
1360 config.set_key_value("toolchange_count", new ConfigOptionInt((int) gcodegen.m_toolchange_count + 1));
1361 // BBS: fan speed is useless placeholer now, but we don't remove it to avoid
1362 // slicing error in old change_filament_gcode in old 3MF
1363 config.set_key_value("fan_speed", new ConfigOptionInt((int) 0));
1364 config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length));
1365 config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length));
1366 // Expose the old filament's nozzle-change retract length (filament_retract_length_nc; nil/-1 -> 0).
1367 config.set_key_value("filament_retract_length_nc", new ConfigOptionFloat(
1368 (old_filament_id != -1) ? (float) full_config.filament_retract_length_nc.get_at(old_fi) : 0.f));
1369 config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange));
1370 config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange));
1371 // Current parked-retract length of the incoming filament's extruder.
1372 config.set_key_value("new_extruder_retracted_length",
1373 new ConfigOptionFloat(gcode_writer.get_extruder_retracted_length((int) new_filament_id)));
1374 config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp));
1375 int interface_temp = full_config.filament_tower_interface_print_temp.get_at(new_filament_id);
1376 if (interface_temp == -1)
1377 interface_temp = full_config.nozzle_temperature_range_high.get_at(new_fi);
1378 if (full_config.enable_tower_interface_features && tcr.is_contact)
1379 new_filament_temp = interface_temp;
1380 config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp));
1381 if (full_config.enable_tower_interface_features && tcr.is_contact) {
1382 // Rebuild in filament order: the config arrays may carry per-variant columns,
1383 // while these placeholder vectors are consumed indexed by filament id.
1384 size_t num_filaments = full_config.filament_type.values.size();
1385 std::vector<int> temps(num_filaments);
1386 std::vector<int> first_layer_temps(num_filaments);
1387 for (size_t i = 0; i < num_filaments; ++i) {
1388 size_t fi_i = gcodegen.get_filament_config_index((int)i);
1389 temps[i] = full_config.nozzle_temperature.get_at(fi_i);
1390 first_layer_temps[i] = full_config.nozzle_temperature_initial_layer.get_at(fi_i);
1391 }
1392 if (new_filament_id >= 0 && new_filament_id < (int)temps.size())
1393 temps[new_filament_id] = interface_temp;
1394 config.set_key_value("temperature", new ConfigOptionInts(temps));
1395 if (new_filament_id >= 0 && new_filament_id < (int)first_layer_temps.size())
1396 first_layer_temps[new_filament_id] = interface_temp;
1397 config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps));
1398 }
1399 config.set_key_value("x_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(0)));
1400 config.set_key_value("y_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(1)));
1401 config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2)));
1402 config.set_key_value("first_flush_volume", new ConfigOptionFloat(purge_length / 2.f));
1403 config.set_key_value("second_flush_volume", new ConfigOptionFloat(purge_length / 2.f));
1404 config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate));
1405 config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate));
1406 config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x())));
1407 config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y())));
1408 config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x())));
1409 config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y())));
1410 config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x())));
1411 config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y())));
1412
1413 {
1414 size_t num_filaments = m_print_config->filament_type.values.size();
1415 // Fast purge mode uses filament_flush_temp_fast; Default is inert.
1416 bool use_fast_flush = m_print_config->prime_volume_mode == PrimeVolumeMode::pvmFast;
1417 std::vector<double> flush_v_speed(num_filaments);
1418 std::vector<int> flush_temps(num_filaments);
1419 std::vector<double> filament_cooling_before_tower(num_filaments);
1420 for (size_t idx = 0; idx < num_filaments; ++idx) {
1421 size_t fi = gcodegen.get_filament_config_index(idx);
1422 flush_v_speed[idx] = m_print_config->filament_flush_volumetric_speed.get_at(fi);
1423 if (flush_v_speed[idx] == 0)
1424 flush_v_speed[idx] = m_print_config->filament_max_volumetric_speed.get_at(fi);
1425 flush_temps[idx] = use_fast_flush ? m_print_config->filament_flush_temp_fast.get_at(fi)
1426 : m_print_config->filament_flush_temp.get_at(fi);
1427 if (flush_temps[idx] == 0)
1428 flush_temps[idx] = m_print_config->nozzle_temperature_range_high.get_at(fi);
1429 filament_cooling_before_tower[idx] = m_print_config->filament_cooling_before_tower.get_at(fi);
1430 }
1431 if (tcr.is_contact || gcodegen.m_layer_index == 0)
1432 std::fill(filament_cooling_before_tower.begin(), filament_cooling_before_tower.end(), 0);
1433 config.set_key_value("flush_volumetric_speeds", new ConfigOptionFloats(flush_v_speed));
1434 config.set_key_value("flush_temperatures", new ConfigOptionInts(flush_temps));
1435 config.set_key_value("filament_cooling_before_tower", new ConfigOptionFloats(filament_cooling_before_tower));
1436 }
1437 config.set_key_value("flush_length", new ConfigOptionFloat(purge_length));
1438 config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter));
1439 config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x));
1440 config.set_key_value("is_prime_tower_interface", new ConfigOptionBool(tcr.is_contact));
1441 config.set_key_value("filament_tower_interface_purge_volume", new ConfigOptionFloat(full_config.filament_tower_interface_purge_volume.get_at(new_filament_id)));
1442 config.set_key_value("filament_tower_interface_print_temp", new ConfigOptionInt(interface_temp));
1443
1444 int flush_count = std::min(g_max_flush_count, (int) std::round(purge_volume / g_purge_volume_one_time));
1445 float flush_unit = purge_length / flush_count;
1446 int flush_idx = 0;
1447 for (; flush_idx < flush_count; flush_idx++) {
1448 char key_value[64] = {0};
1449 snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1);
1450 config.set_key_value(key_value, new ConfigOptionFloat(flush_unit));
1451 }
1452
1453 for (; flush_idx < g_max_flush_count; flush_idx++) {
1454 char key_value[64] = {0};
1455 snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1);
1456 config.set_key_value(key_value, new ConfigOptionFloat(0.f));
1457 }
1458 }
1459 toolchange_gcode_str = gcodegen.placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_filament_id, &config);
1460
1461 check_add_eol(toolchange_gcode_str);
1462 // Type1 wipe tower path (every BBL printer): bracket the same way set_extruder() does,
1463 // so FanMover doesn't split a G1 inside this custom gcode either.
1464 toolchange_gcode_str = wrap_custom_gcode_for_fan_mover(gcodegen.config(), toolchange_gcode_str);
1465
1466 //BBS
1467 {
1468 //BBS: current position and fan_speed is unclear after interting change_filament_gcode
1469 check_add_eol(toolchange_gcode_str);
1470 toolchange_gcode_str += ";_FORCE_RESUME_FAN_SPEED\n";
1471 gcodegen.writer().set_current_position_clear(false);
1472 // BBS: check whether custom gcode changes the z position. Update if changed
1473 double temp_z_after_tool_change;
1474 if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_str, temp_z_after_tool_change)) {
1475 Vec3d pos = gcodegen.writer().get_position();
1476 pos(2) = temp_z_after_tool_change;
1477 gcodegen.writer().set_position(pos);
1478 }
1479 }
1480 need_travel_after_change_filament_gcode = true;
1481 }
1482
1483 std::string toolchange_command;
1484 if (tcr.priming || (new_filament_id >= 0 && gcodegen.writer().need_toolchange(new_filament_id)))
1485 // Orca: null-safe, layer-aware nozzle lookup — group_result may be null on
1486 // non-multi-nozzle paths (the helper falls back to the extruder id).
1487 toolchange_command = gcodegen.writer().toolchange(new_filament_id,
1488 nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx));
1489 if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_filament_id))
1490 toolchange_gcode_str += toolchange_command;
1491 else {
1492 // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code.
1493 }
1494
1495 // The change G-code may move the carriage even when the incoming
1496 // filament uses the same physical nozzle (the common AMS case).
1497 // Synchronize before planning the tower-entry travel so exclusion
1498 // avoidance starts from the emitted position, not the stale one.
1499 if (position_before_toolchange)
1500 gcodegen.synchronize_toolchange_position(*position_before_toolchange, toolchange_gcode_str);
1501
1502 if (need_travel_after_change_filament_gcode) {
1503 // move to start_pos for wiping after toolchange
1504 if (!is_used_travel_avoid_perimeter) {
1505 std::string start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d), erMixed, "Move to start pos");
1506 check_add_eol(start_pos_str);
1507 wipe_next_start_point_str = start_pos_str;
1508 } else {
1509 // BBS:change travel_path
1510 Vec3f gcode_last_pos;
1511 GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, gcode_last_pos);
1512 Vec2f gcode_last_pos2d{gcode_last_pos[0], gcode_last_pos[1]};
1513 Point gcode_last_pos2d_object = gcodegen.gcode_to_point(gcode_last_pos2d.cast<double>() + plate_origin_2d.cast<double>());
1514 Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, tool_change_start_pos + plate_origin_2d);
1515 BoundingBox avoid_bbx;
1516 {
1517 // set avoid_bbx
1518 avoid_bbx = scaled(m_wipe_tower_bbx);
1519 Polygon avoid_points = avoid_bbx.polygon();
1520 for (auto &p : avoid_points.points) {
1521 Vec2f pp = transform_wt_pt(unscale(p).cast<float>());
1522 p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d);
1523 }
1524 avoid_bbx = BoundingBox(avoid_points.points);
1525 }
1526 std::string travel_to_wipe_tower_gcode;
1527 Polyline travel_polyline = generate_path_to_wipe_tower(gcode_last_pos2d_object, start_wipe_pos, avoid_bbx, shared_printable_area(gcodegen));
1528
1529 for (size_t i = 0; i < travel_polyline.points.size(); ++i) {
1530 const auto &p = travel_polyline.points[i];
1531 if (i == travel_polyline.points.size() - 1) {
1532 wipe_next_start_point_str = gcodegen.travel_to(p, erMixed, "Move to start pos");
1533 check_add_eol(wipe_next_start_point_str);
1534 break;
1535 }
1536 travel_to_wipe_tower_gcode += gcodegen.travel_to(p, erMixed, "Move to start pos");
1537 check_add_eol(travel_to_wipe_tower_gcode);
1538 }
1539 toolchange_gcode_str += travel_to_wipe_tower_gcode;
1540 gcodegen.set_last_pos(start_wipe_pos);
1541 }
1542 }
1543
1544 // do unretract after setting current extruder_id
1545 // BBS pattern: the wipe tower shifts the toolchange start position outward for the
1546 // tower-interface (contact) pre-extrusion and for the PETG-with-filament-switcher case;
1547 // the pre-extrusion material itself is laid down here as extra unretract on the approach.
1548 // has_filament_switcher is a develop-only key read defensively from the full config (Orca
1549 // does not carry it as a static PrintConfig member — same convention as
1550 // enable_filament_dynamic_map); no shipping profile sets it, so is_petg_pre_extrusion is
1551 // always false fleet-wide.
1552 const ConfigOptionBool* has_filament_switcher_opt = gcodegen.m_print->full_print_config().option<ConfigOptionBool>("has_filament_switcher");
1553 bool is_contact_pre_extrusion = tcr.is_contact && gcodegen.m_config.enable_tower_interface_features;
1554 bool is_petg_pre_extrusion = !is_contact_pre_extrusion
1555 && gcodegen.config().filament_type.get_at(tcr.new_tool) == "PETG"
1556 && has_filament_switcher_opt && has_filament_switcher_opt->value;
1557 float extra_unretract = 0.f;
1558 if (is_contact_pre_extrusion)
1559 extra_unretract = gcodegen.m_config.filament_tower_interface_pre_extrusion_length.get_at(tcr.new_tool);
1560 else if (is_petg_pre_extrusion)
1561 extra_unretract = 2.f;
1562 std::string toolchange_unretract_str = (extra_unretract > 0.f) ? gcodegen.unretract(extra_unretract) : gcodegen.unretract();
1563 check_add_eol(toolchange_unretract_str);
1564
1565 gcodegen.placeholder_parser().set("current_extruder", new_filament_id);
1566 gcodegen.placeholder_parser().set("current_filament_id", new_filament_id);
1567 gcodegen.placeholder_parser().set("current_extruder_id", new_extruder_id);
1568 gcodegen.placeholder_parser().set("current_nozzle_id",
1569 nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx));
1570 gcodegen.placeholder_parser().set("current_hotend",
1571 hotend_id_for_gcode_placeholder(group_result, gcodegen.m_print->is_BBL_printer(), new_filament_id, new_extruder_id, m_layer_idx));
1572 {
1573 size_t fi = gcodegen.get_filament_config_index(new_filament_id);
1574 gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(fi));
1575 gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(fi));
1576 gcodegen.placeholder_parser().set("retraction_distance_when_ec", gcodegen.m_config.retraction_distances_when_ec.get_at(fi));
1577 gcodegen.placeholder_parser().set("long_retraction_when_ec", gcodegen.m_config.long_retractions_when_ec.get_at(fi));
1578 }
1579
1580 // Process the start filament gcode.
1581 std::string start_filament_gcode_str;
1582 const std::string &filament_start_gcode = gcodegen.config().filament_start_gcode.get_at(new_filament_id);
1583 if (!filament_start_gcode.empty()) {
1584 // Process the filament_start_gcode for the active filament only.
1585 DynamicConfig config;
1586 config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_filament_id));
1587 config.set_key_value("current_filament_id", new ConfigOptionInt(new_filament_id));
1588 config.set_key_value("current_nozzle_id", new ConfigOptionInt(nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx)));
1589 config.set_key_value("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get())));
1590 config.set_key_value("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get())));
1591 config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
1592 config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
1593 config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z));
1594 start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config);
1595 if (add_change_filament_624) {
1596 start_filament_gcode_str += "M625\n";
1597 add_change_filament_624 = false;
1598 }
1599 check_add_eol(start_filament_gcode_str);
1600 }
1601
1602 start_filament_gcode_str = start_filament_gcode_str + wipe_next_start_point_str + toolchange_unretract_str;
1603
1604 // Orca: the custom change_filament_gcode lifts to the object layer height and the unretract
1605 // de-hops back to it, so every tower extrusion emitted after it (purge moves, and the wall
1606 // when it prints after the toolchange) would float above the compacted tower. Descend first.
1607 if (compacted_below_object) {
1608 std::string z_descend = gcodegen.writer().travel_to_z(z, "Descend to compacted wipe tower z (no sparse layers)");
1609 check_add_eol(z_descend);
1610 start_filament_gcode_str += z_descend;
1611 }
1612
1613 // Insert the end filament, toolchange, and start filament gcode into the generated gcode.
1614 DynamicConfig config;
1615 config.set_key_value("filament_end_gcode", new ConfigOptionString(end_filament_gcode_str));
1616 config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str));
1617 config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str));
1618 std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_filament_id, &config);
1619 unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
1620 gcode += tower_height_tag(gcodegen, tcr, tcr_gcode);
1621 gcode += tcr_gcode;
1622 // Count the toolchange only when the emitted block really changed the tool —
1623 // tower visits without a filament change must not advance the ordinal.
1624 if (custom_gcode_changes_tool(tcr_gcode, gcodegen.writer().toolchange_prefix(), new_filament_id))
1625 gcodegen.m_toolchange_count++;
1626 check_add_eol(toolchange_gcode_str);
1627
1628 // SoftFever: set new PA for new filament
1629 gcode += gcodegen.set_filament_pressure_advance(new_filament_id);
1630
1631 // A phony move to the end position at the wipe tower.
1632 gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
1633 gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d));
1634 if (!is_approx(z, current_z)) {
1635 gcode += gcodegen.writer().retract();
1636 gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
1637 gcode += gcodegen.writer().unretract();
1638 }
1639
1640 else {
1641 // Prepare a future wipe.
1642 gcodegen.m_wipe.reset_path();
1643 for (const Vec2f &wipe_pt : tcr.wipe_path)
1644 gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)));
1645 gcode += gcodegen.retract(false, false, auto_lift_type, true);
1646 }
1647
1648 // Let the planner know we are traveling between objects.
1649 gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
1650 if (position_before_toolchange)
1651 gcodegen.synchronize_toolchange_position(*position_before_toolchange, gcode);
1652 return gcode;
1653 }
#define EPSILON
Definition ColorUtils.cpp:64
#define M_PI
Definition ExtrusionSimulator.cpp:31
static bool get_last_position_from_gcode(const std::string &gcode_str, Vec3f &pos)
Definition GCodeProcessor.cpp:4310
static bool get_last_z_from_gcode(const std::string &gcode_str, double &z)
Definition GCodeProcessor.cpp:4256
Vec2f m_rib_offset
Definition GCode.hpp:196
Polygons shared_printable_area(GCode &gcodegen) const
Definition GCode.cpp:1012
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult &tcr, const Vec2f &translation, float angle) const
Definition GCode.cpp:1957
std::string tower_height_tag(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, const std::string &tcr_gcode) const
Definition GCode.cpp:1079
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const Polygons &bed_polygons) const
Definition GCode.cpp:887
BoundingBoxf m_wipe_tower_bbx
Definition GCode.hpp:195
@ gcode
Definition Time.hpp:14
Kernel::Point_3 Point
Definition MeshRepair.cpp:81
Vec3d pos(const Pt &p)
Definition ReprojectPointsOnMesh.hpp:16
zaa_minimize_perimeter_height ConfigOptionFloat
Definition PrintConfig.hpp:1954
static int nozzle_id_for_gcode_placeholder(const std::shared_ptr< MultiNozzleUtils::LayeredNozzleGroupResult > &group_result, int filament_id, int extruder_id, int layer_id=-1)
Definition GCode.cpp:241
zaa_minimize_perimeter_height nozzle_diameter((ConfigOptionBool, reduce_infill_retraction))((ConfigOptionBool
BoundingBox scaled(const BoundingBoxf &bb)
Definition BoundingBox.hpp:284
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition Point.hpp:68
ZHopType
Definition PrintConfig.hpp:582
@ zhtSlope
Definition PrintConfig.hpp:585
@ zhtAuto
Definition PrintConfig.hpp:583
@ zhtSpiral
Definition PrintConfig.hpp:586
size_t get_extruder_index(const GCodeConfig &config, unsigned int filament_id)
Definition PrintConfig.cpp:140
static const float g_min_purge_volume
Definition GCode.cpp:208
static std::vector< double > get_nozzle_diameters_by_nozzle_id(const MultiNozzleUtils::NozzleGroupResultBase *group_result)
Definition GCode.cpp:280
BoundingBox get_extents(const ExPolygonsWithIds &e)
Definition Emboss.cpp:1374
std::string transform_gcode(const std::string &gcode, Vec2f pos, const Vec2f &translation, float angle)
Definition GCode.cpp:605
Eigen::Matrix< float, 3, 1, Eigen::DontAlign > Vec3f
Definition Point.hpp:65
LiftType
Definition PrintConfig.hpp:429
@ SpiralLift
Definition PrintConfig.hpp:431
@ NormalLift
Definition PrintConfig.hpp:430
@ erMixed
Definition ExtrusionEntity.hpp:47
static Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt)
Definition GCode.cpp:752
@ pvmFast
Definition PrintConfig.hpp:659
float get_wipe_avoid_pos_x(const Vec2f &wt_min, const Vec2f &wt_max, float offset)
Definition GCode.cpp:655
static int hotend_id_for_gcode_placeholder(const std::shared_ptr< MultiNozzleUtils::LayeredNozzleGroupResult > &group_result, bool is_bbl_printer, int filament_id, int extruder_id, int layer_id=-1)
Definition GCode.cpp:229
ConfigOptionIntsTempl< false > ConfigOptionInts
Definition Config.hpp:1154
constexpr bool is_approx(Number value, Number test_value, Number precision=EPSILON)
Definition libslic3r.h:320
constexpr Eigen::Matrix< Tout, 2, EigenArgs... > unscaled(const Slic3r::Point &v) noexcept
Definition Arrange.cpp:81
static const float g_purge_volume_one_time
Definition GCode.cpp:209
static std::vector< std::string > get_nozzle_volume_types_by_nozzle_id(const MultiNozzleUtils::NozzleGroupResultBase *group_result)
Definition GCode.cpp:296
zaa_minimize_perimeter_height change_filament_gcode((ConfigOptionString, change_extrusion_role_gcode))((ConfigOptionString
static const int g_max_flush_count
Definition GCode.cpp:210
zaa_minimize_perimeter_height ConfigOptionString
Definition PrintConfig.hpp:1702
T unscale(Q v)
Definition libslic3r.h:138
Vec2d travel_point_2
Definition GCode.cpp:322
static std::string wrap_custom_gcode_for_fan_mover(const FullPrintConfig &config, const std::string &gcode)
Definition GCode.cpp:1095
Vec2d travel_point_1
Definition GCode.cpp:321
Vec2d travel_point_3
Definition GCode.cpp:323
static float get_outer_wall_volumetric_speed(const FullPrintConfig &config, const Print &print, int filament_id, int filament_variant_idx, int extruder_id)
Definition GCode.cpp:760
Eigen::Matrix< float, 2, 1, Eigen::DontAlign > Vec2f
Definition Point.hpp:64
bool unescape_string_cstyle(const std::string &str, std::string &str_out)
Definition Config.cpp:150
auto cast(const BoundingBoxBase< Tin > &b)
Definition BoundingBox.hpp:299
static void check_add_eol(std::string &gcode)
Definition GCode.cpp:447
ConfigOptionFloatsTempl< false > ConfigOptionFloats
Definition Config.hpp:1015
static bool custom_gcode_changes_tool(const std::string &custom_gcode, const std::string &tch_prefix, unsigned next_extruder)
Definition GCode.cpp:455
Slic3r::Polygon Polygon
Definition Emboss.cpp:101

References Slic3r::GCodeWriter::add_object_end_labels(), Slic3r::GCode::capture_toolchange_position(), Slic3r::cast(), Slic3r::BoundingBoxBase< PointType, APointsType >::center(), Slic3r::change_filament_gcode(), Slic3r::check_add_eol(), Slic3r::GCode::config(), Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionString, Slic3r::custom_gcode_changes_tool(), Slic3r::WipeTower::NozzleChangeResult::end_pos, Slic3r::WipeTower::ToolChangeResult::end_pos, EPSILON, Slic3r::erMixed, Slic3r::Extruder::extruder_id(), Slic3r::GCodeWriter::filament(), Slic3r::PrintBase::full_print_config(), Slic3r::g_max_flush_count, Slic3r::g_min_purge_volume, Slic3r::g_purge_volume_one_time, Slic3r::WipeTower::NozzleChangeResult::gcode, Slic3r::GCode::gcode_to_point(), generate_path_to_wipe_tower(), Slic3r::get_extents(), Slic3r::GCode::get_extruder_id(), Slic3r::get_extruder_index(), Slic3r::GCodeWriter::get_extruder_retracted_length(), Slic3r::Print::get_extruder_shared_printable_polygon(), Slic3r::GCode::get_filament_config_index(), Slic3r::GCodeProcessor::get_last_position_from_gcode(), Slic3r::GCodeProcessor::get_last_z_from_gcode(), Slic3r::Print::get_layered_nozzle_group_result(), Slic3r::get_nozzle_diameters_by_nozzle_id(), Slic3r::get_nozzle_volume_types_by_nozzle_id(), Slic3r::get_outer_wall_volumetric_speed(), Slic3r::GCodeWriter::get_position(), Slic3r::get_wipe_avoid_pos_x(), Slic3r::hotend_id_for_gcode_placeholder(), Slic3r::Extruder::id(), Slic3r::is_approx(), Slic3r::Print::is_BBL_printer(), Slic3r::WipeTower::ToolChangeResult::is_contact, Slic3r::WipeTower::NozzleChangeResult::is_extruder_change, Slic3r::WipeTower::ToolChangeResult::is_finish_first, Slic3r::WipeTower::ToolChangeResult::is_tool_change, Slic3r::GCode::m_avoid_crossing_perimeters, Slic3r::GCode::m_config, Slic3r::GCode::m_filament_instances_code, m_layer_idx, Slic3r::GCode::m_layer_index, Slic3r::GCode::m_max_layer_z, M_PI, m_plate_origin, Slic3r::GCode::m_print, m_print_config, m_rib_offset, m_sparse_layers_skipped, Slic3r::GCode::m_toolchange_count, Slic3r::GCode::m_wipe, m_wipe_tower_bbx, m_wipe_tower_pos, m_wipe_tower_rotation, Slic3r::GCode::m_writer, Slic3r::GCodeWriter::need_toolchange(), Slic3r::WipeTower::ToolChangeResult::new_tool, Slic3r::NormalLift, Slic3r::WipeTower::ToolChangeResult::nozzle_change_result, Slic3r::nozzle_diameter(), Slic3r::nozzle_id_for_gcode_placeholder(), Slic3r::GCode::on_first_layer(), Slic3r::ConfigBase::option(), Slic3r::Wipe::path, Slic3r::GCode::placeholder_parser(), Slic3r::GCode::placeholder_parser_process(), Slic3r::MultiPoint::points, Slic3r::BoundingBox::polygon(), post_process_wipe_tower_moves(), Slic3r::WipeTower::ToolChangeResult::priming, Slic3r::WipeTower::ToolChangeResult::print_z, Slic3r::WipeTower::ToolChangeResult::purge_volume, Slic3r::pvmFast, Slic3r::Wipe::reset_path(), Slic3r::GCode::retract(), Slic3r::GCodeWriter::retract(), Slic3r::scaled(), Slic3r::PlaceholderParser::set(), Slic3r::GCodeWriter::set_current_position_clear(), Slic3r::GCode::set_filament_pressure_advance(), Slic3r::DynamicConfig::set_key_value(), Slic3r::GCode::set_last_pos(), Slic3r::GCodeWriter::set_position(), shared_printable_area(), Slic3r::SpiralLift, Slic3r::WipeTower::NozzleChangeResult::start_pos, Slic3r::WipeTower::ToolChangeResult::start_pos, Slic3r::GCode::synchronize_toolchange_position(), Slic3r::WipeTower::ToolChangeResult::tool_change_start_pos, Slic3r::GCodeWriter::toolchange(), Slic3r::GCodeWriter::toolchange_prefix(), tower_height_tag(), Slic3r::transform_gcode(), Slic3r::BoundingBoxBase< PointType, APointsType >::translate(), Slic3r::travel_point_1, Slic3r::travel_point_2, Slic3r::travel_point_3, Slic3r::GCode::travel_to(), Slic3r::GCodeWriter::travel_to_xy(), Slic3r::GCodeWriter::travel_to_z(), Slic3r::unescape_string_cstyle(), Slic3r::GCode::unretract(), Slic3r::GCodeWriter::unretract(), Slic3r::unscale(), Slic3r::unscaled(), Slic3r::AvoidCrossingPerimeters::use_external_mp_once(), Slic3r::ConfigOptionSingle< T >::value, Slic3r::WipeTower::NozzleChangeResult::wipe_path, Slic3r::WipeTower::ToolChangeResult::wipe_path, Slic3r::wipe_tower_point_to_object_point(), Slic3r::wrap_custom_gcode_for_fan_mover(), Slic3r::GCode::writer(), Slic3r::zhtAuto, Slic3r::zhtSlope, and Slic3r::zhtSpiral.

Referenced by tool_change().

Here is the caller graph for this function:

◆ append_tcr2()

std::string Slic3r::WipeTowerIntegration::append_tcr2 ( GCode & gcodegen,
const WipeTower::ToolChangeResult & tcr,
int new_extruder_id,
double z = -1. ) const
private
1659 {
1660 if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
1661 throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
1662
1663 const std::optional<GCode::ToolchangePositionState> position_before_toolchange =
1664 gcodegen.capture_toolchange_position();
1665
1666 std::string gcode;
1667
1668 // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
1669 // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
1670 float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
1671
1672 // Priming lines are absolute bed moves; everything else is tower-local
1673 // (transform_wt2_pt).
1674 Vec2f start_pos = tcr.start_pos;
1675 Vec2f end_pos = tcr.end_pos;
1676 if (!tcr.priming) {
1677 start_pos = transform_wt2_pt(start_pos);
1678 end_pos = transform_wt2_pt(end_pos);
1679 }
1680
1681 Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : Vec2f(m_wipe_tower_pos + Eigen::Rotation2Df(alpha) * m_rib_offset);
1682 float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
1683 Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
1684
1685
1686 std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
1687
1688 gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't).
1689
1690 double current_z = gcodegen.writer().get_position().z();
1691
1692
1693 if (z == -1.) // in case no specific z was provided, print at current_z pos
1694 z = current_z;
1695
1696 const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
1697 const bool will_go_down = !is_approx(z, current_z);
1698 const bool is_ramming = (gcodegen.config().single_extruder_multi_material) ||
1699 (!gcodegen.config().single_extruder_multi_material &&
1700 gcodegen.config().filament_multitool_ramming.get_at(gcodegen.get_filament_config_index(tcr.initial_tool)));
1701 // Orca: user-facing override (Printer Settings > Wipe tower > "Tool change on wipe tower").
1702 // Forces the toolhead to travel over the wipe tower before issuing Tx even on multi-toolhead
1703 // printers without ramming, where Orca would otherwise emit Tx in place (potentially over the part).
1704 const bool tool_change_on_wipe_tower = gcodegen.config().tool_change_on_wipe_tower.value;
1705 const bool should_travel_to_tower = !tcr.priming && (tcr.force_travel // wipe tower says so
1706 || !needs_toolchange // this is just finishing the tower with no toolchange
1707 || will_go_down // Make sure to move to prime tower before moving down
1708 || is_ramming
1709 || tool_change_on_wipe_tower);
1710
1711 const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
1712 const bool travel_to_tower_now = should_travel_to_tower || gcodegen.m_need_change_layer_lift_z;
1713 if (travel_to_tower_now) {
1714 // FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
1715 // then we could simplify the condition and make it more readable.
1716
1717 // Orca: pass the configured lift type, as append_tcr does above. lazy_lift() keeps
1718 // the first type it is given, so the NormalLift default would pin this hop to a
1719 // standing move. Slope and spiral both need a known head position.
1720 LiftType lift_type = LiftType::NormalLift;
1721 if (gcodegen.writer().filament() != nullptr && gcodegen.writer().is_current_position_clear()) {
1722 ZHopType z_hop_type = ZHopType(gcodegen.config().z_hop_types.get_at(
1723 gcodegen.get_filament_config_index((int) gcodegen.writer().filament()->id())));
1724 if (z_hop_type == ZHopType::zhtAuto)
1725 z_hop_type = ZHopType::zhtSpiral;
1726 lift_type = gcodegen.to_lift_type(z_hop_type);
1727 }
1728 gcode += gcodegen.retract(false, false, lift_type);
1729 gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
1730 if (!tcr.priming && gcodegen.last_pos_defined())
1731 gcode += travel_to_tower_gap(gcodegen, gcodegen.last_pos(), start_wipe_pos);
1732 gcode += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
1733 gcode += gcodegen.unretract();
1734 } else {
1735 // When this is multiextruder printer without any ramming, we can just change
1736 // the tool without travelling to the tower. The tower entry travel then lives
1737 // inside the tcr gcode; with skip points on it is rerouted below, once the
1738 // toolchange gcode (and the head position it ends at) is known.
1739 }
1740
1741 if (will_go_down) {
1742 gcode += gcodegen.writer().retract();
1743 gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
1744 gcode += gcodegen.writer().unretract();
1745 }
1746
1747 std::string toolchange_gcode_str;
1748 std::string deretraction_str;
1749 int toolchange_temp_override = -1;
1750 int interface_temp = -1;
1751 if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
1752 if (is_ramming)
1753 gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
1754 if (gcodegen.config().enable_tower_interface_features && tcr.is_contact) {
1755 interface_temp = gcodegen.config().filament_tower_interface_print_temp.get_at(new_extruder_id);
1756 if (interface_temp == -1)
1757 interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(gcodegen.get_filament_config_index(new_extruder_id));
1758 toolchange_temp_override = interface_temp;
1759 }
1760 toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override,
1761 WipeTower2::wait_for_temp_enabled(gcodegen.m_config)); // TODO: toolchange_z vs print_z
1762 if (!travel_to_tower_now && !tcr.priming && WipeTower2::use_gap_wall(gcodegen.m_config)) {
1763 // The tool changed in place (multi-tool printer without ramming), so the
1764 // tower entry is the tcr's own positioning move — a straight line across
1765 // the printed wall. Route it around the tower and in through the wall
1766 // opening instead, riding at the end of the change_filament_gcode
1767 // substitution so the generator's positioning move degrades to a
1768 // zero-length one (append_tcr parity: travel after the filament change,
1769 // retracted, with the new filament).
1770 Vec3f last_gcode_pos = gcodegen.writer().get_position().cast<float>();
1771 Point route_start;
1772 bool have_start = false;
1773 if (GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, last_gcode_pos)) {
1774 // A custom change_filament_gcode may have moved the head (tool docks
1775 // etc.); recover the real position from the emitted gcode.
1776 route_start = gcodegen.gcode_to_point(Vec2d(last_gcode_pos.x(), last_gcode_pos.y()) + plate_origin_2d.cast<double>());
1777 have_start = true;
1778 } else if (gcodegen.last_pos_defined()) {
1779 route_start = gcodegen.last_pos();
1780 have_start = true;
1781 }
1782 if (have_start) {
1783 gcodegen.set_last_pos(route_start);
1784 gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
1785 std::string travel = travel_to_tower_gap(gcodegen, route_start, start_wipe_pos);
1786 travel += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
1787 check_add_eol(travel);
1788 toolchange_gcode_str += travel;
1789 gcodegen.set_last_pos(start_wipe_pos);
1790 }
1791 }
1792 if (gcodegen.config().enable_prime_tower) {
1793 deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
1794 Vec3d position{gcodegen.writer().get_position()};
1795 position.z() = z;
1796 gcodegen.writer().set_position(position);
1797 deretraction_str += gcodegen.unretract();
1798 }
1799 }
1800
1801 if (toolchange_temp_override > 0) {
1802 // new_extruder_id is the incoming filament id; resolve its per-variant config column.
1803 size_t new_fi = gcodegen.get_filament_config_index(new_extruder_id);
1804 int base_temp = gcodegen.on_first_layer() ? gcodegen.config().nozzle_temperature_initial_layer.get_at(new_fi)
1805 : gcodegen.config().nozzle_temperature.get_at(new_fi);
1806 if (std::abs(tcr.print_z) < EPSILON)
1807 base_temp = gcodegen.config().nozzle_temperature_initial_layer.get_at(new_fi);
1808 // This pass strips the M109 that OozePrevention::post_toolchange just emitted into
1809 // toolchange_gcode_str, so it must look for the tool index that emission actually
1810 // wrote -- physical, translated by GCodeWriter::set_temperature -- not the logical
1811 // id the temperature lookups above use. Matching on the logical id would leave the
1812 // blocking M109 in place and silently defeat the tower interface temperature.
1813 // (The sibling scan further down reads WipeTower2 output, whose set_extruder_temp
1814 // emits no T at all, so it is deliberately left alone.)
1815 const int heater_id = imex_physical_heater_for(
1816 gcodegen.config().is_imex.value, gcodegen.config().physical_extruder_map, new_extruder_id);
1817 const std::string t_token = " T" + std::to_string(heater_id);
1818 std::string out;
1819 out.reserve(toolchange_gcode_str.size());
1820 size_t pos = 0;
1821 while (pos < toolchange_gcode_str.size()) {
1822 size_t line_end = toolchange_gcode_str.find('\n', pos);
1823 if (line_end == std::string::npos)
1824 line_end = toolchange_gcode_str.size();
1825 std::string line = toolchange_gcode_str.substr(pos, line_end - pos);
1826 std::string trimmed = line;
1827 trimmed.erase(0, trimmed.find_first_not_of(" \t"));
1828 bool skip_line = false;
1829 if (boost::starts_with(trimmed, "M109")) {
1830 bool matches_extruder = trimmed.find(t_token) != std::string::npos;
1831 if (!matches_extruder) {
1832 size_t t_pos = trimmed.find('T');
1833 if (t_pos != std::string::npos) {
1834 size_t t_end = trimmed.find_first_not_of("0123456789", t_pos + 1);
1835 const std::string t_val = trimmed.substr(t_pos + 1, t_end == std::string::npos ? std::string::npos : t_end - (t_pos + 1));
1836 if (!t_val.empty()) {
1837 try {
1838 matches_extruder = std::stoi(t_val) == heater_id;
1839 } catch (...) {
1840 matches_extruder = false;
1841 }
1842 }
1843 }
1844 }
1845 if (matches_extruder) {
1846 size_t s_pos = trimmed.find('S');
1847 if (s_pos != std::string::npos) {
1848 size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1);
1849 const std::string s_val = trimmed.substr(s_pos + 1, s_end == std::string::npos ? std::string::npos : s_end - (s_pos + 1));
1850 if (!s_val.empty()) {
1851 try {
1852 skip_line = std::stoi(s_val) == base_temp;
1853 } catch (...) {
1854 skip_line = false;
1855 }
1856 }
1857 }
1858 }
1859 }
1860 if (!skip_line) {
1861 out.append(line);
1862 if (line_end < toolchange_gcode_str.size())
1863 out.push_back('\n');
1864 }
1865 pos = line_end + 1;
1866 }
1867 toolchange_gcode_str.swap(out);
1868 }
1869
1870 if (toolchange_temp_override > 0 && interface_temp > 0) {
1871 const std::string t_token = " T" + std::to_string(new_extruder_id);
1872 std::string out;
1873 out.reserve(tcr_rotated_gcode.size());
1874 size_t pos = 0;
1875 while (pos < tcr_rotated_gcode.size()) {
1876 size_t line_end = tcr_rotated_gcode.find('\n', pos);
1877 if (line_end == std::string::npos)
1878 line_end = tcr_rotated_gcode.size();
1879 std::string line = tcr_rotated_gcode.substr(pos, line_end - pos);
1880 std::string trimmed = line;
1881 trimmed.erase(0, trimmed.find_first_not_of(" \t"));
1882 bool skip_line = false;
1883 if (boost::starts_with(trimmed, "M109") && trimmed.find(WipeTower2::wait_for_temp_tag()) == std::string::npos) {
1884 bool matches_extruder = true;
1885 if (trimmed.find('T') != std::string::npos)
1886 matches_extruder = trimmed.find(t_token) != std::string::npos;
1887 if (matches_extruder) {
1888 size_t s_pos = trimmed.find('S');
1889 if (s_pos != std::string::npos) {
1890 size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1);
1891 const std::string s_val = trimmed.substr(s_pos + 1, s_end == std::string::npos ? std::string::npos : s_end - (s_pos + 1));
1892 if (!s_val.empty()) {
1893 try {
1894 skip_line = std::stoi(s_val) == interface_temp;
1895 } catch (...) {
1896 skip_line = false;
1897 }
1898 }
1899 }
1900 }
1901 }
1902 if (!skip_line) {
1903 out.append(line);
1904 if (line_end < tcr_rotated_gcode.size())
1905 out.push_back('\n');
1906 }
1907 pos = line_end + 1;
1908 }
1909 tcr_rotated_gcode.swap(out);
1910 }
1911
1912 // Insert the toolchange and deretraction gcode into the generated gcode.
1913
1914 DynamicConfig config;
1915 config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str));
1916 config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
1917 config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index));
1918 config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z));
1919 config.set_key_value("toolchange_z", new ConfigOptionFloat(z));
1920
1921 std::string tcr_gcode,
1922 tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
1923 unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
1924 gcode += tower_height_tag(gcodegen, tcr, tcr_gcode);
1925 gcode += tcr_gcode;
1926 check_add_eol(toolchange_gcode_str);
1927
1928 // SoftFever: set new PA for new filament
1929 if (new_extruder_id != -1)
1930 gcode += gcodegen.set_filament_pressure_advance(new_extruder_id);
1931
1932 // A phony move to the end position at the wipe tower.
1933 gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast<double>());
1934 gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d));
1935 if (!is_approx(z, current_z)) {
1936 gcode += gcodegen.writer().retract();
1937 gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
1938 gcode += gcodegen.writer().unretract();
1939 }
1940
1941 else {
1942 // Prepare a future wipe.
1943 gcodegen.m_wipe.reset_path();
1944 for (const Vec2f& wipe_pt : tcr.wipe_path)
1945 gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(wipe_pt) + plate_origin_2d));
1946 }
1947
1948 // Let the planner know we are traveling between objects.
1949 gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
1950 if (position_before_toolchange)
1951 gcodegen.synchronize_toolchange_position(*position_before_toolchange, gcode);
1952 return gcode;
1953 }
static const std::string wait_for_temp_tag()
Definition WipeTower2.hpp:34
static bool wait_for_temp_enabled(const PrintConfig &config)
Definition WipeTower2.cpp:1035
static bool use_gap_wall(const PrintConfig &config)
Definition WipeTower2.cpp:1029
Vec2f transform_wt2_pt(const Vec2f &pt) const
Definition GCode.cpp:1003
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const
Definition GCode.cpp:1029
int imex_physical_heater_for(bool is_imex, const ConfigOptionInts &pem, int logical_id)
Definition IMEXHelpers.cpp:88
zaa_minimize_perimeter_height single_extruder_multi_material((ConfigOptionBool, manual_filament_change))((ConfigOptionBool
Eigen::Matrix< double, 2, 1, Eigen::DontAlign > Vec2d
Definition Point.hpp:67

References Slic3r::GCode::capture_toolchange_position(), Slic3r::cast(), Slic3r::check_add_eol(), Slic3r::GCode::config(), Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionString, Slic3r::WipeTower::ToolChangeResult::end_pos, EPSILON, Slic3r::erMixed, Slic3r::GCodeWriter::filament(), Slic3r::WipeTower::ToolChangeResult::force_travel, Slic3r::GCode::gcode_to_point(), Slic3r::GCode::get_filament_config_index(), Slic3r::GCodeProcessor::get_last_position_from_gcode(), Slic3r::GCodeWriter::get_position(), Slic3r::Extruder::id(), Slic3r::imex_physical_heater_for(), Slic3r::WipeTower::ToolChangeResult::initial_tool, Slic3r::is_approx(), Slic3r::WipeTower::ToolChangeResult::is_contact, Slic3r::GCodeWriter::is_current_position_clear(), Slic3r::GCode::last_pos(), Slic3r::GCode::last_pos_defined(), Slic3r::GCode::m_avoid_crossing_perimeters, Slic3r::GCode::m_config, Slic3r::GCode::m_layer_index, Slic3r::GCode::m_need_change_layer_lift_z, M_PI, m_plate_origin, m_rib_offset, Slic3r::GCode::m_wipe, m_wipe_tower_pos, m_wipe_tower_rotation, Slic3r::GCodeWriter::need_toolchange(), Slic3r::WipeTower::ToolChangeResult::new_tool, Slic3r::NormalLift, Slic3r::GCode::on_first_layer(), Slic3r::Wipe::path, Slic3r::GCode::placeholder_parser_process(), Slic3r::MultiPoint::points, post_process_wipe_tower_moves(), Slic3r::WipeTower::ToolChangeResult::priming, Slic3r::WipeTower::ToolChangeResult::print_z, Slic3r::Wipe::reset_path(), Slic3r::GCode::retract(), Slic3r::GCodeWriter::retract(), Slic3r::GCode::set_extruder(), Slic3r::GCode::set_filament_pressure_advance(), Slic3r::DynamicConfig::set_key_value(), Slic3r::GCode::set_last_pos(), Slic3r::GCodeWriter::set_position(), Slic3r::single_extruder_multi_material(), Slic3r::WipeTower::ToolChangeResult::start_pos, Slic3r::GCode::synchronize_toolchange_position(), Slic3r::GCode::to_lift_type(), tower_height_tag(), transform_wt2_pt(), Slic3r::GCode::travel_to(), travel_to_tower_gap(), Slic3r::GCodeWriter::travel_to_xy(), Slic3r::GCodeWriter::travel_to_z(), Slic3r::unescape_string_cstyle(), Slic3r::GCodeWriter::unlift(), Slic3r::GCode::unretract(), Slic3r::GCodeWriter::unretract(), Slic3r::AvoidCrossingPerimeters::use_external_mp_once(), Slic3r::WipeTower2::use_gap_wall(), Slic3r::WipeTower2::wait_for_temp_enabled(), Slic3r::WipeTower2::wait_for_temp_tag(), Slic3r::WipeTower::ToolChangeResult::wipe_path, Slic3r::wipe_tower_point_to_object_point(), Slic3r::GCode::writer(), Slic3r::zhtAuto, and Slic3r::zhtSpiral.

Referenced by finalize(), prime(), and tool_change().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ enable_timelapse_print()

bool Slic3r::WipeTowerIntegration::enable_timelapse_print ( ) const
inline

◆ finalize()

std::string Slic3r::WipeTowerIntegration::finalize ( GCode & gcodegen)
2142 {
2143 std::string gcode;
2144 if (gcodegen.wipe_tower_type() == WipeTowerType::Type2 && !m_final_purge.gcode.empty()) {
2145 if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
2146 gcode += gcodegen.change_layer(m_final_purge.print_z);
2147 gcode += append_tcr2(gcodegen, m_final_purge, -1);
2148 }
2149
2150 return gcode;
2151 }
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z=-1.) const
Definition GCode.cpp:1655
@ Type2
Definition PrintConfig.hpp:115

References append_tcr2(), Slic3r::GCode::change_layer(), EPSILON, Slic3r::GCodeWriter::get_position(), m_final_purge, Slic3r::Type2, Slic3r::GCode::wipe_tower_type(), and Slic3r::GCode::writer().

Here is the call graph for this function:

◆ generate_path_to_wipe_tower()

Polyline Slic3r::WipeTowerIntegration::generate_path_to_wipe_tower ( const Point & start_pos,
const Point & end_pos,
const BoundingBox & avoid_polygon,
const Polygons & bed_polygons ) const
private
888 {
889 Polyline res;
890 coord_t alpha = scaled(wipe_tower_routing_clearance); // offset distance
891 BoundingBox avoid_polygon_inner = avoid_polygon;
892 avoid_polygon_inner.offset(alpha);
893 coord_t width = avoid_polygon_inner.max[0] - avoid_polygon_inner.min[0];
894 Vec2f v(1, 0); // the first print direction of end_pos.
895 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.
896 // Judge whether the avoid_polygon_inner is outside the bed. The real printable
897 // outline is tested (not its bounding box), so on circular/custom beds corners
898 // hanging off the bed are rejected.
899 // If so, do nothing and just go directly to the end_pos.
900 Points avoid_points = avoid_polygon_inner.polygon().points;
901 const bool is_bbx_in_bed = std::all_of(avoid_points.begin(), avoid_points.end(),
902 [&bed_polygons](const Point &pt) { return contains(bed_polygons, pt, /*border_result=*/false); });
903 if (!is_bbx_in_bed) {
904 res.points.push_back(end_pos);
905 return res;
906 }
907 // Ray-Line Segment Intersection
908 auto ray_intersetion_line = [](const Vec2d &a, const Vec2d &v1, const Vec2d &b, const Vec2d &c) -> std::pair<bool, Point> {
909 const Vec2d v2 = c - b;
910 double denom = cross2(v1, v2);
911 if (fabs(denom) < EPSILON) return {false, Point(0, 0)};
912 const Vec2d v12 = (a - b);
913 double nume_a = cross2(v2, v12);
914 double nume_b = cross2(v1, v12);
915 double t1 = nume_a / denom;
916 double t2 = nume_b / denom;
917 if (t1 >= 0 && t2 >= 0 && t2 <= 1.) {
918 // Get the intersection point.
919 Vec2d res = a + t1 * v1;
920 return std::pair<bool, Point>(true, scaled(res));
921 }
922 return std::pair<bool, Point>(false, {0, 0});
923 };
924 struct Inter_info
925 {
926 int inter_idx0 = -1;
927 Point inter_p;
928 };
929 auto calc_path_len = [](Points &points, Inter_info &beg_info, Inter_info &end_info, bool is_add) -> std::pair<std::vector<Point>, double> {
930 int beg = is_add ? (beg_info.inter_idx0 + 1) % points.size() : beg_info.inter_idx0;
931 int end = is_add ? end_info.inter_idx0 : (end_info.inter_idx0 + 1) % points.size();
932 int i = beg;
933 double len = 0;
934 std::vector<Point> path;
935 path.push_back(beg_info.inter_p);
936 len += (unscale(beg_info.inter_p) - unscale(points[beg])).squaredNorm();
937 while (i != end) {
938 int ni = is_add ? (i + 1) % points.size() : (i - 1 + points.size()) % points.size();
939 auto a = unscale(points[i]);
940 auto b = unscale(points[ni]);
941 len += (a - b).squaredNorm();
942 path.push_back(points[i]);
943 i = ni;
944 }
945 path.push_back(points[end]);
946 path.push_back(end_info.inter_p);
947 len += (unscale(end_info.inter_p) - unscale(points[end])).squaredNorm();
948 return {path, len};
949 };
950 // calculate the intersection point of end_pos along vector v with the avoid_polygon.
951 // store in inter_info.
952 // represent this intersection by 'p'.
953 Inter_info inter_info;
954 for (size_t i = 0; i < avoid_points.size(); i++) {
955 const auto &a = avoid_points[i];
956 const auto &b = avoid_points[(i + 1) % avoid_points.size()];
957 auto [is_inter, inter_p] = ray_intersetion_line(unscale(end_pos), v.cast<double>(), unscale(a), unscale(b));
958 if (is_inter) {
959 inter_info.inter_idx0 = i;
960 inter_info.inter_p = inter_p;
961 break;
962 }
963 }
964 if (inter_info.inter_idx0 == -1) {
965 res.points.push_back(end_pos);
966 return res;
967 }
968 // calculate the other intersection of start_to_p with the avoid_polygon.
969 // represent this intersection by 'p_'.
970 Inter_info inter_info2;
971 Linef start_to_p(unscale(start_pos), unscale(inter_info.inter_p));
972 for (size_t i = 0; i < avoid_points.size(); i++) {
973 if (i == inter_info.inter_idx0) continue;
974 Vec2d a = unscale(avoid_points[i]);
975 Vec2d b = unscale(avoid_points[(i + 1) % avoid_points.size()]);
976 Linef tower_edge(a, b);
977 Vec2d inter;
978 if (line_alg::intersection(start_to_p, tower_edge, &inter)) {
979 inter_info2.inter_p = scaled(inter);
980 inter_info2.inter_idx0 = i;
981 break;
982 }
983 }
984 // if p_ does not exist, go directly to p.
985 // else p travels along the shorter path on the wipe_tower_offset_polygon to p_
986 if (inter_info2.inter_idx0 == -1) {
987 res.points.push_back(inter_info.inter_p);
988 } else {
989 std::vector<Point> path;
990 auto [path1, len1] = calc_path_len(avoid_points, inter_info2, inter_info, true);
991 auto [path2, len2] = calc_path_len(avoid_points, inter_info2, inter_info, false);
992 path = len1 < len2 ? path1 : path2;
993 for (size_t i = 0; i < path.size(); i++) {
994 res.points.push_back(path[i]);
995 }
996 }
997 res.points.push_back(end_pos);
998 return res;
999 }
int64_t coord_t
Definition libslic3r.h:48
T * end(Slic3r::Mat< N, M, T > &mat)
Definition Point.hpp:841
coord_t width(const BoundingBox &box)
Definition Arrange.cpp:1184
bool intersection(const L &l1, const L &l2, Vec< Dim< L >, Scalar< L > > *intersection_pt)
Definition Line.hpp:126
int32_t cross2(const Eigen::MatrixBase< Eigen::Matrix< int32_t, 2, 1, Options > > &v1, const Eigen::MatrixBase< Eigen::Matrix< int32_t, 2, 1, Options > > &v2)=delete
static constexpr float wipe_tower_routing_clearance
Definition GCode.cpp:881
std::vector< Point, PointsAllocator< Point > > Points
Definition Point.hpp:73
std::pair< bool, Vec2f > ray_intersetion_line(const Vec2f &a, const Vec2f &v1, const Vec2f &b, const Vec2f &c)
Definition WipeTower.cpp:330

References Slic3r::cross2(), EPSILON, Slic3r::line_alg::intersection(), Slic3r::BoundingBoxBase< PointType, APointsType >::max, Slic3r::BoundingBoxBase< PointType, APointsType >::min, Slic3r::BoundingBoxBase< PointType, APointsType >::offset(), Slic3r::MultiPoint::points, Slic3r::BoundingBox::polygon(), Slic3r::ray_intersetion_line(), Slic3r::scaled(), Slic3r::unscale(), and Slic3r::wipe_tower_routing_clearance.

Referenced by append_tcr(), and travel_to_tower_gap().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_empty_wipe_tower_gcode()

bool Slic3r::WipeTowerIntegration::is_empty_wipe_tower_gcode ( GCode & gcodegen,
int extruder_id,
bool finish_layer )
2115 {
2116 assert(m_layer_idx >= 0);
2117 if (m_layer_idx >= (int) m_tool_changes.size())
2118 return true;
2119
2123
2125 return false;
2126 }
2127
2128 if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
2129 if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
2130 throw Slic3r::RuntimeError(_(L("Wipe tower generation failed, possibly due to empty first layer.")));
2131
2132 if (!ignore_sparse) {
2133 return false;
2134 }
2135 }
2136
2137 return true;
2138 }
#define _(s)
Definition 3mf.cpp:306
#define L(s)
Definition ExtrusionEntity.cpp:25
bool wipe_tower_layer_is_sparse(const std::vector< WipeTower::ToolChangeResult > &layer_tool_changes)
Definition WipeTower.cpp:59
bool wipe_tower_layer_is_combined_away(const std::vector< WipeTower::ToolChangeResult > &layer_tool_changes)
Definition WipeTower.cpp:83

References _, L, m_enable_timelapse_print, m_enable_wrapping_detection, m_is_first_print, m_layer_idx, m_sparse_layers_skipped, m_tool_change_idx, m_tool_changes, Slic3r::GCodeWriter::need_toolchange(), Slic3r::wipe_tower_layer_is_combined_away(), Slic3r::wipe_tower_layer_is_sparse(), and Slic3r::GCode::writer().

Here is the call graph for this function:

◆ is_first_print()

bool Slic3r::WipeTowerIntegration::is_first_print ( ) const
inline
151{ return m_is_first_print;}

References m_is_first_print.

◆ next_layer()

void Slic3r::WipeTowerIntegration::next_layer ( )
inline

References m_layer_idx, and m_tool_change_idx.

◆ operator=()

WipeTowerIntegration & Slic3r::WipeTowerIntegration::operator= ( const WipeTowerIntegration & )
private

References WipeTowerIntegration(), and Slic3r::angle().

Here is the call graph for this function:

◆ post_process_wipe_tower_moves()

std::string Slic3r::WipeTowerIntegration::post_process_wipe_tower_moves ( const WipeTower::ToolChangeResult & tcr,
const Vec2f & translation,
float angle ) const
private
1958 {
1961 extruder_offset = m_extruder_offsets[0].cast<float>();
1962 else
1963 extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
1964
1965 std::istringstream gcode_str(tcr.gcode);
1966 std::string gcode_out;
1967 std::string line;
1968 Vec2f pos = tcr.start_pos;
1969 auto trans_pos = [wt_rot = Eigen::Rotation2Df(angle), &translation](const Vec2f& p) -> Vec2f { return wt_rot * p + translation; };
1970 Vec2f transformed_pos = trans_pos(pos);
1971 Vec2f old_pos(-1000.1f, -1000.1f);
1972
1973 while (gcode_str) {
1974 std::getline(gcode_str, line); // we read the gcode line by line
1975
1976 // All G1 commands should be translated and rotated. X and Y coords are
1977 // only pushed to the output when they differ from last time.
1978 // WT generator can override this by appending the never_skip_tag
1979 if (line.find("G1 ") == 0 || line.find("G2 ") == 0 || line.find("G3 ") == 0) {
1980 std::string cur_gcode_start = line.find("G1 ") == 0 ? "G1 " : (line.find("G2 ") == 0 ? "G2 " : "G3 ");
1981 bool never_skip = false;
1982 auto it = line.find(WipeTower::never_skip_tag());
1983 if (it != std::string::npos) {
1984 // remove the tag and remember we saw it
1985 never_skip = true;
1986 line.erase(it, it + WipeTower::never_skip_tag().size());
1987 }
1988 std::ostringstream line_out;
1989 std::istringstream line_str(line);
1990 line_str >> std::noskipws; // don't skip whitespace
1991 char ch = 0;
1992 while (line_str >> ch) {
1993 if (ch == 'X' || ch == 'Y')
1994 line_str >> (ch == 'X' ? pos.x() : pos.y());
1995 else
1996 line_out << ch;
1997 }
1998 // Strip original wipe tower X/Y even if position unchanged (fixes out of bed moves).
1999 line = line_out.str();
2000
2001 transformed_pos = trans_pos(pos);
2002
2003 if (transformed_pos != old_pos || never_skip) {
2004 std::ostringstream oss;
2005 oss << std::fixed << std::setprecision(3) << cur_gcode_start;
2006 if (transformed_pos.x() != old_pos.x() || never_skip)
2007 oss << " X" << transformed_pos.x() - extruder_offset.x();
2008 if (transformed_pos.y() != old_pos.y() || never_skip)
2009 oss << " Y" << transformed_pos.y() - extruder_offset.y();
2010 oss << " ";
2011 line.replace(line.find(cur_gcode_start), 3, oss.str());
2012 old_pos = transformed_pos;
2013 }
2014 }
2015
2016 gcode_out += line + "\n";
2017
2018 // If this was a toolchange command, we should change current extruder offset
2019 if (line == "[change_filament_gcode]") {
2020 // BBS
2022 extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
2023
2024 // If the extruder offset changed, add an extra move so everything is continuous
2025 if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
2026 std::ostringstream oss;
2027 oss << std::fixed << std::setprecision(3)
2028 << "G1 X" << transformed_pos.x() - extruder_offset.x()
2029 << " Y" << transformed_pos.y() - extruder_offset.y()
2030 << "\n";
2031 gcode_out += oss.str();
2032 }
2033 }
2034 old_pos = Vec2f{-1000.1f, -1000.1f};
2035 pos = tcr.tool_change_start_pos;
2036 transformed_pos = trans_pos(pos);
2037 }
2038 }
2039 return gcode_out;
2040 }
static const std::string never_skip_tag()
Definition WipeTower.hpp:46
zaa_minimize_perimeter_height extruder_offset((ConfigOptionBools, reduce_fan_stop_start_freq))((ConfigOptionBools
double angle(const Eigen::MatrixBase< Derived > &v1, const Eigen::MatrixBase< Derived2 > &v2)
Definition Point.hpp:138

References Slic3r::angle(), Slic3r::extruder_offset(), Slic3r::WipeTower::ToolChangeResult::gcode, Slic3r::WipeTower::ToolChangeResult::initial_tool, m_extruder_offsets, m_single_extruder_multi_material, Slic3r::WipeTower::never_skip_tag(), Slic3r::WipeTower::ToolChangeResult::new_tool, Slic3r::WipeTower::ToolChangeResult::start_pos, and Slic3r::WipeTower::ToolChangeResult::tool_change_start_pos.

Referenced by append_tcr(), and append_tcr2().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ prime()

std::string Slic3r::WipeTowerIntegration::prime ( GCode & gcodegen)
2043 {
2044 std::string gcode;
2045 if (gcodegen.wipe_tower_type() == WipeTowerType::Type2) {
2046 for (const WipeTower::ToolChangeResult &tcr : m_priming) {
2047 if (!tcr.extrusions.empty())
2048 gcode += append_tcr2(gcodegen, tcr, tcr.new_tool);
2049 }
2050 }
2051 return gcode;
2052 }

References append_tcr2(), m_priming, Slic3r::Type2, and Slic3r::GCode::wipe_tower_type().

Here is the call graph for this function:

◆ set_is_first_print()

void Slic3r::WipeTowerIntegration::set_is_first_print ( bool is)
inline
152{ m_is_first_print = is; }

References m_is_first_print.

◆ set_rib_offset()

void Slic3r::WipeTowerIntegration::set_rib_offset ( const Vec2f & rib_offset)
inline
157{ m_rib_offset = rib_offset; }

References m_rib_offset.

◆ set_wipe_tower_bbx()

void Slic3r::WipeTowerIntegration::set_wipe_tower_bbx ( const BoundingBoxf & bbx)
inline
156{ m_wipe_tower_bbx = bbx; }

References m_wipe_tower_bbx.

◆ set_wipe_tower_depth()

void Slic3r::WipeTowerIntegration::set_wipe_tower_depth ( float depth)
inline
155{ m_wipe_tower_depth = depth; }
float m_wipe_tower_depth
Definition GCode.hpp:194

References m_wipe_tower_depth.

◆ shared_printable_area()

Polygons Slic3r::WipeTowerIntegration::shared_printable_area ( GCode & gcodegen) const
private
1013 {
1014 // The frame change is a pure translation, so transform the origin once.
1016 Polygons bed_polygons = gcodegen.m_print->get_extruder_shared_printable_polygon();
1017 for (Polygon &poly : bed_polygons)
1018 poly.translate(offset);
1019 return bed_polygons;
1020 }
void poly(std::vector< std::pair< Vec2d, Vec2d > > &out, std::initializer_list< Vec2d > p)
Definition DesignSketchTool.cpp:7751
std::vector< Polygon, PointsAllocator< Polygon > > Polygons
Definition Polygon.hpp:22
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:582

References Slic3r::Print::get_extruder_shared_printable_polygon(), m_plate_origin, Slic3r::GCode::m_print, Slic3r::offset(), and Slic3r::wipe_tower_point_to_object_point().

Referenced by append_tcr(), and travel_to_tower_gap().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ tool_change()

std::string Slic3r::WipeTowerIntegration::tool_change ( GCode & gcodegen,
int extruder_id,
bool finish_layer )
2055 {
2056 std::string gcode;
2057
2058 assert(m_layer_idx >= 0);
2059 if (m_layer_idx >= (int) m_tool_changes.size())
2060 return gcode;
2061 if (gcodegen.wipe_tower_type() == WipeTowerType::Type2) {
2062 if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
2063 if (m_layer_idx < (int) m_tool_changes.size()) {
2064 if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
2065 throw Slic3r::RuntimeError(_(L("Wipe tower generation failed, possibly due to empty first layer.")));
2066
2067 // Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
2068 // resulting in a wipe tower with sparse layers.
2069 double wipe_tower_z = -1;
2070 // Folded into a later, thicker layer that prints at its own z: nothing to emit.
2073 wipe_tower_z = m_last_wipe_tower_print_z;
2075 if (m_tool_change_idx == 0 && !ignore_sparse)
2076 wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
2077 }
2078
2079 if (!ignore_sparse) {
2080 gcode += append_tcr2(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
2081 m_last_wipe_tower_print_z = wipe_tower_z;
2082 }
2083 }
2084 }
2085 } else {
2086 // Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
2087 // resulting in a wipe tower with sparse layers.
2088 double wipe_tower_z = -1;
2089 // Folded into a later, thicker layer that prints at its own z: nothing to emit.
2093 wipe_tower_z = m_compacted_tower_z[m_layer_idx];
2094 }
2095
2097 gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][0], m_tool_changes[m_layer_idx][0].new_tool, wipe_tower_z);
2099 m_is_first_print = false;
2100 }
2101
2102 if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
2103 if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
2104 throw Slic3r::RuntimeError(_(L("Wipe tower generation failed, possibly due to empty first layer.")));
2105
2106 if (!ignore_sparse)
2107 gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
2108 }
2109 }
2110
2111 return gcode;
2112 }
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z=-1.) const
Definition GCode.cpp:1102

References _, append_tcr(), append_tcr2(), L, m_compacted_tower_z, m_enable_timelapse_print, m_enable_wrapping_detection, m_is_first_print, m_last_wipe_tower_print_z, m_layer_idx, m_sparse_layers_skipped, m_tool_change_idx, m_tool_changes, Slic3r::GCodeWriter::need_toolchange(), Slic3r::Type2, Slic3r::wipe_tower_layer_is_combined_away(), Slic3r::wipe_tower_layer_is_sparse(), Slic3r::GCode::wipe_tower_type(), and Slic3r::GCode::writer().

Here is the call graph for this function:

◆ tower_height_tag()

std::string Slic3r::WipeTowerIntegration::tower_height_tag ( GCode & gcodegen,
const WipeTower::ToolChangeResult & tcr,
const std::string & tcr_gcode ) const
private
1081 {
1082 const std::string tag = ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height);
1083 if (! m_sparse_layers_combined || std::abs(gcodegen.m_last_height - tcr.layer_height) <= EPSILON ||
1084 tcr_gcode.find(tag) != std::string::npos)
1085 return {};
1086 // Keep m_last_height what the G-code last declared, so a second visit does not repeat it.
1087 gcodegen.m_last_height = tcr.layer_height;
1088 char buf[64];
1089 sprintf(buf, "%s%g\n", tag.c_str(), tcr.layer_height);
1090 return buf;
1091 }
static const std::string & reserved_tag(ETags tag)
Definition GCodeProcessor.hpp:636
@ Height
Definition GCodeProcessor.hpp:607

References EPSILON, Slic3r::GCodeProcessor::Height, Slic3r::WipeTower::ToolChangeResult::layer_height, Slic3r::GCode::m_last_height, m_sparse_layers_combined, and Slic3r::GCodeProcessor::reserved_tag().

Referenced by append_tcr(), and append_tcr2().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ transform_wt2_pt()

Vec2f Slic3r::WipeTowerIntegration::transform_wt2_pt ( const Vec2f & pt) const
private
1004 {
1005 const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
1006 return Eigen::Rotation2Df(alpha) * (pt + m_rib_offset) + m_wipe_tower_pos;
1007 }

References M_PI, m_rib_offset, m_wipe_tower_pos, and m_wipe_tower_rotation.

Referenced by append_tcr2(), and travel_to_tower_gap().

Here is the caller graph for this function:

◆ travel_to_tower_gap()

std::string Slic3r::WipeTowerIntegration::travel_to_tower_gap ( GCode & gcodegen,
const Point & route_start,
const Point & start_wipe_pos ) const
private
1030 {
1031 if (!WipeTower2::use_gap_wall(gcodegen.m_config))
1032 return {};
1033 const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
1034 // Transform tower-local corners exactly like the tcr points; a rotated tower gets a
1035 // conservative axis-aligned envelope from the result.
1036 auto tower_polygon = [&](const BoundingBoxf &bbx) {
1037 Polygon poly = scaled(bbx).polygon();
1038 for (Point &p : poly.points)
1039 p = wipe_tower_point_to_object_point(gcodegen, transform_wt2_pt(unscale(p).cast<float>()) + plate_origin_2d);
1040 return poly;
1041 };
1042 // The avoid envelope covers the first-layer brim (and rib flare), which a travel may
1043 // cross freely: early-out only when the approach already starts over the tower body
1044 // itself, so a start between the wall and the brim edge still gets routed in through
1045 // the wall opening. Test the rotated polygon, not its bounding box — at angles off the
1046 // axes the box's corner triangles cover most of the brim ring.
1047 const float body_width = gcodegen.m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwRib ? m_wipe_tower_depth : m_right;
1048 if (tower_polygon(BoundingBoxf(Vec2d(0., 0.), Vec2d(body_width, m_wipe_tower_depth))).contains(route_start))
1049 return {};
1050
1051 const Polygons bed = shared_printable_area(gcodegen);
1052 BoundingBox avoid_bbx = get_extents(tower_polygon(m_wipe_tower_bbx));
1053 // The inflated corners must stay on the bed for the router to generate a route at all:
1054 // clamp the box against the bed shrunk by the clearance the router adds, so a tower
1055 // parked near the bed edge is still routed along the clamped side instead of always
1056 // travelling straight across the tower.
1057 BoundingBox clamp_bbx = get_extents(bed);
1058 clamp_bbx.offset(-(scaled(wipe_tower_routing_clearance) + SCALED_EPSILON));
1059 avoid_bbx.min = avoid_bbx.min.cwiseMax(clamp_bbx.min);
1060 avoid_bbx.max = avoid_bbx.max.cwiseMin(clamp_bbx.max);
1061 if (avoid_bbx.min.x() >= avoid_bbx.max.x() || avoid_bbx.min.y() >= avoid_bbx.max.y())
1062 return {};
1063
1064 Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, bed);
1065 std::string gcode;
1066 // The polyline's last point is start_wipe_pos itself — emitted by the caller.
1067 for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
1068 gcode += gcodegen.travel_to(travel_polyline.points[i], erMixed, "Travel to a Wipe Tower");
1069 return gcode;
1070 }
void polygon(Polygon *polygon) const
Definition BoundingBox.cpp:20
#define SCALED_EPSILON
Definition libslic3r.h:109
bool contains(const std::vector< T > &vector, const Point &point)
Definition Geometry.cpp:55
@ wtwRib
Definition PrintConfig.hpp:608

References Slic3r::cast(), Slic3r::Geometry::contains(), Slic3r::erMixed, generate_path_to_wipe_tower(), Slic3r::get_extents(), Slic3r::GCode::m_config, m_plate_origin, m_right, m_wipe_tower_bbx, m_wipe_tower_depth, Slic3r::BoundingBoxBase< PointType, APointsType >::max, Slic3r::BoundingBoxBase< PointType, APointsType >::min, Slic3r::BoundingBoxBase< PointType, APointsType >::offset(), Slic3r::MultiPoint::points, Slic3r::BoundingBox::polygon(), Slic3r::scaled(), SCALED_EPSILON, shared_printable_area(), transform_wt2_pt(), Slic3r::GCode::travel_to(), Slic3r::unscale(), Slic3r::WipeTower2::use_gap_wall(), Slic3r::wipe_tower_point_to_object_point(), Slic3r::wipe_tower_routing_clearance, and Slic3r::wtwRib.

Referenced by append_tcr2().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ used_filament_length()

std::vector< float > Slic3r::WipeTowerIntegration::used_filament_length ( ) const

Member Data Documentation

◆ m_compacted_tower_z

std::vector<float> Slic3r::WipeTowerIntegration::m_compacted_tower_z
private

◆ m_enable_timelapse_print

bool Slic3r::WipeTowerIntegration::m_enable_timelapse_print
private

◆ m_enable_wrapping_detection

bool Slic3r::WipeTowerIntegration::m_enable_wrapping_detection
private

◆ m_extruder_offsets

std::vector<Vec2d> Slic3r::WipeTowerIntegration::m_extruder_offsets
private

◆ m_final_purge

const WipeTower::ToolChangeResult& Slic3r::WipeTowerIntegration::m_final_purge
private

Referenced by WipeTowerIntegration(), and finalize().

◆ m_is_first_print

bool Slic3r::WipeTowerIntegration::m_is_first_print
private

◆ m_last_wipe_tower_print_z

double Slic3r::WipeTowerIntegration::m_last_wipe_tower_print_z
private

◆ m_layer_idx

int Slic3r::WipeTowerIntegration::m_layer_idx
private

◆ m_left

const float Slic3r::WipeTowerIntegration::m_left
private

Referenced by WipeTowerIntegration().

◆ m_plate_origin

Vec3d Slic3r::WipeTowerIntegration::m_plate_origin
private

◆ m_priming

const std::vector<WipeTower::ToolChangeResult>& Slic3r::WipeTowerIntegration::m_priming
private

Referenced by WipeTowerIntegration(), and prime().

◆ m_print_config

const PrintConfig* Slic3r::WipeTowerIntegration::m_print_config
private

Referenced by WipeTowerIntegration(), and append_tcr().

◆ m_rib_offset

Vec2f Slic3r::WipeTowerIntegration::m_rib_offset {Vec2f(0, 0)}
private

◆ m_right

const float Slic3r::WipeTowerIntegration::m_right
private

◆ m_single_extruder_multi_material

bool Slic3r::WipeTowerIntegration::m_single_extruder_multi_material
private

◆ m_sparse_layers_combined

const bool Slic3r::WipeTowerIntegration::m_sparse_layers_combined
private

◆ m_sparse_layers_skipped

const bool Slic3r::WipeTowerIntegration::m_sparse_layers_skipped
private

◆ m_tool_change_idx

int Slic3r::WipeTowerIntegration::m_tool_change_idx
private

◆ m_tool_changes

const std::vector<std::vector<WipeTower::ToolChangeResult> >& Slic3r::WipeTowerIntegration::m_tool_changes
private

◆ m_wipe_tower_bbx

BoundingBoxf Slic3r::WipeTowerIntegration::m_wipe_tower_bbx
private

◆ m_wipe_tower_depth

float Slic3r::WipeTowerIntegration::m_wipe_tower_depth
private

◆ m_wipe_tower_pos

const Vec2f Slic3r::WipeTowerIntegration::m_wipe_tower_pos
private

◆ m_wipe_tower_rotation

const float Slic3r::WipeTowerIntegration::m_wipe_tower_rotation
private

The documentation for this class was generated from the following files:
  • /home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/GCode.hpp
  • /home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/GCode.cpp