mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-29 03:53:25 +03:00
Compare commits
9 Commits
main
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bef47b2c70 | ||
|
|
5b475e5e98 | ||
|
|
56810c8c7f | ||
|
|
c3c37e474a | ||
|
|
1696d5ca39 | ||
|
|
466c36eaa3 | ||
|
|
5792fef805 | ||
|
|
bc016af1c9 | ||
|
|
7a378d2fc4 |
@@ -2,6 +2,7 @@
|
||||
#define slic3r_Config_hpp_
|
||||
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <climits>
|
||||
#include <cfloat>
|
||||
@@ -780,10 +781,14 @@ public:
|
||||
this->values[i] = rhs_vec->values[i];
|
||||
modified = true;
|
||||
} else {
|
||||
if ((i < default_index.size()) && (default_index[i] < default_value.size()))
|
||||
// Orca: a negative slot (failed variant lookup) must not silently collapse the
|
||||
// whole array to the first slot's value — the int-vs-size_t comparison used to
|
||||
// promote -1 past the bounds check. Keep the slot's own value (get_at-style
|
||||
// clamp) when no valid index is available.
|
||||
if ((i < default_index.size()) && (default_index[i] >= 0) && (size_t(default_index[i]) < default_value.size()))
|
||||
this->values[i] = default_value[default_index[i]];
|
||||
else
|
||||
this->values[i] = default_value[0];
|
||||
this->values[i] = default_value[std::min(i, default_value.size() - 1)];
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
@@ -2106,6 +2111,11 @@ public:
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||
this->value = rhs->getInt();
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() can emit names.
|
||||
if (this->keys_map == nullptr)
|
||||
if (auto rhs_generic = dynamic_cast<const ConfigOptionEnumGeneric *>(rhs))
|
||||
this->keys_map = rhs_generic->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
@@ -2162,7 +2172,12 @@ public:
|
||||
if (rhs->type() != this->type())
|
||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
||||
this->values = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs)->values;
|
||||
auto rhs_enums = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs);
|
||||
this->values = rhs_enums->values;
|
||||
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||
// adopt the source's so a later serialize() emits names instead of empty tokens.
|
||||
if (this->keys_map == nullptr)
|
||||
this->keys_map = rhs_enums->keys_map;
|
||||
}
|
||||
|
||||
std::string serialize() const override
|
||||
|
||||
@@ -888,6 +888,50 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
return res;
|
||||
}
|
||||
|
||||
// With skip points enabled the Type2 tower wall has an opening at each toolchange's
|
||||
// entry (tcr.start_pos): route the approach around the tower's bounding box so the
|
||||
// nozzle enters through that opening instead of dragging across the printed wall
|
||||
// (append_tcr parity). Emits only the waypoints leading up to the opening — the
|
||||
// caller still travels to start_wipe_pos itself. Returns an empty string when the
|
||||
// option is off, the cone wall is active (it has no gap machinery), or the approach
|
||||
// already starts inside the tower: such hops never cross the wall and must stay direct.
|
||||
std::string WipeTowerIntegration::travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const
|
||||
{
|
||||
if (!gcodegen.m_config.prime_tower_skip_points.value
|
||||
|| gcodegen.m_config.wipe_tower_wall_type.value == WipeTowerWallType::wtwCone)
|
||||
return {};
|
||||
const Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
BoundingBox printer_bbx;
|
||||
if (is_multi_nozzle_printer(gcodegen.m_config)) {
|
||||
printer_bbx = get_extents(gcodegen.m_print->get_extruder_shared_printable_polygon());
|
||||
printer_bbx.min = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.min) + plate_origin_2d);
|
||||
printer_bbx.max = wipe_tower_point_to_object_point(gcodegen, unscaled<float>(printer_bbx.max) + plate_origin_2d);
|
||||
} else {
|
||||
Points bed_points;
|
||||
for (const auto& p : gcodegen.m_config.printable_area.values)
|
||||
bed_points.push_back(wipe_tower_point_to_object_point(gcodegen, p.cast<float>() + plate_origin_2d));
|
||||
printer_bbx = BoundingBox(bed_points);
|
||||
}
|
||||
// Transform the tower-local bbx corners exactly like the tcr points (rib
|
||||
// offset, rotation, tower position); a rotated tower gets a conservative
|
||||
// axis-aligned envelope.
|
||||
const float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
Polygon avoid_points = scaled(m_wipe_tower_bbx).polygon();
|
||||
for (auto& p : avoid_points.points) {
|
||||
Vec2f pp = Eigen::Rotation2Df(alpha) * (unscale(p).cast<float>() + m_rib_offset) + m_wipe_tower_pos;
|
||||
p = wipe_tower_point_to_object_point(gcodegen, pp + plate_origin_2d);
|
||||
}
|
||||
BoundingBox avoid_bbx(avoid_points.points);
|
||||
if (avoid_bbx.contains(route_start))
|
||||
return {};
|
||||
Polyline travel_polyline = generate_path_to_wipe_tower(route_start, start_wipe_pos, avoid_bbx, printer_bbx);
|
||||
std::string gcode;
|
||||
// The polyline's last point is start_wipe_pos itself — emitted by the caller.
|
||||
for (size_t i = 0; i + 1 < travel_polyline.points.size(); ++i)
|
||||
gcode += gcodegen.travel_to(travel_polyline.points[i], erMixed, "Travel to a Wipe Tower");
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_filament_id, double z) const
|
||||
{
|
||||
if (new_filament_id != -1 && new_filament_id != tcr.new_tool)
|
||||
@@ -998,6 +1042,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
std::string change_filament_gcode = gcodegen.config().change_filament_gcode.value;
|
||||
|
||||
bool is_used_travel_avoid_perimeter = gcodegen.m_config.prime_tower_skip_points.value;
|
||||
if (is_nozzle_change && !tcr.nozzle_change_result.is_extruder_change) is_used_travel_avoid_perimeter = false;
|
||||
|
||||
// add nozzle change gcode into change filament gcode
|
||||
std::string nozzle_change_gcode_trans;
|
||||
@@ -1307,20 +1352,23 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
}
|
||||
|
||||
// do unretract after setting current extruder_id
|
||||
// PETG filaments on a device with a filament switcher get a small (2 mm) pre-extrusion
|
||||
// before the tool change. has_filament_switcher is a develop-only key read defensively from the
|
||||
// full config (Orca does not carry it as a static PrintConfig member — same convention as
|
||||
// enable_filament_dynamic_map); no shipping profile sets it (grep resources/profiles = 0), so
|
||||
// is_petg_pre_extrusion is always false -> extra_unretract stays 0 -> byte-identical to the plain
|
||||
// unretract() fleet-wide. The tower-interface contact pre-extrusion length (the
|
||||
// is_contact_pre_extrusion branch) is NOT applied here; it is only computed as the guard used to
|
||||
// give the contact path priority over PETG.
|
||||
// BBS pattern: the wipe tower shifts the toolchange start position outward for the
|
||||
// tower-interface (contact) pre-extrusion and for the PETG-with-filament-switcher case;
|
||||
// the pre-extrusion material itself is laid down here as extra unretract on the approach.
|
||||
// has_filament_switcher is a develop-only key read defensively from the full config (Orca
|
||||
// does not carry it as a static PrintConfig member — same convention as
|
||||
// enable_filament_dynamic_map); no shipping profile sets it, so is_petg_pre_extrusion is
|
||||
// always false fleet-wide.
|
||||
const ConfigOptionBool* has_filament_switcher_opt = gcodegen.m_print->full_print_config().option<ConfigOptionBool>("has_filament_switcher");
|
||||
bool is_contact_pre_extrusion = tcr.is_contact && gcodegen.m_config.enable_tower_interface_features;
|
||||
bool is_petg_pre_extrusion = !is_contact_pre_extrusion
|
||||
&& gcodegen.config().filament_type.get_at(tcr.new_tool) == "PETG"
|
||||
&& has_filament_switcher_opt && has_filament_switcher_opt->value;
|
||||
float extra_unretract = is_petg_pre_extrusion ? 2.f : 0.f;
|
||||
float extra_unretract = 0.f;
|
||||
if (is_contact_pre_extrusion)
|
||||
extra_unretract = gcodegen.m_config.filament_tower_interface_pre_extrusion_length.get_at(tcr.new_tool);
|
||||
else if (is_petg_pre_extrusion)
|
||||
extra_unretract = 2.f;
|
||||
std::string toolchange_unretract_str = (extra_unretract > 0.f) ? gcodegen.unretract(extra_unretract) : gcodegen.unretract();
|
||||
check_add_eol(toolchange_unretract_str);
|
||||
|
||||
@@ -1418,8 +1466,10 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
|
||||
// The rib-wall offset is tower-local, so it rotates with the tower (unlike the BBL
|
||||
// tower in append_tcr, which never rotates). Priming lines are absolute bed moves.
|
||||
auto transform_wt_pt = [&alpha, this](const Vec2f &pt) -> Vec2f {
|
||||
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
|
||||
Vec2f out = Eigen::Rotation2Df(alpha) * (pt + m_rib_offset);
|
||||
out += m_wipe_tower_pos;
|
||||
return out;
|
||||
};
|
||||
@@ -1431,7 +1481,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
end_pos = transform_wt_pt(end_pos);
|
||||
}
|
||||
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : Vec2f(m_wipe_tower_pos + Eigen::Rotation2Df(alpha) * m_rib_offset);
|
||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||
Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1));
|
||||
|
||||
@@ -1461,16 +1511,22 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
|| is_ramming
|
||||
|| tool_change_on_wipe_tower);
|
||||
|
||||
if (should_travel_to_tower || gcodegen.m_need_change_layer_lift_z) {
|
||||
const bool travel_to_tower_now = should_travel_to_tower || gcodegen.m_need_change_layer_lift_z;
|
||||
if (travel_to_tower_now) {
|
||||
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
|
||||
// then we could simplify the condition and make it more readable.
|
||||
gcode += gcodegen.retract();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, "Travel to a Wipe Tower");
|
||||
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
|
||||
if (!tcr.priming && gcodegen.last_pos_defined())
|
||||
gcode += travel_to_tower_gap(gcodegen, gcodegen.last_pos(), start_wipe_pos);
|
||||
gcode += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
// the tool without travelling to the tower.
|
||||
// the tool without travelling to the tower. The tower entry travel then lives
|
||||
// inside the tcr gcode; with skip points on it is rerouted below, once the
|
||||
// toolchange gcode (and the head position it ends at) is known.
|
||||
}
|
||||
|
||||
if (will_go_down) {
|
||||
@@ -1493,6 +1549,39 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
toolchange_temp_override = interface_temp;
|
||||
}
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z
|
||||
if (!travel_to_tower_now && !tcr.priming && needs_toolchange
|
||||
&& gcodegen.m_config.prime_tower_skip_points.value
|
||||
&& gcodegen.m_config.wipe_tower_wall_type.value != WipeTowerWallType::wtwCone) {
|
||||
// The tool changed in place (multi-tool printer without ramming), so the
|
||||
// tower entry is the tcr's own positioning move — a straight line across
|
||||
// the printed wall. Route it around the tower and in through the wall
|
||||
// opening instead, riding at the end of the change_filament_gcode
|
||||
// substitution so the generator's positioning move degrades to a
|
||||
// zero-length one (append_tcr parity: travel after the filament change,
|
||||
// retracted, with the new filament).
|
||||
Vec3f last_gcode_pos = gcodegen.writer().get_position().cast<float>();
|
||||
Point route_start;
|
||||
bool have_start = false;
|
||||
if (GCodeProcessor::get_last_position_from_gcode(toolchange_gcode_str, last_gcode_pos)) {
|
||||
// A custom change_filament_gcode may have moved the head (tool docks
|
||||
// etc.); recover the real position from the emitted gcode.
|
||||
route_start = gcodegen.gcode_to_point(Vec2d(last_gcode_pos.x(), last_gcode_pos.y()) + plate_origin_2d.cast<double>());
|
||||
have_start = true;
|
||||
} else if (gcodegen.last_pos_defined()) {
|
||||
route_start = gcodegen.last_pos();
|
||||
have_start = true;
|
||||
}
|
||||
if (have_start) {
|
||||
gcodegen.set_last_pos(route_start);
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
const Point start_wipe_pos = wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d);
|
||||
std::string travel = travel_to_tower_gap(gcodegen, route_start, start_wipe_pos);
|
||||
travel += gcodegen.travel_to(start_wipe_pos, erMixed, "Travel to a Wipe Tower");
|
||||
check_add_eol(travel);
|
||||
toolchange_gcode_str += travel;
|
||||
gcodegen.set_last_pos(start_wipe_pos);
|
||||
}
|
||||
}
|
||||
if (gcodegen.config().enable_prime_tower) {
|
||||
deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true);
|
||||
Vec3d position{gcodegen.writer().get_position()};
|
||||
|
||||
@@ -132,6 +132,7 @@ private:
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
Polyline generate_path_to_wipe_tower(const Point &start_pos, const Point &end_pos, const BoundingBox &avoid_polygon, const BoundingBox &printer_bbx) const;
|
||||
std::string append_tcr2(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
std::string travel_to_tower_gap(GCode &gcodegen, const Point &route_start, const Point &start_wipe_pos) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
|
||||
@@ -1450,8 +1450,8 @@ void GCodeProcessor::run_post_process()
|
||||
// flag) runs none of this. It is pure data construction — it only fills m_filament_blocks /
|
||||
// m_extruder_blocks / m_machine_*_gcode_*_line_id and never touches the exported g-code, so even
|
||||
// the enable_pre_heating fleet stays byte-identical (nothing reads the blocks until the injection
|
||||
// pass). In practice it also stays empty/degenerate today because no template/code yet emits the
|
||||
// MACHINE_*_GCODE_* / NOZZLE_CHANGE_* / CP_TOOLCHANGE_WIPE markers it keys off.
|
||||
// pass). The wipe tower emits the NOZZLE_CHANGE_* (ramming) and CP_TOOLCHANGE_WIPE markers this
|
||||
// builder keys off; the MACHINE_*_GCODE_* markers come from the machine g-code templates.
|
||||
m_filament_blocks.clear();
|
||||
m_extruder_blocks.clear();
|
||||
m_machine_start_gcode_end_line_id = (unsigned int) (-1);
|
||||
|
||||
@@ -143,7 +143,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
|
||||
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
||||
Transform2d trafo =
|
||||
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value)) *
|
||||
Eigen::Translation2d(print.wipe_tower_data().rib_offset.cast<double>()); // tower-local rib-wall shift, zero unless rib
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
#include "libslic3r/MultiNozzleUtils.hpp"
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
@@ -84,7 +84,6 @@ public:
|
||||
bool priming;
|
||||
|
||||
bool is_tool_change{false};
|
||||
bool is_contact{false};
|
||||
Vec2f tool_change_start_pos;
|
||||
|
||||
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
||||
@@ -108,6 +107,7 @@ public:
|
||||
// executing the gcode finish_layer_tcr.
|
||||
bool is_finish_first = false;
|
||||
|
||||
bool is_contact = false;
|
||||
NozzleChangeResult nozzle_change_result;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
@@ -122,6 +122,8 @@ public:
|
||||
}
|
||||
return e_length;
|
||||
}
|
||||
// Orca: set by WipeTower2 (non-BBL tower) to force a travel to the tower even when the
|
||||
// previous position is unknown; read by WipeTowerIntegration::append_tcr2 (GCode.cpp).
|
||||
bool force_travel = false;
|
||||
};
|
||||
|
||||
@@ -162,15 +164,12 @@ public:
|
||||
bool priming,
|
||||
size_t old_tool,
|
||||
bool is_finish,
|
||||
bool is_tool_change,
|
||||
float purge_volume,
|
||||
bool is_contact = false) const;
|
||||
bool is_tool_change, float purge_volume, bool is_contact) const;
|
||||
|
||||
ToolChangeResult construct_block_tcr(WipeTowerWriter& writer,
|
||||
bool priming,
|
||||
size_t filament_id,
|
||||
bool is_finish,
|
||||
float purge_volume) const;
|
||||
bool is_finish, float purge_volume) const;
|
||||
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
@@ -184,9 +183,14 @@ public:
|
||||
// Set the extruder properties.
|
||||
void set_extruder(size_t idx, const PrintConfig& config);
|
||||
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member here, so it is pushed in from
|
||||
// Print via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// Appends into internal structure m_plan containing info about the future wipe tower
|
||||
// to be used before building begins. The entries must be added ordered in z.
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume_ec = 0.f, float wipe_volume_nc = 0.f, float prime_volume = 0.f);
|
||||
|
||||
|
||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
@@ -219,9 +223,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void set_wipe_volume(std::vector<std::vector<float>>& wiping_matrix) {
|
||||
wipe_volumes = wiping_matrix;
|
||||
}
|
||||
|
||||
// Switch to a next layer.
|
||||
void set_layer(
|
||||
@@ -250,7 +251,6 @@ public:
|
||||
|
||||
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
||||
m_extrusion_flow = extrusion_flow(layer_height);
|
||||
|
||||
// Advance m_layer_info iterator, making sure we got it right
|
||||
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
||||
++m_layer_info;
|
||||
@@ -309,20 +309,9 @@ public:
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
void set_filament_map(const std::vector<int> &filament_map) { m_filament_map = filament_map; }
|
||||
// Vortek H2C: filament_id → physical nozzle_id for carousel rotation detection
|
||||
void set_filament_nozzle_map(const std::vector<int> &nozzle_map) { m_filament_nozzle_map = nozzle_map; }
|
||||
|
||||
void set_has_tpu_filament(bool has_tpu) { m_has_tpu_filament = has_tpu; }
|
||||
|
||||
bool has_tpu_filament() const { return m_has_tpu_filament; }
|
||||
|
||||
// Orca: has_filament_switcher is not a static PrintConfig member, so it is pushed in from Print
|
||||
// via a setter rather than read in the ctor. Device-set only.
|
||||
void set_has_filament_switcher(bool v) { m_has_filament_switcher = v; }
|
||||
// The region every extruder can reach, used to clamp the PETG pre-extrusion offset to the
|
||||
// printable bed.
|
||||
void set_shared_print_bed(const Polygons &bed) { m_shared_print_bed = bed; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
int category;
|
||||
@@ -331,15 +320,15 @@ public:
|
||||
bool is_support = false;
|
||||
int nozzle_temperature = 0;
|
||||
int nozzle_temperature_initial_layer = 0;
|
||||
int interface_print_temperature = 0;
|
||||
float loading_speed = 0.f;
|
||||
float loading_speed_start = 0.f;
|
||||
float unloading_speed = 0.f;
|
||||
float unloading_speed_start = 0.f;
|
||||
float delay = 0.f ;
|
||||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float loading_speed = 0.f;
|
||||
//float loading_speed_start = 0.f;
|
||||
//float unloading_speed = 0.f;
|
||||
//float unloading_speed_start = 0.f;
|
||||
//float delay = 0.f ;
|
||||
//int cooling_moves = 0;
|
||||
//float cooling_initial_speed = 0.f;
|
||||
//float cooling_final_speed = 0.f;
|
||||
float ramming_line_width_multiplicator = 1.f;
|
||||
float ramming_step_multiplicator = 1.f;
|
||||
float max_e_speed = std::numeric_limits<float>::max();
|
||||
@@ -349,41 +338,41 @@ public:
|
||||
float retract_length;
|
||||
float retract_speed;
|
||||
float wipe_dist;
|
||||
float tower_interface_pre_extrusion_dist = 0.f;
|
||||
float tower_interface_pre_extrusion_length = 0.f;
|
||||
// Outward shift of the wipe start for a PETG pre-extrusion on filament-switcher devices;
|
||||
// set from filament_tower_interface_pre_extrusion_dist.
|
||||
float petg_pre_extrusion_offset_dist = 0.f;
|
||||
float tower_ironing_area = 4.f;
|
||||
float tower_interface_purge_length = 0.f;
|
||||
// Distance (in mm of filament) that a hotend is allowed to pre-cool before the
|
||||
// tower is reached; drives the prime-tower heating-during-wipe model (multi-nozzle only).
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
// .first = extruder change, .second = nozzle change (carousel)
|
||||
std::pair<float,float> max_e_ramming_speed{0.f, 0.f};
|
||||
std::pair<float,float> ramming_travel_time{0.f, 0.f};
|
||||
std::pair<int,int> precool_target_temp{0, 0};
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t_first_layer;
|
||||
std::pair<float,float> max_e_ramming_speed;//[0]extruder change [1]nozzle change
|
||||
std::pair<float, float> ramming_travel_time; // Travel time after ramming
|
||||
std::pair<std::vector<float>,std::vector<float>> precool_t;//Pre-cooling time, set to 0 to ensure the ramming speed is controlled solely by ramming volumetric speed.
|
||||
std::pair<std::vector<float>, std::vector<float>> precool_t_first_layer;
|
||||
std::pair<int,int> precool_target_temp;
|
||||
float filament_cooling_before_tower = 0.f;
|
||||
float flat_iron_area;
|
||||
float filament_tower_interface_print_temp;
|
||||
float filament_tower_interface_pre_extrusion_dist = 0;
|
||||
float filament_tower_interface_pre_extrusion_length = 0;
|
||||
float filament_petg_pre_extrusion_offset_dist = 0;
|
||||
};
|
||||
|
||||
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_used_filament_ids(const std::vector<int> &used_filament_ids) { m_used_filament_ids = used_filament_ids; };
|
||||
void set_filament_categories(const std::vector<int> & filament_categories) { m_filament_categories = filament_categories;};
|
||||
std::vector<int> m_used_filament_ids;
|
||||
void set_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult &multi_nozzle_group_result) { m_multi_nozzle_group_result = &multi_nozzle_group_result; };
|
||||
std::vector<int> m_used_filament_ids;
|
||||
std::vector<int> m_filament_categories;
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult *m_multi_nozzle_group_result{nullptr};
|
||||
|
||||
enum class WipeTowerLayerType : unsigned char { Normal, Contact, Solid, Contact_UP};// Contact layer should be solid and reduce feed
|
||||
|
||||
struct WipeTowerBlock
|
||||
{
|
||||
int block_id{0};
|
||||
int filament_adhesiveness_category{0};
|
||||
std::vector<float> layer_depths;
|
||||
std::vector<bool> solid_infill;
|
||||
//std::vector<bool> solid_infill;
|
||||
std::vector<float> finish_depth{0}; // the start pos of finish frame for every layer
|
||||
std::vector<WipeTowerLayerType> layers_type; // type of the layer, normal, Contact or Solid
|
||||
float depth{0};
|
||||
float start_depth{0};
|
||||
float cur_depth{0};
|
||||
int last_filament_change_id{-1};
|
||||
int last_filament_change_id{-1};
|
||||
int last_nozzle_change_id{-1};
|
||||
};
|
||||
|
||||
@@ -403,25 +392,33 @@ public:
|
||||
WipeTowerBlock* get_block_by_category(int filament_adhesiveness_category, bool create);
|
||||
void add_depth_to_block(int filament_id, int filament_adhesiveness_category, float depth, bool is_nozzle_change = false);
|
||||
int get_filament_category(int filament_id);
|
||||
bool is_in_same_extruder(int filament_id_1, int filament_id_2);
|
||||
// Vortek H2C: format BBS-compatible NOZZLE_CHANGE_START/END tag with OF/NF/ON/NN payload
|
||||
std::string format_nozzle_change_tag(bool start, int old_filament_id, int new_filament_id) const;
|
||||
void reset_block_status();
|
||||
int get_wall_filament_for_all_layer();
|
||||
// for generate new wipe tower
|
||||
void generate_new(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
|
||||
|
||||
void plan_tower_new();
|
||||
void generate_wipe_tower_blocks();
|
||||
void generate_wipe_tower_blocks(bool add_solid_flag);
|
||||
void update_all_layer_depth(float wipe_tower_depth);
|
||||
|
||||
void set_nozzle_last_layer_id();
|
||||
void set_first_layer_flow_ratio(const float flow_ratio);
|
||||
// Orca: default/initial-layer/travel acceleration are object-scope options here (PrintConfig
|
||||
// members in BBS), so Print pushes the resolved per-variant columns in via this setter.
|
||||
void set_accelerations(const std::vector<double> &normal, const std::vector<double> &first_layer_normal,
|
||||
const std::vector<double> &travel, const std::vector<double> &first_layer_travel);
|
||||
void calc_block_infill_gap();
|
||||
ToolChangeResult tool_change_new(size_t new_tool, bool solid_change = false, bool solid_nozzlechange=false);
|
||||
NozzleChangeResult nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_change = false);
|
||||
NozzleChangeResult ramming(int old_filament_id, int new_filament_id, bool solid_change = false, bool extruder_change = true); // extruder_chang means nozzle_change
|
||||
ToolChangeResult finish_layer_new(bool extrude_perimeter = true, bool extrude_fill = true, bool extrude_fill_wall = true);
|
||||
ToolChangeResult finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true ,bool interface_solid =false);
|
||||
ToolChangeResult finish_block_solid(const WipeTowerBlock &block, int filament_id, bool extrude_fill = true, WipeTowerLayerType layer_type = WipeTowerLayerType::Normal);
|
||||
void toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_length,bool solid_toolchange=false);
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
bool is_need_ramming(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_extruder(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
bool is_same_nozzle(int filament_id_1, int filament_id_2, int layer_id) const;
|
||||
int get_nozzle_id(int filament_id, int layer_id) const;
|
||||
int get_extruder_id(int filament_id, int layer_id) const;
|
||||
|
||||
private:
|
||||
enum wipe_shape // A fill-in direction
|
||||
@@ -441,7 +438,6 @@ private:
|
||||
bool m_enable_wrapping_detection = false;
|
||||
bool m_enable_timelapse_print = false;
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
bool m_purge_in_prime_tower = false; // Do we purge in the prime tower?
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
@@ -459,12 +455,11 @@ private:
|
||||
float m_travel_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
|
||||
std::vector<double> m_filaments_change_length;
|
||||
Vec2f m_origin;
|
||||
std::vector<int> m_last_layer_id;
|
||||
std::pair<std::vector<double>,std::vector<double>> m_filaments_change_length;//[0]extruder change [1]nozzle change
|
||||
size_t m_cur_layer_id;
|
||||
NozzleChangeResult m_nozzle_change_result;
|
||||
std::vector<int> m_filament_map;
|
||||
std::vector<int> m_filament_nozzle_map; // Vortek H2C: filament_id → physical nozzle_id
|
||||
bool m_has_tpu_filament{false};
|
||||
bool m_is_multi_extruder{false};
|
||||
bool m_use_gap_wall{false};
|
||||
@@ -475,33 +470,32 @@ private:
|
||||
bool m_used_fillet{false};
|
||||
Vec2f m_rib_offset{Vec2f(0.f, 0.f)};
|
||||
bool m_tower_framework{false};
|
||||
|
||||
bool m_need_reverse_travel{false};
|
||||
bool m_enable_tower_interface_features{false};
|
||||
// G-code generator parameters.
|
||||
float m_cooling_tube_retraction = 0.f;
|
||||
float m_cooling_tube_length = 0.f;
|
||||
float m_parking_pos_retraction = 0.f;
|
||||
float m_extra_loading_move = 0.f;
|
||||
// BBS: remove useless config
|
||||
//float m_cooling_tube_retraction = 0.f;
|
||||
//float m_cooling_tube_length = 0.f;
|
||||
//float m_parking_pos_retraction = 0.f;
|
||||
//float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_no_sparse_layers = false;
|
||||
bool m_set_extruder_trimpot = false;
|
||||
// BBS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
// Multi-nozzle prime-tower heating during wipe. m_is_multiple_nozzle gates the whole
|
||||
// feature; it is false for every current (single-nozzle) printer (extruder_max_nozzle_count
|
||||
// defaults to 1), so the pre-heat/pre-cool path is inert and wipe-tower g-code is unchanged.
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<double> m_hotend_heating_rate; // config.hotend_heating_rate (deg/s per extruder)
|
||||
std::vector<int> m_physical_extruder_map; // logical extruder -> physical tool number (M104 T param)
|
||||
|
||||
// Per-extruder printable-height clamp. m_printable_height = config.extruder_printable_height
|
||||
// (per-extruder Z limit; empty for single-extruder printers, [320,325] for H2D). m_last_layer_id
|
||||
// records, per extruder, the last wipe-tower layer that uses it. is_valid_last_layer() is gated on
|
||||
// m_is_multi_extruder so single-extruder wipe-tower g-code is unchanged; the clamp only bites a
|
||||
// multi-extruder wipe tower whose final per-extruder layer exceeds that extruder's printable
|
||||
// height (near the Z limit).
|
||||
std::vector<double> m_printable_height;
|
||||
std::vector<int> m_last_layer_id;
|
||||
bool m_is_multiple_nozzle = false;
|
||||
std::vector<unsigned int> m_normal_accels;
|
||||
std::vector<unsigned int> m_first_layer_normal_accels;
|
||||
std::vector<unsigned int> m_travel_accels;
|
||||
std::vector<unsigned int> m_first_layer_travel_accels;
|
||||
unsigned int m_max_accels;
|
||||
bool m_accel_to_decel_enable;
|
||||
float m_accel_to_decel_factor;
|
||||
bool m_enable_arc_fitting = true;
|
||||
std::vector<double> m_hotend_heating_rate;
|
||||
std::vector<double> m_hotend_cooling_rate;
|
||||
Polygons m_shared_print_bed;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
@@ -512,10 +506,11 @@ private:
|
||||
float m_bed_width; // width of the bed bounding box
|
||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||
|
||||
float m_first_layer_flow_ratio;
|
||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||
float m_nozzle_change_perimeter_width = 0.4f * Width_To_Nozzle_Ratio;
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
|
||||
std::unordered_map<int, std::pair<float,float>> m_block_infill_gap_width; // categories to infill_gap: toolchange gap, nozzlechange gap
|
||||
// Extruder specific parameters.
|
||||
std::vector<FilamentParameters> m_filpar;
|
||||
|
||||
@@ -528,50 +523,52 @@ private:
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
// Orca: support mmu wipe tower
|
||||
std::vector<std::vector<float>> wipe_volumes;
|
||||
// BBS
|
||||
//const std::vector<std::vector<float>> wipe_volumes;
|
||||
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
float m_tpu_fixed_spacing = 2;
|
||||
std::vector<Vec2f> m_wall_skip_points;
|
||||
float m_max_speed = 5400.f; // the maximum printing speed on the prime tower.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
std::map<float,Polylines> m_outer_wall;
|
||||
std::vector<double> m_printable_height;
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
bool m_flat_ironing=false;
|
||||
bool m_enable_tower_interface_features=false;
|
||||
bool m_enable_tower_interface_cooldown_during_tower=false;
|
||||
// Filament-switcher device flag + shared printable bed for the PETG pre-extrusion offset.
|
||||
// m_has_filament_switcher is false for the whole shipping fleet (no profile sets the key), so
|
||||
// the PETG branch in get_next_pos never runs -> no change fleet-wide.
|
||||
bool m_has_filament_switcher=false;
|
||||
Polygons m_shared_print_bed;
|
||||
bool m_prev_layer_had_interface=false;
|
||||
bool m_current_layer_has_interface=false;
|
||||
bool m_contact_ironing = false;
|
||||
bool m_has_filament_switcher = false;
|
||||
float m_contact_speed = 20 * 60.f;
|
||||
std::vector<int> m_physical_extruder_map;
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates volume of extrusion line
|
||||
float length_to_volume(float length,float line_width, float layer_height) const
|
||||
{
|
||||
return std::max(0.f, length * (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool);
|
||||
Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool solid_toolchange);
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
|
||||
bool is_tpu_filament(int filament_id) const;
|
||||
bool is_petg_filament(int filament_id) const;
|
||||
bool is_need_reverse_travel(int filament_id, bool extruder_change) const;
|
||||
|
||||
bool is_need_reverse_travel(int filament, bool extruder_change) const;
|
||||
// BBS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
void set_for_wipe_tower_writer(WipeTowerWriter &writer);
|
||||
|
||||
// to store information about tool changes for a given layer
|
||||
struct WipeTowerInfo{
|
||||
@@ -584,6 +581,7 @@ private:
|
||||
float wipe_volume;
|
||||
float wipe_length;
|
||||
float nozzle_change_depth{0};
|
||||
float nozzle_change_length{0};
|
||||
// BBS
|
||||
float purge_volume;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
||||
@@ -613,7 +611,7 @@ private:
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
WipeTowerInfo::ToolChange set_toolchange(int old_tool, int new_tool, float layer_height, float wipe_volume, float purge_volume,int layer_id);
|
||||
void toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
@@ -633,13 +631,10 @@ private:
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer);
|
||||
|
||||
// Per-extruder printable-height clamp (see m_printable_height). is_valid_last_layer returns
|
||||
// false only for a multi-extruder wipe tower's final per-extruder layer that exceeds that
|
||||
// extruder's printable height; returns true (no clamp) in every other case.
|
||||
bool is_valid_last_layer(int tool, int layer_id, double layer_z) const;
|
||||
void set_nozzle_last_layer_id();
|
||||
void get_wall_skip_points(const WipeTowerInfo &layer,int layer_id);
|
||||
void get_all_wall_skip_points();
|
||||
ToolChangeResult merge_tcr(ToolChangeResult &first, ToolChangeResult &second);
|
||||
float get_block_gap_width(int tool, bool is_nozzlechangle = false);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -428,16 +428,109 @@ static void insert_points(std::vector<PointWithFlag>& pl, int idx, Vec2f pos, in
|
||||
}
|
||||
}
|
||||
|
||||
static Polylines remove_points_from_polygon(
|
||||
const Polygon& polygon, const std::vector<Vec2f>& skip_points, double range, bool is_left, Polygon& insert_skip_pg)
|
||||
// For skip_point
|
||||
// TODO: Optimize the skip_point algorithm itself instead of adding guards here
|
||||
static Polygon add_extra_point(const Polygon& polygon, int scale_range)
|
||||
{
|
||||
assert(polygon.size() > 2);
|
||||
Polygon res;
|
||||
if (polygon.size() < 2) return polygon;
|
||||
|
||||
// Compute bounding box of the polygon
|
||||
auto polygon_box = get_extents(polygon);
|
||||
|
||||
// Anchor point: X at bbox center, Y at bbox bottom
|
||||
Vec2f anchor_point(float(polygon_box.center()[0]), float(polygon_box.min[1]));
|
||||
|
||||
// Find the edge whose midpoint is closest to the anchor point
|
||||
size_t closest_edge_idx = 0;
|
||||
float min_dist_sq = std::numeric_limits<float>::max();
|
||||
|
||||
for (size_t i = 0; i < polygon.size(); ++i) {
|
||||
const Point &a_i = polygon[i];
|
||||
const Point &b_i = polygon[(i + 1) % polygon.size()];
|
||||
|
||||
Vec2f a(float(a_i.x()), float(a_i.y()));
|
||||
Vec2f b(float(b_i.x()), float(b_i.y()));
|
||||
Vec2f mid = (a + b) * 0.5f;
|
||||
|
||||
float dist_sq = (anchor_point - mid).squaredNorm();
|
||||
if (dist_sq < min_dist_sq) {
|
||||
min_dist_sq = dist_sq;
|
||||
closest_edge_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Edge endpoints (integer space)
|
||||
const Point &a_i = polygon[closest_edge_idx];
|
||||
const Point &b_i = polygon[(closest_edge_idx + 1) % polygon.size()];
|
||||
|
||||
// Convert to float for geometric computation
|
||||
Vec2f a(float(a_i.x()), float(a_i.y()));
|
||||
Vec2f b(float(b_i.x()), float(b_i.y()));
|
||||
|
||||
Vec2f mid = (a + b) * 0.5f;
|
||||
|
||||
// Direction vectors from midpoint towards A and B
|
||||
Vec2f dir_to_a = a - mid;
|
||||
Vec2f dir_to_b = b - mid;
|
||||
|
||||
float len_a = dir_to_a.norm();
|
||||
float len_b = dir_to_b.norm();
|
||||
|
||||
// Guard against degenerated edges
|
||||
if (len_a < EPSILON || len_b < EPSILON) return polygon;
|
||||
|
||||
dir_to_a /= len_a;
|
||||
dir_to_b /= len_b;
|
||||
|
||||
// Clamp range to avoid overshooting the edge
|
||||
float max_range = std::min(len_a, len_b) * 0.9f;
|
||||
float range = std::min(float(scale_range), max_range);
|
||||
|
||||
// Offset points (float space)
|
||||
Vec2f offset_to_a_f = mid + dir_to_a * range;
|
||||
Vec2f offset_to_b_f = mid + dir_to_b * range;
|
||||
|
||||
// Safe cast back to scaled integer Point
|
||||
auto to_int_point = [](const Vec2f &p) {
|
||||
auto clamp = [](float v) -> coord_t {
|
||||
constexpr float kMin = float(std::numeric_limits<coord_t>::min());
|
||||
constexpr float kMax = float(std::numeric_limits<coord_t>::max());
|
||||
v = std::clamp(v, kMin, kMax);
|
||||
return static_cast<coord_t>(std::lround(v));
|
||||
};
|
||||
return Point(clamp(p.x()), clamp(p.y()));
|
||||
};
|
||||
|
||||
Point mid_i = to_int_point(mid);
|
||||
Point offset_to_a_i = to_int_point(offset_to_a_f);
|
||||
Point offset_to_b_i = to_int_point(offset_to_b_f);
|
||||
|
||||
// Rebuild polygon with inserted points
|
||||
for (size_t i = 0; i < polygon.size(); ++i) {
|
||||
res.points.push_back(polygon[i]);
|
||||
|
||||
// Insert points right after the selected edge start vertex
|
||||
if (i == closest_edge_idx) {
|
||||
res.points.push_back(offset_to_a_i);
|
||||
res.points.push_back(mid_i);
|
||||
res.points.push_back(offset_to_b_i);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static Polylines remove_points_from_polygon(
|
||||
const Polygon& polygon_ori, const std::vector<Vec2f>& skip_points, double range, float wt_width, Polygon& insert_skip_pg)
|
||||
{
|
||||
Polygon polygon = add_extra_point(polygon_ori, scale_(range));
|
||||
if (polygon.size() < 2) return Polylines{to_polyline(polygon)};
|
||||
Polylines result;
|
||||
std::vector<PointWithFlag> new_pl; // add intersection points for gaps, where bool indicates whether it's a gap point.
|
||||
std::vector<IntersectionInfo> inter_info;
|
||||
Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0);
|
||||
auto polygon_box = get_extents(polygon);
|
||||
Point anchor_point = is_left ? Point{polygon_box.max[0], polygon_box.min[1]} : polygon_box.min; // rd:ld
|
||||
Point anchor_point = Point{polygon_box.center()[0], polygon_box.min[1]}; // for next reconnect
|
||||
std::vector<Vec2f> points;
|
||||
{
|
||||
points.reserve(polygon.points.size());
|
||||
@@ -449,6 +542,8 @@ static Polylines remove_points_from_polygon(
|
||||
}
|
||||
|
||||
for (int i = 0; i < skip_points.size(); i++) {
|
||||
bool is_left = abs(skip_points[i].x()) < wt_width / 2.f;
|
||||
Vec2f ray = is_left ? Vec2f(-1, 0) : Vec2f(1, 0);
|
||||
for (int j = 0; j < points.size(); j++) {
|
||||
Vec2f& p1 = points[j];
|
||||
Vec2f& p2 = points[(j + 1) % points.size()];
|
||||
@@ -526,12 +621,7 @@ static Polylines contrust_gap_for_skip_points(
|
||||
insert_skip_polygon = polygon;
|
||||
return Polylines{to_polyline(polygon)};
|
||||
}
|
||||
bool is_left = false;
|
||||
const auto& pt = skip_points.front();
|
||||
if (abs(pt.x()) < wt_width / 2.f) {
|
||||
is_left = true;
|
||||
}
|
||||
return remove_points_from_polygon(polygon, skip_points, gap_length, is_left, insert_skip_polygon);
|
||||
return remove_points_from_polygon(polygon, skip_points, gap_length, wt_width, insert_skip_polygon);
|
||||
};
|
||||
|
||||
static Polygon generate_rectange_polygon(const Vec2f& wt_box_min, const Vec2f& wt_box_max)
|
||||
@@ -1272,6 +1362,8 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
|
||||
m_rib_width(config.wipe_tower_rib_width),
|
||||
m_extra_rib_length(config.wipe_tower_extra_rib_length),
|
||||
m_wall_type((int)config.wipe_tower_wall_type),
|
||||
// The cone wall has its own fully separate generator with no gap machinery.
|
||||
m_use_gap_wall(config.prime_tower_skip_points.value && config.wipe_tower_wall_type.value != wtwCone),
|
||||
m_flat_ironing(config.prime_tower_flat_ironing.value),
|
||||
m_enable_tower_interface_features(config.enable_tower_interface_features.value),
|
||||
m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value)
|
||||
@@ -1342,6 +1434,7 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
|
||||
m_filpar[idx].is_soluble = (idx != size_t(m_wipe_tower_filament - 1));
|
||||
else
|
||||
m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
|
||||
m_filpar[idx].is_support = config.filament_is_support.get_at(idx);
|
||||
m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx);
|
||||
m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx);
|
||||
m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx);
|
||||
@@ -1478,11 +1571,11 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
|
||||
toolchange_Load(writer, cleaning_box); // Prime the tool.
|
||||
if (idx_tool + 1 == tools.size()) {
|
||||
// Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false);
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false, true);
|
||||
} else {
|
||||
// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
|
||||
//writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f, false);
|
||||
toolchange_Wipe(writer, cleaning_box , 20.f, false, true);
|
||||
WipeTower::box_coordinates box = cleaning_box;
|
||||
box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width);
|
||||
toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
|
||||
@@ -1920,7 +2013,8 @@ void WipeTower2::toolchange_Wipe(
|
||||
WipeTowerWriter2 &writer,
|
||||
const WipeTower::box_coordinates &cleaning_box,
|
||||
float wipe_volume,
|
||||
bool interface_layer)
|
||||
bool interface_layer,
|
||||
bool priming)
|
||||
{
|
||||
// Increase flow on first layer, slow down print.
|
||||
writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f))
|
||||
@@ -1963,6 +2057,26 @@ void WipeTower2::toolchange_Wipe(
|
||||
}
|
||||
|
||||
float traversed_x = writer.x();
|
||||
|
||||
// BBS gap wall: iron the first few mm of the purge, then drag the retracted nozzle
|
||||
// back out through the wall gap so the toolchange start blob is not left on the wall.
|
||||
// WT2's entry gap always sits at the left-edge entry point, so only iron when the
|
||||
// purge actually starts there heading right (in-place toolchangers do; SEMM
|
||||
// ram/cooling moves leave the nozzle mid-box, far from any gap).
|
||||
if (i == 0 && m_use_gap_wall && !interface_layer && !priming && m_left_to_right &&
|
||||
writer.x() - xl < 2.5f * line_width) {
|
||||
float ironing_length = 3.f;
|
||||
if (xr - writer.x() < ironing_length)
|
||||
ironing_length = std::max(xr - writer.x(), 0.f);
|
||||
const float retract_length = m_filpar[m_current_tool].retract_length;
|
||||
const float retract_speed = m_filpar[m_current_tool].retract_speed * 60.f;
|
||||
writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed);
|
||||
writer.retract(retract_length, retract_speed);
|
||||
writer.travel(writer.x() - 1.5f * ironing_length, writer.y(), 600.f);
|
||||
writer.travel(writer.x() + 1.5f * ironing_length, writer.y(), 240.f);
|
||||
writer.retract(-retract_length, retract_speed);
|
||||
}
|
||||
|
||||
if (m_left_to_right)
|
||||
writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5f*line_width), writer.y(), wipe_speed);
|
||||
else
|
||||
@@ -2110,7 +2224,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
|
||||
poly = generate_support_cone_wall(writer, wt_box, feedrate, infill_cone, spacing);
|
||||
} else {
|
||||
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
|
||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, false);
|
||||
poly = generate_support_rib_wall(writer, wt_box, feedrate, first_layer, m_wall_type == (int)wtwRib, true, m_use_gap_wall);
|
||||
}
|
||||
|
||||
// brim (first layer only)
|
||||
@@ -2228,15 +2342,21 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
|
||||
return;
|
||||
|
||||
// this is an actual toolchange - let's calculate depth to reserve on the wipe tower
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
||||
m_plan.back().tool_changes.push_back(set_toolchange(old_tool, new_tool, layer_height_par, wipe_volume, first_layer_plan));
|
||||
}
|
||||
|
||||
WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan)
|
||||
{
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width;
|
||||
float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
|
||||
m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
|
||||
layer_height_par);
|
||||
layer_height);
|
||||
// Orca: Set ramming depth to 0 if ramming is disabled.
|
||||
float ramming_depth = m_enable_filament_ramming ? ((int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator) * m_extra_spacing_ramming) : 0;
|
||||
float first_wipe_line = - (width*((length_to_extrude / width)-int(length_to_extrude / width)) - width);
|
||||
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par);
|
||||
float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height);
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
@@ -2245,12 +2365,11 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = (m_plan.size() - 1) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume));
|
||||
float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
return WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume);
|
||||
}
|
||||
|
||||
|
||||
@@ -2288,49 +2407,64 @@ void WipeTower2::save_on_last_wipe()
|
||||
continue;
|
||||
|
||||
// Which toolchange will finish_layer extrusions be subtracted from?
|
||||
int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes);
|
||||
int idx = first_toolchange_to_nonsoluble_nonsupport(m_layer_info->tool_changes);
|
||||
|
||||
if (idx == -1) {
|
||||
// In this case, finish_layer will be called at the very beginning.
|
||||
finish_layer().total_extrusion_length_in_plane();
|
||||
}
|
||||
|
||||
const float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
auto recompute_toolchange = [this, width](WipeTowerInfo::ToolChange& toolchange, float volume_to_save) {
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
||||
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
};
|
||||
|
||||
for (int i=0; i<int(m_layer_info->tool_changes.size()); ++i) {
|
||||
auto& toolchange = m_layer_info->tool_changes[i];
|
||||
tool_change(toolchange.new_tool);
|
||||
|
||||
if (i == idx) {
|
||||
float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
|
||||
|
||||
float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height);
|
||||
float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save);
|
||||
float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, m_perimeter_width*m_extra_flow, m_layer_info->height));
|
||||
|
||||
// ORCA: Keep wipe-depth planning consistent with toolchange_Wipe().
|
||||
// ORCA: On the first layer, toolchange_Wipe() advances purge rows using
|
||||
// ORCA: m_extra_flow * m_perimeter_width, while later layers use
|
||||
// ORCA: m_extra_spacing_wipe * m_perimeter_width.
|
||||
// ORCA: float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width;
|
||||
// ORCA: Use the same spacing here so reserved depth matches consumed depth
|
||||
// ORCA: and first-layer purge segments do not leave visible gaps.
|
||||
const bool first_layer_plan = size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx;
|
||||
const float planning_spacing = first_layer_plan ? m_extra_flow : m_extra_spacing_wipe;
|
||||
|
||||
float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, planning_spacing, width);
|
||||
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.wipe_volume = volume_left_to_wipe;
|
||||
recompute_toolchange(toolchange, length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, m_layer_info->height));
|
||||
} else if (toolchange.wipe_volume < m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower) {
|
||||
// Keep filament_minimal_purge_on_wipe_tower enforced for toolchanges that get
|
||||
// no finish-layer saving, e.g. a support/soluble filament skipped as the
|
||||
// finish filament above. Recomputing only when the clamp binds leaves all
|
||||
// other toolchanges with their planned values bit-for-bit.
|
||||
recompute_toolchange(toolchange, 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int WipeTower2::first_toolchange_to_nonsoluble(
|
||||
// Return the index of the toolchange whose new filament should print the layer's
|
||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
||||
// layer's incoming filament before any toolchange happens.
|
||||
// Like WipeTower::first_toolchange_to_nonsoluble_nonsupport(): support and soluble
|
||||
// filaments bond poorly to the material printed on top of them, so they must not
|
||||
// print the tower's shell when another filament is available on the layer.
|
||||
int WipeTower2::first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const
|
||||
{
|
||||
if (tool_changes.empty())
|
||||
return -1;
|
||||
|
||||
// If a specific wipe tower filament is forced, use it to decide where to finish the layer.
|
||||
if (m_wipe_tower_filament > 0) {
|
||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx) {
|
||||
@@ -2339,8 +2473,19 @@ int WipeTower2::first_toolchange_to_nonsoluble(
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
// Orca: allow calculation of the required depth and wipe volume for soluble toolchanges as well.
|
||||
return tool_changes.empty() ? -1 : 0;
|
||||
|
||||
auto is_wall_filament = [this](size_t tool) {
|
||||
return !m_filpar[tool].is_soluble && !m_filpar[tool].is_support;
|
||||
};
|
||||
for (size_t idx = 0; idx < tool_changes.size(); ++idx)
|
||||
if (is_wall_filament(tool_changes[idx].new_tool))
|
||||
return idx;
|
||||
if (is_wall_filament(tool_changes.front().old_tool))
|
||||
return -1;
|
||||
// Only support/soluble filaments on this layer: keep the first toolchange so the
|
||||
// finish-layer saving and the minimal-purge clamp still apply to it (Orca depth
|
||||
// and wipe volume accounting, see save_on_last_wipe()).
|
||||
return 0;
|
||||
}
|
||||
|
||||
static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
||||
@@ -2365,6 +2510,23 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first,
|
||||
|
||||
// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
|
||||
// Resulting ToolChangeResults are appended into vector "result"
|
||||
// Precompute, for every plan layer, the wall openings ("skip points") at each toolchange's
|
||||
// entry, like WipeTower::get_all_wall_skip_points(). The entry is where tool_change()
|
||||
// starts: cleaning_box.ld + (0, m_depth_traversed), with m_depth_traversed advancing by
|
||||
// required_depth per toolchange — reproduced here from the finalized plan so each gap
|
||||
// coincides with the entry travel's target (tcr.start_pos, pre-rotation frame).
|
||||
void WipeTower2::compute_wall_skip_points()
|
||||
{
|
||||
m_wall_skip_points.assign(m_plan.size(), std::vector<Vec2f>());
|
||||
for (size_t layer_id = 0; layer_id < m_plan.size(); ++layer_id) {
|
||||
float depth_traversed = 0.f;
|
||||
for (const auto& toolchange : m_plan[layer_id].tool_changes) {
|
||||
m_wall_skip_points[layer_id].emplace_back(m_perimeter_width / 2.f, m_perimeter_width / 2.f + depth_traversed);
|
||||
depth_traversed += toolchange.required_depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
|
||||
{
|
||||
if (m_plan.empty())
|
||||
@@ -2378,12 +2540,41 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
}
|
||||
#endif
|
||||
|
||||
m_rib_length = std::max({m_rib_length, sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width)});
|
||||
if (m_wall_type == (int)wtwRib) {
|
||||
// Rib wall: force a square tower like WipeTower::plan_tower_new(), ignoring the
|
||||
// configured prime_tower_width (the GUI greys it out in rib mode). The planned depths
|
||||
// already include the extra-spacing factors, so sqrt(depth * width) preserves the
|
||||
// purge area. Replan every toolchange for the new width, then re-derive the depths.
|
||||
float max_depth = 0.f;
|
||||
for (const auto& current_plan : m_plan)
|
||||
max_depth = std::max(max_depth, current_plan.depth);
|
||||
if (max_depth > EPSILON) {
|
||||
m_wipe_tower_width = align_ceil(std::sqrt(max_depth * m_wipe_tower_width), m_perimeter_width);
|
||||
for (size_t idx = 0; idx < m_plan.size(); ++idx)
|
||||
for (auto& toolchange : m_plan[idx].tool_changes)
|
||||
toolchange = set_toolchange(toolchange.old_tool, toolchange.new_tool,
|
||||
m_plan[idx].height, toolchange.wipe_volume,
|
||||
idx == m_first_layer_idx);
|
||||
plan_tower();
|
||||
}
|
||||
|
||||
// Like WipeTower::plan_tower_new(): extend the ribs instead of the tower when the
|
||||
// tower is smaller than the height-based stability minimum.
|
||||
const float min_depth = WipeTower::get_limit_depth_by_height(m_wipe_tower_height);
|
||||
if (m_wipe_tower_depth + EPSILON < min_depth)
|
||||
m_rib_length = std::max(m_rib_length, min_depth * (float)std::sqrt(2.f));
|
||||
}
|
||||
|
||||
const float diagonal = std::sqrt(m_wipe_tower_depth * m_wipe_tower_depth + m_wipe_tower_width * m_wipe_tower_width);
|
||||
m_rib_length = std::max(m_rib_length, diagonal);
|
||||
m_rib_length += m_extra_rib_length;
|
||||
m_rib_length = std::max(0.f, m_rib_length);
|
||||
m_rib_length = std::max(diagonal, m_rib_length); // a negative extra length must not shrink the ribs below the diagonal
|
||||
m_rib_width = std::min(m_rib_width, std::min(m_wipe_tower_depth, m_wipe_tower_width) /
|
||||
2.f); // Ensure that the rib wall of the wipetower are attached to the infill.
|
||||
|
||||
if (m_use_gap_wall)
|
||||
compute_wall_skip_points();
|
||||
|
||||
m_layer_info = m_plan.begin();
|
||||
m_current_height = 0.f;
|
||||
|
||||
@@ -2410,7 +2601,7 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
|
||||
if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
|
||||
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
|
||||
|
||||
int idx = first_toolchange_to_nonsoluble(layer.tool_changes);
|
||||
int idx = first_toolchange_to_nonsoluble_nonsupport(layer.tool_changes);
|
||||
WipeTower::ToolChangeResult finish_layer_tcr;
|
||||
|
||||
if (idx == -1) {
|
||||
@@ -2525,17 +2716,27 @@ Polygon WipeTower2::generate_support_rib_wall(WipeTowerWriter2&
|
||||
return wall_polygon;
|
||||
|
||||
if (skip_points) {
|
||||
result_wall = contrust_gap_for_skip_points(wall_polygon, std::vector<Vec2f>(), m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
// Cut the wall open at each toolchange's entry (see compute_wall_skip_points()).
|
||||
// The vector is empty during the save_on_last_wipe planning passes, which therefore
|
||||
// measure the un-gapped wall — same approximation as the BBL tower.
|
||||
static const std::vector<Vec2f> no_skip_points;
|
||||
const size_t layer_id = size_t(m_layer_info - m_plan.begin());
|
||||
const std::vector<Vec2f>& layer_skip_points =
|
||||
layer_id < m_wall_skip_points.size() ? m_wall_skip_points[layer_id] : no_skip_points;
|
||||
result_wall = contrust_gap_for_skip_points(wall_polygon, layer_skip_points, m_wipe_tower_width, 2.5 * m_perimeter_width,
|
||||
insert_skip_polygon);
|
||||
} else {
|
||||
result_wall.push_back(to_polyline(wall_polygon));
|
||||
insert_skip_polygon = wall_polygon;
|
||||
}
|
||||
writer.generate_path(result_wall, feedrate, retract_length, retract_speed, m_used_fillet);
|
||||
//if (m_cur_layer_id == 0) {
|
||||
// BoundingBox bbox = get_extents(result_wall);
|
||||
// m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
||||
//}
|
||||
// Tower-local shift that puts the rib wall's protruding first-layer min corner at the
|
||||
// configured tower position, like WipeTower::generate_support_wall_new(). Measured on
|
||||
// the un-gapped outline so a wall gap cannot shift the tower.
|
||||
if (rib_wall && is_first_layer()) {
|
||||
BoundingBox bbox = get_extents(insert_skip_polygon);
|
||||
m_rib_offset = Vec2f(-unscaled<float>(bbox.min.x()), -unscaled<float>(bbox.min.y()));
|
||||
}
|
||||
|
||||
return insert_skip_polygon;
|
||||
}
|
||||
|
||||
@@ -69,9 +69,9 @@ public:
|
||||
const float brim = m_wipe_tower_brim_width_real;
|
||||
return BoundingBoxf(Vec2d(-brim, -brim), Vec2d(double(m_wipe_tower_width) + brim, double(m_wipe_tower_depth) + brim));
|
||||
}
|
||||
// WT2 doesn't currently compute a rib-origin compensation like WipeTower (m_rib_offset),
|
||||
// so expose a zero offset for consistency purposes (to maintain API parity).
|
||||
Vec2f get_rib_offset() const { return Vec2f::Zero(); }
|
||||
// Tower-local shift that puts the rib wall's first-layer min corner at the configured
|
||||
// tower position, like WipeTower::get_rib_offset(). Zero unless the rib wall is used.
|
||||
Vec2f get_rib_offset() const { return m_rib_offset; }
|
||||
float get_rib_width() const { return m_rib_width; }
|
||||
float get_rib_length() const { return m_rib_length; }
|
||||
|
||||
@@ -149,6 +149,7 @@ public:
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
bool is_soluble = false;
|
||||
bool is_support = false;
|
||||
int temperature = 0;
|
||||
int first_layer_temperature = 0;
|
||||
int interface_print_temperature = 0;
|
||||
@@ -231,6 +232,12 @@ private:
|
||||
float m_rib_width = 10;
|
||||
float m_extra_rib_length = 0;
|
||||
float m_rib_length = 0;
|
||||
Vec2f m_rib_offset = Vec2f::Zero();
|
||||
bool m_use_gap_wall = false;
|
||||
// Per plan layer, each toolchange's entry position (tower-local, un-shifted frame):
|
||||
// where the wall is cut open so the entry travel does not cross the printed wall.
|
||||
// Filled by compute_wall_skip_points() once the plan is final.
|
||||
std::vector<std::vector<Vec2f>> m_wall_skip_points;
|
||||
|
||||
bool m_enable_arc_fitting = false;
|
||||
|
||||
@@ -328,9 +335,10 @@ private:
|
||||
std::vector<float> m_used_filament_length;
|
||||
std::vector<std::pair<float, std::vector<float>>> m_used_filament_length_until_layer;
|
||||
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble(
|
||||
// Return the index of the toolchange whose new filament should print the layer's
|
||||
// finish extrusions (sparse infill + wall + brim), or -1 to print them with the
|
||||
// layer's incoming filament before any toolchange happens.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
void toolchange_Unload(
|
||||
@@ -353,7 +361,8 @@ private:
|
||||
WipeTowerWriter2 &writer,
|
||||
const WipeTower::box_coordinates &cleaning_box,
|
||||
float wipe_volume,
|
||||
bool interface_layer);
|
||||
bool interface_layer,
|
||||
bool priming = false);
|
||||
|
||||
|
||||
Polygon generate_support_rib_wall(WipeTowerWriter2& writer,
|
||||
@@ -372,6 +381,12 @@ private:
|
||||
float spacing);
|
||||
|
||||
Polygon generate_rib_polygon(const WipeTower::box_coordinates& wt_box);
|
||||
|
||||
void compute_wall_skip_points();
|
||||
|
||||
// Computes the depth reserved for a toolchange (shared by plan_toolchange() and the
|
||||
// rib-wall square-tower replanning in generate()).
|
||||
WipeTowerInfo::ToolChange set_toolchange(size_t old_tool, size_t new_tool, float layer_height, float wipe_volume, bool first_layer_plan);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -129,13 +129,13 @@ public:
|
||||
std::vector<PathFittingData> fitting_result;
|
||||
//BBS: simplify points by arc fitting
|
||||
void simplify_by_fitting_arc(double tolerance);
|
||||
//BBS:
|
||||
void reset_to_linear_move();
|
||||
//BBS:
|
||||
Polylines equally_spaced_lines(double distance) const;
|
||||
|
||||
private:
|
||||
void append_fitting_result_after_append_points();
|
||||
void append_fitting_result_after_append_polyline(const Polyline& src);
|
||||
void reset_to_linear_move();
|
||||
bool split_fitting_result_before_index(const size_t index, Point &new_endpoint, std::vector<PathFittingData>& data) const;
|
||||
bool split_fitting_result_after_index(const size_t index, Point &new_startpoint, std::vector<PathFittingData>& data) const;
|
||||
};
|
||||
|
||||
@@ -3409,7 +3409,11 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
|
||||
}
|
||||
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
||||
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
||||
m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
// Orca: when the process variant columns cannot be matched (degenerate
|
||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||
// above instead of poisoning the map with -1.
|
||||
int slot_index = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : f_maps[index] - 1;
|
||||
}
|
||||
|
||||
m_full_print_config = m_ori_full_print_config;
|
||||
@@ -4017,10 +4021,33 @@ void Print::_make_wipe_tower()
|
||||
// in BBL machine, wipe tower is only use to prime extruder. So just use a global wipe volume.
|
||||
WipeTower wipe_tower(m_config, m_plate_index, m_origin, m_wipe_tower_data.tool_ordering.first_extruder(),
|
||||
m_wipe_tower_data.tool_ordering.empty() ? 0.f : m_wipe_tower_data.tool_ordering.back().print_z, m_wipe_tower_data.tool_ordering.all_extruders());
|
||||
// Orca: the tower's first-layer flow follows the user's first-layer flow ratio (BBS reads
|
||||
// its initial_layer_flow_ratio here — STUDIO-14254; first_layer_flow_ratio is Orca's analog,
|
||||
// default 1.0 in both). Honor the set_other_flow_ratios gate that governs the option
|
||||
// everywhere else.
|
||||
wipe_tower.set_first_layer_flow_ratio(m_default_object_config.set_other_flow_ratios
|
||||
? float(m_default_region_config.first_layer_flow_ratio)
|
||||
: 1.f);
|
||||
wipe_tower.set_has_tpu_filament(this->has_tpu_filament());
|
||||
wipe_tower.set_filament_map(this->get_filament_maps());
|
||||
// Vortek H2C: pass nozzle-level map for carousel rotation detection in tool_change_new()
|
||||
wipe_tower.set_filament_nozzle_map(this->get_filament_nozzle_maps());
|
||||
// Per-layer filament->nozzle grouping. sort_and_build_data() above publishes it on the Print
|
||||
// for by-layer prints; by-object prints publish only later (psSkirtBrim), so fall back to the
|
||||
// ToolOrdering's own copy there. set_extruder() below dereferences it, so it must be set first.
|
||||
auto print_group_result = get_layered_nozzle_group_result();
|
||||
const MultiNozzleUtils::LayeredNozzleGroupResult &nozzle_group_result =
|
||||
print_group_result ? *print_group_result : m_wipe_tower_data.tool_ordering.get_layered_nozzle_group_result();
|
||||
wipe_tower.set_nozzle_group_result(nozzle_group_result);
|
||||
{
|
||||
// Orca: acceleration options are object-scope (PrintConfig members in BBS), so resolve
|
||||
// the per-variant columns here; initial_layer_travel_acceleration is FloatOrPercent
|
||||
// over travel_acceleration and needs the full config to resolve.
|
||||
std::vector<double> first_layer_travel_accels;
|
||||
for (size_t i = 0; i < m_config.initial_layer_travel_acceleration.values.size(); ++i)
|
||||
first_layer_travel_accels.emplace_back(m_full_print_config.get_abs_value_at("initial_layer_travel_acceleration", i));
|
||||
wipe_tower.set_accelerations(m_default_object_config.default_acceleration.values,
|
||||
m_default_object_config.initial_layer_acceleration.values,
|
||||
m_default_object_config.travel_acceleration.values,
|
||||
first_layer_travel_accels);
|
||||
}
|
||||
// Feed the has_filament_switcher device flag (develop-only dynamic key, read defensively from
|
||||
// the full config — no shipping profile sets it) and the shared printable bed used by the PETG
|
||||
// pre-extrusion offset clamp. Both are inert unless has_filament_switcher is set.
|
||||
@@ -4056,27 +4083,19 @@ void Print::_make_wipe_tower()
|
||||
multi_extruder_flush.emplace_back(wipe_volumes);
|
||||
}
|
||||
|
||||
// Use NozzleStatusRecorder for per-carousel-slot tracking (BBS pattern).
|
||||
// The original Orca code tracked per-extruder (2 slots), which collapsed all
|
||||
// carousel filaments into one slot and caused massive redundant AMS flushing.
|
||||
auto group_result = get_layered_nozzle_group_result();
|
||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder (BBS pattern); the layered
|
||||
// group result set on the tower above resolves each filament to its nozzle slot per layer.
|
||||
MultiNozzleUtils::NozzleStatusRecorder nozzle_recorder;
|
||||
// Fallback (group_result == null) per-physical-nozzle tracking, matching the original
|
||||
// pre-port behavior: remembers the last filament loaded in each physical nozzle slot.
|
||||
std::vector<unsigned int> nozzle_cur_filament_ids(nozzle_nums, (unsigned int) -1);
|
||||
|
||||
std::vector<int>filament_maps = get_filament_maps();
|
||||
int layer_idx = -1;
|
||||
|
||||
unsigned int current_filament_id = m_wipe_tower_data.tool_ordering.first_extruder();
|
||||
// Initialize NozzleStatusRecorder with the first filament's carousel slot
|
||||
if (group_result) {
|
||||
auto nozzle = group_result->get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||
{
|
||||
auto nozzle = nozzle_group_result.get_nozzle_for_filament(current_filament_id, layer_idx);
|
||||
if (nozzle)
|
||||
nozzle_recorder.set_nozzle_status(nozzle->group_id, current_filament_id, nozzle->extruder_id);
|
||||
} else {
|
||||
size_t cur_nozzle_id = filament_maps[current_filament_id] - 1;
|
||||
nozzle_cur_filament_ids[cur_nozzle_id] = current_filament_id;
|
||||
}
|
||||
|
||||
for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
|
||||
@@ -4095,8 +4114,8 @@ void Print::_make_wipe_tower()
|
||||
float volume_to_purge = 0;
|
||||
|
||||
// Per-carousel-slot purge tracking via NozzleStatusRecorder
|
||||
if (group_result) {
|
||||
auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_idx);
|
||||
{
|
||||
auto nozzle_info = nozzle_group_result.get_nozzle_for_filament(filament_id, layer_idx);
|
||||
if (nozzle_info) {
|
||||
int extruder_id = nozzle_info->extruder_id;
|
||||
int nozzle_id = nozzle_info->group_id;
|
||||
@@ -4115,22 +4134,6 @@ void Print::_make_wipe_tower()
|
||||
}
|
||||
nozzle_recorder.set_nozzle_status(nozzle_id, filament_id, extruder_id);
|
||||
}
|
||||
} else {
|
||||
// Fallback: original Orca per-physical-nozzle path (non-carousel printers).
|
||||
// Flush source is the last filament that occupied THIS nozzle, guarded so the
|
||||
// first use of a nozzle incurs no flush.
|
||||
int nozzle_id = filament_maps[filament_id] - 1;
|
||||
unsigned int pre_filament_id = nozzle_cur_filament_ids[nozzle_id];
|
||||
if (pre_filament_id != (unsigned int) -1 && pre_filament_id != filament_id) {
|
||||
volume_to_purge = multi_extruder_flush[nozzle_id][pre_filament_id][filament_id];
|
||||
float flush_multiplier = (m_config.prime_volume_mode == PrimeVolumeMode::pvmFast)
|
||||
? m_config.flush_multiplier_fast.get_at(nozzle_id)
|
||||
: m_config.flush_multiplier.get_at(nozzle_id);
|
||||
volume_to_purge *= flush_multiplier;
|
||||
volume_to_purge = layer_tools.wiping_extrusions().mark_wiping_extrusions(
|
||||
*this, current_filament_id, filament_id, volume_to_purge);
|
||||
}
|
||||
nozzle_cur_filament_ids[nozzle_id] = filament_id;
|
||||
}
|
||||
|
||||
//During the filament change, the extruder will extrude an extra length of grab_length for the corresponding detection, so the purge can reduce this length.
|
||||
@@ -4138,29 +4141,21 @@ void Print::_make_wipe_tower()
|
||||
float grab_purge_volume = m_config.grab_length.get_at(grab_extruder_id) * 2.4; //(diameter/2)^2*PI=2.4
|
||||
volume_to_purge = std::max(0.f, volume_to_purge - grab_purge_volume);
|
||||
|
||||
// Select prime volume per-filament: nozzle change (carousel rotation) uses
|
||||
// filament_prime_volume_nc, filament change (same nozzle slot) uses filament_prime_volume.
|
||||
// Prime volume per-filament: the tower now picks extruder-change vs nozzle-change
|
||||
// (carousel) internally per plan layer, so pass both candidates (BBS pattern).
|
||||
float wipe_volume_ec = filament_id < m_config.filament_prime_volume.values.size()
|
||||
? m_config.filament_prime_volume.values[filament_id]
|
||||
: (float) m_config.prime_volume;
|
||||
float wipe_volume_nc = filament_id < m_config.filament_prime_volume_nc.values.size()
|
||||
? m_config.filament_prime_volume_nc.values[filament_id]
|
||||
: (float) m_config.prime_volume;
|
||||
|
||||
float prime_volume = wipe_volume_ec;
|
||||
if (group_result) {
|
||||
bool is_nozzle_change = group_result->are_filaments_same_extruder(current_filament_id, filament_id, layer_idx) &&
|
||||
!group_result->are_filaments_same_nozzle(current_filament_id, filament_id, layer_idx);
|
||||
if (is_nozzle_change) {
|
||||
prime_volume = wipe_volume_nc;
|
||||
}
|
||||
}
|
||||
if (m_config.prime_volume_mode == PrimeVolumeMode::pvmSaving) {
|
||||
prime_volume = 15.f;
|
||||
wipe_volume_ec = 15.f;
|
||||
wipe_volume_nc = 15.f;
|
||||
}
|
||||
|
||||
wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id,
|
||||
prime_volume, volume_to_purge);
|
||||
wipe_volume_ec, wipe_volume_nc, volume_to_purge);
|
||||
current_filament_id = filament_id;
|
||||
}
|
||||
layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
|
||||
@@ -4338,7 +4333,12 @@ void Print::_make_wipe_tower()
|
||||
wipe_tower.get_rib_width(), wipe_tower.get_rib_length(),
|
||||
config().wipe_tower_fillet_wall.value);
|
||||
const Vec3d origin = Vec3d::Zero();
|
||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position(), wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
||||
// FakeWipeTower::pos is a bed-frame translation applied after rotation
|
||||
// (getFakeExtrusionPathsFromWipeTower2 rotates about the local origin), so the
|
||||
// tower-local rib offset must be rotated into the bed frame first.
|
||||
m_fake_wipe_tower.rib_offset = Eigen::Rotation2Df(Geometry::deg2rad((float)config().wipe_tower_rotation_angle.value)) *
|
||||
wipe_tower.get_rib_offset();
|
||||
m_fake_wipe_tower.set_fake_extrusion_data(wipe_tower.position() + m_fake_wipe_tower.rib_offset, wipe_tower.width(), wipe_tower.get_wipe_tower_height(),
|
||||
config().initial_layer_print_height, m_wipe_tower_data.depth,
|
||||
m_wipe_tower_data.z_and_depth_pairs, m_wipe_tower_data.brim_width,
|
||||
config().wipe_tower_rotation_angle, config().wipe_tower_cone_angle,
|
||||
|
||||
@@ -1355,7 +1355,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
||||
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
||||
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
||||
m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
// Orca: when the process variant columns cannot be matched (degenerate
|
||||
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||
// above instead of poisoning the map with -1.
|
||||
int slot_index = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : filament_maps[index] - 1;
|
||||
}
|
||||
|
||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||
@@ -1411,6 +1415,16 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
num_extruders_changed = true;
|
||||
}
|
||||
}
|
||||
else if (! print_diff.empty()) {
|
||||
// Orca: m_config can diverge from an unchanged full config (e.g. the in-slice retract
|
||||
// override recompute writing different values than the apply-time computation). The
|
||||
// invalidation above already fired for print_diff, so repair m_config here as well;
|
||||
// otherwise the divergence is never corrected and every subsequent apply of the same
|
||||
// config invalidates the result again, forever.
|
||||
m_placeholder_parser.apply_config(filament_overrides);
|
||||
m_config.apply_only(new_full_config, print_diff, true);
|
||||
m_config.apply(filament_overrides);
|
||||
}
|
||||
|
||||
ModelObjectStatusDB model_object_status_db;
|
||||
|
||||
|
||||
@@ -10495,6 +10495,44 @@ int DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std
|
||||
return count;
|
||||
}
|
||||
|
||||
// Orca: BBL system profiles ship full-width print_extruder_id/print_extruder_variant columns, but
|
||||
// custom multi-extruder printers only ever get the machine-scope columns synthesized for them (see
|
||||
// extend_extruder_variant); the process scope keeps the length-1 defaults, both in presets and in
|
||||
// 3mf project configs. Expanding with that degenerate map makes every per-extruder lookup fail, and
|
||||
// because both keys are themselves in print_options_with_variant, the expansion then latches a
|
||||
// full-width-but-wrong [1,1,...] map that also defeats the generated_extruder_id fallback in
|
||||
// get_index_for_extruder. Synthesize the process columns from the printer's extruder_variant_list
|
||||
// (same token walk as extend_extruder_variant) before expanding.
|
||||
static void ensure_process_variant_columns(DynamicPrintConfig &config, const DynamicPrintConfig &printer_config)
|
||||
{
|
||||
auto id_opt = dynamic_cast<ConfigOptionInts *>(config.option("print_extruder_id"));
|
||||
auto variant_opt = dynamic_cast<ConfigOptionStrings *>(config.option("print_extruder_variant"));
|
||||
auto list_opt = dynamic_cast<const ConfigOptionStrings *>(printer_config.option("extruder_variant_list"));
|
||||
if (!id_opt || !variant_opt || !list_opt)
|
||||
return;
|
||||
if (id_opt->values.size() != 1 || variant_opt->values.size() != 1)
|
||||
return;
|
||||
|
||||
std::vector<int> ids;
|
||||
std::vector<std::string> variants;
|
||||
for (int i = 0; i < int(list_opt->values.size()); ++i) {
|
||||
std::vector<std::string> tokens;
|
||||
boost::split(tokens, list_opt->get_at(i), boost::is_any_of(","), boost::token_compress_on);
|
||||
for (std::string &token : tokens) {
|
||||
boost::trim(token);
|
||||
if (token.empty())
|
||||
continue;
|
||||
ids.push_back(i + 1);
|
||||
variants.push_back(token);
|
||||
}
|
||||
}
|
||||
// A single column is the legitimate single-extruder layout, not a degenerate one.
|
||||
if (ids.size() <= 1)
|
||||
return;
|
||||
id_opt->values = std::move(ids);
|
||||
variant_opt->values = std::move(variants);
|
||||
}
|
||||
|
||||
std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt)
|
||||
{
|
||||
@@ -10536,6 +10574,8 @@ std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicP
|
||||
variant_count = 1;
|
||||
}
|
||||
else {
|
||||
if (id_name == "print_extruder_id")
|
||||
ensure_process_variant_columns(*this, printer_config);
|
||||
// Orca: emit the slots first, then size variant_count from what was actually
|
||||
// emitted. extruder_nozzle_volume_count only equals the emitted total when every
|
||||
// extruder carries per-type stats; an extruder with an empty stats entry combined
|
||||
|
||||
@@ -2298,6 +2298,7 @@ arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const Dynamic
|
||||
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
|
||||
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
|
||||
int plate_width=m_width, plate_depth=m_depth;
|
||||
w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
|
||||
float depth = wt_size(1);
|
||||
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
|
||||
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
|
||||
|
||||
@@ -43,18 +43,33 @@ TEST_CASE("apply_override fills nil entries from the 0-based default index", "[C
|
||||
REQUIRE(resolved.values == std::vector<double>({30., 42.}));
|
||||
}
|
||||
|
||||
SECTION("an index past the machine slots falls back to the first slot") {
|
||||
SECTION("an index past the machine slots keeps the slot's own value") {
|
||||
std::vector<int> slot_index{5, 0};
|
||||
ConfigOptionFloats resolved(machine);
|
||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
||||
}
|
||||
|
||||
SECTION("a negative index (unresolved slot) falls back to the first slot") {
|
||||
std::vector<int> slot_index{-1, 0};
|
||||
SECTION("a negative index (unresolved slot) keeps the slot's own value") {
|
||||
ConfigOptionFloatsNullable all_nil;
|
||||
all_nil.values = {ConfigOptionFloatsNullable::nil_value(), ConfigOptionFloatsNullable::nil_value(),
|
||||
ConfigOptionFloatsNullable::nil_value()};
|
||||
std::vector<int> slot_index{2, -1, 0};
|
||||
ConfigOptionFloats resolved(machine);
|
||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
||||
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({30., 20., 10.}));
|
||||
}
|
||||
|
||||
SECTION("all-nil overrides keyed by unresolved slots leave the machine values intact") {
|
||||
// The failed-lookup map a degenerate print_extruder_id used to produce; the negative
|
||||
// slots must not collapse the machine array to its first value.
|
||||
ConfigOptionFloats per_extruder({100., 70., 70., 70., 100.});
|
||||
ConfigOptionFloatsNullable all_nil;
|
||||
all_nil.values.assign(5, ConfigOptionFloatsNullable::nil_value());
|
||||
std::vector<int> slot_index{0, -1, -1, -1, 0};
|
||||
ConfigOptionFloats resolved(per_extruder);
|
||||
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +287,102 @@ TEST_CASE("update_values_to_printer_extruders expands one slot per (extruder x v
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders synthesizes degenerate process variant columns", "[Config]")
|
||||
{
|
||||
// Non-BBL process presets and 3mf project configs keep the length-1 defaults for
|
||||
// print_extruder_id/print_extruder_variant; only BBL system presets ship full-width columns.
|
||||
auto add_degenerate_print_columns = [](DynamicPrintConfig &config) {
|
||||
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1};
|
||||
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard"};
|
||||
config.option<ConfigOptionFloats>("outer_wall_speed", true)->values = {30.};
|
||||
};
|
||||
|
||||
SECTION("a single-column pair on a multi-extruder machine expands to one column per extruder") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(variant_index == std::vector<int>({0, 1}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2}));
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||
// width-1 data arrays replicate their only column into every slot
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30.}));
|
||||
}
|
||||
|
||||
SECTION("a multi-variant list synthesizes one column per (extruder x variant)") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
REQUIRE(count == 3);
|
||||
|
||||
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
// same slot resolution as the explicit BBL-style 4-column layout
|
||||
REQUIRE(variant_index == std::vector<int>({0, 2, 3}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2, 2}));
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard", "Direct Drive High Flow"}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30., 30.}));
|
||||
}
|
||||
|
||||
SECTION("a single-extruder single-column layout is not treated as degenerate") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30.}));
|
||||
}
|
||||
|
||||
SECTION("a second expansion leaves the synthesized layout unchanged") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
DynamicPrintConfig once = config;
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values ==
|
||||
once.option<ConfigOptionInts>("print_extruder_id")->values);
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
once.option<ConfigOptionStrings>("print_extruder_variant")->values);
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values ==
|
||||
once.option<ConfigOptionFloats>("outer_wall_speed")->values);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves per-filament slots", "[Config]")
|
||||
{
|
||||
auto make_filament_arrays = [](DynamicPrintConfig &config) {
|
||||
|
||||
@@ -500,6 +500,52 @@ TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid"
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
}
|
||||
|
||||
TEST_CASE("A degenerate process variant map on a custom multi-extruder printer slices to a stable result", "[Print][Regression]")
|
||||
{
|
||||
// Non-BBL multi-extruder printers get machine-scope variant columns synthesized on preset
|
||||
// load (extend_extruder_variant), but nothing ships process-scope print_extruder_id /
|
||||
// print_extruder_variant: presets and 3mf project configs carry the length-1 defaults. The
|
||||
// apply-time expansion must synthesize the process columns from extruder_variant_list;
|
||||
// otherwise the failed per-extruder lookups collapse the per-extruder retract overrides
|
||||
// during slicing and the post-slice re-apply invalidates every fresh result, forever.
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4, 0.4, 0.4, 0.4};
|
||||
config.set_num_extruders(5);
|
||||
// per-extruder machine values that a first-slot collapse would destroy
|
||||
config.option<ConfigOptionPercents>("retract_before_wipe", true)->values = {100., 70., 70., 70., 100.};
|
||||
config.option<ConfigOptionEnumsGeneric>("z_hop_types", true)->values = {zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope};
|
||||
// filament presets carry the nullable override twins (all-nil = "no override"); they are what
|
||||
// routes the machine values through apply_override in the in-slice override recompute
|
||||
config.option<ConfigOptionPercentsNullable>("filament_retract_before_wipe", true)->values =
|
||||
std::vector<double>(5, ConfigOptionPercentsNullable::nil_value());
|
||||
config.option<ConfigOptionEnumsGenericNullable>("filament_z_hop_types", true)->values =
|
||||
std::vector<int>(5, ConfigOptionEnumsGenericNullable::nil_value());
|
||||
config.option<ConfigOptionFloats>("filament_diameter", true)->values = std::vector<double>(5, 1.75);
|
||||
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#00FFFF"};
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 3, 4, 1};
|
||||
|
||||
Model model;
|
||||
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance()->set_offset(Vec3d(100., 100., 0.));
|
||||
|
||||
Print print;
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
|
||||
// BackgroundSlicingProcess reads the engine-computed maps back into the plate config after
|
||||
// slicing; the next apply overlays that written-back state.
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = print.get_filament_maps();
|
||||
config.option<ConfigOptionInts>("filament_volume_map", true)->values = print.get_filament_volume_maps();
|
||||
config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = print.get_filament_nozzle_maps();
|
||||
|
||||
auto status = print.apply(model, config);
|
||||
REQUIRE(status == PrintBase::APPLY_STATUS_UNCHANGED);
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
// the per-extruder machine values must survive the in-slice override recompute
|
||||
REQUIRE(print.config().retract_before_wipe.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||
REQUIRE(print.config().z_hop_types.values == std::vector<int>({zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope}));
|
||||
}
|
||||
|
||||
TEST_CASE("normalize_nozzle_map_per_layer makes per-filament assignments gap-free", "[MultiNozzle][H2C][Dynamic]")
|
||||
{
|
||||
SECTION("gaps inherit the last used nozzle, entries on used layers stay untouched") {
|
||||
|
||||
Reference in New Issue
Block a user