From 67099d8e5d7b7d6c7440b87cc2da9061773ad293 Mon Sep 17 00:00:00 2001 From: cjw Date: Tue, 4 Mar 2025 10:11:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=9D=90=E6=96=99=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E5=8F=AF=E6=98=BE=E7=A4=BA=E5=86=85=E5=AE=B9=E3=80=82?= =?UTF-8?q?Gcode=E4=BB=A3=E7=A0=81=E7=A0=94=E7=A9=B6=E3=80=82=E5=85=B6?= =?UTF-8?q?=E4=BB=96bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/libslic3r/GCode.cpp | 5802 +++++++++++++++++++++++++++ src/libslic3r/PrintConfig.cpp | 79 +- src/slic3r/GUI/GUI_App.cpp | 201 +- src/slic3r/GUI/GUI_App.hpp | 689 ++++ src/slic3r/GUI/ParamsPanel.hpp | 178 + src/slic3r/GUI/Plater.cpp | 11 + src/slic3r/GUI/Plater.hpp | 788 ++++ src/slic3r/GUI/PresetComboBoxes.cpp | 199 +- src/slic3r/GUI/PresetComboBoxes.hpp | 257 ++ src/slic3r/GUI/Tab.cpp | 516 ++- src/slic3r/GUI/Tab.hpp | 2 +- 11 files changed, 8379 insertions(+), 343 deletions(-) create mode 100644 src/libslic3r/GCode.cpp create mode 100644 src/slic3r/GUI/GUI_App.hpp create mode 100644 src/slic3r/GUI/ParamsPanel.hpp create mode 100644 src/slic3r/GUI/Plater.hpp create mode 100644 src/slic3r/GUI/PresetComboBoxes.hpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp new file mode 100644 index 000000000..f19ae1670 --- /dev/null +++ b/src/libslic3r/GCode.cpp @@ -0,0 +1,5802 @@ +#include "libslic3r.h" +#include "I18N.hpp" +#include "GCode.hpp" +#include "Exception.hpp" +#include "ExtrusionEntity.hpp" +#include "EdgeGrid.hpp" +#include "Geometry/ConvexHull.hpp" +#include "GCode/PrintExtents.hpp" +#include "GCode/WipeTower.hpp" +#include "ShortestPath.hpp" +#include "Print.hpp" +#include "Utils.hpp" +#include "ClipperUtils.hpp" +#include "libslic3r.h" +#include "LocalesUtils.hpp" +#include "libslic3r/format.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "SVG.hpp" + +#include + +// Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332. +// We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. +#if ! defined(TBB_VERSION_MAJOR) + #include +#endif +#if ! defined(TBB_VERSION_MAJOR) + static_assert(false, "TBB_VERSION_MAJOR not defined"); +#endif +#if TBB_VERSION_MAJOR >= 2021 + #include + using slic3r_tbb_filtermode = tbb::filter_mode; +#else + #include + using slic3r_tbb_filtermode = tbb::filter; +#endif + +#include + +#include "miniz_extension.hpp" + +using namespace std::literals::string_view_literals; + +#if 0 +// Enable debugging and asserts, even in the release build. +#define DEBUG +#define _DEBUG +#undef NDEBUG +#endif + +#include + +namespace Slic3r { + + //! macro used to mark string used at localization, + //! return same string +#define L(s) (s) +#define _(s) Slic3r::I18N::translate(s) + +static const float g_min_purge_volume = 100.f; +static const float g_purge_volume_one_time = 135.f; +static const int g_max_flush_count = 4; +static const size_t g_max_label_object = 64; +static const double smooth_speed_step = 10; +static const double not_split_length = scale_(1.0); + +Vec2d travel_point_1; +Vec2d travel_point_2; +Vec2d travel_point_3; + +static std::vector get_path_of_change_filament(const Print& print) +{ + // give safe value in case there is no start_end_points in config + std::vector out_points; + out_points.emplace_back(Vec2d(54, 0)); + out_points.emplace_back(Vec2d(54, 0)); + out_points.emplace_back(Vec2d(54, 245)); + + // get the start_end_points from config (20, -3) (54, 245) + Pointfs points = print.config().start_end_points.values; + if (points.size() != 2) + return out_points; + + Vec2d start_point = points[0]; + Vec2d end_point = points[1]; + + // the cutter area size(18, 28) + Pointfs excluse_area = print.config().bed_exclude_area.values; + if (excluse_area.size() != 4) + return out_points; + + double cutter_area_x = excluse_area[2].x() + 2; + double cutter_area_y = excluse_area[2].y() + 2; + + double start_x_position = start_point.x(); + double end_x_position = end_point.x(); + double end_y_position = end_point.y(); + + bool can_travel_form_left = true; + + // step 1: get the x-range intervals of all objects + std::vector> object_intervals; + for (PrintObject *print_object : print.objects()) { + const PrintInstances &print_instances = print_object->instances(); + BoundingBoxf3 bounding_box = print_instances[0].model_instance->get_object()->bounding_box(); + + if (bounding_box.min.x() < start_x_position && bounding_box.min.y() < cutter_area_y) + can_travel_form_left = false; + + std::pair object_scope = std::make_pair(bounding_box.min.x() - 2, bounding_box.max.x() + 2); + if (object_intervals.empty()) + object_intervals.push_back(object_scope); + else { + std::vector> new_object_intervals; + bool intervals_intersect = false; + std::pair new_merged_scope; + for (auto object_interval : object_intervals) { + if (object_interval.second >= object_scope.first && object_interval.first <= object_scope.second) { + if (intervals_intersect) { + new_merged_scope = std::make_pair(std::min(object_interval.first, new_merged_scope.first), std::max(object_interval.second, new_merged_scope.second)); + } else { // it is the first intersection + new_merged_scope = std::make_pair(std::min(object_interval.first, object_scope.first), std::max(object_interval.second, object_scope.second)); + } + intervals_intersect = true; + } else { + new_object_intervals.push_back(object_interval); + } + } + + if (intervals_intersect) { + new_object_intervals.push_back(new_merged_scope); + object_intervals = new_object_intervals; + } else + object_intervals.push_back(object_scope); + } + } + + // step 2: get the available x-range + std::sort(object_intervals.begin(), object_intervals.end(), + [](const std::pair &left, const std::pair &right) { + return left.first < right.first; + }); + std::vector> available_intervals; + double start_position = 0; + for (auto object_interval : object_intervals) { + if (object_interval.first > start_position) + available_intervals.push_back(std::make_pair(start_position, object_interval.first)); + start_position = object_interval.second; + } + available_intervals.push_back(std::make_pair(start_position, 255)); + + // step 3: get the nearest path + double new_path = 255; + for (auto available_interval : available_intervals) { + if (available_interval.first > end_x_position) { + double distance = available_interval.first - end_x_position; + new_path = abs(end_x_position - new_path) < distance ? new_path : available_interval.first; + break; + } else { + if (available_interval.second >= end_x_position) { + new_path = end_x_position; + break; + } else if (!can_travel_form_left && available_interval.second < start_x_position) { + continue; + } else { + new_path = available_interval.second; + } + } + } + + // step 4: generate path points (new_path == start_x_position means not need to change path) + Vec2d out_point_1; + Vec2d out_point_2; + Vec2d out_point_3; + if (new_path < start_x_position) { + out_point_1 = Vec2d(start_x_position, cutter_area_y); + out_point_2 = Vec2d(new_path, cutter_area_y); + out_point_3 = Vec2d(new_path, end_y_position); + } else { + out_point_1 = Vec2d(new_path, 0); + out_point_2 = Vec2d(new_path, 0); + out_point_3 = Vec2d(new_path, end_y_position); + } + + out_points.clear(); + out_points.emplace_back(out_point_1); + out_points.emplace_back(out_point_2); + out_points.emplace_back(out_point_3); + + return out_points; +} + +// Only add a newline in case the current G-code does not end with a newline. + static inline void check_add_eol(std::string& gcode) + { + if (!gcode.empty() && gcode.back() != '\n') + gcode += '\n'; + } + + + // Return true if tch_prefix is found in custom_gcode + static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder) + { + bool ok = false; + size_t from_pos = 0; + size_t pos = 0; + while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) { + if (pos + 1 == custom_gcode.size()) + break; + from_pos = pos + 1; + // only whitespace is allowed before the command + while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') { + if (!std::isspace(custom_gcode[pos])) + goto NEXT; + } + { + // we should also check that the extruder changes to what was expected + std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos)); + unsigned num = 0; + if (ss >> num) + ok = (num == next_extruder); + } + NEXT:; + } + return ok; + } + + std::string OozePrevention::pre_toolchange(GCode& gcodegen) + { + std::string gcode; + + // move to the nearest standby point + if (!this->standby_points.empty()) { + // get current position in print coordinates + Vec3d writer_pos = gcodegen.writer().get_position(); + Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); + + // find standby point + Point standby_point; + pos.nearest_point(this->standby_points, &standby_point); + + /* We don't call gcodegen.travel_to() because we don't need retraction (it was already + triggered by the caller) nor reduce_crossing_wall and also because the coordinates + of the destination point must not be transformed by origin nor current extruder offset. */ + gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), + "move to standby position"); + } + + if (gcodegen.config().standby_temperature_delta.value != 0) { + // we assume that heating is always slower than cooling, so no need to block + gcode += gcodegen.writer().set_temperature + (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, gcodegen.writer().extruder()->id()); + } + + return gcode; + } + + std::string OozePrevention::post_toolchange(GCode& gcodegen) + { + return (gcodegen.config().standby_temperature_delta.value != 0) ? + gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) : + std::string(); + } + + int + OozePrevention::_get_temp(GCode& gcodegen) + { + return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0) + ? gcodegen.config().nozzle_temperature_initial_layer.get_at(gcodegen.writer().extruder()->id()) + : gcodegen.config().nozzle_temperature.get_at(gcodegen.writer().extruder()->id()); + } + + std::string Wipe::wipe(GCode& gcodegen, bool toolchange, bool is_last) + { + std::string gcode; + + /* Reduce feedrate a bit; travel speed is often too high to move on existing material. + Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ + //OrcaSlicer + double wipe_speed = gcodegen.writer().config.travel_speed.value * gcodegen.config().wipe_speed.value / 100; + + // get the retraction length + double length = toolchange + ? gcodegen.writer().extruder()->retract_length_toolchange() + : gcodegen.writer().extruder()->retraction_length(); + // Shorten the retraction length by the amount already retracted before wipe. + length *= (1. - gcodegen.writer().extruder()->retract_before_wipe()); + + if (length >= 0) { + /* Calculate how long we need to travel in order to consume the required + amount of retraction. In other words, how far do we move in XY at wipe_speed + for the time needed to consume retraction_length at retraction_speed? */ + // BBS + double wipe_dist = scale_(gcodegen.config().wipe_distance.get_at(gcodegen.writer().extruder()->id())); + + /* Take the stored wipe path and replace first point with the current actual position + (they might be different, for example, in case of loop clipping). */ + Polyline wipe_path; + wipe_path.append(gcodegen.last_pos()); + wipe_path.append( + this->path.points.begin() + 1, + this->path.points.end() + ); + + wipe_path.clip_end(wipe_path.length() - wipe_dist); + + // subdivide the retraction in segments + if (!wipe_path.empty()) { + // BBS. Handle short path case. + if (wipe_path.length() < wipe_dist) { + wipe_dist = wipe_path.length(); + //BBS: avoid to divide 0 + wipe_dist = wipe_dist < EPSILON ? EPSILON : wipe_dist; + } + + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n"; + //BBS: don't need to enable cooling makers when this is the last wipe. Because no more cooling layer will clean this "_WIPE" + gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", (gcodegen.enable_cooling_markers() && !is_last) ? ";_WIPE" : ""); + for (const Line& line : wipe_path.lines()) { + double segment_length = line.length(); + /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one + due to rounding (TODO: test and/or better math for this) */ + double dE = length * (segment_length / wipe_dist) * 0.95; + //BBS: fix this FIXME + //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. + // Is it here for the cooling markers? Or should it be outside of the cycle? + //gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); + gcode += gcodegen.writer().extrude_to_xy( + gcodegen.point_to_gcode(line.b), + -dE, + "wipe and retract" + ); + } + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; + gcodegen.set_last_pos(wipe_path.points.back()); + } + + // prevent wiping again on same path + this->reset_path(); + } + + return gcode; + } + + static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt) + { + return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); + } + + std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const + { + if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + + std::string gcode; + + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); + + auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { + Vec2f out = Eigen::Rotation2Df(alpha) * pt; + out += m_wipe_tower_pos; + return out; + }; + + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; + if (! tcr.priming) { + start_pos = transform_wt_pt(start_pos); + end_pos = transform_wt_pt(end_pos); + } + + Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + + std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); + + // BBS: add partplate logic + Vec2f plate_origin_2d(m_plate_origin(0), m_plate_origin(1)); + + // BBS: toolchange gcode will move to start_pos, + // so only perform movement when printing sparse partition to support upper layer. + // start_pos is the position in plate coordinate. + if (! tcr.priming && tcr.is_finish_first) { + // Move over the wipe tower. + 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"); + gcode += gcodegen.unretract(); + } + + //BBS: if needed, write the gcode_label_objects_end then priming tower, if the retract, didn't did it. + gcodegen.m_writer.add_object_end_labels(gcode); + + double current_z = gcodegen.writer().get_position().z(); + if (z == -1.) // in case no specific z was provided, print at current_z pos + z = current_z; + if (! is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); + gcode += gcodegen.writer().unretract(); + } + + + // Process the end filament gcode. + std::string end_filament_gcode_str; + if (gcodegen.writer().extruder() != nullptr) { + // Process the custom filament_end_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); + const std::string& filament_end_gcode = gcodegen.config().filament_end_gcode.get_at(old_extruder_id); + if (gcodegen.writer().extruder() != nullptr && !filament_end_gcode.empty()) { + end_filament_gcode_str = gcodegen.placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_extruder_id); + check_add_eol(end_filament_gcode_str); + } + } + //BBS: increase toolchange count + gcodegen.m_toolchange_count++; + + // BBS: should be placed before toolchange parsing + std::string toolchange_retract_str = gcodegen.retract(true, false); + check_add_eol(toolchange_retract_str); + + // Process the custom change_filament_gcode. If it is empty, provide a simple Tn command to change the filament. + // Otherwise, leave control to the user completely. + std::string toolchange_gcode_str; + const std::string& change_filament_gcode = gcodegen.config().change_filament_gcode.value; +// m_max_layer_z = std::max(m_max_layer_z, tcr.print_z); + if (! change_filament_gcode.empty()) { + DynamicConfig config; + int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + config.set_key_value("toolchange_z", new ConfigOptionFloat(z)); +// config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + // BBS + { + GCodeWriter& gcode_writer = gcodegen.m_writer; + FullPrintConfig& full_config = gcodegen.m_config; + float old_retract_length = gcode_writer.extruder() != nullptr ? full_config.retraction_length.get_at(previous_extruder_id) : 0; + float new_retract_length = full_config.retraction_length.get_at(new_extruder_id); + float old_retract_length_toolchange = gcode_writer.extruder() != nullptr ? full_config.retract_length_toolchange.get_at(previous_extruder_id) : 0; + float new_retract_length_toolchange = full_config.retract_length_toolchange.get_at(new_extruder_id); + int old_filament_temp = gcode_writer.extruder() != nullptr ? (gcodegen.on_first_layer()? full_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : full_config.nozzle_temperature.get_at(previous_extruder_id)) : 210; + int new_filament_temp = gcodegen.on_first_layer() ? full_config.nozzle_temperature_initial_layer.get_at(new_extruder_id) : full_config.nozzle_temperature.get_at(new_extruder_id); + Vec3d nozzle_pos = gcode_writer.get_position(); + + float purge_volume = tcr.purge_volume < EPSILON ? 0 : std::max(tcr.purge_volume, g_min_purge_volume); + float filament_area = float((M_PI / 4.f) * pow(full_config.filament_diameter.get_at(new_extruder_id), 2)); + float purge_length = purge_volume / filament_area; + + int old_filament_e_feedrate = gcode_writer.extruder() != nullptr ? (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area) : 200; + old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; + int new_filament_e_feedrate = (int)(60.0 * full_config.filament_max_volumetric_speed.get_at(new_extruder_id) / filament_area); + new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; + + config.set_key_value("max_layer_z", new ConfigOptionFloat(gcodegen.m_max_layer_z)); + config.set_key_value("relative_e_axis", new ConfigOptionBool(full_config.use_relative_e_distances)); + config.set_key_value("toolchange_count", new ConfigOptionInt((int)gcodegen.m_toolchange_count)); + //BBS: fan speed is useless placeholer now, but we don't remove it to avoid + //slicing error in old change_filament_gcode in old 3MF + config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); + config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); + config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); + config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); + config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); + config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); + config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); + config.set_key_value("x_after_toolchange", new ConfigOptionFloat(start_pos(0))); + config.set_key_value("y_after_toolchange", new ConfigOptionFloat(start_pos(1))); + config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); + config.set_key_value("first_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); + config.set_key_value("second_flush_volume", new ConfigOptionFloat(purge_length / 2.f)); + config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate)); + config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate)); + config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x()))); + config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y()))); + config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x()))); + config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y()))); + config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); + config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); + + config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); + + int flush_count = std::min(g_max_flush_count, (int)std::round(purge_volume / g_purge_volume_one_time)); + // handle cases for very small purge + if (flush_count == 0 && purge_volume > 0) + flush_count += 1; + float flush_unit = purge_length / flush_count; + int flush_idx = 0; + for (; flush_idx < flush_count; flush_idx++) { + char key_value[64] = { 0 }; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); + } + + for (; flush_idx < g_max_flush_count; flush_idx++) { + char key_value[64] = { 0 }; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + config.set_key_value(key_value, new ConfigOptionFloat(0.f)); + } + } + toolchange_gcode_str = gcodegen.placeholder_parser_process("change_filament_gcode", change_filament_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + + // retract before toolchange + toolchange_gcode_str = toolchange_retract_str + toolchange_gcode_str; + //BBS + { + //BBS: current position and fan_speed is unclear after interting change_filament_gcode + check_add_eol(toolchange_gcode_str); + toolchange_gcode_str += ";_FORCE_RESUME_FAN_SPEED\n"; + gcodegen.writer().set_current_position_clear(false); + //BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_tool_change; + if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_str, temp_z_after_tool_change)) { + Vec3d pos = gcodegen.writer().get_position(); + pos(2) = temp_z_after_tool_change; + gcodegen.writer().set_position(pos); + } + } + + // move to start_pos for wiping after toolchange + std::string start_pos_str; + start_pos_str = gcodegen.travel_to(wipe_tower_point_to_object_point(gcodegen, start_pos + plate_origin_2d), erMixed, "Move to start pos"); + check_add_eol(start_pos_str); + toolchange_gcode_str += start_pos_str; + + // unretract before wiping + toolchange_gcode_str += gcodegen.unretract(); + check_add_eol(toolchange_gcode_str); + } + + std::string toolchange_command; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) + toolchange_gcode_str += toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. + } + + gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); + gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(new_extruder_id)); + gcodegen.placeholder_parser().set("long_retraction_when_cut", gcodegen.m_config.long_retractions_when_cut.get_at(new_extruder_id)); + + // Process the start filament gcode. + std::string start_filament_gcode_str; + const std::string& filament_start_gcode = gcodegen.config().filament_start_gcode.get_at(new_extruder_id); + if (!filament_start_gcode.empty()) { + // Process the filament_start_gcode for the active filament only. + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); + start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_extruder_id, &config); + check_add_eol(start_filament_gcode_str); + } + + // Insert the end filament, toolchange, and start filament gcode into the generated gcode. + DynamicConfig config; + config.set_key_value("filament_end_gcode", new ConfigOptionString(end_filament_gcode_str)); + config.set_key_value("change_filament_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("filament_start_gcode", new ConfigOptionString(start_filament_gcode_str)); + std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + check_add_eol(toolchange_gcode_str); + + //OrcaSlicer: set new PA for new filament. BBS: never use for Bambu Printer + if (!gcodegen.is_BBL_Printer() && gcodegen.config().enable_pressure_advance.get_at(new_extruder_id)) + gcode += gcodegen.writer().set_pressure_advance(gcodegen.config().pressure_advance.get_at(new_extruder_id)); + + // A phony move to the end position at the wipe tower. + gcodegen.writer().travel_to_xy((end_pos + plate_origin_2d).cast()); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos + plate_origin_2d)); + if (!is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); + gcode += gcodegen.writer().unretract(); + } + + else { + // Prepare a future wipe. + gcodegen.m_wipe.reset_path(); + for (const Vec2f& wipe_pt : tcr.wipe_path) + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); + } + + // Let the planner know we are traveling between objects. + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + return gcode; + } + + // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode + // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) + std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const + { + Vec2f extruder_offset; + if (m_single_extruder_multi_material) + extruder_offset = m_extruder_offsets[0].cast(); + else + extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); + + std::istringstream gcode_str(tcr.gcode); + std::string gcode_out; + std::string line; + Vec2f pos = tcr.start_pos; + Vec2f transformed_pos = pos; + Vec2f old_pos(-1000.1f, -1000.1f); + + while (gcode_str) { + std::getline(gcode_str, line); // we read the gcode line by line + + // All G1 commands should be translated and rotated. X and Y coords are + // only pushed to the output when they differ from last time. + // WT generator can override this by appending the never_skip_tag + if (line.find("G1 ") == 0) { + bool never_skip = false; + auto it = line.find(WipeTower::never_skip_tag()); + if (it != std::string::npos) { + // remove the tag and remember we saw it + never_skip = true; + line.erase(it, it + WipeTower::never_skip_tag().size()); + } + std::ostringstream line_out; + std::istringstream line_str(line); + line_str >> std::noskipws; // don't skip whitespace + char ch = 0; + while (line_str >> ch) { + if (ch == 'X' || ch == 'Y') + line_str >> (ch == 'X' ? pos.x() : pos.y()); + else + line_out << ch; + } + + transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; + + if (transformed_pos != old_pos || never_skip) { + line = line_out.str(); + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << "G1 "; + if (transformed_pos.x() != old_pos.x() || never_skip) + oss << " X" << transformed_pos.x() - extruder_offset.x(); + if (transformed_pos.y() != old_pos.y() || never_skip) + oss << " Y" << transformed_pos.y() - extruder_offset.y(); + oss << " "; + line.replace(line.find("G1 "), 3, oss.str()); + old_pos = transformed_pos; + } + } + + gcode_out += line + "\n"; + + // If this was a toolchange command, we should change current extruder offset + if (line == "[change_filament_gcode]") { + // BBS + if (!m_single_extruder_multi_material) { + extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); + + // If the extruder offset changed, add an extra move so everything is continuous + if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) + << "G1 X" << transformed_pos.x() - extruder_offset.x() + << " Y" << transformed_pos.y() - extruder_offset.y() + << "\n"; + gcode_out += oss.str(); + } + } + } + } + return gcode_out; + } + + + std::string WipeTowerIntegration::prime(GCode& gcodegen) + { + std::string gcode; +#if 0 + for (const WipeTower::ToolChangeResult& tcr : m_priming) { + if (! tcr.extrusions.empty()) + gcode += append_tcr(gcodegen, tcr, tcr.new_tool); + } +#endif + return gcode; + } + + std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer) + { + std::string gcode; + + assert(m_layer_idx >= 0); + if (m_layer_idx >= (int) m_tool_changes.size()) return gcode; + + // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, + // resulting in a wipe tower with sparse layers. + double wipe_tower_z = -1; + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) { + wipe_tower_z = m_last_wipe_tower_print_z; + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + if (m_tool_change_idx == 0 && !ignore_sparse) + wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; + } + + if (m_enable_timelapse_print && m_is_first_print) { + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][0], m_tool_changes[m_layer_idx][0].new_tool, wipe_tower_z); + m_tool_change_idx++; + m_is_first_print = false; + } + + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); + + if (!ignore_sparse) { + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); + m_last_wipe_tower_print_z = wipe_tower_z; + } + } + + return gcode; + } + + bool WipeTowerIntegration::is_empty_wipe_tower_gcode(GCode &gcodegen, int extruder_id, bool finish_layer) + { + assert(m_layer_idx >= 0); + if (m_layer_idx >= (int) m_tool_changes.size()) + return true; + + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) { + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + } + + if (m_enable_timelapse_print && m_is_first_print) { + return false; + } + + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) + throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); + + if (!ignore_sparse) { + return false; + } + } + + return true; + } + + // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. + std::string WipeTowerIntegration::finalize(GCode& gcodegen) + { + std::string gcode; + // BBS +#if 0 + if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON) + gcode += gcodegen.change_layer(m_final_purge.print_z); + gcode += append_tcr(gcodegen, m_final_purge, -1); +#endif + return gcode; + } + + const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; + +#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) + +// Collect pairs of object_layer + support_layer sorted by print_z. +// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. +std::vector GCode::collect_layers_to_print(const PrintObject& object) +{ + std::vector layers_to_print; + layers_to_print.reserve(object.layers().size() + object.support_layers().size()); + + /* + // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. + // This is the same logic as in support generator. + //FIXME should we use the printing extruders instead? + double gap_over_supports = object.config().support_top_z_distance; + // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports. + assert(!object.has_support() || gap_over_supports != 0. || object.config().support_material_synchronize_layers); + if (gap_over_supports != 0.) { + gap_over_supports = std::max(0., gap_over_supports); + // Not a soluble support, + double support_layer_height_min = 1000000.; + for (auto lh : object.print()->config().min_layer_height.values) + support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh)); + gap_over_supports += support_layer_height_min; + }*/ + + std::vector> warning_ranges; + + // Pair the object layers with the support layers by z. + size_t idx_object_layer = 0; + size_t idx_support_layer = 0; + const LayerToPrint* last_extrusion_layer = nullptr; + while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { + LayerToPrint layer_to_print; + double print_z_min = std::numeric_limits::max(); + if (idx_object_layer < object.layers().size()) { + layer_to_print.object_layer = object.layers()[idx_object_layer++]; + print_z_min = std::min(print_z_min, layer_to_print.object_layer->print_z); + } + + if (idx_support_layer < object.support_layers().size()) { + layer_to_print.support_layer = object.support_layers()[idx_support_layer++]; + print_z_min = std::min(print_z_min, layer_to_print.support_layer->print_z); + } + + if (layer_to_print.object_layer && layer_to_print.object_layer->print_z > print_z_min + EPSILON) { + layer_to_print.object_layer = nullptr; + --idx_object_layer; + } + + if (layer_to_print.support_layer && layer_to_print.support_layer->print_z > print_z_min + EPSILON) { + layer_to_print.support_layer = nullptr; + --idx_support_layer; + } + + layer_to_print.original_object = &object; + layers_to_print.push_back(layer_to_print); + + bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); + + // Check that there are extrusions on the very first layer. The case with empty + // first layer may result in skirt/brim in the air and maybe other issues. + if (layers_to_print.size() == 1u) { + if (!has_extrusions) + throw Slic3r::SlicingError(_(L("The following object(s) have empty initial layer and can't be printed. Please cut the bottom or enable supports.")), object.id().id); + } + + // In case there are extrusions on this layer, check there is a layer to lay it on. + if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. + || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) { + double top_cd = object.config().support_top_z_distance; + //double bottom_cd = object.config().support_bottom_z_distance == 0. ? top_cd : object.config().support_bottom_z_distance; + double bottom_cd = top_cd; + + double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd); + + // raft contact distance should not trigger any warning + if(last_extrusion_layer && last_extrusion_layer->support_layer) + extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); + + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + + layer_to_print.layer()->height + + std::max(0., extra_gap); + // Negative support_contact_z is not taken into account, it can result in false positives in cases + + if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) + warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z())); + } + // Remember last layer with extrusions. + if (has_extrusions) + last_extrusion_layer = &layers_to_print.back(); + } + + if (! warning_ranges.empty()) { + std::string warning; + size_t i = 0; + for (i = 0; i < std::min(warning_ranges.size(), size_t(5)); ++i) + warning += Slic3r::format(_(L("Object can't be printed for empty layer between %1% and %2%.")), + warning_ranges[i].first, warning_ranges[i].second) + "\n"; + warning += Slic3r::format(_(L("Object: %1%")), object.model_object()->name) + "\n" + + _(L("Maybe parts of the object at these height are too thin, or the object has faulty mesh")); + + const_cast(object.print())->active_step_add_warning( + PrintStateBase::WarningLevel::CRITICAL, warning, PrintStateBase::SlicingEmptyGcodeLayers); + } + + return layers_to_print; +} + +// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z +// will be printed for all objects at once. +// Return a list of items. +std::vector>> GCode::collect_layers_to_print(const Print& print) +{ + struct OrderingItem { + coordf_t print_z; + size_t object_idx; + size_t layer_idx; + }; + + std::vector> per_object(print.objects().size(), std::vector()); + std::vector ordering; + + std::vector errors; + + for (size_t i = 0; i < print.objects().size(); ++i) { + try { + per_object[i] = collect_layers_to_print(*print.objects()[i]); + } catch (const Slic3r::SlicingError &e) { + errors.push_back(e); + continue; + } + OrderingItem ordering_item; + ordering_item.object_idx = i; + ordering.reserve(ordering.size() + per_object[i].size()); + const LayerToPrint& front = per_object[i].front(); + for (const LayerToPrint& ltp : per_object[i]) { + ordering_item.print_z = ltp.print_z(); + ordering_item.layer_idx = <p - &front; + ordering.emplace_back(ordering_item); + } + } + + if (!errors.empty()) { throw Slic3r::SlicingErrors(errors); } + + std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; }); + + std::vector>> layers_to_print; + + // Merge numerically very close Z values. + for (size_t i = 0; i < ordering.size();) { + // Find the last layer with roughly the same print_z. + size_t j = i + 1; + coordf_t zmax = ordering[i].print_z + EPSILON; + for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j); + // Merge into layers_to_print. + std::pair> merged; + // Assign an average print_z to the set of layers with nearly equal print_z. + merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z); + merged.second.assign(print.objects().size(), LayerToPrint()); + for (; i < j; ++i) { + const OrderingItem& oi = ordering[i]; + assert(merged.second[oi.object_idx].layer() == nullptr); + merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); + } + layers_to_print.emplace_back(std::move(merged)); + } + + return layers_to_print; +} + +// free functions called by GCode::do_export() +namespace DoExport { +// static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) +// { +// const GCodeProcessorResult& result = processor.get_result(); +// print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); +// print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? +// get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; +// } + + static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) + { + const GCodeProcessorResult& result = processor.get_result(); + print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? + get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; + + // update filament statictics + double total_extruded_volume = 0.0; + double total_used_filament = 0.0; + double total_weight = 0.0; + double total_cost = 0.0; + + for (auto volume : result.print_statistics.total_volumes_per_extruder) { + total_extruded_volume += volume.second; + + size_t extruder_id = volume.first; + auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder& extr) {return extr.id() == extruder_id; }); + if (extruder == extruders.end()) + continue; + + double s = PI * sqr(0.5* extruder->filament_diameter()); + double weight = volume.second * extruder->filament_density() * 0.001; + total_used_filament += volume.second/s; + total_weight += weight; + total_cost += weight * extruder->filament_cost() * 0.001; + } + + print_statistics.total_extruded_volume = total_extruded_volume; + print_statistics.total_used_filament = total_used_filament; + print_statistics.total_weight = total_weight; + print_statistics.total_cost = total_cost; + + print_statistics.filament_stats = result.print_statistics.model_volumes_per_extruder; + } + + // if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found + // into pairs containing: + // first: source + // second: keyword + // to be shown in the warning notification + // The returned vector is empty if no keyword has been found + static std::vector> validate_custom_gcode(const Print& print) { + static const unsigned int MAX_TAGS_COUNT = 5; + std::vector> ret; + + auto check = [&ret](const std::string& source, const std::string& gcode) { + std::vector tags; + if (GCodeProcessor::contains_reserved_tags(gcode, MAX_TAGS_COUNT, tags)) { + if (!tags.empty()) { + size_t i = 0; + while (ret.size() < MAX_TAGS_COUNT && i < tags.size()) { + ret.push_back({ source, tags[i] }); + ++i; + } + } + } + }; + + const GCodeConfig& config = print.config(); + check(_(L("Machine start G-code")), config.machine_start_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Machine end G-code")), config.machine_end_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Before layer change G-code")), config.before_layer_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Layer change G-code")), config.layer_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Time lapse G-code")), config.time_lapse_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Change filament G-code")), config.change_filament_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Printing by object G-code")), config.printing_by_object_gcode.value); + //if (ret.size() < MAX_TAGS_COUNT) check(_(L("Color Change G-code")), config.color_change_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Pause G-code")), config.machine_pause_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) check(_(L("Template Custom G-code")), config.template_custom_gcode.value); + if (ret.size() < MAX_TAGS_COUNT) { + for (const std::string& value : config.filament_start_gcode.values) { + check(_(L("Filament start G-code")), value); + if (ret.size() == MAX_TAGS_COUNT) + break; + } + } + if (ret.size() < MAX_TAGS_COUNT) { + for (const std::string& value : config.filament_end_gcode.values) { + check(_(L("Filament end G-code")), value); + if (ret.size() == MAX_TAGS_COUNT) + break; + } + } + //BBS: no custom_gcode_per_print_z, don't need to check + //if (ret.size() < MAX_TAGS_COUNT) { + // const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; + // for (const auto& gcode : custom_gcode_per_print_z.gcodes) { + // check(_(L("Custom G-code")), gcode.extra); + // if (ret.size() == MAX_TAGS_COUNT) + // break; + // } + //} + + return ret; + } +} // namespace DoExport + +bool GCode::is_BBL_Printer() +{ + if (m_curr_print) + return m_curr_print->is_BBL_Printer(); + return false; +} + +//BBS : get the plate model's projection on first layer, contain plate offset,unscaled data +BoundingBoxf GCode::first_layer_projection(const Print& print) const +{ + // too slow +#if 0 + // seperatre points into object for parallel + std::vectorpoints(print.objects().size()); + auto objects = print.objects(); + // use parallel for to speed the iterate + tbb::parallel_for(tbb::blocked_range(0, points.size()), [&points,&objects](tbb::blocked_range r) { + for (auto index = r.begin(); index < r.end(); ++index) { + Polygons obj_islands; + unsigned int estimate_size = (objects[index]->layers().empty() ? 0 : objects[index]->layers().size() * objects[index]->layers().front()->lslices.size()); + obj_islands.reserve(estimate_size); + for (auto& layer : objects[index]->layers()) + for (auto& expoly : layer->lslices) + obj_islands.emplace_back(expoly.contour); + if (!objects[index]->support_layers().empty()) { + for (auto& layer : objects[index]->support_layers()) { + if (layer->support_type == stInnerNormal) + layer->support_fills.polygons_covered_by_spacing(obj_islands, float(SCALED_EPSILON)); + else if (layer->support_type == stInnerTree) { + for (auto& expoly : layer->lslices) + obj_islands.emplace_back(expoly.contour); + } + } + } + // caculate the transform + for (auto& instance : objects[index]->instances()) { + for (Polygon &poly : obj_islands) { + poly.translate(instance.shift); + Pointfs poly_points; + poly_points.reserve(poly.points.size()); + for (auto& point : poly.points) + poly_points.emplace_back(unscale(point)); + append(points[index], std::move(poly_points)); + } + } + } + }); + + Pointfs total_points; + //consider first layers for skirt,brim,wipe tower + int estimate_size =std::accumulate(points.begin(), points.end(), print.first_layer_convex_hull().size(), [](int sum, const Pointfs& point) {return sum + point.size(); });; + total_points.reserve(estimate_size); + + for (const auto& pt : print.first_layer_convex_hull().points) + total_points.emplace_back(unscale(pt.x(),pt.y())); + for (auto& point : points) + append(total_points, std::move(point)); + return BoundingBoxf(total_points); +#endif + + BoundingBoxf bbox; + for (auto& obj : print.objects()) { + for (auto& instance : obj->instances()) { + auto instance_bbox = instance.get_bounding_box(); + bbox.merge(BoundingBoxf{ { instance_bbox.min.x(),instance_bbox.min.y() }, { instance_bbox.max.x(),instance_bbox.max.y() } }); + } + } + + Pointfs points; + auto first_layer_point = print.first_layer_convex_hull().points; + points.reserve(first_layer_point.size()); + for (const auto& pt : first_layer_point) + points.emplace_back(unscale(pt.x(),pt.y())); + BoundingBoxf initial_layer_bbox(points); + + bbox.merge(initial_layer_bbox); + return bbox; +} + +void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) +{ + PROFILE_CLEAR(); + + // BBS + m_curr_print = print; + + CNumericLocalesSetter locales_setter; + + // Does the file exist? If so, we hope that it is still valid. + if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path))) + return; + + BOOST_LOG_TRIVIAL(info) << boost::format("Will export G-code to %1% soon")%path; + + GCodeProcessor::s_IsBBLPrinter = print->is_BBL_Printer(); + + print->set_started(psGCodeExport); + + // check if any custom gcode contains keywords used by the gcode processor to + // produce time estimation and gcode toolpaths + std::vector> validation_res = DoExport::validate_custom_gcode(*print); + if (!validation_res.empty()) { + std::string reports; + for (const auto& [source, keyword] : validation_res) { + reports += source + ": \"" + keyword + "\"\n"; + } + //print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, + // _(L("In the custom G-code were found reserved keywords:")) + "\n" + + // reports + + // _(L("This may cause problems in g-code visualization and printing time estimation."))); + std::string temp = "Dangerous keywords in custom Gcode: " + reports + "\nThis may cause problems in g-code visualization and printing time estimation."; + BOOST_LOG_TRIVIAL(warning) << temp; + } + + BOOST_LOG_TRIVIAL(info) << "Exporting G-code..." << log_memory_info(); + + // Remove the old g-code if it exists. + boost::nowide::remove(path); + + fs::path file_path(path); + fs::path folder = file_path.parent_path(); + if (!fs::exists(folder)) { + fs::create_directory(folder); + BOOST_LOG_TRIVIAL(error) << "[WARNING]: the parent path " + folder.string() +" is not there, create it!" << std::endl; + } + + std::string path_tmp(path); + path_tmp += ".tmp"; + + m_processor.initialize(path_tmp); + GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); + if (! file.is_open()) { + BOOST_LOG_TRIVIAL(error) << std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n" << std::endl; + if (!fs::exists(folder)) { + //fs::create_directory(folder); + BOOST_LOG_TRIVIAL(error) << "the parent path " + folder.string() +" is not there!!!" << std::endl; + } + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); + } + + try { + m_placeholder_parser_failed_templates.clear(); + this->_do_export(*print, file, thumbnail_cb); + file.flush(); + if (file.is_error()) { + file.close(); + boost::nowide::remove(path_tmp.c_str()); + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + } + } catch (std::exception & /* ex */) { + // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. + // Close and remove the file. + file.close(); + boost::nowide::remove(path_tmp.c_str()); + throw; + } + file.close(); + + check_placeholder_parser_failed(); + + BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); + // Post-process the G-code to update time stamps. + + m_timelapse_warning_code = 0; + if (m_config.printer_structure.value == PrinterStructure::psI3 && m_spiral_vase) { + m_timelapse_warning_code += 1; + } + if (m_config.printer_structure.value == PrinterStructure::psI3 && print->config().print_sequence == PrintSequence::ByObject) { + m_timelapse_warning_code += (1 << 1); + } + m_processor.result().timelapse_warning_code = m_timelapse_warning_code; + m_processor.result().support_traditional_timelapse = m_support_traditional_timelapse; + + bool activate_long_retraction_when_cut = false; + for (const auto& extruder : m_writer.extruders()) + activate_long_retraction_when_cut |= ( + m_config.long_retractions_when_cut.get_at(extruder.id()) + && m_config.retraction_distances_when_cut.get_at(extruder.id()) > 0 + ); + + m_processor.result().long_retraction_when_cut = activate_long_retraction_when_cut; + + { //BBS:check bed and filament compatible + const ConfigOptionDef *bed_type_def = print_config_def.get("curr_bed_type"); + assert(bed_type_def != nullptr); + const t_config_enum_values *bed_type_keys_map = bed_type_def->enum_keys_map; + const ConfigOptionInts *bed_temp_opt = m_config.option(get_bed_temp_key(m_config.curr_bed_type)); + for(auto extruder_id : m_initial_layer_extruders){ + int cur_bed_temp = bed_temp_opt->get_at(extruder_id); + if (cur_bed_temp == 0 && bed_type_keys_map != nullptr) { + for (auto item : *bed_type_keys_map) { + if (item.second == m_config.curr_bed_type) { + m_processor.result().bed_match_result = BedMatchResult(false, item.first, extruder_id); + break; + } + } + } + if (m_processor.result().bed_match_result.match == false) + break; + } + } + m_processor.set_filaments(m_writer.extruders()); + m_processor.finalize(true); +// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); + DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); + if (result != nullptr) { + *result = std::move(m_processor.extract_result()); + // set the filename to the correct value + result->filename = path; + } + + //BBS: add some log for error output + BOOST_LOG_TRIVIAL(debug) << boost::format("Finished processing gcode to %1% ") % path_tmp; + + std::error_code ret = rename_file(path_tmp, path); + if (ret) { + throw Slic3r::RuntimeError( + std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "error code " + ret.message() + '\n' + + "Is " + path_tmp + " locked?" + '\n'); + } + else { + BOOST_LOG_TRIVIAL(info) << boost::format("rename_file from %1% to %2% successfully")% path_tmp % path; + } + + BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); + print->set_done(psGCodeExport); + //BBS: set enable_label_object + result->label_object_enabled = m_enable_label_object; + + // Write the profiler measurements to file + PROFILE_UPDATE(); + PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); +} + +// free functions called by GCode::_do_export() +namespace DoExport { + static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled,const std::vector& filaments) + { + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware) + && config.silent_mode; + processor.reset(); + processor.apply_config(config); + processor.enable_stealth_time_estimator(silent_time_estimator_enabled); + processor.set_filaments(filaments); + } + +#if 0 + static double autospeed_volumetric_limit(const Print &print) + { + // get the minimum cross-section used in the print + std::vector mm3_per_mm; + for (auto object : print.objects()) { + for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = object->printing_region(region_id); + for (auto layer : object->layers()) { + const LayerRegion* layerm = layer->regions()[region_id]; + if (region.config().get_abs_value("inner_wall_speed") == 0 || + // BBS: remove small small_perimeter_speed config, and will absolutely + // remove related code if no other issue in the coming release. + //region.config().get_abs_value("small_perimeter_speed") == 0 || + region.config().outer_wall_speed.value == 0 || + region.config().get_abs_value("bridge_speed") == 0) + mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); + if (region.config().get_abs_value("sparse_infill_speed") == 0 || + region.config().get_abs_value("internal_solid_infill_speed") == 0 || + region.config().get_abs_value("top_surface_speed") == 0 || + region.config().get_abs_value("bridge_speed") == 0) + { + // Minimal volumetric flow should not be calculated over ironing extrusions. + // Use following lambda instead of the built-it method. + auto min_mm3_per_mm_no_ironing = [](const ExtrusionEntityCollection& eec) -> double { + double min = std::numeric_limits::max(); + for (const ExtrusionEntity* ee : eec.entities) + if (ee->role() != erIroning) + min = std::min(min, ee->min_mm3_per_mm()); + return min; + }; + + mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills)); + } + } + } + if (object->config().get_abs_value("support_speed") == 0 || + object->config().get_abs_value("support_interface_speed") == 0) + for (auto layer : object->support_layers()) + mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm()); + } + // filter out 0-width segments + mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); + double volumetric_speed = 0.; + if (! mm3_per_mm.empty()) { + // In order to honor max_print_speed we need to find a target volumetric + // speed that we can use throughout the print. So we define this target + // volumetric speed as the volumetric speed produced by printing the + // smallest cross-section at the maximum speed: any larger cross-section + // will need slower feedrates. + volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value; + // limit such volumetric speed with max_volumetric_speed if set + //BBS + //if (print.config().max_volumetric_speed.value > 0) + // volumetric_speed = std::min(volumetric_speed, print.config().max_volumetric_speed.value); + } + return volumetric_speed; + } +#endif + + static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) + { + // Calculate wiping points if needed + if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) { + Points skirt_points; + for (const ExtrusionEntity *ee : print.skirt().entities) + for (const ExtrusionPath &path : dynamic_cast(ee)->paths) + append(skirt_points, path.polyline.points); + if (! skirt_points.empty()) { + Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); + Polygons skirts; + for (unsigned int extruder_id : print.extruders()) { + const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id); + Polygon s(outer_skirt); + s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); + skirts.emplace_back(std::move(s)); + } + ooze_prevention.enable = true; + ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), float(scale_(3.))).front().equally_spaced_points(float(scale_(10.))); + #if 0 + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "ooze_prevention.svg", + red_polygons => \@skirts, + polygons => [$outer_skirt], + points => $gcodegen->ooze_prevention->standby_points, + ); + #endif + } + } + } + + //BBS: add plate id for thumbnail generate param + template + static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, int plate_id, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) + { + // Write thumbnails using base64 encoding + if (thumbnail_cb != nullptr) + { + const size_t max_row_length = 78; + //BBS: add plate id for thumbnail generate param + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true, plate_id }); + for (const ThumbnailData& data : thumbnails) + { + if (data.is_valid()) + { + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) + { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); + encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); + + output("; THUMBNAIL_BLOCK_START\n"); + output((boost::format("; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str()); + + unsigned int row_count = 0; + //BBS: optimize performance ,reduce too much memeory operation + size_t current_index = 0; + while(current_index &extruders, + PrintStatistics &print_statistics) + { + std::string filament_stats_string_out; + + print_statistics.clear(); + print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); + if (! extruders.empty()) { + //std::pair out_filament_used_mm ("; filament used [mm] = ", 0); + //std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); + //std::pair out_filament_used_g ("; filament used [g] = ", 0); + //std::pair out_filament_cost ("; filament cost = ", 0); + for (const Extruder &extruder : extruders) { + double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter + double filament_weight = extruded_volume * extruder.filament_density() * 0.001; + double filament_cost = filament_weight * extruder.filament_cost() * 0.001; + auto append = [&extruder](std::pair &dst, const char *tmpl, double value) { + assert(is_decimal_separator_point()); + while (dst.second < extruder.id()) { + // Fill in the non-printing extruders with zeros. + dst.first += (dst.second > 0) ? ", 0" : "0"; + ++ dst.second; + } + if (dst.second > 0) + dst.first += ", "; + char buf[64]; + sprintf(buf, tmpl, value); + dst.first += buf; + ++ dst.second; + }; + //append(out_filament_used_mm, "%.2lf", used_filament); + //append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); + if (filament_weight > 0.) { + print_statistics.total_weight = print_statistics.total_weight + filament_weight; + //append(out_filament_used_g, "%.2lf", filament_weight); + if (filament_cost > 0.) { + print_statistics.total_cost = print_statistics.total_cost + filament_cost; + //append(out_filament_cost, "%.2lf", filament_cost); + } + } + print_statistics.total_used_filament += used_filament; + print_statistics.total_extruded_volume += extruded_volume; + print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; + print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; + } + //filament_stats_string_out += out_filament_used_mm.first; + //filament_stats_string_out += "\n" + out_filament_used_cm3.first; + //if (out_filament_used_g.second) + //filament_stats_string_out += "\n" + out_filament_used_g.first; + //if (out_filament_cost.second) + // filament_stats_string_out += "\n" + out_filament_cost.first; + } + return filament_stats_string_out; + } +} + +#if 0 +// Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints. +static inline std::vector sort_object_instances_by_max_z(const Print &print) +{ + std::vector objects(print.objects().begin(), print.objects().end()); + std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); }); + std::vector instances; + instances.reserve(objects.size()); + for (const PrintObject *object : objects) + for (size_t i = 0; i < object->instances().size(); ++ i) + instances.emplace_back(&object->instances()[i]); + return instances; +} +#endif + +// Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model(). +//BBS: add sort logic for seq-print +std::vector sort_object_instances_by_model_order(const Print& print, bool init_order) +{ + // Build up map from ModelInstance* to PrintInstance* + std::vector> model_instance_to_print_instance; + model_instance_to_print_instance.reserve(print.num_object_instances()); + for (const PrintObject *print_object : print.objects()) + for (const PrintInstance &print_instance : print_object->instances()) + { + if (init_order) + const_cast(print_instance.model_instance)->arrange_order = print_instance.model_instance->id().id; + model_instance_to_print_instance.emplace_back(print_instance.model_instance, &print_instance); + } + std::sort(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), [](auto &l, auto &r) { return l.first->arrange_order < r.first->arrange_order; }); + + std::vector instances; + instances.reserve(model_instance_to_print_instance.size()); + for (const ModelObject *model_object : print.model().objects) + for (const ModelInstance *model_instance : model_object->instances) { + auto it = std::lower_bound(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), std::make_pair(model_instance, nullptr), [](auto &l, auto &r) { return l.first->arrange_order < r.first->arrange_order; }); + if (it != model_instance_to_print_instance.end() && it->first == model_instance) + instances.emplace_back(it->second); + } + std::sort(instances.begin(), instances.end(), [](auto& l, auto& r) { return l->model_instance->arrange_order < r->model_instance->arrange_order; }); + return instances; +} + +enum BambuBedType { + bbtUnknown = 0, + bbtCoolPlate = 1, + bbtEngineeringPlate = 2, + bbtHighTemperaturePlate = 3, + bbtTexturedPEIPlate = 4, +}; + +static BambuBedType to_bambu_bed_type(BedType type) +{ + BambuBedType bambu_bed_type = bbtUnknown; + if (type == btPC) + bambu_bed_type = bbtCoolPlate; + else if (type == btEP) + bambu_bed_type = bbtEngineeringPlate; + else if (type == btPEI) + bambu_bed_type = bbtHighTemperaturePlate; + else if (type == btPTE) + bambu_bed_type = bbtTexturedPEIPlate; + + return bambu_bed_type; +} + +void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) +{ + PROFILE_FUNC(); + + // modifies m_silent_time_estimator_enabled + DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled, m_writer.extruders()); + // resets analyzer's tracking data + m_last_height = 0.f; + m_last_layer_z = 0.f; + m_max_layer_z = 0.f; + m_last_width = 0.f; +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + m_last_mm3_per_mm = 0.; +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + // How many times will be change_layer() called? + // change_layer() in turn increments the progress bar status. + m_layer_count = 0; + if (print.config().print_sequence == PrintSequence::ByObject) { + // Add each of the object's layers separately. + for (auto object : print.objects()) { + std::vector zs; + zs.reserve(object->layers().size() + object->support_layers().size()); + for (auto layer : object->layers()) + zs.push_back(layer->print_z); + for (auto layer : object->support_layers()) + zs.push_back(layer->print_z); + std::sort(zs.begin(), zs.end()); + //BBS: merge numerically very close Z values. + auto end_it = std::unique(zs.begin(), zs.end()); + unsigned int temp_layer_count = (unsigned int)(end_it - zs.begin()); + for (auto it = zs.begin(); it != end_it - 1; it++) { + if (abs(*it - *(it + 1)) < EPSILON) + temp_layer_count--; + } + m_layer_count += (unsigned int)(object->instances().size() * temp_layer_count); + } + } else { + // Print all objects with the same print_z together. + std::vector zs; + for (auto object : print.objects()) { + zs.reserve(zs.size() + object->layers().size() + object->support_layers().size()); + for (auto layer : object->layers()) + zs.push_back(layer->print_z); + for (auto layer : object->support_layers()) + zs.push_back(layer->print_z); + } + if (!zs.empty()) + { + std::sort(zs.begin(), zs.end()); + //BBS: merge numerically very close Z values. + auto end_it = std::unique(zs.begin(), zs.end()); + m_layer_count = (unsigned int)(end_it - zs.begin()); + for (auto it = zs.begin(); it != end_it - 1; it++) { + if (abs(*it - *(it + 1)) < EPSILON) + m_layer_count--; + } + } + } + print.throw_if_canceled(); + + m_enable_cooling_markers = true; + this->apply_print_config(print.config()); + + //m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); + print.throw_if_canceled(); + + if (print.config().spiral_mode.value) + m_spiral_vase = make_unique(print.config()); +#ifdef HAS_PRESSURE_EQUALIZER + if (print.config().max_volumetric_extrusion_rate_slope_positive.value > 0 || + print.config().max_volumetric_extrusion_rate_slope_negative.value > 0) + m_pressure_equalizer = make_unique(&print.config()); + m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; +#else /* HAS_PRESSURE_EQUALIZER */ + m_enable_extrusion_role_markers = false; +#endif /* HAS_PRESSURE_EQUALIZER */ + + file.write_format("; HEADER_BLOCK_START\n"); + // Write information on the generator. + file.write_format("; %s\n", Slic3r::header_slic3r_generated().c_str()); + //BBS: total estimated printing time + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); + //BBS: total layer number + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Layer_Number_Placeholder).c_str()); + + //BBS: total filament used in mm + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder).c_str()); + //BBS: total filament used in cm3 + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Volume_Placeholder).c_str()); + //BBS: total filament used in g + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Weight_Placeholder).c_str()); + + //BBS: judge whether support skipping, if yes, list all label_object_id with sorted order here + if (print.num_object_instances() <= g_max_label_object && //Don't support too many objects on one plate + (print.num_object_instances() > 1) && //Don't support skipping single object + print.calib_params().mode == CalibMode::Calib_None) { //Don't support skipping in cali mode + m_enable_label_object = true; + m_label_objects_ids.clear(); + m_label_objects_ids.reserve(print.num_object_instances()); + for (const PrintObject* print_object : print.objects()) + for (const PrintInstance& print_instance : print_object->instances()) + m_label_objects_ids.push_back(print_instance.model_instance->get_labeled_id()); + + std::sort(m_label_objects_ids.begin(), m_label_objects_ids.end()); + + std::string objects_id_list = "; model label id: "; + for (auto it = m_label_objects_ids.begin(); it != m_label_objects_ids.end(); it++) + objects_id_list += (std::to_string(*it) + (it != m_label_objects_ids.end() - 1 ? "," : "\n")); + file.writeln(objects_id_list); + } + else { + m_enable_label_object = false; + m_label_objects_ids.clear(); + } + + { + std::string filament_density_list = "; filament_density: "; + (filament_density_list+=m_config.filament_density.serialize()) +='\n'; + file.writeln(filament_density_list); + + std::string filament_diameter_list = "; filament_diameter: "; + (filament_diameter_list += m_config.filament_diameter.serialize()) += '\n'; + file.writeln(filament_diameter_list); + + coordf_t max_height_z = -1; + for (const auto& object : print.objects()) + max_height_z = std::max(object->layers().back()->print_z, max_height_z); + + std::ostringstream max_height_z_tip; + max_height_z_tip<<"; max_z_height: " << std::fixed << std::setprecision(2) << max_height_z << '\n'; + file.writeln(max_height_z_tip.str()); + } + + file.write_format("; HEADER_BLOCK_END\n\n"); + + //BBS: write global config at the beginning of gcode file because printer need these config information + // Append full config, delimited by two 'phony' configuration keys CONFIG_BLOCK_START and CONFIG_BLOCK_END. + // The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer. + { + file.write("; CONFIG_BLOCK_START\n"); + std::string full_config; + append_full_config(print, full_config); + if (!full_config.empty()) + file.write(full_config); + file.write("; CONFIG_BLOCK_END\n\n"); + } + + //BBS: add plate id into thumbnail render logic + if(!print.is_BBL_Printer()){ + DoExport::export_thumbnails_to_file(thumbnail_cb, print.get_plate_index(), print.full_print_config().option("thumbnail_size")->values, + [&file](const char* sz) { file.write(sz); }, + [&print]() { print.throw_if_canceled(); }); + } + + // Write some terse information on the slicing parameters. + const PrintObject *first_object = print.objects().front(); + const double layer_height = first_object->config().layer_height.value; + const double initial_layer_print_height = print.config().initial_layer_print_height.value; + //BBS: remove useless information in gcode file +#if 0 + for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) { + const PrintRegion ®ion = print.get_print_region(region_id); + file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); + file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); + file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); + file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); + file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); + if (print.has_support_material()) + file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); + if (print.config().initial_layer_line_width.value > 0) + file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, initial_layer_print_height, true).width()); + file.write_format("\n"); + } + print.throw_if_canceled(); +#endif + + file.write_format("; EXECUTABLE_BLOCK_START\n"); + + // OrcaSlicer: Orca's implementation for skipping object, for klipper firmware printer only + if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) + file.write(set_object_info(&print)); + + // adds tags for time estimators + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str()); + + // Prepare the helper object for replacing placeholders in custom G-code and output filename. + m_placeholder_parser = print.placeholder_parser(); + m_placeholder_parser.update_timestamp(); + m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); + + // Get optimal tool ordering to minimize tool switches of a multi-exruder print. + // For a print by objects, find the 1st printing object. + ToolOrdering tool_ordering; + unsigned int initial_extruder_id = (unsigned int)-1; + //BBS: first non-support filament extruder + unsigned int initial_non_support_extruder_id; + unsigned int final_extruder_id = (unsigned int)-1; + bool has_wipe_tower = false; + std::vector print_object_instances_ordering; + std::vector::const_iterator print_object_instance_sequential_active; + if (print.config().print_sequence == PrintSequence::ByObject) { + // Order object instances for sequential print. + print_object_instances_ordering = sort_object_instances_by_model_order(print); +// print_object_instances_ordering = sort_object_instances_by_max_z(print); + // Find the 1st printing object, find its tool ordering and the initial extruder ID. + print_object_instance_sequential_active = print_object_instances_ordering.begin(); + for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) { + tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast(-1)) { + //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + initial_non_support_extruder_id = initial_extruder_id; + if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) { + bool has_non_support_filament = false; + for (unsigned int extruder : tool_ordering.all_extruders()) { + if (!print.config().filament_is_support.get_at(extruder)) { + has_non_support_filament = true; + break; + } + } + //BBS: find the non-support filament extruder of object + if (has_non_support_filament) { + bool find_initial_non_support_filament = false; + for (LayerTools layer_tools : tool_ordering.layer_tools()) { + if (!layer_tools.has_object) + continue; + for (unsigned int extruder : layer_tools.extruders) { + if (print.config().filament_is_support.get_at(extruder)) + continue; + initial_non_support_extruder_id = extruder; + find_initial_non_support_filament = true; + break; + } + + if (find_initial_non_support_filament) + break; + } + } + } + + break; + } + } + if (initial_extruder_id == static_cast(-1)) + // No object to print was found, cancel the G-code export. + throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small"))); + // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode. + // Use the extruder IDs collected from Regions. + this->set_extruders(print.extruders()); + + has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); + } else { + // Find tool ordering for all the objects at once, and the initial extruder ID. + // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. + tool_ordering = print.tool_ordering(); + tool_ordering.assign_custom_gcodes(print); + if (tool_ordering.all_extruders().empty()) + // No object to print was found, cancel the G-code export. + throw Slic3r::SlicingError(_(L("No object can be printed. Maybe too small"))); + has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); + // BBS: priming logic is removed, so 1st layer tool_ordering also respect the object tool sequence +#if 0 + initial_extruder_id = (has_wipe_tower && !print.config().single_extruder_multi_material_priming) ? + // The priming towers will be skipped. + tool_ordering.all_extruders().back() : + // Don't skip the priming towers. + tool_ordering.first_extruder(); +#else + initial_extruder_id = tool_ordering.first_extruder(); +#endif + //BBS: try to find the non-support filament extruder if is multi color and initial_extruder is support filament + if (initial_extruder_id != static_cast(-1)) { + initial_non_support_extruder_id = initial_extruder_id; + if (tool_ordering.all_extruders().size() > 1 && print.config().filament_is_support.get_at(initial_extruder_id)) { + bool has_non_support_filament = false; + for (unsigned int extruder : tool_ordering.all_extruders()) { + if (!print.config().filament_is_support.get_at(extruder)) { + has_non_support_filament = true; + break; + } + } + //BBS: find the non-support filament extruder of object + if (has_non_support_filament){ + bool find_initial_non_support_filament = false; + for (LayerTools layer_tools : tool_ordering.layer_tools()) { + if (!layer_tools.has_object) + continue; + for (unsigned int extruder : layer_tools.extruders) { + if (print.config().filament_is_support.get_at(extruder)) + continue; + initial_non_support_extruder_id = extruder; + find_initial_non_support_filament = true; + break; + } + + if (find_initial_non_support_filament) + break; + } + } + } + } + + // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z. + // Therefore initialize the printing extruders from there. + this->set_extruders(tool_ordering.all_extruders()); + // Order object instances using a nearest neighbor search. + print_object_instances_ordering = chain_print_object_instances(print); + } + if (initial_extruder_id == (unsigned int)-1) { + // Nothing to print! + initial_extruder_id = 0; + initial_non_support_extruder_id = 0; + } + print.throw_if_canceled(); + + m_cooling_buffer = make_unique(*this); + m_cooling_buffer->set_current_extruder(initial_extruder_id); + + // Emit machine envelope limits for the Marlin firmware. + this->print_machine_envelope(file, print); + + // Disable fan. + if (print.config().close_fan_the_first_x_layers.get_at(initial_extruder_id)) { + file.write(m_writer.set_fan(0)); + //BBS: disable additional fan + file.write(m_writer.set_additional_fan(0)); + } + + // Let the start-up script prime the 1st printing tool. + m_placeholder_parser.set("initial_tool", initial_extruder_id); + m_placeholder_parser.set("initial_extruder", initial_extruder_id); + //BBS + m_placeholder_parser.set("initial_no_support_tool", initial_non_support_extruder_id); + m_placeholder_parser.set("initial_no_support_extruder", initial_non_support_extruder_id); + m_placeholder_parser.set("current_extruder", initial_extruder_id); + //set the key for compatibilty + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(initial_extruder_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(initial_extruder_id)); + + m_placeholder_parser.set("retraction_distances_when_cut", new ConfigOptionFloats(m_config.retraction_distances_when_cut)); + m_placeholder_parser.set("long_retractions_when_cut",new ConfigOptionBools(m_config.long_retractions_when_cut)); + //Set variable for total layer count so it can be used in custom gcode. + m_placeholder_parser.set("total_layer_count", m_layer_count); + // Useful for sequential prints. + m_placeholder_parser.set("current_object_idx", 0); + // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. + m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); + //m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); + m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). + Vec2f plate_offset = m_writer.get_xy_offset(); + { + BoundingBoxf bbox(print.config().printable_area.values); + m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({ bbox.min.x() - plate_offset.x(), bbox.min.y() - plate_offset.y() })); + m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({ bbox.max.x() - plate_offset.x(), bbox.max.y() - plate_offset.y() })); + m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + } + { + // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + auto pts = std::make_unique(); + pts->values.reserve(print.first_layer_convex_hull().size()); + for (const Point &pt : print.first_layer_convex_hull().points) + pts->values.emplace_back(unscale(pt)); + + BoundingBoxf bbox = first_layer_projection(print); + BoundingBoxf bbox_without_plate_offset{ + {bbox.min.x() - plate_offset.x(),bbox.min.y() - plate_offset.y()}, + {bbox.max.x() - plate_offset.x(),bbox.max.y() - plate_offset.y()} + }; + + m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); + m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox_without_plate_offset.min.x(),bbox_without_plate_offset.min.y() })); + m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox_without_plate_offset.max.x(),bbox_without_plate_offset.max.y() })); + m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + + { // BBS:deal with head wrap detect + // use first layer convex_hull union with each object's bbox to check whether in head detect zone + Polygons object_projections; + for (auto& obj : print.objects()) { + for (auto& instance : obj->instances()) { + const auto& bbox = instance.get_bounding_box(); + Point min_p{ coord_t(scale_(bbox.min.x())),coord_t(scale_(bbox.min.y())) }; + Point max_p{ coord_t(scale_(bbox.max.x())),coord_t(scale_(bbox.max.y())) }; + Polygon instance_projection = { + {min_p.x(),min_p.y()}, + {max_p.x(),min_p.y()}, + {max_p.x(),max_p.y()}, + {min_p.x(),max_p.y()} + }; + object_projections.emplace_back(std::move(instance_projection)); + } + } + object_projections.emplace_back(print.first_layer_convex_hull()); + + Polygons project_polys = union_(object_projections); + Polygon head_wrap_detect_zone; + for (auto& point : print.config().head_wrap_detect_zone.values) + head_wrap_detect_zone.append(scale_(point).cast() + scale_(plate_offset).cast()); + + m_placeholder_parser.set("in_head_wrap_detect_zone", !intersection_pl(project_polys, {head_wrap_detect_zone}).empty()); + } + + // get center without wipe tower + BoundingBoxf bbox_wo_wt;// bounding box without wipe tower + for (auto& objPtr : print.objects()) { + BBoxData data; + bbox_wo_wt.merge(unscaled(objPtr->get_first_layer_bbox(data.area, data.layer_height, data.name))); + } + auto center = bbox_wo_wt.center(); + m_placeholder_parser.set("first_layer_center_no_wipe_tower", new ConfigOptionFloats{ {center.x(),center.y()}}); + } + + int max_chamber_temp = 0; + { + int curr_bed_type = m_config.curr_bed_type.getInt(); + + for (const auto& extruder : m_writer.extruders()) + max_chamber_temp = std::max(max_chamber_temp, m_config.chamber_temperatures.get_at(extruder.id())); + + std::string first_layer_bed_temp_str; + const ConfigOptionInts* first_bed_temp_opt = m_config.option(get_bed_temp_1st_layer_key((BedType)curr_bed_type)); + const ConfigOptionInts* bed_temp_opt = m_config.option(get_bed_temp_key((BedType)curr_bed_type)); + m_placeholder_parser.set("bbl_bed_temperature_gcode", new ConfigOptionBool(false)); + m_placeholder_parser.set("bed_temperature_initial_layer", new ConfigOptionInts(*first_bed_temp_opt)); + m_placeholder_parser.set("bed_temperature", new ConfigOptionInts(*bed_temp_opt)); + m_placeholder_parser.set("bed_temperature_initial_layer_single", new ConfigOptionInt(first_bed_temp_opt->get_at(initial_extruder_id))); + m_placeholder_parser.set("bed_temperature_initial_layer_vector", new ConfigOptionString("")); + m_placeholder_parser.set("chamber_temperature", new ConfigOptionInts({max_chamber_temp})); + m_placeholder_parser.set("overall_chamber_temperature", new ConfigOptionInt(max_chamber_temp)); + + //support variables `first_layer_temperature` and `first_layer_bed_temperature` + m_placeholder_parser.set("first_layer_bed_temperature", new ConfigOptionInts(*first_bed_temp_opt)); + m_placeholder_parser.set("first_layer_temperature", new ConfigOptionInts(m_config.nozzle_temperature_initial_layer)); + m_placeholder_parser.set("max_print_height", new ConfigOptionInt(m_config.printable_height)); + m_placeholder_parser.set("z_offset", new ConfigOptionFloat(0.0f)); + m_placeholder_parser.set("plate_name", new ConfigOptionString(print.get_plate_name())); + + //add during_print_exhaust_fan_speed + std::vector during_print_exhaust_fan_speed_num; + during_print_exhaust_fan_speed_num.reserve(m_config.during_print_exhaust_fan_speed.size()); + for (const auto& item : m_config.during_print_exhaust_fan_speed.values) + during_print_exhaust_fan_speed_num.emplace_back((int)(item / 100.0 * 255)); + m_placeholder_parser.set("during_print_exhaust_fan_speed_num",new ConfigOptionInts(during_print_exhaust_fan_speed_num)); + //BBS: calculate the volumetric speed of outer wall. Ignore pre-object setting and multi-filament, and just use the default setting + { + float filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_non_support_extruder_id); + float outer_wall_line_width = print.default_region_config().outer_wall_line_width.value; + if (outer_wall_line_width == 0.0) { + float default_line_width = print.default_object_config().line_width.value; + outer_wall_line_width = default_line_width == 0.0 ? m_config.nozzle_diameter.get_at(initial_non_support_extruder_id) : default_line_width; + } + Flow outer_wall_flow = Flow(outer_wall_line_width, m_config.layer_height, m_config.nozzle_diameter.get_at(initial_non_support_extruder_id)); + float outer_wall_speed = print.default_region_config().outer_wall_speed.value; + float outer_wall_volumetric_speed = outer_wall_speed * outer_wall_flow.mm3_per_mm(); + if (outer_wall_volumetric_speed > filament_max_volumetric_speed) + outer_wall_volumetric_speed = filament_max_volumetric_speed; + m_placeholder_parser.set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); + } + + if (print.calib_params().mode == CalibMode::Calib_PA_Line) { + m_placeholder_parser.set("scan_first_layer", new ConfigOptionBool(false)); + } + } + std::string machine_start_gcode = this->placeholder_parser_process("machine_start_gcode", print.config().machine_start_gcode.value, initial_extruder_id); + if (print.config().gcode_flavor != gcfKlipper) { + // Set bed temperature if the start G-code does not contain any bed temp control G-codes. + this->_print_first_layer_bed_temperature(file, print, machine_start_gcode, initial_extruder_id, true); + // Set extruder(s) temperature before and after start G-code. + this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, false); + } + + // BBS: chamber temp control for 3rd printers + if (!is_BBL_Printer() && print.config().support_chamber_temp_control.value && max_chamber_temp > 0 ){ + file.write(m_writer.set_chamber_temperature(max_chamber_temp,true)); + } + + // adds tag for processor + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + + // Write the custom start G-code + file.writeln(machine_start_gcode); + //BBS: gcode writer doesn't know where the real position of extruder is after inserting custom gcode + m_writer.set_current_position_clear(false); + m_start_gcode_filament = GCodeProcessor::get_gcode_last_filament(machine_start_gcode); + + + // Process filament-specific gcode. + /* if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the filament_start_gcode. + } else { + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); + file.writeln(this->placeholder_parser_process("filament_start_gcode", print.config().filament_start_gcode.values[initial_extruder_id], initial_extruder_id, &config)); + } +*/ + this->_print_first_layer_extruder_temperatures(file, print, machine_start_gcode, initial_extruder_id, true); + + if (m_config.support_air_filtration.getBool() && m_config.activate_air_filtration.get_at(initial_extruder_id)) { + file.write(m_writer.set_exhaust_fan(m_config.during_print_exhaust_fan_speed.get_at(initial_extruder_id), true)); + } + + print.throw_if_canceled(); + + // Set other general things. + file.write(this->preamble()); + + // Calculate wiping points if needed + DoExport::init_ooze_prevention(print, m_ooze_prevention); + print.throw_if_canceled(); + + // Collect custom seam data from all objects. + std::function throw_if_canceled_func = [&print]() { print.throw_if_canceled(); }; + m_seam_placer.init(print, throw_if_canceled_func); + + // BBS: get path for change filament + if (m_writer.multiple_extruders) { + std::vector points = get_path_of_change_filament(print); + if (points.size() == 3) { + travel_point_1 = points[0]; + travel_point_2 = points[1]; + travel_point_3 = points[2]; + } + } + + // BBS: priming logic is removed, always set first extruer here. + //if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) + { + // Set initial extruder only after custom start G-code. + // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. + file.write(this->set_extruder(initial_extruder_id, 0.)); + } + // BBS: set that indicates objs with brim + for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) { + if (!iter->second.empty()) + this->m_objsWithBrim.insert(iter->first); + } + for (auto iter = print.m_supportBrimMap.begin(); iter != print.m_supportBrimMap.end(); ++iter) { + if (!iter->second.empty()) + this->m_objSupportsWithBrim.insert(iter->first); + } + if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) m_brim_done = true; + + // OrcaSlicer: calib + if (print.calib_params().mode == CalibMode::Calib_PA_Line) { + std::string gcode; + if ((m_config.default_acceleration.value > 0 && m_config.outer_wall_acceleration.value > 0)) { + gcode += m_writer.set_acceleration((unsigned int) floor(m_config.outer_wall_acceleration.value + 0.5)); + } + + if (m_config.default_jerk.value > 0 && !this->is_BBL_Printer()) { + double jerk = m_config.outer_wall_jerk.value; + gcode += m_writer.set_jerk_xy(jerk); + } + + CalibPressureAdvanceLine pa_test(this); + double filament_max_volumetric_speed = m_config.option("filament_max_volumetric_speed")->get_at(initial_extruder_id); + Flow pattern_line = Flow(pa_test.line_width(), 0.2, m_config.nozzle_diameter.get_at(0)); + auto fast_speed = std::min(print.default_region_config().outer_wall_speed.value, filament_max_volumetric_speed / pattern_line.mm3_per_mm()); + auto slow_speed = fast_speed / 4; /*std::max(20.0, fast_speed / 10.0);*/ + pa_test.set_speed(fast_speed, slow_speed); + pa_test.draw_numbers() = print.calib_params().print_numbers; + auto params = print.calib_params(); + gcode += pa_test.generate_test(params.start, params.step, std::llround(std::ceil((params.end - params.start) / params.step)) + 1); + + file.write(gcode); + } + else { + // BBS: open spaghetti detector + // if (print.config().spaghetti_detector.value) + if (print.is_BBL_Printer()) file.write("M981 S1 P20000 ;open spaghetti detector\n"); + + // Do all objects for each layer. + if (print.config().print_sequence == PrintSequence::ByObject && !has_wipe_tower) { + size_t finished_objects = 0; + const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object; + for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { + const PrintObject &object = *(*print_object_instance_sequential_active)->print_object; + if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) { + tool_ordering = ToolOrdering(object, final_extruder_id); + unsigned int new_extruder_id = tool_ordering.first_extruder(); + if (new_extruder_id == (unsigned int) -1) + // Skip this object. + continue; + initial_extruder_id = new_extruder_id; + final_extruder_id = tool_ordering.last_extruder(); + assert(final_extruder_id != (unsigned int) -1); + } + print.throw_if_canceled(); + this->set_origin(unscale((*print_object_instance_sequential_active)->shift)); + + // BBS: prime extruder if extruder change happens before this object instance + bool prime_extruder = false; + if (finished_objects > 0) { + // Move to the origin position for the copy we're going to print. + // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. + m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer + m_avoid_crossing_perimeters.use_external_mp_once(); + // BBS. change tool before moving to origin point. + if (m_writer.need_toolchange(initial_extruder_id)) { + const PrintObjectConfig &object_config = object.config(); + coordf_t initial_layer_print_height = print.config().initial_layer_print_height.value; + file.write(this->set_extruder(initial_extruder_id, initial_layer_print_height, true)); + prime_extruder = true; + } else { + file.write(this->retract()); + } + file.write(m_writer.travel_to_z(m_max_layer_z)); + file.write(this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); + m_enable_cooling_markers = true; + // Disable motion planner when traveling to first object point. + m_avoid_crossing_perimeters.disable_once(); + // Ff we are printing the bottom layer of an object, and we have already finished + // another one, set first layer temperatures. This happens before the Z move + // is triggered, so machine has more time to reach such temperatures. + m_placeholder_parser.set("current_object_idx", int(finished_objects)); + std::string printing_by_object_gcode = this->placeholder_parser_process("printing_by_object_gcode", print.config().printing_by_object_gcode.value, + initial_extruder_id); + // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. + this->_print_first_layer_bed_temperature(file, print, printing_by_object_gcode, initial_extruder_id, false); + this->_print_first_layer_extruder_temperatures(file, print, printing_by_object_gcode, initial_extruder_id, false); + file.writeln(printing_by_object_gcode); + } + // Reset the cooling buffer internal state (the current position, feed rate, accelerations). + m_cooling_buffer->reset(this->writer().get_position()); + m_cooling_buffer->set_current_extruder(initial_extruder_id); + // Process all layers of a single object instance (sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file, + prime_extruder); + // BBS: close powerlost recovery + { + if (m_second_layer_things_done && print.is_BBL_Printer()) { + file.write("; close powerlost recovery\n"); + file.write("M1003 S0\n"); + } + } +#ifdef HAS_PRESSURE_EQUALIZER + if (m_pressure_equalizer) file.write(m_pressure_equalizer->process("", true)); +#endif /* HAS_PRESSURE_EQUALIZER */ + ++finished_objects; + // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. + // Reset it when starting another object from 1st layer. + m_second_layer_things_done = false; + prev_object = &object; + } + } else { + // Sort layers by Z. + // All extrusion moves with the same top layer height are extruded uninterrupted. + std::vector>> layers_to_print = collect_layers_to_print(print); + // Prusa Multi-Material wipe tower. + if (has_wipe_tower && !layers_to_print.empty()) { + m_wipe_tower.reset(new WipeTowerIntegration(print.config(), print.get_plate_index(), print.get_plate_origin(), *print.wipe_tower_data().priming.get(), + print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); + // BBS + // file.write(m_writer.travel_to_z(initial_layer_print_height + m_config.z_offset.value, "Move to the first layer height")); + file.write(m_writer.travel_to_z(initial_layer_print_height, "Move to the first layer height")); +#if 0 + if (print.config().single_extruder_multi_material_priming) { + file.write(m_wipe_tower->prime(*this)); + // Verify, whether the print overaps the priming extrusions. + BoundingBoxf bbox_print(get_print_extrusions_extents(print)); + coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; + for (const PrintObject *print_object : print.objects()) + bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); + bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); + BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); + bbox_prime.offset(0.5f); + bool overlap = bbox_prime.overlap(bbox_print); + + if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) { + file.write(this->retract()); + file.write("M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. + if (overlap) { + // Wait for the user to remove the priming extrusions. + file.write("M1 Remove priming towers and click button.\n"); + } else { + // Just wait for a bit to let the user check, that the priming succeeded. + //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. + file.write("M1 S10\n"); + } + } + //BBS: only support Marlin + //else { + // This is not Marlin, M1 command is probably not supported. + //if (overlap) { + // print.active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + // _(L("Your print is very close to the priming regions. " + // "Make sure there is no collision."))); + //} else { + // // Just continue printing, no action necessary. + //} + //} + } +#endif + print.throw_if_canceled(); + } + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); + // BBS: close powerlost recovery + { + if (m_second_layer_things_done && print.is_BBL_Printer()) { + file.write("; close powerlost recovery\n"); + file.write("M1003 S0\n"); + } + } +#ifdef HAS_PRESSURE_EQUALIZER + if (m_pressure_equalizer) file.write(m_pressure_equalizer->process("", true)); +#endif /* HAS_PRESSURE_EQUALIZER */ + if (m_wipe_tower) + // Purge the extruder, pull out the active filament. + file.write(m_wipe_tower->finalize(*this)); + } + } + + //BBS: the last retraction + // Write end commands to file. + file.write(this->retract(false, true)); + + // if needed, write the gcode_label_objects_end + { + std::string gcode; + m_writer.add_object_change_labels(gcode); + file.write(gcode); + } + + file.write(m_writer.set_fan(0)); + //BBS: make sure the additional fan is closed when end + file.write(m_writer.set_additional_fan(0)); + //BBS: close spaghetti detector + //Note: M981 is also used to tell xcam the last layer is finished, so we need always send it even if spaghetti option is disabled. + //if (print.config().spaghetti_detector.value) + if (print.is_BBL_Printer()) + file.write("M981 S0 P20000 ; close spaghetti detector\n"); + + // adds tag for processor + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + + // Process filament-specific gcode in extruder order. + { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + //BBS + //config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value)); + config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2))); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + if (print.config().single_extruder_multi_material) { + // Process the filament_end_gcode for the active filament only. + int extruder_id = m_writer.extruder()->id(); + config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); + file.writeln(this->placeholder_parser_process("filament_end_gcode", print.config().filament_end_gcode.get_at(extruder_id), extruder_id, &config)); + } else { + for (const std::string &end_gcode : print.config().filament_end_gcode.values) { + int extruder_id = (unsigned int)(&end_gcode - &print.config().filament_end_gcode.values.front()); + config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); + file.writeln(this->placeholder_parser_process("filament_end_gcode", end_gcode, extruder_id, &config)); + } + } + file.writeln(this->placeholder_parser_process("machine_end_gcode", print.config().machine_end_gcode, m_writer.extruder()->id(), &config)); + } + file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% + file.write(m_writer.postamble()); + + if (print.config().support_chamber_temp_control.value && max_chamber_temp>0) + file.write(m_writer.set_chamber_temperature(0, false)); //close chamber_temperature + + + // adds tags for time estimators + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); + file.write_format("; EXECUTABLE_BLOCK_END\n\n"); + + print.throw_if_canceled(); + + // Get filament stats. + file.write(DoExport::update_print_stats_and_format_filament_stats( + // Const inputs + has_wipe_tower, print.wipe_tower_data(), + m_writer.extruders(), + // Modifies + print.m_print_statistics)); + + bool activate_air_filtration = false; + for (const auto& extruder : m_writer.extruders()) + activate_air_filtration |= m_config.activate_air_filtration.get_at(extruder.id()); + activate_air_filtration &= m_config.support_air_filtration.getBool(); + + if (activate_air_filtration) { + int complete_print_exhaust_fan_speed = 0; + for (const auto& extruder : m_writer.extruders()) + if (m_config.activate_air_filtration.get_at(extruder.id())) + complete_print_exhaust_fan_speed = std::max(complete_print_exhaust_fan_speed, m_config.complete_print_exhaust_fan_speed.get_at(extruder.id())); + file.write(m_writer.set_exhaust_fan(complete_print_exhaust_fan_speed, true)); + } + + print.throw_if_canceled(); +} + +//BBS +void GCode::check_placeholder_parser_failed() +{ + if (! m_placeholder_parser_failed_templates.empty()) { + // G-code export proceeded, but some of the PlaceholderParser substitutions failed. + std::string msg = Slic3r::format(_(L("Failed to generate gcode for invalid custom G-code.\n\n"))); + for (const auto &name_and_error : m_placeholder_parser_failed_templates) + msg += name_and_error.first + " " + name_and_error.second + "\n"; + msg += Slic3r::format(_(L("Please check the custom G-code or use the default custom G-code."))); + throw Slic3r::PlaceholderParserError(msg); + } +} + +// Process all layers of all objects (non-sequential mode) with a parallel pipeline: +// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser +// and export G-code into file. +void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector>> &layers_to_print, + GCodeOutputStream &output_stream) +{ + // The pipeline is variable: The vase mode filter is optional. + size_t layer_to_print_idx = 0; + const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult { + if (layer_to_print_idx == layers_to_print.size()) { + fc.stop(); + return {}; + } else { + const std::pair>& layer = layers_to_print[layer_to_print_idx++]; + const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); + print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); + if (m_wipe_tower && layer_tools.has_wipe_tower) + m_wipe_tower->next_layer(); + //BBS + check_placeholder_parser_failed(); + print.throw_if_canceled(); + return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); + } + }); + if (m_spiral_vase) { + float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); + float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); + this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); + } + const auto spiral_mode = tbb::make_filter( + slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), & layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { + spiral_mode.enable(in.spiral_vase_enable); + bool last_layer = in.layer_id == layers_to_print.size() - 1; + return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; + }); + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { + return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + }); + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); } + ); + + // The pipeline elements are joined using const references, thus no copying is performed. + if (m_spiral_vase) + tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); + else + tbb::parallel_pipeline(12, generator & cooling & output); +} + +// Process all layers of a single object instance (sequential mode) with a parallel pipeline: +// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser +// and export G-code into file. +void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + std::vector layers_to_print, + const size_t single_object_idx, + GCodeOutputStream &output_stream, + // BBS + const bool prime_extruder) +{ + // The pipeline is variable: The vase mode filter is optional. + size_t layer_to_print_idx = 0; + const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx, prime_extruder](tbb::flow_control& fc) -> GCode::LayerResult { + if (layer_to_print_idx == layers_to_print.size()) { + fc.stop(); + return {}; + } else { + LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; + print.set_status(80, Slic3r::format(_(L("Generating G-code: layer %1%")), std::to_string(layer_to_print_idx))); + //BBS + check_placeholder_parser_failed(); + print.throw_if_canceled(); + return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx, prime_extruder); + } + }); + if (m_spiral_vase) { + float nozzle_diameter = EXTRUDER_CONFIG(nozzle_diameter); + float max_xy_smoothing = m_config.get_abs_value("spiral_mode_max_xy_smoothing", nozzle_diameter); + this->m_spiral_vase->set_max_xy_smoothing(max_xy_smoothing); + } + const auto spiral_mode = tbb::make_filter( + slic3r_tbb_filtermode::serial_in_order, [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](GCode::LayerResult in) -> GCode::LayerResult { + spiral_mode.enable(in.spiral_vase_enable); + bool last_layer = in.layer_id == layers_to_print.size() - 1; + return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + }); + const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in)->std::string { + return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + }); + const auto output = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); } + ); + + // The pipeline elements are joined using const references, thus no copying is performed. + if (m_spiral_vase) + tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); + else + tbb::parallel_pipeline(12, generator & cooling & output); +} + +std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) +{ + try { + return m_placeholder_parser.process(templ, current_extruder_id, config_override, &m_placeholder_parser_context); + } catch (std::runtime_error &err) { + // Collect the names of failed template substitutions for error reporting. + auto it = m_placeholder_parser_failed_templates.find(name); + if (it == m_placeholder_parser_failed_templates.end()) + // Only if there was no error reported for this template, store the first error message into the map to be reported. + // We don't want to collect error message for each and every occurence of a single custom G-code section. + m_placeholder_parser_failed_templates.insert(it, std::make_pair(name, std::string(err.what()))); + // Insert the macro error message into the G-code. + return + std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + + err.what() + + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; + } +} + +// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code. +// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. +static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out) +{ + temp_out = -1; + if (gcode.empty()) + return false; + + const char *ptr = gcode.data(); + bool temp_set_by_gcode = false; + while (*ptr != 0) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'M' || // Line starts with 'M'. It is a machine command. + (*ptr == 'G' && include_g10)) { // Only check for G10 if requested + bool is_gcode = *ptr == 'G'; + ++ ptr; + // Parse the M or G code value. + char *endptr = nullptr; + int mgcode = int(strtol(ptr, &endptr, 10)); + if (endptr != nullptr && endptr != ptr && + is_gcode ? + // G10 found + mgcode == 10 : + // M104/M109 or M140/M190 found. + (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) { + ptr = endptr; + if (! is_gcode) + // Let the caller know that the custom M-code sets the temperature. + temp_set_by_gcode = true; + // Now try to parse the temperature value. + // While not at the end of the line: + while (strchr(";\r\n\0", *ptr) == nullptr) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'S') { + // Skip whitespaces. + for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); + // Parse an int. + endptr = nullptr; + long temp_parsed = strtol(ptr, &endptr, 10); + if (endptr > ptr) { + ptr = endptr; + temp_out = temp_parsed; + // Let the caller know that the custom G-code sets the temperature + // Only do this after successfully parsing temperature since G10 + // can be used for other reasons + temp_set_by_gcode = true; + } + } else { + // Skip this word. + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + } + } + } + } + // Skip the rest of the line. + for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); + // Skip the end of line indicators. + for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); + } + return temp_set_by_gcode; +} + +// Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. +// Do not process this piece of G-code by the time estimator, it already knows the values through another sources. +void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) +{ + if (print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) { + file.write_format("M201 X%d Y%d Z%d E%d\n", + int(print.config().machine_max_acceleration_x.values.front() + 0.5), + int(print.config().machine_max_acceleration_y.values.front() + 0.5), + int(print.config().machine_max_acceleration_z.values.front() + 0.5), + int(print.config().machine_max_acceleration_e.values.front() + 0.5)); + file.write_format("M203 X%d Y%d Z%d E%d\n", + int(print.config().machine_max_speed_x.values.front() + 0.5), + int(print.config().machine_max_speed_y.values.front() + 0.5), + int(print.config().machine_max_speed_z.values.front() + 0.5), + int(print.config().machine_max_speed_e.values.front() + 0.5)); + + // Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about + // Legacy Marlin should export travel acceleration the same as printing acceleration. + // MarlinFirmware has the two separated. + int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy + ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) + : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); + file.write_format("M204 P%d R%d T%d\n", + int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), + int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), + travel_acc); + + + assert(is_decimal_separator_point()); + file.write_format("M205 X%.2lf Y%.2lf Z%.2lf E%.2lf\n", + print.config().machine_max_jerk_x.values.front(), + print.config().machine_max_jerk_y.values.front(), + print.config().machine_max_jerk_z.values.front(), + print.config().machine_max_jerk_e.values.front()); + //BBS: don't support M205 Sx Tx + //file.write_format("M205 S%d T%d\n", + // int(print.config().machine_min_extruding_rate.values.front() + 0.5), + // int(print.config().machine_min_travel_rate.values.front() + 0.5)); + } +} + +// BBS +int GCode::get_bed_temperature(const int extruder_id, const bool is_first_layer, const BedType bed_type) const +{ + std::string bed_temp_key = is_first_layer ? get_bed_temp_1st_layer_key(bed_type) : get_bed_temp_key(bed_type); + const ConfigOptionInts* bed_temp_opt = m_config.option(bed_temp_key); + return bed_temp_opt->get_at(extruder_id); +} + + +// Write 1st layer bed temperatures into the G-code. +// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. +// M140 - Set Extruder Temperature +// M190 - Set Extruder Temperature and Wait +void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +{ + // Initial bed temperature based on the first extruder. + // BBS + std::vector temps_per_bed; + int bed_temp = get_bed_temperature(first_printing_extruder_id, true, print.config().curr_bed_type); + + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode); + // BBS +#if 0 + if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000) + temp = temp_by_gcode; +#endif + + // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if + // the custom start G-code emited these. + std::string set_temp_gcode = m_writer.set_bed_temperature(bed_temp, wait); + if (! temp_set_by_gcode) + file.write(set_temp_gcode); +} + +// Write 1st layer extruder temperatures into the G-code. +// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. +// M104 - Set Extruder Temperature +// M109 - Set Extruder Temperature and Wait +// RepRapFirmware: G10 Sxx +void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +{ + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware; + if (custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) { + // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. + int temp = print.config().nozzle_temperature_initial_layer.get_at(first_printing_extruder_id); + if (temp_by_gcode >= 0 && temp_by_gcode < 1000) + temp = temp_by_gcode; + m_writer.set_temperature(temp, wait, first_printing_extruder_id); + } else { + // Custom G-code does not set the extruder temperature. Do it now. + if (print.config().single_extruder_multi_material.value) { + // Set temperature of the first printing extruder only. + int temp = print.config().nozzle_temperature_initial_layer.get_at(first_printing_extruder_id); + if (temp > 0) + file.write(m_writer.set_temperature(temp, wait, first_printing_extruder_id)); + } else { + // Set temperatures of all the printing extruders. + for (unsigned int tool_id : print.extruders()) { + int temp = print.config().nozzle_temperature_initial_layer.get_at(tool_id); + if (print.config().ooze_prevention.value) + temp += print.config().standby_temperature_delta.value; + if (temp > 0) + file.write(m_writer.set_temperature(temp, wait, tool_id)); + } + } + } +} + +inline GCode::ObjectByExtruder& object_by_extruder( + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, + size_t num_objects) +{ + std::vector &objects_by_extruder = by_extruder[extruder_id]; + if (objects_by_extruder.empty()) + objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); + return objects_by_extruder[object_idx]; +} + +inline std::vector& object_islands_by_extruder( + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, + size_t num_objects, + size_t num_islands) +{ + std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; + if (islands.empty()) + islands.assign(num_islands, GCode::ObjectByExtruder::Island()); + return islands; +} + +std::vector GCode::sort_print_object_instances( + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx) +{ + std::vector out; + + if (ordering == nullptr) { + // Sequential print, single object is being printed. + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + //BBS:add the support of shared print object + const PrintObject *print_object = layers[layer_id].original_object; + //const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx, print_object->instances()[single_object_instance_idx].model_instance->get_labeled_id()); + } + } else { + // Create mapping from PrintObject* to ObjectByExtruder*. + std::vector> sorted; + sorted.reserve(objects_by_extruder.size()); + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + //BBS:add the support of shared print object + const PrintObject *print_object = layers[layer_id].original_object; + //const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + sorted.emplace_back(print_object, &object_by_extruder); + } + std::sort(sorted.begin(), sorted.end()); + + if (! sorted.empty()) { + out.reserve(sorted.size()); + for (const PrintInstance *instance : *ordering) { + const PrintObject &print_object = *instance->print_object; + //BBS:add the support of shared print object + //const PrintObject* print_obj_ptr = &print_object; + //if (print_object.get_shared_object()) + // print_obj_ptr = print_object.get_shared_object(); + std::pair key(&print_object, nullptr); + auto it = std::lower_bound(sorted.begin(), sorted.end(), key); + if (it != sorted.end() && it->first == &print_object) + // ObjectByExtruder for this PrintObject was found. + out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data(), instance->model_instance->get_labeled_id()); + } + } + } + return out; +} + +namespace ProcessLayer +{ + + static std::string emit_custom_gcode_per_print_z( + GCode &gcodegen, + const CustomGCode::Item *custom_gcode, + unsigned int current_extruder_id, + // ID of the first extruder printing this layer. + unsigned int first_extruder_id, + const PrintConfig &config) + { + std::string gcode; + // BBS + bool single_filament_print = config.filament_diameter.size() == 1; + + if (custom_gcode != nullptr) { + // Extruder switches are processed by LayerTools, they should be filtered out. + assert(custom_gcode->type != CustomGCode::ToolChange); + + CustomGCode::Type gcode_type = custom_gcode->type; + bool color_change = gcode_type == CustomGCode::ColorChange; + bool tool_change = gcode_type == CustomGCode::ToolChange; + // Tool Change is applied as Color Change for a single extruder printer only. + assert(!tool_change || single_filament_print); + + std::string pause_print_msg; + int m600_extruder_before_layer = -1; + if (color_change && custom_gcode->extruder > 0) + m600_extruder_before_layer = custom_gcode->extruder - 1; + else if (gcode_type == CustomGCode::PausePrint) + pause_print_msg = custom_gcode->extra; + //BBS: inserting color gcode is removed +#if 0 + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (color_change || tool_change) + { + assert(m600_extruder_before_layer >= 0); + // Color Change or Tool Change as Color Change. + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Color_Change) + ",T" + std::to_string(m600_extruder_before_layer) + "," + custom_gcode->color + "\n"; + + if (!single_filament_print && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer + // && !MMU1 + ) { + //! FIXME_in_fw show message during print pause + DynamicConfig cfg; + cfg.set_key_value("color_change_extruder", new ConfigOptionInt(m600_extruder_before_layer)); + gcode += gcodegen.placeholder_parser_process("machine_pause_gcode", config.machine_pause_gcode, current_extruder_id, &cfg); + gcode += "\n"; + gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; + } + else { + gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id); + gcode += "\n"; + //FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after + // return from M600. Thus the G-code generated by the following line is ignored. + // see GH issue #6362 + gcodegen.writer().unretract(); + } + } + else { +#endif + if (gcode_type == CustomGCode::PausePrint) // Pause print + { + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n"; + //! FIXME_in_fw show message during print pause + //if (!pause_print_msg.empty()) + // gcode += "M117 " + pause_print_msg + "\n"; + gcode += gcodegen.placeholder_parser_process("machine_pause_gcode", config.machine_pause_gcode, current_extruder_id) + "\n"; + } + else { + // add tag for processor + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n"; + if (gcode_type == CustomGCode::Template) // Template Custom Gcode + gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id); + else // custom Gcode + gcode += custom_gcode->extra; + + } + gcode += "\n"; +#if 0 + } +#endif + } + + return gcode; + } +} // namespace ProcessLayer + +namespace Skirt { + static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) + { + // Prime all extruders printing over the 1st layer over the skirt lines. + size_t n_loops = print.skirt().entities.size(); + size_t n_tools = layer_tools.extruders.size(); + size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools; + + // BBS. Extrude skirt with first extruder if min_skirt_length is zero + const PrintConfig &config = print.config(); + if (Print::min_skirt_length < EPSILON) { + skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, n_loops); + } else { + for (size_t i = 0; i < n_loops; i += lines_per_extruder) + skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair(i, std::min(i + lines_per_extruder, n_loops)); + } + } + + static std::map> make_skirt_loops_per_extruder_1st_layer( + const Print &print, + const LayerTools &layer_tools, + // Heights (print_z) at which the skirt has already been extruded. + std::vector &skirt_done) + { + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder_out; + //For sequential print, the following test may fail when extruding the 2nd and other objects. + // assert(skirt_done.empty()); + if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) { + skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); + skirt_done.emplace_back(layer_tools.print_z); + } + return skirt_loops_per_extruder_out; + } + + static std::map> make_skirt_loops_per_extruder_other_layers( + const Print &print, + const LayerTools &layer_tools, + // Heights (print_z) at which the skirt has already been extruded. + std::vector &skirt_done) + { + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder_out; + if (print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt && + // Not enough skirt layers printed yet. + //FIXME infinite or high skirt does not make sense for sequential print! + (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) { + bool valid = ! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON; + assert(valid); + // This print_z has not been extruded yet (sequential print) + // FIXME: The skirt_done should not be empty at this point. The check is a workaround + if (valid) { +#if 0 + // Prime just the first printing extruder. This is original Slic3r's implementation. + skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirt_loops.value); +#else + // Prime all extruders planned for this layer, see + skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); +#endif + assert(!skirt_done.empty()); + skirt_done.emplace_back(layer_tools.print_z); + } + } + return skirt_loops_per_extruder_out; + } + +} // namespace Skirt + +inline std::string get_instance_name(const PrintObject* object, size_t inst_id) { + auto obj_name = object->model_object()->name; + // replace space in obj_name with '-' + std::replace(obj_name.begin(), obj_name.end(), ' ', '_'); + + return (boost::format("%1%_id_%2%_copy_%3%") % obj_name % object->get_klipper_object_id() % inst_id).str(); +} + +inline std::string get_instance_name(const PrintObject* object, const PrintInstance& inst) { + return get_instance_name(object, inst.id); +} + +// In sequential mode, process_layer is called once per each object and its copy, +// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. +// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. +// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths +// and performing the extruder specific extrusions together. +//在顺序模式下,每个对象及其副本调用一次process_layer, +//因此,层将包含一个条目,singleobjectinstance-idx将指向对象的副本。 +//在非顺序模式下,根据每个print_z高度调用process_layer,并累积所有对象和支持层。 +//对于多材料打印,该程序通过收集挤出机特定的挤出路径,最大限度地减少了挤出机的切换 +//并一起进行挤出机特定的挤出。 +GCode::LayerResult GCode::process_layer( + const Print &print, + // Set of object & print layers of the same PrintObject and with the same print_z. + //具有相同PrintObject和相同print_z的对象和打印层集。 + const std::vector &layers, + const LayerTools &layer_tools, + const bool last_layer, + // Pairs of PrintObject index and its instance index. + //成对的PrintObject索引及其实例索引。 + const std::vector *ordering, + // If set to size_t(-1), then print all copies of all objects. + // Otherwise print a single copy of a single object. + //如果设置为size_t(-1),则打印所有对象的所有副本。 + //否则,打印单个对象的单个副本。 + const size_t single_object_instance_idx, + // BBS + const bool prime_extruder) +{ + assert(! layers.empty()); + // Either printing all copies of all objects, or just a single copy of a single object. + //打印所有对象的所有副本,或仅打印单个对象的单个副本。 + assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); + + // First object, support and raft layer, if available. + //第一个对象,支撑和筏板层(如果可用)。 + const Layer *object_layer = nullptr; + const SupportLayer *support_layer = nullptr; + const SupportLayer *raft_layer = nullptr; + for (const LayerToPrint &l : layers) { + if (l.object_layer && ! object_layer) + object_layer = l.object_layer; + if (l.support_layer) { + if (! support_layer) + support_layer = l.support_layer; + if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers()) + raft_layer = support_layer; + } + } + + const Layer* layer_ptr = nullptr; + if (object_layer != nullptr) + layer_ptr = object_layer; + else if (support_layer != nullptr) + layer_ptr = support_layer; + const Layer& layer = *layer_ptr; + GCode::LayerResult result { {}, layer.id(), false, last_layer }; + if (layer_tools.extruders.empty()) + // Nothing to extrude. + return result; + + // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. + //使用相等的print_z提取这组层的第一个object_layer和support_layer。 + coordf_t print_z = layer.print_z; + //BBS: using layer id to judge whether the layer is first layer is wrong. Because if the normal + //support is attached above the object, and support layers has independent layer height, then the lowest support + //interface layer id is 0. + //BBS:使用层id判断第一层是否错误。因为如果正常 + //支撑物附着在物体上方,支撑层具有独立的层高,然后是最低的支撑物 + //接口层id为0。 + bool first_layer = (layer.id() == 0 && abs(layer.bottom_z()) < EPSILON); + unsigned int first_extruder_id = layer_tools.extruders.front(); + + // Initialize config with the 1st object to be printed at this layer. + //使用此层要打印的第一个对象初始化配置。 + m_config.apply(layer.object()->config(), true); + + // Check whether it is possible to apply the spiral vase logic for this layer. + // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. + //检查是否可以对此层应用螺旋花瓶逻辑。 + //提醒一下:螺旋花瓶模式只允许用于单个对象、单个材质打印。 + m_enable_loop_clipping = true; + if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { + bool enable = (layer.id() > 0 || !print.has_brim()) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); + if (enable) { + for (const LayerRegion *layer_region : layer.regions()) + if (size_t(layer_region->region().config().bottom_shell_layers.value) > layer.id() || + layer_region->perimeters.items_count() > 1u || + layer_region->fills.items_count() > 0) { + enable = false; + break; + } + } + result.spiral_vase_enable = enable; + // If we're going to apply spiralvase to this layer, disable loop clipping. + //如果我们要将spiralvase应用于此层,请禁用循环剪裁。 + m_enable_loop_clipping = !enable; + } + + std::string gcode; + assert(is_decimal_separator_point()); // for the sprintfs + + // add tag for processor + //为处理器添加标签 + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n"; + // export layer z + char buf[64]; + sprintf(buf, "; Z_HEIGHT: %g\n", print_z); + gcode += buf; + // export layer height + float height = first_layer ? static_cast(print_z) : static_cast(print_z) - m_last_layer_z; + sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), height); + gcode += buf; + // update caches + m_last_layer_z = static_cast(print_z); + m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); + m_last_height = height; + + // Set new layer - this will change Z and force a retraction if retract_when_changing_layer is enabled. + //设置新图层-如果启用了retrackt_when_changing_layer,这将更改Z并强制收缩。 + if (! print.config().before_layer_change_gcode.value.empty()) { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + gcode += this->placeholder_parser_process("before_layer_change_gcode", + print.config().before_layer_change_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; + } + + PrinterStructure printer_structure = m_config.printer_structure.value; + bool need_insert_timelapse_gcode_for_traditional = false; + if (printer_structure == PrinterStructure::psI3 && + !m_spiral_vase && + (!m_wipe_tower || !m_wipe_tower->enable_timelapse_print()) && + print.config().print_sequence == PrintSequence::ByLayer) { + need_insert_timelapse_gcode_for_traditional = true; + } + bool has_insert_timelapse_gcode = false; + bool has_wipe_tower = (layer_tools.has_wipe_tower && m_wipe_tower); + + auto insert_timelapse_gcode = [this, print_z, &print]() -> std::string { + std::string gcode_res; + if (!print.config().time_lapse_gcode.value.empty()) { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + gcode_res = this->placeholder_parser_process("timelapse_gcode", print.config().time_lapse_gcode.value, m_writer.extruder()->id(), &config) + "\n"; + } + return gcode_res; + }; + + // BBS: don't use lazy_raise when enable spiral vase + //BBS:启用螺旋花瓶时不要使用lazy_reed + gcode += this->change_layer(print_z); // this will increase m_layer_index + m_layer = &layer; + m_object_layer_over_raft = false; + if (printer_structure == PrinterStructure::psI3 && !need_insert_timelapse_gcode_for_traditional && !m_spiral_vase && print.config().print_sequence == PrintSequence::ByLayer) { + std::string timepals_gcode = insert_timelapse_gcode(); + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + //BBS: check whether custom gcode changes the z position. Update if changed + //BBS:检查自定义gcode是否更改了z位置。更改后更新 + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } + } + if (! print.config().layer_change_gcode.value.empty()) { + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += this->placeholder_parser_process("layer_change_gcode", + print.config().layer_change_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; + config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + } + //BBS: set layer time fan speed after layer change gcode + //BBS:设置换层后的层时间风扇速度gcode + gcode += ";_SET_FAN_SPEED_CHANGING_LAYER\n"; + + if (print.calib_mode() == CalibMode::Calib_PA_Tower) { + gcode += writer().set_pressure_advance(print.calib_params().start + static_cast(print_z) * print.calib_params().step); + } + else if (print.calib_mode() == CalibMode::Calib_Temp_Tower) { + auto offset = static_cast(print_z / 10.001) * 5; + gcode += writer().set_temperature(print.calib_params().start - offset); + } + else if (print.calib_mode() == CalibMode::Calib_Vol_speed_Tower) { + auto _speed = print.calib_params().start + print_z * print.calib_params().step; + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); + } + else if (print.calib_mode() == CalibMode::Calib_VFA_Tower) { + auto _speed = print.calib_params().start + std::floor(print_z / 5.0) * print.calib_params().step; + m_calib_config.set_key_value("outer_wall_speed", new ConfigOptionFloat(std::round(_speed))); + } + else if (print.calib_mode() == CalibMode::Calib_Retraction_tower) { + auto _length = print.calib_params().start + std::floor(std::max(0.0, print_z - 0.4)) * print.calib_params().step; + DynamicConfig _cfg; + _cfg.set_key_value("retraction_length", new ConfigOptionFloats{_length}); + writer().config.apply(_cfg); + sprintf(buf, "; Calib_Retraction_tower: Z_HEIGHT: %g, length:%g\n", print_z, _length); + gcode += buf; + } + + //BBS + if (first_layer) { + //BBS: set first layer global acceleration + //BBS:设置第一层全局加速 + if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { + double acceleration = m_config.initial_layer_acceleration.value; + gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + } + + if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.initial_layer_jerk.value); + } + + if (! first_layer && ! m_second_layer_things_done) { + //BBS: open powerlost recovery + //BBS:开放式断电恢复 + { + if (print.is_BBL_Printer()) { + gcode += "; open powerlost recovery\n"; + gcode += "M1003 S1\n"; + } + } + // BBS: open first layer inspection at second layer + //BBS:第二层开放式第一层检查 + if (print.config().scan_first_layer.value) { + // BBS: retract first to avoid droping when scan model + //BBS:扫描模型时先缩回,避免掉落 + gcode += this->retract(); + gcode += "M976 S1 P1 ; scan model before printing 2nd layer\n"; + gcode += "M400 P100\n"; + gcode += this->unretract(); + } + + //BBS: reset acceleration at sencond layer + //BBS:重置第二层加速度 + if (m_config.default_acceleration.value > 0 && m_config.initial_layer_acceleration.value > 0) { + double acceleration = m_config.default_acceleration.value; + gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + } + + if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + + // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent + // nozzle_temperature_initial_layer vs. temperature settings. + //从第一层过渡到第二层。根据喷嘴的具体情况调整喷嘴温度 + //喷嘴温度初始层与温度设置。 + for (const Extruder &extruder : m_writer.extruders()) { + if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id()) + // In single extruder multi material mode, set the temperature for the current extruder only. + continue; + int temperature = print.config().nozzle_temperature.get_at(extruder.id()); + if (temperature > 0 && temperature != print.config().nozzle_temperature_initial_layer.get_at(extruder.id())) + gcode += m_writer.set_temperature(temperature, false, extruder.id()); + } + + // BBS + int bed_temp = get_bed_temperature(first_extruder_id, false, print.config().curr_bed_type); + gcode += m_writer.set_bed_temperature(bed_temp); + // Mark the temperature transition from 1st to 2nd layer to be finished. + //标记待完成的第1层到第2层的温度转变。 + m_second_layer_things_done = true; + } + + // Map from extruder ID to index of skirt loops to be extruded with that extruder. + //从挤出机ID到使用该挤出机挤出的裙环的索引的映射。 + std::map> skirt_loops_per_extruder; + + if (single_object_instance_idx == size_t(-1)) { + // Normal (non-sequential) print. + gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); + } + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + //在筏层和普通对象层的print_z处挤出裙部 + //而不是在交错支撑材料层的印刷z处。 + skirt_loops_per_extruder = first_layer ? + Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : + Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); + + // BBS: get next extruder according to flush and soluble + //BBS:根据冲洗和溶解情况获得下一台挤出机 + auto get_next_extruder = [&](int current_extruder,const std::vector&extruders) { + std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); + const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); + // Extract purging volumes for each extruder pair: + //提取每对挤出机的吹扫体积: + std::vector> wipe_volumes; + for (unsigned int i = 0; i < number_of_extruders; ++i) + wipe_volumes.push_back(std::vector(flush_matrix.begin() + i * number_of_extruders, flush_matrix.begin() + (i + 1) * number_of_extruders)); + unsigned int next_extruder = current_extruder; + float min_flush = std::numeric_limits::max(); + for (auto extruder_id : extruders) { + if (print.config().filament_soluble.get_at(extruder_id) || extruder_id == current_extruder) + continue; + if (wipe_volumes[current_extruder][extruder_id] < min_flush) { + next_extruder = extruder_id; + min_flush = wipe_volumes[current_extruder][extruder_id]; + } + } + return next_extruder; + }; + + // Group extrusions by an extruder, then by an object, an island and a region. + //将挤出物按挤出机分组,然后按物体、岛屿和区域分组。 + std::map> by_extruder; + bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); + for (const LayerToPrint &layer_to_print : layers) { + if (layer_to_print.support_layer != nullptr) { + const SupportLayer &support_layer = *layer_to_print.support_layer; + const PrintObject& object = *layer_to_print.original_object; + if (! support_layer.support_fills.entities.empty()) { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role == erMixed || role == erSupportMaterial || role == erSupportTransition; + bool has_interface = role == erMixed || role == erSupportMaterialInterface; + // Extruder ID of the support base. -1 if "don't care". + //支撑底座的挤出机ID-1如果“不在乎”。 + unsigned int support_extruder = object.config().support_filament.value - 1; + // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + //支架是否应使用主动挤出机印刷,最好是不溶性的,以避免更换工具? + bool support_dontcare = object.config().support_filament.value == 0; + // Extruder ID of the support interface. -1 if "don't care". + //支撑接口的挤出机ID-1如果“不在乎”。 + unsigned int interface_extruder = object.config().support_interface_filament.value - 1; + // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? + //支撑接口是否应使用主动挤出机打印,最好是不可溶的,以避免更换工具? + bool interface_dontcare = object.config().support_interface_filament.value == 0; + + // BBS: apply wiping overridden extruders + //BBS:应用擦拭超控挤出机 + WipingExtrusions& wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); + if (support_dontcare) { + int extruder_override = wiping_extrusions.get_support_extruder_overrides(&object); + if (extruder_override >= 0) { + support_extruder = extruder_override; + support_dontcare = false; + } + } + + if (interface_dontcare) { + int extruder_override = wiping_extrusions.get_support_interface_extruder_overrides(&object); + if (extruder_override >= 0) { + interface_extruder = extruder_override; + interface_dontcare = false; + } + } + + // BBS: try to print support base with a filament other than interface filament + //BBS:尝试用界面灯丝以外的灯丝打印支撑底座 + if (support_dontcare && !interface_dontcare) { + unsigned int dontcare_extruder = first_extruder_id; + for (unsigned int extruder_id : layer_tools.extruders) { + if (print.config().filament_soluble.get_at(extruder_id)) + continue; + + //BBS: now we don't consider interface filament used in other object + //BBS:现在我们不考虑其他物体中使用的界面灯丝 + if (extruder_id == interface_extruder) + continue; + + dontcare_extruder = extruder_id; + break; + } + #if 0 + //BBS: not found a suitable extruder in current layer ,dontcare_extruider==first_extruder_id==interface_extruder + //BBS:在当前层中找不到合适的挤出机,请不要使用extruider==first_extruder_id==interface_extruder + if (dontcare_extruder == interface_extruder && (object.config().support_interface_not_for_body && object.config().support_interface_filament.value!=0)) { + // BBS : get a suitable extruder from other layer + auto all_extruders = print.extruders(); + dontcare_extruder = get_next_extruder(dontcare_extruder, all_extruders); + } + #endif + + if (support_dontcare) + support_extruder = dontcare_extruder; + } + else if (support_dontcare || interface_dontcare) { + // Some support will be printed with "don't care" material, preferably non-soluble. + // Is the current extruder assigned a soluble filament? + //一些支架将印有“不在乎”的材料,最好是不溶性的。 + //当前的挤出机是否分配了可溶性长丝? + unsigned int dontcare_extruder = first_extruder_id; + if (print.config().filament_soluble.get_at(dontcare_extruder)) { + // The last extruder printed on the previous layer extrudes soluble filament. + // Try to find a non-soluble extruder on the same layer. + //印刷在前一层上的最后一台挤出机挤出可溶性长丝。 + //试着在同一层上找到一个不溶的挤出机。 + for (unsigned int extruder_id : layer_tools.extruders) + if (! print.config().filament_soluble.get_at(extruder_id)) { + dontcare_extruder = extruder_id; + break; + } + } + if (support_dontcare) + support_extruder = dontcare_extruder; + if (interface_dontcare) + interface_extruder = dontcare_extruder; + } + // Both the support and the support interface are printed with the same extruder, therefore + // the interface may be interleaved with the support base. + //支架和支架接口都是用同一台挤出机印刷的,因此 + //接口可以与支撑基座交错。 + bool single_extruder = ! has_support || support_extruder == interface_extruder; + // Assign an extruder to the base. + ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); + obj.support = &support_layer.support_fills; + obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; + if (! single_extruder && has_interface) { + ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); + obj_interface.support = &support_layer.support_fills; + obj_interface.support_extrusion_role = erSupportMaterialInterface; + } + } + } + + if (layer_to_print.object_layer != nullptr) { + const Layer &layer = *layer_to_print.object_layer; + // We now define a strategy for building perimeters and fills. The separation + // between regions doesn't matter in terms of printing order, as we follow + // another logic instead: + // - we group all extrusions by extruder so that we minimize toolchanges + // - we start from the last used extruder + // - for each extruder, we group extrusions by island + // - for each island, we extrude perimeters first, unless user set the infill_first + // option + // (Still, we have to keep track of regions because we need to apply their config) + //我们现在定义一个构建边界和填充物的策略。分离 + //就打印顺序而言,地区之间并不重要,如下所述 + //另一种逻辑: + //-我们按挤出机对所有挤出物进行分组,以尽量减少工具更改 + //-我们从上次使用的挤出机开始 + //-对于每台挤出机,我们按岛对挤出机进行分组 + //-对于每个岛屿,我们首先拉伸周界,除非用户设置了fill_first + //选项 + //(不过,我们必须跟踪区域,因为我们需要应用它们的配置) + size_t n_slices = layer.lslices.size(); + const std::vector &layer_surface_bboxes = layer.lslices_bboxes; + // Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first, + // so we can just test a point inside ExPolygon::contour and we may skip testing the holes. + //按照边界框大小的递增顺序遍历切片,以便首先测试另一个岛内的岛, + //所以我们可以只测试ExPolygon::轮廓内的一个点,我们可以跳过测试孔。 + std::vector slices_test_order; + slices_test_order.reserve(n_slices); + for (size_t i = 0; i < n_slices; ++ i) + slices_test_order.emplace_back(i); + std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) { + const Vec2d s1 = layer_surface_bboxes[i].size().cast(); + const Vec2d s2 = layer_surface_bboxes[j].size().cast(); + return s1.x() * s1.y() < s2.x() * s2.y(); + }); + auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { + const BoundingBox &bbox = layer_surface_bboxes[i]; + return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && + point(1) >= bbox.min(1) && point(1) < bbox.max(1) && + layer.lslices[i].contour.contains(point); + }; + + for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { + const LayerRegion *layerm = layer.regions()[region_id]; + if (layerm == nullptr) + continue; + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + //PrintObjects拥有PrintRegions,因此指向PrintRegion的指针对于PrintObject来说是唯一的,它们不会 + //在整个打印中唯一标识PrintRegion的内容。转换为特定于打印的打印区域。 + const PrintRegion ®ion = print.get_print_region(layerm->region().print_region_id()); + + // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. + // It is also necessary to save which extrusions are part of MM wiping and which are not. + // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: + //现在,我们必须处理周界和填充,并在by_region std::map中创建拉伸岛。 + //还需要保存哪些挤压件是MM擦拭的一部分,哪些不是。 + //对于周界和填充物,这个过程几乎是一样的——我们将在一个重复两次的循环中完成: + std::vector printing_extruders; + for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { + for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) { + // extrusions represents infill or perimeter extrusions of a single island. + //拉伸表示单个岛的填充或周边拉伸。 + assert(dynamic_cast(ee) != nullptr); + const auto *extrusions = static_cast(ee); + if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail.//这不应该发生,但first_point()会失败。 + continue; + + // This extrusion is part of certain Region, which tells us which extruder should be used for it: + //该挤压机是某个区域的一部分,该区域告诉我们应使用哪个挤压机: + int correct_extruder_id = layer_tools.extruder(*extrusions, region); + + // Let's recover vector of extruder overrides: + //让我们恢复挤出机覆盖的向量: + const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; + if (! layer_tools.has_extruder(correct_extruder_id)) { + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) + //此实体未被覆盖,但其挤出机不在layertools中-我们将打印它 + //通过该层上的最后一个挤出机(例如,当擦拭物体比其他物体高时,可能会发生这种情况-不小心挤出机从层工具中消失) + correct_extruder_id = layer_tools.extruders.back(); + } + printing_extruders.clear(); + if (is_anything_overridden) { + entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, layer_to_print.original_object, correct_extruder_id, layer_to_print.object()->instances().size()); + if (entity_overrides == nullptr) { + printing_extruders.emplace_back(correct_extruder_id); + } else { + printing_extruders.reserve(entity_overrides->size()); + for (int extruder : *entity_overrides) + printing_extruders.emplace_back(extruder >= 0 ? + // at least one copy is overridden to use this extruder + //至少有一个副本被覆盖以使用此挤出机 + extruder : + // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) + //此挤出机通常至少会打印一份副本(有关说明,请参阅get_extruderoverride函数) + static_cast(- extruder - 1)); + Slic3r::sort_remove_duplicates(printing_extruders); + } + } else + printing_extruders.emplace_back(correct_extruder_id); + + // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: + //现在,我们必须将此挤出添加到by_extruder映射中,每个将打印它的挤出机一次: + for (unsigned int extruder : printing_extruders) + { + std::vector &islands = object_islands_by_extruder( + by_extruder, + extruder, + &layer_to_print - layers.data(), + layers.size(), n_slices+1); + for (size_t i = 0; i <= n_slices; ++ i) { + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; + if (// extrusions->first_point does not fit inside any slice //挤出->first_point不适合任何切片 + last || + // extrusions->first_point fits inside ith slice //挤压件->第一点与切片内部相匹配 + point_inside_surface(island_idx, extrusions->first_point())) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, extrusions, entity_overrides); + break; + } + } + } + } + } + } // for regions + } + } // for objects + + if (m_wipe_tower) + m_wipe_tower->set_is_first_print(true); + + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. + //按照挤出机的顺序挤出裙子、帽沿、支撑、周边和填充物。 + for (unsigned int extruder_id : layer_tools.extruders) + { + if (has_wipe_tower) { + if (!m_wipe_tower->is_empty_wipe_tower_gcode(*this, extruder_id, extruder_id == layer_tools.extruders.back())) { + if (need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { + gcode += this->retract(false, false, LiftType::NormalLift); + m_writer.add_object_change_labels(gcode); + + std::string timepals_gcode = insert_timelapse_gcode(); + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + //BBS: check whether custom gcode changes the z position. Update if changed + //BBS:检查自定义gcode是否更改了z位置。更改后更新 + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } + has_insert_timelapse_gcode = true; + } + gcode += m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()); + } + } else { + gcode += this->set_extruder(extruder_id, print_z); + } + + // let analyzer tag generator aware of a role type change + //让分析器标签生成器知道角色类型的更改 + if (layer_tools.has_wipe_tower && m_wipe_tower) + m_last_processor_extrusion_role = erWipeTower; + + if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { + const std::pair loops = loops_it->second; + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp(); + Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); + double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); + for (size_t i = loops.first; i < loops.second; ++i) { + // Adjust flow according to this layer's layer height. + //根据此层的层高调整流量。 + ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); + for (ExtrusionPath &path : loop.paths) { + path.height = layer_skirt_flow.height(); + path.mm3_per_mm = mm3_per_mm; + } + //FIXME using the support_speed of the 1st object printed. + //FIXME使用打印的第一个对象的support_speed。 + gcode += this->extrude_loop(loop, "skirt", m_config.support_speed.value); + } + m_avoid_crossing_perimeters.use_external_mp(false); + // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). + //如果这是第一层,则允许直线移动到第一个对象点(但不要在下一层中)。 + if (first_layer && loops.first == 0) + m_avoid_crossing_perimeters.disable_once(); + } + + auto objects_by_extruder_it = by_extruder.find(extruder_id); + if (objects_by_extruder_it == by_extruder.end()) + continue; + + // BBS: ordering instances by extruder + //BBS:按挤出机订购实例 + std::vector instances_to_print; + bool has_prime_tower = print.config().enable_prime_tower + && print.extruders().size() > 1 + && (print.config().print_sequence == PrintSequence::ByLayer + || (print.config().print_sequence == PrintSequence::ByObject && print.objects().size() == 1)); + if (has_prime_tower) { + int plate_idx = print.get_plate_index(); + Point wt_pos(print.config().wipe_tower_x.get_at(plate_idx), print.config().wipe_tower_y.get_at(plate_idx)); + + std::vector& objects_by_extruder = objects_by_extruder_it->second; + std::vector print_objects; + for (int obj_idx = 0; obj_idx < objects_by_extruder.size(); obj_idx++) { + auto& object_by_extruder = objects_by_extruder[obj_idx]; + if (object_by_extruder.islands.empty() && (object_by_extruder.support == nullptr || object_by_extruder.support->empty())) + continue; + + print_objects.push_back(print.get_object(obj_idx)); + } + + std::vector new_ordering = chain_print_object_instances(print_objects, &wt_pos); + std::reverse(new_ordering.begin(), new_ordering.end()); + instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, &new_ordering, single_object_instance_idx); + } + else { + instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + } + + // BBS + if (print.has_skirt() && print.config().print_sequence == PrintSequence::ByObject && prime_extruder && first_layer && extruder_id == first_extruder_id) { + for (InstanceToPrint& instance_to_print : instances_to_print) { + if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && + print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities.size() > 0) + continue; + + if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && + print.m_brimMap.at(instance_to_print.print_object.id()).entities.size() > 0) + continue; + + const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; + set_origin(unscaled(offset)); + for (ExtrusionEntity* ee : layer.object()->object_skirt().entities) + //FIXME using the support_speed of the 1st object printed. + gcode += this->extrude_entity(*ee, "skirt", m_config.support_speed.value); + } + } + + // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): + //我们几乎准备好打印了。但是,我们必须对所有对象进行两次检查,才能首先打印覆盖的拉伸(填充/周界擦拭功能): + std::vector by_region_per_copy_cache; + for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { + if (is_anything_overridden && print_wipe_extrusions == 0) + gcode+="; PURGING FINISHED\n"; + for (InstanceToPrint &instance_to_print : instances_to_print) { + const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id]; + const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; + // To control print speed of the 1st object layer printed over raft interface. + //控制在筏界面上打印的第一个对象层的打印速度。 + bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && + instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); + m_config.apply(instance_to_print.print_object.config(), true); + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; + if (m_config.reduce_crossing_wall) + m_avoid_crossing_perimeters.init_layer(*m_layer); + + std::string temp_start_str; + if (m_enable_label_object) { + std::string start_str = std::string("; start printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) { + start_str += ("M624 " + _encode_label_ids_to_base64({ instance_to_print.label_object_id })); + start_str += "\n"; + } else { + // BBS: support octoprint exclude object + //BBS:支持octoprint排除对象 + start_str += std::string("; printing object ") + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; + } + temp_start_str = start_str; + m_writer.set_object_start_str(start_str); + } + //Orca's implementation for skipping object, for klipper firmware printer only + //Orca的跳过对象实现,仅适用于klipper固件打印机 + bool reset_e = false; + if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) { + gcode += std::string("EXCLUDE_OBJECT_START NAME=") + + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; + reset_e = true; + } + if (reset_e && !m_config.use_relative_e_distances) + gcode += m_writer.reset_e(true); + + // When starting a new object, use the external motion planner for the first travel move. + //启动新对象时,使用外部运动规划器进行第一次移动。 + const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; + std::pair this_object_copy(&instance_to_print.print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + if (instance_to_print.object_by_extruder.support != nullptr) { + m_layer = layers[instance_to_print.layer_id].support_layer; + m_object_layer_over_raft = false; + + //BBS: print supports' brims first + //BBS:打印支持'边缘优先 + if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && !print_wipe_extrusions) { + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp(); + for (const ExtrusionEntity* ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) { + gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); + } + m_avoid_crossing_perimeters.use_external_mp(false); + // Allow a straight travel move to the first object point. + //允许直线移动到第一个目标点。 + m_avoid_crossing_perimeters.disable_once(); + this->m_objSupportsWithBrim.erase(instance_to_print.print_object.id()); + } + // When starting a new object, use the external motion planner for the first travel move. + //启动新对象时,使用外部运动规划器进行第一次移动。 + const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; + std::pair this_object_copy(&instance_to_print.print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + ExtrusionEntityCollection support_eec; + + // BBS + WipingExtrusions& wiping_extrusions = const_cast(layer_tools).wiping_extrusions(); + bool support_overridden = wiping_extrusions.is_support_overridden(layer_to_print.original_object); + bool support_intf_overridden = wiping_extrusions.is_support_interface_overridden(layer_to_print.original_object); + + ExtrusionRole support_extrusion_role = instance_to_print.object_by_extruder.support_extrusion_role; + bool is_overridden = support_extrusion_role == erSupportMaterialInterface ? support_intf_overridden : support_overridden; + if (is_overridden == (print_wipe_extrusions != 0)) + gcode += this->extrude_support( + // support_extrusion_role is erSupportMaterial, erSupportTransition, erSupportMaterialInterface or erMixed for all extrusion paths. + //对于所有挤出路径,support_extrusion_role为erSupportMaterial、erSupportTransition、erSupportMaterialInterface或erMixed。 + instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, support_extrusion_role)); + + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; + } + //FIXME order islands? + // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) + //FIXME订购岛屿? + //同一对象内多个零件的顺序刀具路径排序,又名。周界跟踪(#5511) + for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { + const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; + //BBS: add brim by obj by extruder + //BBS:通过挤出机通过obj添加边缘 + if (this->m_objsWithBrim.find(instance_to_print.print_object.id()) != this->m_objsWithBrim.end() && !print_wipe_extrusions) { + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp(); + for (const ExtrusionEntity* ee : print.m_brimMap.at(instance_to_print.print_object.id()).entities) { + gcode += this->extrude_entity(*ee, "brim", m_config.support_speed.value); + } + m_avoid_crossing_perimeters.use_external_mp(false); + // Allow a straight travel move to the first object point. + //允许直线移动到第一个目标点。 + m_avoid_crossing_perimeters.disable_once(); + this->m_objsWithBrim.erase(instance_to_print.print_object.id()); + } + // When starting a new object, use the external motion planner for the first travel move. + //启动新对象时,使用外部运动规划器进行第一次移动。 + const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; + std::pair this_object_copy(&instance_to_print.print_object, offset); + if (m_last_obj_copy != this_object_copy) + m_avoid_crossing_perimeters.use_external_mp_once(); + m_last_obj_copy = this_object_copy; + this->set_origin(unscale(offset)); + //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. + //FIXME以下代码按定义的顺序打印区域,路径没有以任何方式优化。 + bool is_infill_first =print.config().is_infill_first; + + auto has_infill = [](const std::vector &by_region) { + for (auto region : by_region) { + if (!region.infills.empty()) + return true; + } + return false; + }; + + //BBS: for first layer, we always print wall firstly to get better bed adhesive force + //This behaviour is same with cura + //BBS:对于第一层,我们总是先打印墙壁,以获得更好的床附着力 + //这种行为与cura相同 + if (is_infill_first && !first_layer) { + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { + gcode += this->retract(false, false, LiftType::NormalLift); + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) { + std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) + end_str += "M625\n"; + gcode += end_str; + } + + std::string timepals_gcode = insert_timelapse_gcode(); + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + //BBS: check whether custom gcode changes the z position. Update if changed + //BBS:检查自定义gcode是否更改了z位置。更改后更新 + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } + + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) + gcode += temp_start_str; + temp_start_str.clear(); + has_insert_timelapse_gcode = true; + } + gcode += this->extrude_infill(print, by_region_specific, false); + gcode += this->extrude_perimeters(print, by_region_specific); + } else { + gcode += this->extrude_perimeters(print, by_region_specific); + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode && has_infill(by_region_specific)) { + gcode += this->retract(false, false, LiftType::NormalLift); + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) { + std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) + end_str += "M625\n"; + gcode += end_str; + } + + std::string timepals_gcode = insert_timelapse_gcode(); + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + //BBS: check whether custom gcode changes the z position. Update if changed + //BBS:检查自定义gcode是否更改了z位置。更改后更新 + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } + + if (!temp_start_str.empty() && m_writer.empty_object_start_str()) + gcode += temp_start_str; + temp_start_str.clear(); + has_insert_timelapse_gcode = true; + } + gcode += this->extrude_infill(print,by_region_specific, false); + } + // ironing + gcode += this->extrude_infill(print,by_region_specific, true); + } + // Don't set m_gcode_label_objects_end if you don't had to write the m_gcode_label_objects_start. + //如果不必编写m_gcode_label_objects_start,则不要设置m_gcode_label_objects\end。 + if (!m_writer.empty_object_start_str()) { + m_writer.set_object_start_str(""); + } else if (m_enable_label_object) { + std::string end_str = std::string("; stop printing object, unique label id: ") + std::to_string(instance_to_print.label_object_id) + "\n"; + if (print.is_BBL_Printer()) + end_str += "M625\n"; + m_writer.set_object_end_str(end_str); + } + //Orca's implementation for skipping object, for klipper firmware printer only + //Orca的跳过对象实现,仅适用于klipper固件打印机 + if (this->config().exclude_object && print.config().gcode_flavor.value == gcfKlipper) { + gcode += std::string("EXCLUDE_OBJECT_END NAME=") + + get_instance_name(&instance_to_print.print_object, inst.id) + "\n"; + reset_e = true; + } + if (reset_e && !m_config.use_relative_e_distances) + gcode += m_writer.reset_e(true); + } + } + } + if (first_layer) { + for (auto iter = by_extruder.begin(); iter != by_extruder.end(); ++iter) { + if (!iter->second.empty()) + m_initial_layer_extruders.insert(iter->first); + } + } + +#if 0 + // Apply spiral vase post-processing if this layer contains suitable geometry + // (we must feed all the G-code into the post-processor, including the first + // bottom non-spiral layers otherwise it will mess with positions) + // we apply spiral vase at this stage because it requires a full layer. + // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only. + if (m_spiral_vase) + gcode = m_spiral_vase->process_layer(std::move(gcode)); + + // Apply cooling logic; this may alter speeds. + if (m_cooling_buffer) + gcode = m_cooling_buffer->process_layer(std::move(gcode), layer.id(), + // Flush the cooling buffer at each object layer or possibly at the last layer, even if it contains just supports (This should not happen). + object_layer || last_layer); + +#ifdef HAS_PRESSURE_EQUALIZER + // Apply pressure equalization if enabled; + // printf("G-code before filter:\n%s\n", gcode.c_str()); + if (m_pressure_equalizer) + gcode = m_pressure_equalizer->process(gcode.c_str(), false); + // printf("G-code after filter:\n%s\n", out.c_str()); +#endif /* HAS_PRESSURE_EQUALIZER */ + + file.write(gcode); +#endif + + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << + log_memory_info(); + + if (!has_wipe_tower && need_insert_timelapse_gcode_for_traditional && !has_insert_timelapse_gcode) { + if (m_support_traditional_timelapse) + m_support_traditional_timelapse = false; + + gcode += this->retract(false, false, LiftType::NormalLift); + m_writer.add_object_change_labels(gcode); + + std::string timepals_gcode = insert_timelapse_gcode(); + gcode += timepals_gcode; + m_writer.set_current_position_clear(false); + //BBS: check whether custom gcode changes the z position. Update if changed + //BBS:检查自定义gcode是否更改了z位置。更改后更新 + double temp_z_after_timepals_gcode; + if (GCodeProcessor::get_last_z_from_gcode(timepals_gcode, temp_z_after_timepals_gcode)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_timepals_gcode; + m_writer.set_position(pos); + } + } + + result.gcode = std::move(gcode); + result.cooling_buffer_flush = object_layer || raft_layer || last_layer; + return result; +} + +void GCode::apply_print_config(const PrintConfig &print_config) +{ + m_writer.apply_print_config(print_config); + m_config.apply(print_config); + m_scaled_resolution = scaled(print_config.resolution.value); +} + +void GCode::append_full_config(const Print &print, std::string &str) +{ + const DynamicPrintConfig &cfg = print.full_print_config(); + // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. + static const std::set banned_keys({ + "compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "print_host_webui"sv, + "printhost_apikey"sv, + "printhost_cafile"sv, + "printhost_user"sv, + "printhost_password"sv, + "printhost_port"sv + }); + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); + auto is_banned = [](const std::string &key) { + return banned_keys.find(key) != banned_keys.end(); + }; + for (const std::string &key : cfg.keys()) + if (! is_banned(key) && ! cfg.option(key)->is_nil()) + str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; +} + +void GCode::set_extruders(const std::vector &extruder_ids) +{ + m_writer.set_extruders(extruder_ids); + + // enable wipe path generation if any extruder has wipe enabled + m_wipe.enable = false; + for (auto id : extruder_ids) + if (m_config.wipe.get_at(id)) { + m_wipe.enable = true; + break; + } +} + +void GCode::set_origin(const Vec2d &pointf) +{ + // if origin increases (goes towards right), last_pos decreases because it goes towards left + const Point translate( + scale_(m_origin(0) - pointf(0)), + scale_(m_origin(1) - pointf(1)) + ); + m_last_pos += translate; + m_wipe.path.translate(translate); + m_origin = pointf; +} + +std::string GCode::preamble() +{ + std::string gcode = m_writer.preamble(); + + /* Perform a *silent* move to z_offset: we need this to initialize the Z + position of our writer object so that any initial lift taking place + before the first layer change will raise the extruder from the correct + initial Z instead of 0. */ + //m_writer.travel_to_z(m_config.z_offset.value); + m_writer.travel_to_z(0.0); + + return gcode; +} + +// called by GCode::process_layer() +std::string GCode::change_layer(coordf_t print_z) +{ + std::string gcode; + if (m_layer_count > 0) + // Increment a progress bar indicator. + gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); + //BBS + //coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates + coordf_t z = print_z; // in unscaled coordinates + if (EXTRUDER_CONFIG(retract_when_changing_layer) && m_writer.will_move_z(z)) { + LiftType lift_type = this->to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); + //BBS: force to use SpiralLift when change layer if lift type is auto + gcode += this->retract(false, false, ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto ? LiftType::SpiralLift : lift_type); + } + + m_writer.add_object_change_labels(gcode); + + if (m_spiral_vase) { + //BBS: force to normal lift immediately in spiral vase mode + std::ostringstream comment; + comment << "move to next layer (" << m_layer_index << ")"; + gcode += m_writer.travel_to_z(z, comment.str()); + } + else { + //BBS: set m_need_change_layer_lift_z to be true so that z lift can be done in travel_to() function + m_need_change_layer_lift_z = true; + } + + m_nominal_z = print_z; + + // forget last wiping path as wiping after raising Z is pointless + // BBS. Dont forget wiping path to reduce stringing. + //m_wipe.reset_path(); + + return gcode; +} + + + +static std::unique_ptr calculate_layer_edge_grid(const Layer& layer) +{ + auto out = make_unique(); + + // Create the distance field for a layer below. + const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); + out->create(layer.lslices, distance_field_resolution); + out->calculate_sdf(); +#if 0 + { + static int iRun = 0; + BoundingBox bbox = (*lower_layer_edge_grid)->bbox(); + bbox.min(0) -= scale_(5.f); + bbox.min(1) -= scale_(5.f); + bbox.max(0) += scale_(5.f); + bbox.max(1) += scale_(5.f); + EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++)); + } +#endif + return out; +} + +static bool has_overhang_path_on_slope(const ExtrusionLoop &loop, double slope_length) +{ + double count_length = 0.0; + for (ExtrusionPath path : loop.paths) { + if (count_length > slope_length) + return false; + + if (path.overhang_degree > 1) + return true; + + count_length += path.length(); + } + + return false; +} + +static std::map overhang_speed_key_map = +{ + {1, "overhang_1_4_speed"}, + {2, "overhang_2_4_speed"}, + {3, "overhang_3_4_speed"}, + {4, "overhang_4_4_speed"}, + {5, "overhang_totally_speed"}, + {6, "bridge_speed"}, +}; + +double GCode::get_path_speed(const ExtrusionPath &path) +{ + double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.extruder()->id())); + // set speed + double speed = 0; + if (path.role() == erPerimeter) { + speed = m_config.get_abs_value("inner_wall_speed"); + if (m_config.enable_overhang_speed.value) { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + speed = new_speed == 0.0 ? speed : new_speed; + } + } else if (path.role() == erExternalPerimeter) { + speed = m_config.get_abs_value("outer_wall_speed"); + if (m_config.enable_overhang_speed.value) { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + speed = new_speed == 0.0 ? speed : new_speed; + } + } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) + speed = m_config.get_abs_value("overhang_totally_speed"); + else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { + speed = m_config.get_abs_value("bridge_speed"); + } + auto _mm3_per_mm = path.mm3_per_mm * double(m_curr_print->calib_mode() == CalibMode::Calib_Flow_Rate ? this->config().print_flow_ratio.value : 1); + + // BBS: if not set the speed, then use the filament_max_volumetric_speed directly + if (speed == 0) { + if (_mm3_per_mm > 0) + speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + else + speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; + } + if (this->on_first_layer()) { + // BBS: for solid infill of initial layer, speed can be higher as long as + // wall lines have be attached + if (path.role() != erBottomSurface) speed = m_config.get_abs_value("initial_layer_speed"); + } + + if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { + double extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; + if (_mm3_per_mm > 0) extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = std::min(speed, extrude_speed); + } + + return speed; +} + +std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed) +{ + // get a copy; don't modify the orientation of the original loop object otherwise + // next copies (if any) would not detect the correct orientation + + // extrude all loops ccw + bool was_clockwise = loop.make_counter_clockwise(); + bool is_hole = loop.loop_role() == elrPerimeterHole; + // find the point of the loop that is closest to the current extruder position + // or randomize if requested + Point last_pos = this->last_pos(); + if (!m_config.spiral_mode && description == "perimeter") { + assert(m_layer != nullptr); + bool is_outer_wall_first = m_config.wall_sequence == WallSequence::OuterInner; + m_seam_placer.place_seam(m_layer, loop, is_outer_wall_first, this->last_pos()); + } else + loop.split_at(last_pos, false); + + const auto seam_scarf_type = m_config.seam_slope_type.value; + // BBS: not apply on fist layer, too small E has stick issue with hotend plate + bool enable_seam_slope = ((seam_scarf_type == SeamScarfType::External && !is_hole) || + seam_scarf_type == SeamScarfType::All) && + !m_config.spiral_mode && + (loop.role() == erExternalPerimeter || + (loop.role() == erPerimeter && m_config.seam_slope_inner_walls)) && + !on_first_layer(); + + if (enable_seam_slope && m_config.seam_slope_conditional.value) { + //BBS: the seam has been decide, only check the seam position angle + enable_seam_slope = loop.check_seam_point_angle(m_config.scarf_angle_threshold.value * M_PI / 180.0); + } + + // clip the path to avoid the extruder to get exactly on the first point of the loop; + // if polyline was shorter than the clipping distance we'd get a null polyline, so + // we discard it in that case + const double seam_gap = scale_(EXTRUDER_CONFIG(nozzle_diameter)) * (m_config.seam_gap.value / 100); + const double clip_length = m_enable_loop_clipping && !enable_seam_slope ? seam_gap : 0; + // get paths + ExtrusionPaths paths; + loop.clip_end(clip_length, &paths); + if (paths.empty()) return ""; + + double small_peri_speed=-1; + // apply the small perimeter speed + if (speed==-1 && loop.length() <= SMALL_PERIMETER_LENGTH(m_config.small_perimeter_threshold.value)) + small_peri_speed = m_config.small_perimeter_speed.get_abs_value(m_config.outer_wall_speed); + + // extrude along the path + std::string gcode; + bool is_small_peri=false; + + const auto speed_for_path = [&speed, &small_peri_speed](const ExtrusionPath &path) { + // don't apply small perimeter setting for overhangs/bridges/non-perimeters + const bool is_small_peri = is_perimeter(path.role()) && !is_bridge(path.role()) && small_peri_speed > 0 && + (path.get_overhang_degree() == 0 || path.get_overhang_degree() > 5); + return is_small_peri ? small_peri_speed : speed; + }; + + //BBS: avoid overhang on conditional scarf mode + bool slope_has_overhang = false; + if (enable_seam_slope) { + // Create seam slope + double start_slope_ratio; + if (m_config.seam_slope_start_height.percent) { + start_slope_ratio = m_config.seam_slope_start_height.value / 100.; + } else { + // Get the ratio against current layer height + double h = paths.front().height; + start_slope_ratio = m_config.seam_slope_start_height.value / h; + } + + double loop_length = 0.; + for (const auto &path : paths) { + loop_length += unscale_(path.length()); + } + const bool slope_entire_loop = m_config.seam_slope_entire_loop; + const double slope_min_length = slope_entire_loop ? loop_length : std::min(m_config.seam_slope_min_length.value, loop_length); + const int slope_steps = m_config.seam_slope_steps; + const double slope_max_segment_length = scale_(slope_min_length / slope_steps); + // BBS: check if has overhang on slope path + if (m_config.seam_slope_conditional.value) + slope_has_overhang = has_overhang_path_on_slope(loop.paths, slope_min_length); + if (!slope_has_overhang) { + // Calculate the sloped loop + //BBS: should has smaller e at start to get better seam + ExtrusionLoopSloped new_loop(paths, seam_gap, slope_min_length, slope_max_segment_length, start_slope_ratio, loop.loop_role()); + + //BBS: clip end and start to get better seam + new_loop.clip_slope(seam_gap); + // BBS: slowdown speed to improve seam, to be fix, cooling need to be apply correctly + //new_loop.target_speed = get_path_speed(new_loop.starts.back()); + //new_loop.slowdown_slope_speed(); + // BBS: smooth speed of discontinuity areas + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && (loop.role() == erExternalPerimeter || loop.role() == erPerimeter)) + smooth_speed_discontinuity_area(new_loop.paths); + // Then extrude it + for (const auto &p : new_loop.get_all_paths()) { + gcode += this->_extrude(*p, description, speed_for_path(*p)); + } + + // Fix path for wipe + if (!new_loop.ends.empty()) { + paths.clear(); + // The start slope part is ignored as it overlaps with the end part + paths.reserve(new_loop.paths.size() + new_loop.ends.size()); + paths.insert(paths.end(), new_loop.paths.begin(), new_loop.paths.end()); + paths.insert(paths.end(), new_loop.ends.begin(), new_loop.ends.end()); + } + } else { + paths.clear(); + loop.clip_end(clip_length, &paths); + if (paths.empty()) return ""; + } + } + + if (!enable_seam_slope || slope_has_overhang) { + // BBS: smooth speed of discontinuity areas + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && (loop.role() == erExternalPerimeter || loop.role() == erPerimeter)) + smooth_speed_discontinuity_area(paths); + + for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { + gcode += this->_extrude(*path, description, speed_for_path(*path)); + } + } + + //BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. + if (!this->on_first_layer()) { + // reset acceleration + gcode += m_writer.set_acceleration((unsigned int) (m_config.default_acceleration.value + 0.5)); + if (!this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + } + + + // BBS + if (m_wipe.enable) { + m_wipe.path = Polyline(); + for (ExtrusionPath &path : paths) { + //BBS: Don't need to save duplicated point into wipe path + if (!m_wipe.path.empty() && !path.empty() && + m_wipe.path.last_point() == path.first_point()) + m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); + else + m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + } + } + + //BBS. move the travel path before wipe to improve the seam + //// make a little move inwards before leaving loop + //if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.wall_loops.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { + // // detect angle between last and first segment + // // the side depends on the original winding order of the polygon (left for contours, right for holes) + // //FIXME improve the algorithm in case the loop is tiny. + // //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). + // Point a = paths.front().polyline.points[1]; // second point + // Point b = *(paths.back().polyline.points.end()-3); // second to last point + // if (was_clockwise) { + // // swap points + // Point c = a; a = b; b = c; + // } + + // double angle = paths.front().first_point().ccw_angle(a, b) / 3; + + // // turn left if contour, turn right if hole + // if (was_clockwise) angle *= -1; + + // // create the destination point along the first segment and rotate it + // // we make sure we don't exceed the segment length because we don't know + // // the rotation of the second segment so we might cross the object boundary + // Vec2d p1 = paths.front().polyline.points.front().cast(); + // Vec2d p2 = paths.front().polyline.points[1].cast(); + // Vec2d v = p2 - p1; + // double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter)); + // double l2 = v.squaredNorm(); + // // Shift by no more than a nozzle diameter. + // //FIXME Hiding the seams will not work nicely for very densely discretized contours! + // //BBS. shorten the travel distant before the wipe path + // double threshold = 0.2; + // Point pt = (p1 + v * threshold).cast(); + // if (nd * nd < l2) + // pt = (p1 + threshold * v * (nd / sqrt(l2))).cast(); + // //Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast(); + // pt.rotate(angle, paths.front().polyline.points.front()); + // // generate the travel move + // gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); + //} + + return gcode; +} + +std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed) +{ + // extrude along the path + std::string gcode; + for (ExtrusionPath path : multipath.paths) + gcode += this->_extrude(path, description, speed); + + // BBS + if (m_wipe.enable) { + m_wipe.path = Polyline(); + for (ExtrusionPath &path : multipath.paths) { + //BBS: Don't need to save duplicated point into wipe path + if (!m_wipe.path.empty() && !path.empty() && + m_wipe.path.last_point() == path.first_point()) + m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end()); + else + m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path + } + m_wipe.path.reverse(); + } + //BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. + if (!this->on_first_layer()) { + // reset acceleration + gcode += m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.value + 0.5)); + if (!this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + } + return gcode; +} + +std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed) +{ + if (const ExtrusionPath* path = dynamic_cast(&entity)) + return this->extrude_path(*path, description, speed); + else if (const ExtrusionMultiPath* multipath = dynamic_cast(&entity)) + return this->extrude_multi_path(*multipath, description, speed); + else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) + return this->extrude_loop(*loop, description, speed); + else + throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); + return ""; +} + +std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) +{ +// description += ExtrusionEntity::role_to_string(path.role()); + std::string gcode = this->_extrude(path, description, speed); + if (m_wipe.enable) { + m_wipe.path = std::move(path.polyline); + m_wipe.path.reverse(); + } + //BBS: don't reset acceleration when printing first layer. During first layer, acceleration is always same value. + if (!this->on_first_layer()) { + // reset acceleration + gcode += m_writer.set_acceleration((unsigned int) floor(m_config.default_acceleration.value + 0.5)); + if (!this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.default_jerk.value); + } + return gcode; +} + +// Extrude perimeters: Decide where to put seams (hide or align seams). +std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region) +{ + std::string gcode; + for (const ObjectByExtruder::Island::Region ®ion : by_region) + if (! region.perimeters.empty()) { + m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); + + for (const ExtrusionEntity* ee : region.perimeters) + gcode += this->extrude_entity(*ee, "perimeter", -1.); + } + return gcode; +} + +// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance. +std::string GCode::extrude_infill(const Print &print, const std::vector &by_region, bool ironing) +{ + std::string gcode; + ExtrusionEntitiesPtr extrusions; + const char* extrusion_name = ironing ? "ironing" : "infill"; + for (const ObjectByExtruder::Island::Region ®ion : by_region) + if (! region.infills.empty()) { + extrusions.clear(); + extrusions.reserve(region.infills.size()); + for (ExtrusionEntity *ee : region.infills) + if ((ee->role() == erIroning) == ironing) + extrusions.emplace_back(ee); + if (! extrusions.empty()) { + m_config.apply(print.get_print_region(®ion - &by_region.front()).config()); + chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); + for (const ExtrusionEntity *fill : extrusions) { + auto *eec = dynamic_cast(fill); + if (eec) { + for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + gcode += this->extrude_entity(*ee, extrusion_name); + } else + gcode += this->extrude_entity(*fill, extrusion_name); + } + } + } + return gcode; +} + +std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) +{ + static constexpr const char *support_label = "support material"; + static constexpr const char *support_interface_label = "support material interface"; + const char* support_transition_label = "support transition"; + + std::string gcode; + if (! support_fills.entities.empty()) { + const double support_speed = m_config.support_speed.value; + const double support_interface_speed = m_config.get_abs_value("support_interface_speed"); + for (const ExtrusionEntity *ee : support_fills.entities) { + ExtrusionRole role = ee->role(); + assert(role == erSupportMaterial || role == erSupportMaterialInterface || role == erSupportTransition); + const char* label = (role == erSupportMaterial) ? support_label : + ((role == erSupportMaterialInterface) ? support_interface_label : support_transition_label); + // BBS + //const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed; + const double speed = -1.0; + const ExtrusionPath* path = dynamic_cast(ee); + const ExtrusionMultiPath* multipath = dynamic_cast(ee); + const ExtrusionLoop* loop = dynamic_cast(ee); + const ExtrusionEntityCollection* collection = dynamic_cast(ee); + if (path) + gcode += this->extrude_path(*path, label, speed); + else if (multipath) { + gcode += this->extrude_multi_path(*multipath, label, speed); + } + else if (loop) { + gcode += this->extrude_loop(*loop, label, speed); + } + else if (collection) { + gcode += extrude_support(*collection); + } + else { + throw Slic3r::InvalidArgument("Unknown extrusion type"); + } + } + } + return gcode; +} + +bool GCode::GCodeOutputStream::is_error() const +{ + return ::ferror(this->f); +} + +void GCode::GCodeOutputStream::flush() +{ + ::fflush(this->f); +} + +void GCode::GCodeOutputStream::close() +{ + if (this->f) { + ::fclose(this->f); + this->f = nullptr; + } +} + +void GCode::GCodeOutputStream::write(const char *what) +{ + if (what != nullptr) { + const char* gcode = what; + // writes string to file + fwrite(gcode, 1, ::strlen(gcode), this->f); + //FIXME don't allocate a string, maybe process a batch of lines? + m_processor.process_buffer(std::string(gcode)); + } +} + +void GCode::GCodeOutputStream::writeln(const std::string &what) +{ + if (! what.empty()) + this->write(what.back() == '\n' ? what : what + '\n'); +} + +void GCode::GCodeOutputStream::write_format(const char* format, ...) +{ + va_list args; + va_start(args, format); + + int buflen; + { + va_list args2; + va_copy(args2, args); + buflen = + #ifdef _MSC_VER + ::_vscprintf(format, args2) + #else + ::vsnprintf(nullptr, 0, format, args2) + #endif + + 1; + va_end(args2); + } + + char buffer[1024]; + bool buffer_dynamic = buflen > 1024; + char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer; + int res = ::vsnprintf(bufptr, buflen, format, args); + if (res > 0) + this->write(bufptr); + + if (buffer_dynamic) + free(bufptr); + + va_end(args); +} + +// BBS: f(x)=2x^2 +double GCode::mapping_speed(double dist) +{ + if (dist <= 0) + return 0; + return this->config().smooth_coefficient * pow(dist, 2); +} + +double GCode::get_speed_coor_x(double speed){ + + double temp = speed / this->config().smooth_coefficient; + return sqrt(temp); +} + +double GCode::get_overhang_degree_corr_speed(float normal_speed, double path_degree) { + + //BBS: protection: overhang degree is float, make sure it not excess degree range + if (path_degree <= 0) + return normal_speed; + + int lower_degree_bound = int(path_degree); + // BBS: use lower speed of 75%-100% for better cooling + if (path_degree >= 4 || path_degree == lower_degree_bound) + return m_config.get_abs_value(overhang_speed_key_map[lower_degree_bound].c_str()); + + int upper_degree_bound = lower_degree_bound + 1; + + double lower_speed_bound = lower_degree_bound == 0 ? normal_speed : m_config.get_abs_value(overhang_speed_key_map[lower_degree_bound].c_str()); + double upper_speed_bound = upper_degree_bound == 0 ? normal_speed : m_config.get_abs_value(overhang_speed_key_map[upper_degree_bound].c_str()); + + lower_speed_bound = lower_speed_bound == 0 ? normal_speed : lower_speed_bound; + upper_speed_bound = upper_speed_bound == 0 ? normal_speed : upper_speed_bound; + + double speed_out = lower_speed_bound + (upper_speed_bound - lower_speed_bound) * (path_degree - lower_degree_bound); + return speed_out; +} + +static bool need_smooth_speed(const ExtrusionPath &other_path, const ExtrusionPath &this_path) +{ + if (this_path.smooth_speed - other_path.smooth_speed > smooth_speed_step) + return true; + + return false; +} + +static void append_split_line(bool split_from_left, Polyline &polyline, Point p1, Point p2) +{ + if (split_from_left) { + polyline.append(p1); + polyline.append(p2); + } else { + polyline.append(p2); + polyline.append(p1); + } +} + +ExtrusionPaths GCode::split_and_mapping_speed(double &other_path_v, double &final_v, ExtrusionPath &this_path, double max_smooth_length, bool split_from_left) +{ + ExtrusionPaths splited_path; + if (this_path.length() <= 0 || this_path.polyline.points.size() < 2) { + return splited_path; + } + + // reverse if this slowdown the speed + Polyline input_polyline = this_path.polyline; + if (!split_from_left) + std::reverse(input_polyline.begin(), input_polyline.end()); + + double this_path_x = scale_(get_speed_coor_x(final_v)); + double x_base = scale_(get_speed_coor_x(other_path_v)); + + double smooth_length = this_path_x - x_base; + + // this length not support to get final v, adjust final v + if (smooth_length > max_smooth_length) + final_v = mapping_speed(unscale_(x_base + max_smooth_length)); + + double max_step_length = scale_(1.0); // cut path if the path too long + double min_step_length = scale_(0.4); // cut step + + double smooth_length_count = 0; + double split_line_speed = 0; + Point line_start_pt = input_polyline.points.front(); + Point line_end_pt = input_polyline.points[1]; + bool get_next_line = false; + size_t end_pt_idx = 1; + + auto insert_speed = [this](double line_lenght, double &pos_x, double &smooth_length_count, double target_v) { + pos_x += line_lenght; + double pos_x_speed = mapping_speed(unscale_(pos_x)); + smooth_length_count += line_lenght; + + if (pos_x_speed > target_v) + pos_x_speed = target_v; + + return pos_x_speed; + }; + + while (end_pt_idx < input_polyline.points.size()) { + // move to next line + if (get_next_line) { + line_start_pt = input_polyline.points[end_pt_idx - 1]; + line_end_pt = input_polyline.points[end_pt_idx]; + } + + Polyline polyline; + Line line(line_start_pt, line_end_pt); + + // split polyline and set speed + if (line.length() < max_step_length || line.length() - min_step_length < min_step_length / 2) { + split_line_speed = insert_speed(line.length(), x_base, smooth_length_count, final_v); + append_split_line(split_from_left, polyline, line_start_pt, line_end_pt); + end_pt_idx++; + get_next_line = true; + } else { + // path is too long, split it + double rate = min_step_length / line.length(); + Point insert_p = line.a + (line.b - line.a) * rate; + + split_line_speed = insert_speed(min_step_length, x_base, smooth_length_count, final_v); + append_split_line(split_from_left, polyline, line_start_pt, insert_p); + + line_start_pt = insert_p; + get_next_line = false; + } + + ExtrusionPath path_step(polyline, this_path); + path_step.smooth_speed = split_line_speed; + splited_path.push_back(std::move(path_step)); + + // stop condition + if (split_line_speed >= final_v) break; + } + + if (!split_from_left) + std::reverse(input_polyline.points.begin(), input_polyline.points.end()); + // get_remain_path + if (end_pt_idx < input_polyline.points.size()) { + // split at index or split at corr length + Polyline p1, p2; + if( !split_from_left ) { + input_polyline.split_at_length(input_polyline.length() - smooth_length_count, &p1, &p2); + this_path.polyline = p1; + } else { + input_polyline.split_at_length(smooth_length_count, &p1, &p2); + this_path.polyline = p2; + } + + } else { + this_path.polyline.clear(); + } + + // reverse paths if this start from right + if (!split_from_left) + std::reverse(splited_path.begin(), splited_path.end()); + + return splited_path; +} + +ExtrusionPaths GCode::merge_same_speed_paths(const ExtrusionPaths &paths) +{ + ExtrusionPaths output_paths; + std::optional merged_path; + + for(size_t path_idx=0;path_idxcan_merge(path)){ + merged_path->polyline.append(path.polyline); + } + else{ + output_paths.push_back(std::move(*merged_path)); + merged_path = path; + } + } + + if(merged_path.has_value()) + output_paths.push_back(std::move(*merged_path)); + + return output_paths; +} + +ExtrusionPaths GCode::set_speed_transition(ExtrusionPaths &paths) +{ + ExtrusionPaths interpolated_paths; + for (int path_idx = 0; path_idx < paths.size(); path_idx++) { + // update path + ExtrusionPath &path = paths[path_idx]; + + double this_path_speed = 0; + // 100% overhang speed will not to set smooth speed + if (path.role() == erOverhangPerimeter) { + interpolated_paths.push_back(path); + continue; + } + + bool smooth_left_path = false; + bool smooth_right_path = false; + // first line do not need to smooth speed on left + // prev line speed may change + if (path_idx > 0) + smooth_left_path = need_smooth_speed(paths[path_idx - 1], path); + + // first line do not need to smooth speed on right + if (path_idx < paths.size() - 1) + smooth_right_path = need_smooth_speed(paths[path_idx + 1], path); + + // get smooth length + double max_smooth_path_length = path.length(); + if (smooth_right_path && smooth_left_path) max_smooth_path_length /= 2; + + // smooth left + ExtrusionPaths left_split_paths; + if (smooth_left_path) { + left_split_paths = split_and_mapping_speed(paths[path_idx - 1].smooth_speed, path.smooth_speed, path, max_smooth_path_length); + if (!left_split_paths.empty()) interpolated_paths.insert(interpolated_paths.end(), left_split_paths.begin(), left_split_paths.end()); + max_smooth_path_length = path.length(); + } + + // smooth right + ExtrusionPaths right_split_paths; + if (smooth_right_path) { + right_split_paths = split_and_mapping_speed(paths[path_idx + 1].smooth_speed, path.smooth_speed, path, max_smooth_path_length, false); } + + if (!path.empty()) + interpolated_paths.push_back(path); + + if (!right_split_paths.empty()) + interpolated_paths.insert(interpolated_paths.end(), right_split_paths.begin(), right_split_paths.end()); + } + + return interpolated_paths; +} + +void GCode::smooth_speed_discontinuity_area(ExtrusionPaths &paths) { + + if (paths.size() <= 1) + return; + + //step 1 merge same speed path + size_t path_tail_pos = 0; + ExtrusionPaths prepare_paths = merge_same_speed_paths(paths); + + //step 2 split path + ExtrusionPaths inter_paths; + inter_paths =set_speed_transition(prepare_paths); + paths = std::move(inter_paths); +} + +std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed, bool is_first_slope) +{ + std::string gcode; + + if (is_bridge(path.role())) + description += " (bridge)"; + + const ExtrusionPathSloped *sloped = dynamic_cast(&path); + const auto get_sloped_z = [&sloped, this](double z_ratio) { + const auto height = sloped->height; + return lerp(m_nominal_z - height, m_nominal_z, z_ratio); + }; + + // go to first point of extrusion path + //BBS: path.first_point is 2D point. But in lazy raise case, lift z is done in travel_to function. + //Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance + if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || (sloped != nullptr && !sloped->is_flat())) { + gcode += this->travel_to( + path.first_point(), + path.role(), + "move to first " + description + " point", + sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio) + ); + m_need_change_layer_lift_z = false; + } + + // if needed, write the gcode_label_objects_end then gcode_label_objects_start + // should be already done by travel_to, but just in case + m_writer.add_object_change_labels(gcode); + + // compensate retraction + gcode += this->unretract(); + m_config.apply(m_calib_config); + + // adjust acceleration + if (m_config.default_acceleration.value > 0) { + double acceleration; + if (this->on_first_layer() && m_config.initial_layer_acceleration.value > 0) { + acceleration = m_config.initial_layer_acceleration.value; +#if 0 + } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { + acceleration = m_config.first_layer_acceleration_over_raft.value; + } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { + acceleration = m_config.bridge_acceleration.value; +#endif + } else if (m_config.outer_wall_acceleration.value > 0 + //BBS: FIXME, in fact,we only need to set acceleration for outer wall. But we don't know + //whether the overhang perimeter is outer or not. So using specific acceleration together. + && (path.role() == erExternalPerimeter || path.role() == erOverhangPerimeter)) { + acceleration = m_config.outer_wall_acceleration.value; + } else if (m_config.top_surface_acceleration.value > 0 && is_top_surface(path.role())) { + acceleration = m_config.top_surface_acceleration.value; + } else if (m_config.inner_wall_acceleration.value > 0 && path.role() == erPerimeter) { + acceleration = m_config.inner_wall_acceleration.value; + } else if (m_config.get_abs_value("sparse_infill_acceleration") > 0 && (path.role() == erInternalInfill)) { + acceleration = m_config.get_abs_value("sparse_infill_acceleration"); + } else { + acceleration = m_config.default_acceleration.value; + } + gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); + } + + if (m_config.default_jerk.value > 0 && !this->is_BBL_Printer()) { + double jerk = m_config.default_jerk.value; + if (this->on_first_layer() && m_config.initial_layer_jerk.value > 0) + jerk = m_config.initial_layer_jerk.value; + else if (m_config.outer_wall_jerk.value > 0 && path.role() == erExternalPerimeter) + jerk = m_config.outer_wall_jerk.value; + else if (m_config.inner_wall_jerk.value > 0 && path.role() == erPerimeter) + jerk = m_config.inner_wall_jerk.value; + else if (m_config.infill_jerk.value > 0 && is_infill(path.role())) + jerk = m_config.infill_jerk.value; + else if (m_config.top_surface_jerk.value > 0 && is_top_surface(path.role())) + jerk = m_config.top_surface_jerk.value; + + gcode += m_writer.set_jerk_xy(jerk); + } + // calculate extrusion length per distance unit + auto _mm3_per_mm = path.mm3_per_mm * double(m_curr_print->calib_mode() == CalibMode::Calib_Flow_Rate ? this->config().print_flow_ratio.value : 1); + + // calculate extrusion length per distance unit + if( path.role() == erTopSolidInfill ) + _mm3_per_mm *= m_config.top_solid_infill_flow_ratio.value; + else if (this->on_first_layer()) + _mm3_per_mm *= m_config.initial_layer_flow_ratio.value; + + double e_per_mm = m_writer.extruder()->e_per_mm3() * _mm3_per_mm; + + double min_speed = double(m_config.slow_down_min_speed.get_at(m_writer.extruder()->id())); + // set speed + if (speed == -1) { + if (path.role() == erPerimeter) { + speed = m_config.get_abs_value("inner_wall_speed"); + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) + speed = path.smooth_speed; + else if (m_config.enable_overhang_speed.value) { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + speed = new_speed == 0.0 ? speed : new_speed; + } + } else if (path.role() == erExternalPerimeter) { + speed = m_config.get_abs_value("outer_wall_speed"); + if (m_config.detect_overhang_wall && m_config.smooth_speed_discontinuity_area && path.smooth_speed != 0) + speed = path.smooth_speed; + else if (m_config.enable_overhang_speed.value) { + double new_speed = 0; + new_speed = get_overhang_degree_corr_speed(speed, path.overhang_degree); + + speed = new_speed == 0.0 ? speed : new_speed; + } + } else if (path.role() == erOverhangPerimeter && path.overhang_degree == 5) { + speed = m_config.get_abs_value("overhang_totally_speed"); + } else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill || path.role() == erSupportTransition) { + speed = m_config.get_abs_value("bridge_speed"); + } else if (path.role() == erInternalInfill) { + speed = m_config.get_abs_value("sparse_infill_speed"); + } else if (path.role() == erSolidInfill) { + speed = m_config.get_abs_value("internal_solid_infill_speed"); + } else if (path.role() == erTopSolidInfill) { + speed = m_config.get_abs_value("top_surface_speed"); + } else if (path.role() == erIroning) { + speed = m_config.get_abs_value("ironing_speed"); + } else if (path.role() == erBottomSurface) { + speed = m_config.get_abs_value("initial_layer_infill_speed"); + } else if (path.role() == erGapFill) { + speed = m_config.get_abs_value("gap_infill_speed"); + } + else if (path.role() == erSupportMaterial || + path.role() == erSupportMaterialInterface) { + const double support_speed = m_config.support_speed.value; + const double support_interface_speed = m_config.get_abs_value("support_interface_speed"); + speed = (path.role() == erSupportMaterial) ? support_speed : support_interface_speed; + } else { + throw Slic3r::InvalidArgument("Invalid speed"); + } + } + //BBS: if not set the speed, then use the filament_max_volumetric_speed directly + if( speed == 0 ) + { + if (_mm3_per_mm>0) + speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + else + speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; + } + if (this->on_first_layer()) { + //BBS: for solid infill of initial layer, speed can be higher as long as + //wall lines have be attached + if (path.role() != erBottomSurface) + speed = m_config.get_abs_value("initial_layer_speed"); + } + //BBS: remove this config + //else if (this->object_layer_over_raft()) + // speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); + //if (m_config.max_volumetric_speed.value > 0) { + // // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + // speed = std::min( + // speed, + // m_config.max_volumetric_speed.value / path.mm3_per_mm + // ); + //} + if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { + double extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm; + if (_mm3_per_mm > 0) + extrude_speed = EXTRUDER_CONFIG(filament_max_volumetric_speed) / _mm3_per_mm; + + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = std::min(speed, extrude_speed); + } + double F = speed * 60; // convert mm/sec to mm/min + + // extrude arc or line + if (m_enable_extrusion_role_markers) + { + if (path.role() != m_last_extrusion_role) + { + m_last_extrusion_role = path.role(); + if (m_enable_extrusion_role_markers) + { + char buf[32]; + sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role)); + gcode += buf; + } + } + } + + // adds processor tags and updates processor tracking data + // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height + // so, if the last role was erWipeTower we force export of GCodeProcessor::Height_Tag lines + bool last_was_wipe_tower = (m_last_processor_extrusion_role == erWipeTower); + char buf[64]; + assert(is_decimal_separator_point()); + + if (path.role() != m_last_processor_extrusion_role) { + m_last_processor_extrusion_role = path.role(); + sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(m_last_processor_extrusion_role).c_str()); + gcode += buf; + } + + if (last_was_wipe_tower || m_last_width != path.width) { + m_last_width = path.width; + sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width).c_str(), m_last_width); + gcode += buf; + } + +#if ENABLE_GCODE_VIEWER_DATA_CHECKING + if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { + m_last_mm3_per_mm = path.mm3_per_mm; + sprintf(buf, ";%s%f\n", GCodeProcessor::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm); + gcode += buf; + } +#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) { + m_last_height = path.height; + sprintf(buf, ";%s%g\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height).c_str(), m_last_height); + gcode += buf; + } + + std::string comment; + if (m_enable_cooling_markers) { + if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { + //BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter + int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? + Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; + if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter)) { + gcode += ";_OVERHANG_FAN_START\n"; + comment = ";_EXTRUDE_SET_SPEED"; + } else if (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role())) + gcode += ";_OVERHANG_FAN_START\n"; + else + comment = ";_EXTRUDE_SET_SPEED"; + } + else { + comment = ";_EXTRUDE_SET_SPEED"; + } + + if (path.role() == erExternalPerimeter) + comment += ";_EXTERNAL_PERIMETER"; + } + + // F is mm per minute. + //if (sloped == nullptr) + gcode += m_writer.set_speed(F, "", comment); + + double path_length = 0.; + { + std::string comment = GCodeWriter::full_gcode_comment ? description : ""; + //BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode + //Attention: G2 and G3 is not supported in spiral_mode mode + if (!m_config.enable_arc_fitting || + path.polyline.fitting_result.empty() || + m_config.spiral_mode || + sloped != nullptr) { + double path_length = 0.; + double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR; + for (const Line &line : path.polyline.lines()) { + const double line_length = line.length() * SCALING_FACTOR; + // BBS: extursion cmd should E0 on cmd line + if (line_length < EPSILON) continue; + path_length += line_length; + + if (sloped == nullptr) { + gcode += m_writer.extrude_to_xy( + this->point_to_gcode(line.b), + e_per_mm * line_length, + comment); + } else { + // Sloped extrusion + auto dE = e_per_mm * line_length; + auto [z_ratio, e_ratio, slope_speed] = sloped->interpolate(path_length / total_length); + //FIX: cooling need to apply correctly + //gcode += m_writer.set_speed(slope_speed * 60, "", comment); + Vec2d dest2d = this->point_to_gcode(line.b); + Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio)); + //BBS: todo, should use small e at start to get good seam + double slope_e = dE * e_ratio; + gcode += m_writer.extrude_to_xyz(dest3d, slope_e); + } + } + } else { + // BBS: start to generate gcode from arc fitting data which includes line and arc + const std::vector& fitting_result = path.polyline.fitting_result; + for (size_t fitting_index = 0; fitting_index < fitting_result.size(); fitting_index++) { + switch (fitting_result[fitting_index].path_type) { + case EMovePathType::Linear_move: { + size_t start_index = fitting_result[fitting_index].start_point_index; + size_t end_index = fitting_result[fitting_index].end_point_index; + for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) { + const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]); + const double line_length = line.length() * SCALING_FACTOR; + // BBS: extursion cmd should E0 on cmd line + if (line_length < EPSILON) + continue; + path_length += line_length; + gcode += m_writer.extrude_to_xy( + this->point_to_gcode(line.b), + e_per_mm * line_length, + comment, path.is_force_no_extrusion()); + } + break; + } + case EMovePathType::Arc_move_cw: + case EMovePathType::Arc_move_ccw: { + const ArcSegment& arc = fitting_result[fitting_index].arc_data; + const double arc_length = fitting_result[fitting_index].arc_data.length * SCALING_FACTOR; + // BBS: extursion cmd should E0 on cmd line + if (arc_length < EPSILON) + continue; + const Vec2d center_offset = this->point_to_gcode(arc.center) - this->point_to_gcode(arc.start_point); + path_length += arc_length; + gcode += m_writer.extrude_arc_to_xy( + this->point_to_gcode(arc.end_point), + center_offset, + e_per_mm * arc_length, + arc.direction == ArcDirection::Arc_Dir_CCW, + comment, path.is_force_no_extrusion()); + break; + } + default: + //BBS: should never happen that a empty path_type has been stored + assert(0); + break; + } + } + } + } + if (m_enable_cooling_markers) { + if (EXTRUDER_CONFIG(enable_overhang_bridge_fan)) { + //BBS: Overhang_threshold_none means Overhang_threshold_1_4 and forcing cooling for all external perimeter + int overhang_threshold = EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none ? + Overhang_threshold_none : EXTRUDER_CONFIG(overhang_fan_threshold) - 1; + if ((EXTRUDER_CONFIG(overhang_fan_threshold) == Overhang_threshold_none && path.role() == erExternalPerimeter)) { + gcode += ";_EXTRUDE_END\n"; + gcode += ";_OVERHANG_FAN_END\n"; + + } else if (path.get_overhang_degree() > overhang_threshold || + is_bridge(path.role())) + gcode += ";_OVERHANG_FAN_END\n"; + else + gcode += ";_EXTRUDE_END\n"; + } + else { + gcode += ";_EXTRUDE_END\n"; + } + } + + this->set_last_pos(path.last_point()); + return gcode; +} + +std::string encodeBase64(uint64_t value) +{ + //Always use big endian mode + uint8_t src[8]; + for (size_t i = 0; i < 8; i++) + src[i] = (value >> (8 * i)) & 0xff; + + std::string dest; + dest.resize(boost::beast::detail::base64::encoded_size(sizeof(src))); + dest.resize(boost::beast::detail::base64::encode(&dest[0], src, sizeof(src))); + return dest; +} + +std::string GCode::_encode_label_ids_to_base64(std::vector ids) +{ + assert(m_label_objects_ids.size() < 64); + + uint64_t bitset = 0; + for (size_t id : ids) { + auto index = std::lower_bound(m_label_objects_ids.begin(), m_label_objects_ids.end(), id); + if (index != m_label_objects_ids.end() && *index == id) + bitset |= (1ull << (index - m_label_objects_ids.begin())); + else + throw Slic3r::LogicError("Unknown label object id!"); + } + if (bitset == 0) + throw Slic3r::LogicError("Label object id error!"); + + return encodeBase64(bitset); +} + +// This method accepts &point in print coordinates. +std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment, double z ) +{ + /* Define the travel move as a line between current position and the taget point. + This is expressed in print coordinates, so it will need to be translated by + this->origin in order to get G-code coordinates. */ + Polyline travel { this->last_pos(), point }; + + // check whether a straight travel move would need retraction + LiftType lift_type = LiftType::SpiralLift; + bool needs_retraction = this->needs_retraction(travel, role, lift_type); + // check whether wipe could be disabled without causing visible stringing + bool could_be_wipe_disabled = false; + // Save state of use_external_mp_once for the case that will be needed to call twice m_avoid_crossing_perimeters.travel_to. + const bool used_external_mp_once = m_avoid_crossing_perimeters.used_external_mp_once(); + + // if a retraction would be needed, try to use reduce_crossing_wall to plan a + // multi-hop travel path inside the configuration space + if (needs_retraction + && m_config.reduce_crossing_wall + && ! m_avoid_crossing_perimeters.disabled_once() + //BBS: don't generate detour travel paths when current position is unclear + && m_writer.is_current_position_clear()) { + travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); + // check again whether the new travel path still needs a retraction + needs_retraction = this->needs_retraction(travel, role, lift_type); + //if (needs_retraction && m_layer_index > 1) exit(0); + } + + // Re-allow reduce_crossing_wall for the next travel moves + m_avoid_crossing_perimeters.reset_once_modifiers(); + + // generate G-code for the travel move + std::string gcode; + if (needs_retraction) { + if (m_config.reduce_crossing_wall && could_be_wipe_disabled) + m_wipe.reset_path(); + + Point last_post_before_retract = this->last_pos(); + gcode += this->retract(false, false, lift_type); + // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. + // Because of it, it is necessary to call avoid crossing perimeters again with new starting point after calling retraction() + // FIXME Lukas H.: Try to predict if this second calling of avoid crossing perimeters will be needed or not. It could save computations. + if (last_post_before_retract != this->last_pos() && m_config.reduce_crossing_wall) { + // If in the previous call of m_avoid_crossing_perimeters.travel_to was use_external_mp_once set to true restore this value for next call. + if (used_external_mp_once) + m_avoid_crossing_perimeters.use_external_mp_once(); + travel = m_avoid_crossing_perimeters.travel_to(*this, point); + // If state of use_external_mp_once was changed reset it to right value. + if (used_external_mp_once) + m_avoid_crossing_perimeters.reset_once_modifiers(); + } + } else + // Reset the wipe path when traveling, so one would not wipe along an old path. + m_wipe.reset_path(); + + // if needed, write the gcode_label_objects_end then gcode_label_objects_start + m_writer.add_object_change_labels(gcode); + + // use G1 because we rely on paths being straight (G0 may make round paths) + if (travel.size() >= 2) { + // OrcaSlicer + if (this->on_first_layer()) { + if (m_config.default_jerk.value > 0 && m_config.initial_layer_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.initial_layer_jerk.value); + } else if (m_config.default_jerk.value > 0 && m_config.travel_jerk.value > 0 && !this->is_BBL_Printer()) + gcode += m_writer.set_jerk_xy(m_config.travel_jerk.value); + + if (m_spiral_vase) { + // No lazy z lift for spiral vase mode + for (size_t i = 1; i < travel.size(); ++i) + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + } else { + if (travel.size() == 2) { + // No extra movements emitted by avoid_crossing_perimeters, simply move to the end point with z change + const auto &dest2d = this->point_to_gcode(travel.points.back()); + Vec3d dest3d(dest2d(0), dest2d(1), z == DBL_MAX ? m_nominal_z : z); + gcode += m_writer.travel_to_xyz(dest3d, comment); + } else { + // Extra movements emitted by avoid_crossing_perimeters, lift the z to normal height at the beginning, then apply the z + // ratio at the last point + for (size_t i = 1; i < travel.size(); ++i) { + if (i == 1) { + // Lift to normal z at beginning + Vec2d dest2d = this->point_to_gcode(travel.points[i]); + Vec3d dest3d(dest2d(0), dest2d(1), m_nominal_z); + gcode += m_writer.travel_to_xyz(dest3d, comment); + } else if (z != DBL_MAX && i == travel.size() - 1) { + // Apply z_ratio for the very last point + Vec2d dest2d = this->point_to_gcode(travel.points[i]); + Vec3d dest3d(dest2d(0), dest2d(1), z); + gcode += m_writer.travel_to_xyz(dest3d, comment); + } else { + // For all points in between, no z change + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment ); + } + } + } + } + this->set_last_pos(travel.points.back()); + } + + return gcode; +} + +//BBS +LiftType GCode::to_lift_type(ZHopType z_hop_types) { + switch (z_hop_types) + { + case ZHopType::zhtNormal: + return LiftType::NormalLift; + case ZHopType::zhtSlope: + return LiftType::LazyLift; + case ZHopType::zhtSpiral: + return LiftType::SpiralLift; + default: + // if no corresponding lift type, use normal lift + return LiftType::NormalLift; + } +}; + +bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftType& lift_type) +{ + if (travel.length() < scale_(EXTRUDER_CONFIG(retraction_minimum_travel))) { + // skip retraction if the move is shorter than the configured threshold + return false; + } + + //BBS: input travel polyline must be in current plate coordinate system + auto is_through_overhang = [this](const Polyline& travel) { + BoundingBox travel_bbox = get_extents(travel); + travel_bbox.inflated(1); + travel_bbox.defined = true; + + // do not scale for z + const float protect_z = 0.4; + std::pair z_range; + z_range.second = m_layer ? m_layer->print_z : 0.f; + z_range.first = std::max(0.f, z_range.second - protect_z); + std::vector layers_of_objects; + std::vector boundingBox_for_objects; + std::vector objects_instances_shift; + std::vector idx_of_object_sorted = m_curr_print->layers_sorted_for_object(z_range.first, z_range.second, layers_of_objects, boundingBox_for_objects, objects_instances_shift); + + std::vector is_layers_of_objects_sorted(layers_of_objects.size(), false); + + for (size_t idx : idx_of_object_sorted) { + for (const Point & instance_shift : objects_instances_shift[idx]) { + BoundingBox instance_bbox = boundingBox_for_objects[idx]; + if (!instance_bbox.defined) //BBS: Don't need to check when bounding box of overhang area is empty(undefined) + continue; + + instance_bbox.offset(scale_(EPSILON)); + instance_bbox.translate(instance_shift.x(), instance_shift.y()); + if (!instance_bbox.overlap(travel_bbox)) + continue; + + Polygons temp; + temp.emplace_back(std::move(instance_bbox.polygon())); + if (intersection_pl(travel, temp).empty()) + continue; + + if (!is_layers_of_objects_sorted[idx]) { + std::sort(layers_of_objects[idx].begin(), layers_of_objects[idx].end(), [](auto left, auto right) { return left->loverhangs_bbox.area() > right->loverhangs_bbox.area();}); + is_layers_of_objects_sorted[idx] = true; + } + + for (const auto& layer : layers_of_objects[idx]) { + for (ExPolygon overhang : layer->loverhangs) { + overhang.translate(instance_shift); + BoundingBox bbox1 = get_extents(overhang); + + if (!bbox1.overlap(travel_bbox)) + continue; + + if (intersection_pl(travel, overhang).empty()) + continue; + + return true; + } + } + } + } + return false; + }; + + float max_z_hop = 0.f; + for (int i = 0; i < m_config.z_hop.size(); i++) + max_z_hop = std::max(max_z_hop, (float)m_config.z_hop.get_at(i)); + float travel_len_thresh = scale_(max_z_hop / tan(GCodeWriter::slope_threshold)); + float accum_len = 0.f; + Polyline clipped_travel; + + clipped_travel.append(Polyline(travel.points[0], travel.points[1])); + if (clipped_travel.length() > travel_len_thresh) + clipped_travel.points.back() = clipped_travel.points.front()+(clipped_travel.points.back() - clipped_travel.points.front()) * (travel_len_thresh / clipped_travel.length()); + //BBS: translate to current plate coordinate system + clipped_travel.translate(Point::new_scale(double(m_origin.x() - m_writer.get_xy_offset().x()), double(m_origin.y() - m_writer.get_xy_offset().y()))); + + //BBS: force to retract when leave from external perimeter for a long travel + //Better way is judging whether the travel move direction is same with last extrusion move. + if (is_perimeter(m_last_processor_extrusion_role) && m_last_processor_extrusion_role != erPerimeter) { + if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { + lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift; + } + else { + lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); + } + return true; + } + + if (role == erSupportMaterial || role == erSupportTransition) { + const SupportLayer* support_layer = dynamic_cast(m_layer); + //FIXME support_layer->support_islands.contains should use some search structure! + if (support_layer != NULL) + // skip retraction if this is a travel move inside a support material island + //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material + // at the end of the extrusion path! + for (const ExPolygon& support_island : support_layer->support_islands) + if (support_island.contains(travel)) + return false; + //reduce the retractions in lightning infills for tree support + if (support_layer != NULL && support_layer->support_type==stInnerTree) + for (auto &area : support_layer->base_areas) + if (area.contains(travel)) + return false; + } + //BBS: need retract when long moving to print perimeter to avoid dropping of material + if (!is_perimeter(role) && m_config.reduce_infill_retraction && m_layer != nullptr && + m_config.sparse_infill_density.value > 0 && m_retract_when_crossing_perimeters.travel_inside_internal_regions(*m_layer, travel)) + // Skip retraction if travel is contained in an internal slice *and* + // internal infill is enabled (so that stringing is entirely not visible). + //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. + return false; + + // retract if reduce_infill_retraction is disabled or doesn't apply when role is perimeter + if (ZHopType(EXTRUDER_CONFIG(z_hop_types)) == ZHopType::zhtAuto) { + lift_type = is_through_overhang(clipped_travel) ? LiftType::SpiralLift : LiftType::LazyLift; + } + else { + lift_type = to_lift_type(ZHopType(EXTRUDER_CONFIG(z_hop_types))); + } + return true; +} + +std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType lift_type) +{ + std::string gcode; + + if (m_writer.extruder() == nullptr) + return gcode; + + // wipe (if it's enabled for this extruder and we have a stored wipe path and no-zero wipe distance) + if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path() && scale_(EXTRUDER_CONFIG(wipe_distance)) > SCALED_EPSILON) { + gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true); + gcode += m_wipe.wipe(*this, toolchange, is_last_retraction); + } + + /* The parent class will decide whether we need to perform an actual retraction + (the extruder might be already retracted fully or partially). We call these + methods even if we performed wipe, since this will ensure the entire retraction + length is honored in case wipe path was too short. */ + gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); + + gcode += m_writer.reset_e(); + //BBS + if (m_writer.extruder()->retraction_length() > 0||m_config.use_firmware_retraction) { + // BBS: force to use normal lift for spiral vase mode + gcode += m_writer.lift(lift_type, m_spiral_vase != nullptr); + } + + return gcode; +} + +std::string GCode::set_extruder(unsigned int extruder_id, double print_z, bool by_object) +{ + if (!m_writer.need_toolchange(extruder_id)) + return ""; + + // if we are running a single-extruder setup, just set the extruder and return nothing + if (!m_writer.multiple_extruders) { + m_placeholder_parser.set("current_extruder", extruder_id); + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(extruder_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(extruder_id)); + + std::string gcode; + // Append the filament start G-code. + const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); + if (! filament_start_gcode.empty()) { + // Process the filament_start_gcode for the filament. + gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, extruder_id); + check_add_eol(gcode); + } + //BBS: never use for Bambu Printer + if (!this->is_BBL_Printer() && m_config.enable_pressure_advance.get_at(extruder_id)) + gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(extruder_id)); + + gcode += m_writer.toolchange(extruder_id); + return gcode; + } + + // BBS. Should be placed before retract. + m_toolchange_count++; + + // prepend retraction on the current extruder + std::string gcode = this->retract(true, false); + + // Always reset the extrusion path, even if the tool change retract is set to zero. + m_wipe.reset_path(); + + // BBS: insert skip object label before change filament while by object + if (by_object) + m_writer.add_object_change_labels(gcode); + + if (m_writer.extruder() != nullptr) { + // Process the custom filament_end_gcode. set_extruder() is only called if there is no wipe tower + // so it should not be injected twice. + unsigned int old_extruder_id = m_writer.extruder()->id(); + const std::string &filament_end_gcode = m_config.filament_end_gcode.get_at(old_extruder_id); + if (! filament_end_gcode.empty()) { + gcode += placeholder_parser_process("filament_end_gcode", filament_end_gcode, old_extruder_id); + check_add_eol(gcode); + } + } + + + // If ooze prevention is enabled, park current extruder in the nearest + // standby point and set it to the standby temperature. + if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) + gcode += m_ooze_prevention.pre_toolchange(*this); + + // BBS + float new_retract_length = m_config.retraction_length.get_at(extruder_id); + float new_retract_length_toolchange = m_config.retract_length_toolchange.get_at(extruder_id); + int new_filament_temp = this->on_first_layer() ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id): m_config.nozzle_temperature.get_at(extruder_id); + // BBS: if print_z == 0 use first layer temperature + if (abs(print_z) < EPSILON) + new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(extruder_id); + + Vec3d nozzle_pos = m_writer.get_position(); + float old_retract_length, old_retract_length_toolchange, wipe_volume; + int old_filament_temp, old_filament_e_feedrate; + + float filament_area = float((M_PI / 4.f) * pow(m_config.filament_diameter.get_at(extruder_id), 2)); + //BBS: add handling for filament change in start gcode + int previous_extruder_id = -1; + if (m_writer.extruder() != nullptr || m_start_gcode_filament != -1) { + std::vector flush_matrix(cast(m_config.flush_volumes_matrix.values)); + const unsigned int number_of_extruders = (unsigned int)(sqrt(flush_matrix.size()) + EPSILON); + if (m_writer.extruder() != nullptr) + assert(m_writer.extruder()->id() < number_of_extruders); + else + assert(m_start_gcode_filament < number_of_extruders); + + previous_extruder_id = m_writer.extruder() != nullptr ? m_writer.extruder()->id() : m_start_gcode_filament; + old_retract_length = m_config.retraction_length.get_at(previous_extruder_id); + old_retract_length_toolchange = m_config.retract_length_toolchange.get_at(previous_extruder_id); + old_filament_temp = this->on_first_layer()? m_config.nozzle_temperature_initial_layer.get_at(previous_extruder_id) : m_config.nozzle_temperature.get_at(previous_extruder_id); + wipe_volume = flush_matrix[previous_extruder_id * number_of_extruders + extruder_id]; + wipe_volume *= m_config.flush_multiplier; + old_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(previous_extruder_id) / filament_area); + old_filament_e_feedrate = old_filament_e_feedrate == 0 ? 100 : old_filament_e_feedrate; + //BBS: must clean m_start_gcode_filament + m_start_gcode_filament = -1; + } else { + old_retract_length = 0.f; + old_retract_length_toolchange = 0.f; + old_filament_temp = 0; + wipe_volume = 0.f; + old_filament_e_feedrate = 200; + } + float wipe_length = wipe_volume / filament_area; + int new_filament_e_feedrate = (int)(60.0 * m_config.filament_max_volumetric_speed.get_at(extruder_id) / filament_area); + new_filament_e_feedrate = new_filament_e_feedrate == 0 ? 100 : new_filament_e_feedrate; + + DynamicConfig dyn_config; + dyn_config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + dyn_config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + dyn_config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + dyn_config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + dyn_config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); + dyn_config.set_key_value("relative_e_axis", new ConfigOptionBool(m_config.use_relative_e_distances)); + dyn_config.set_key_value("toolchange_count", new ConfigOptionInt((int)m_toolchange_count)); + //BBS: fan speed is useless placeholer now, but we don't remove it to avoid + //slicing error in old change_filament_gcode in old 3MF + dyn_config.set_key_value("fan_speed", new ConfigOptionInt((int)0)); + dyn_config.set_key_value("old_retract_length", new ConfigOptionFloat(old_retract_length)); + dyn_config.set_key_value("new_retract_length", new ConfigOptionFloat(new_retract_length)); + dyn_config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); + dyn_config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); + dyn_config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); + dyn_config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); + dyn_config.set_key_value("x_after_toolchange", new ConfigOptionFloat(nozzle_pos(0))); + dyn_config.set_key_value("y_after_toolchange", new ConfigOptionFloat(nozzle_pos(1))); + dyn_config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); + dyn_config.set_key_value("first_flush_volume", new ConfigOptionFloat(wipe_length / 2.f)); + dyn_config.set_key_value("second_flush_volume", new ConfigOptionFloat(wipe_length / 2.f)); + dyn_config.set_key_value("old_filament_e_feedrate", new ConfigOptionInt(old_filament_e_feedrate)); + dyn_config.set_key_value("new_filament_e_feedrate", new ConfigOptionInt(new_filament_e_feedrate)); + dyn_config.set_key_value("travel_point_1_x", new ConfigOptionFloat(float(travel_point_1.x()))); + dyn_config.set_key_value("travel_point_1_y", new ConfigOptionFloat(float(travel_point_1.y()))); + dyn_config.set_key_value("travel_point_2_x", new ConfigOptionFloat(float(travel_point_2.x()))); + dyn_config.set_key_value("travel_point_2_y", new ConfigOptionFloat(float(travel_point_2.y()))); + dyn_config.set_key_value("travel_point_3_x", new ConfigOptionFloat(float(travel_point_3.x()))); + dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); + + dyn_config.set_key_value("flush_length", new ConfigOptionFloat(wipe_length)); + + int flush_count = std::min(g_max_flush_count, (int)std::round(wipe_volume / g_purge_volume_one_time)); + // handle cases for very small purge + if (flush_count == 0 && wipe_volume > 0) + flush_count += 1; + float flush_unit = wipe_length / flush_count; + int flush_idx = 0; + for (; flush_idx < flush_count; flush_idx++) { + char key_value[64] = { 0 }; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + dyn_config.set_key_value(key_value, new ConfigOptionFloat(flush_unit)); + } + + for (; flush_idx < g_max_flush_count; flush_idx++) { + char key_value[64] = { 0 }; + snprintf(key_value, sizeof(key_value), "flush_length_%d", flush_idx + 1); + dyn_config.set_key_value(key_value, new ConfigOptionFloat(0.f)); + } + + // Process the custom change_filament_gcode. + const std::string& change_filament_gcode = m_config.change_filament_gcode.value; + std::string toolchange_gcode_parsed; + if (!change_filament_gcode.empty()) { + toolchange_gcode_parsed = placeholder_parser_process("change_filament_gcode", change_filament_gcode, extruder_id, &dyn_config); + check_add_eol(toolchange_gcode_parsed); + gcode += toolchange_gcode_parsed; + + //BBS + { + //BBS: gcode writer doesn't know where the extruder is and whether fan speed is changed after inserting tool change gcode + //Set this flag so that normal lift will be used the first time after tool change. + gcode += ";_FORCE_RESUME_FAN_SPEED\n"; + m_writer.set_current_position_clear(false); + //BBS: check whether custom gcode changes the z position. Update if changed + double temp_z_after_tool_change; + if (GCodeProcessor::get_last_z_from_gcode(toolchange_gcode_parsed, temp_z_after_tool_change)) { + Vec3d pos = m_writer.get_position(); + pos(2) = temp_z_after_tool_change; + m_writer.set_position(pos); + } + } + } + + // BBS. Reset old extruder E-value. + // Keep retract length because Custom GCode will guarantee retract length be the same as toolchange + if (m_config.single_extruder_multi_material) { + m_writer.reset_e(); + } + + // We inform the writer about what is happening, but we may not use the resulting gcode. + std::string toolchange_command = m_writer.toolchange(extruder_id); + if (! custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), extruder_id)) + gcode += toolchange_command; + else { + // user provided his own toolchange gcode, no need to do anything + } + + // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) + if (m_config.single_extruder_multi_material && !m_config.enable_prime_tower) { + int temp = (m_layer_index <= 0 ? m_config.nozzle_temperature_initial_layer.get_at(extruder_id) : + m_config.nozzle_temperature.get_at(extruder_id)); + + gcode += m_writer.set_temperature(temp, false); + } + + m_placeholder_parser.set("current_extruder", extruder_id); + m_placeholder_parser.set("retraction_distance_when_cut", m_config.retraction_distances_when_cut.get_at(extruder_id)); + m_placeholder_parser.set("long_retraction_when_cut", m_config.long_retractions_when_cut.get_at(extruder_id)); + + // Append the filament start G-code. + const std::string &filament_start_gcode = m_config.filament_start_gcode.get_at(extruder_id); + if (! filament_start_gcode.empty()) { + // Process the filament_start_gcode for the new filament. + gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, extruder_id); + check_add_eol(gcode); + } + // Set the new extruder to the operating temperature. + if (m_ooze_prevention.enable) + gcode += m_ooze_prevention.post_toolchange(*this); + //BBS: never use for Bambu Printer + if (!this->is_BBL_Printer() && m_config.enable_pressure_advance.get_at(extruder_id)) + gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(extruder_id)); + + return gcode; +} + +inline std::string polygon_to_string(const Polygon& polygon, Print* print) { + std::ostringstream gcode; + gcode << "["; + for (const Point& p : polygon.points) { + const auto v = print->translate_to_print_space(p); + gcode << "[" << v.x() << "," << v.y() << "],"; + } + const auto first_v = print->translate_to_print_space(polygon.points.front()); + gcode << "[" << first_v.x() << "," << first_v.y() << "]"; + gcode << "]"; + return gcode.str(); +} +// this function iterator PrintObject and assign a seqential id to each object. +// this id is used to generate unique object id for each object. +std::string GCode::set_object_info(Print* print) +{ + std::ostringstream gcode; + size_t object_id = 0; + for (PrintObject* object : print->objects()) { + object->set_klipper_object_id(object_id++); + size_t inst_id = 0; + for (PrintInstance& inst : object->instances()) { + inst.id = inst_id++; + if (this->config().exclude_object && print->config().gcode_flavor.value == gcfKlipper) { + auto bbox = inst.get_bounding_box(); + auto center = print->translate_to_print_space(Vec2d(bbox.center().x(), bbox.center().y())); + gcode << "EXCLUDE_OBJECT_DEFINE NAME=" << get_instance_name(object, inst) << " CENTER=" << center.x() + << "," << center.y() << " POLYGON=" << polygon_to_string(inst.get_convex_hull_2d(), print) + << "\n"; + } + } + } + return gcode.str(); +} + +// convert a model-space scaled point into G-code coordinates +Vec2d GCode::point_to_gcode(const Point &point) const +{ + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); + return unscale(point) + m_origin - extruder_offset; +} + +// convert a model-space scaled point into G-code coordinates +Point GCode::gcode_to_point(const Vec2d &point) const +{ + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); + return Point( + scale_(point(0) - m_origin(0) + extruder_offset(0)), + scale_(point(1) - m_origin(1) + extruder_offset(1))); +} + +// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed +// during infill/perimeter wiping, or normally (depends on wiping_entities parameter) +// Fills in by_region_per_copy_cache and returns its reference. +const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const +{ + bool has_overrides = false; + for (const auto& reg : by_region) + if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { + has_overrides = true; + break; + } + + // Data is cleared, but the memory is not. + by_region_per_copy_cache.clear(); + + if (! has_overrides) + // Simple case. No need to copy the regions. + return wiping_entities ? by_region_per_copy_cache : this->by_region; + + // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions. + // Some of the extrusions of some object instances are printed later - those are the clean print extrusions. + // Filter out the extrusions based on the infill_overrides / perimeter_overrides: + + for (const auto& reg : by_region) { + by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island + + // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed + // References are used so that we don't have to repeat the same code + for (int iter = 0; iter < 2; ++iter) { + const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); + ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); + const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); + + // Now the most important thing - which extrusion should we print. + // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. + if (wiping_entities) { + // Apply overrides for this region. + for (unsigned int i = 0; i < overrides.size(); ++ i) { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which overrides the default one. + if (this_override != nullptr && (*this_override)[copy] == int(extruder)) + target_eec.emplace_back(entities[i]); + } + } else { + // Apply normal extrusions (non-overrides) for this region. + unsigned int i = 0; + for (; i < overrides.size(); ++ i) { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. + if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) + target_eec.emplace_back(entities[i]); + } + for (; i < entities.size(); ++ i) + target_eec.emplace_back(entities[i]); + } + } + } + return by_region_per_copy_cache; +} + +// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) +// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. +void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder) +{ + // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: + ExtrusionEntitiesPtr* perimeters_or_infills; + std::vector* perimeters_or_infills_overrides; + + switch (type) { + case PERIMETERS: + perimeters_or_infills = &perimeters; + perimeters_or_infills_overrides = &perimeters_overrides; + break; + case INFILL: + perimeters_or_infills = &infills; + perimeters_or_infills_overrides = &infills_overrides; + break; + default: + throw Slic3r::InvalidArgument("Unknown parameter!"); + } + + // First we append the entities, there are eec->entities.size() of them: + size_t old_size = perimeters_or_infills->size(); + size_t new_size = old_size + (eec->can_sort() ? eec->entities.size() : 1); + perimeters_or_infills->reserve(new_size); + if (eec->can_sort()) { + for (auto* ee : eec->entities) + perimeters_or_infills->emplace_back(ee); + } else + perimeters_or_infills->emplace_back(const_cast(eec)); + + if (copies_extruder != nullptr) { + // Don't reallocate overrides if not needed. + // Missing overrides are implicitely considered non-overridden. + perimeters_or_infills_overrides->reserve(new_size); + perimeters_or_infills_overrides->resize(old_size, nullptr); + perimeters_or_infills_overrides->resize(new_size, copies_extruder); + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fff501a80..c8cfe4045 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -940,7 +940,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.emplace_back(L("No-brim")); def->mode = comSimple; - def->set_default_value(new ConfigOptionEnum(btAutoBrim)); + def->set_default_value(new ConfigOptionEnum(btOuterOnly)); def = this->add("brim_object_gap", coFloat); def->label = L("Brim-object gap"); @@ -1163,11 +1163,11 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("monotonicline"); def->enum_values.push_back("alignedrectilinear"); //xiamian- - //def->enum_values.push_back("hilbertcurve"); - //def->enum_values.push_back("archimedeanchords"); - //def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); //xiamian+ - def->enum_values.push_back("fiberspiral"); + //def->enum_values.push_back("fiberspiral"); def->enum_labels.push_back(L("Concentric")); def->enum_labels.push_back(L("Rectilinear")); @@ -1175,11 +1175,11 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Monotonic line")); def->enum_labels.push_back(L("Aligned Rectilinear")); //xiamian- - //def->enum_labels.push_back(L("Hilbert Curve")); - //def->enum_labels.push_back(L("Archimedean Chords")); - //def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); //xiamian+ - def->enum_labels.push_back(L("Fiber Spiral")); + //def->enum_labels.push_back(L("Fiber Spiral")); def->set_default_value(new ConfigOptionEnum(ipRectilinear)); def = this->add("bottom_surface_pattern", coEnum); @@ -1618,14 +1618,14 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("alignedrectilinear"); def->enum_values.push_back("3dhoneycomb"); //xiamian- - //def->enum_values.push_back("hilbertcurve"); - //def->enum_values.push_back("archimedeanchords"); - //def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("hilbertcurve"); + def->enum_values.push_back("archimedeanchords"); + def->enum_values.push_back("octagramspiral"); def->enum_values.push_back("supportcubic"); def->enum_values.push_back("lightning"); def->enum_values.push_back("crosshatch"); //xiamian+ - def->enum_values.push_back("fiberspiral"); + //def->enum_values.push_back("fiberspiral"); def->enum_labels.push_back(L("Concentric")); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); @@ -1639,15 +1639,15 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Aligned Rectilinear")); def->enum_labels.push_back(L("3D Honeycomb")); //xiamian- - //def->enum_labels.push_back(L("Hilbert Curve")); - //def->enum_labels.push_back(L("Archimedean Chords")); - //def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Hilbert Curve")); + def->enum_labels.push_back(L("Archimedean Chords")); + def->enum_labels.push_back(L("Octagram Spiral")); def->enum_labels.push_back(L("Support Cubic")); def->enum_labels.push_back(L("Lightning")); def->enum_labels.push_back(L("Cross Hatch")); //xiamian+ - def->enum_labels.push_back(L("Fiber Spiral")); - def->set_default_value(new ConfigOptionEnum(ipCubic)); + //def->enum_labels.push_back(L("Fiber Spiral")); + def->set_default_value(new ConfigOptionEnum(ipGrid)); def = this->add("top_surface_acceleration", coFloat); def->label = L("Top surface"); @@ -1776,8 +1776,18 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Height of initial layer. Making initial layer height thick slightly can improve build plate adhension"); def->sidetext = L("mm"); def->min = 0; + //def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); + //def = this->add("initial_layer_print_height", coFloat); + //def->label = L("Initial layer height"); + //def->category = L("Quality"); + //def->tooltip = L("Height of initial layer. Making initial layer height thick slightly can improve build plate adhension"); + //def->sidetext = L("mm"); + //def->min = 0; + //def->mode = comSimple; + //def->set_default_value(new ConfigOptionFloat(0.2)); + //def = this->add("adaptive_layer_height", coBool); //def->label = L("Adaptive layer height"); //def->category = L("Quality"); @@ -1788,7 +1798,7 @@ void PrintConfigDef::init_fff_params() def = this->add("default_print_speed", coFloat); def->label = L("Default print speed"); - def->tooltip = L("Speed of initial layer except the solid infill part"); + //def->tooltip = L("Speed of initial layer except the solid infill part"); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; @@ -1857,6 +1867,16 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionFloat(0.3)); + def = this->add("fibre_feed_rate", coFloat); + def->label = L("Fibre feed rate"); + def->category = L("Others"); + //def->tooltip = L("The width within which to jitter. It's adversed to be below outer wall line width"); + def->sidetext = L("mm"); + def->min = 0; + def->max = 1; + def->mode = comSimple; + def->set_default_value(new ConfigOptionFloat(0.3)); + def = this->add("fuzzy_skin_point_distance", coFloat); def->label = L("Fuzzy skin point distance"); def->category = L("Others"); @@ -3007,7 +3027,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = 10; def->mode = comSimple; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(2)); def = this->add("slow_down_layer_time", coInts); def->label = L("Layer time"); @@ -3956,6 +3976,25 @@ void PrintConfigDef::init_fff_params() " Otherwise, rectilinear pattern is used defaultly."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); + + + //def = this->add("temp_fiber_pattern", coEnum); + //def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + //def->enum_values.push_back("concentric"); + //def->enum_values.push_back("zig-zag"); + //def->enum_values.push_back("monotonic"); + //def->enum_values.push_back("monotonicline"); + //def->enum_values.push_back("alignedrectilinear"); + //def->enum_values.push_back("fiberspiral"); + + //def->enum_labels.push_back(L("Concentric")); + //def->enum_labels.push_back(L("Rectilinear")); + //def->enum_labels.push_back(L("Monotonic")); + //def->enum_labels.push_back(L("Monotonic line")); + //def->enum_labels.push_back(L("Aligned Rectilinear")); + //def->enum_labels.push_back(L("Fiber Spiral")); + //def->set_default_value(new ConfigOptionEnum(ipRectilinear)); + } void PrintConfigDef::init_extruder_option_keys() diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 836980c16..4e372f213 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -326,16 +326,9 @@ public: } } wxBitmap new_ground(groundImage); - /* wxMask* mask = new wxMask(new_ground, *wxWHITE); - wxMemoryDC memDC(mask->GetBitmap()); - wxBrush brush(*wxWHITE); - memDC.SetBrush(brush); - memDC.SetPen(*wxTRANSPARENT_PEN); - int radius = 20; - memDC.DrawRoundedRectangle(0, 0, groundWidth, groundHeight, radius); - new_ground.SetMask(mask);*/ - + memDc.DrawBitmap(new_ground, 0, 50, true); + //memDc.SelectObject(wxNullBitmap); BitmapCache logo_cache; @@ -357,17 +350,34 @@ public: int split_width = (width + title_width - version_width) / 2; wxRect title_rect(wxPoint(150, 300), wxPoint(400,450)); //memDc.SetTextForeground(StateColor::darkModeColorFor(wxColour(38, 46, 48))); - memDc.SetTextForeground(StateColor::darkModeColorFor(wxColour(0, 0, 0))); - memDc.SetFont(m_constant_text.title_font); + //memDc.SetTextForeground(StateColor::darkModeColorFor(wxColour(0, 0, 0,255))); + //memDc.SetFont(m_constant_text.title_font); + //memDc.SetTextBackground(*wxBLACK); //memDc.DrawLabel(m_constant_text.title, title_rect, wxALIGN_RIGHT | wxALIGN_BOTTOM); - memDc.DrawLabel(_L("Ui title"), title_rect, wxALIGN_CENTER_VERTICAL); + //memDc.DrawLabel(_L("Ui title"), title_rect, wxALIGN_CENTER_VERTICAL); + //memDc.DrawText(_L("Ui title"), 150, 300); //BBS align bottom of title and version text //wxRect version_rect(wxPoint(split_width + text_padding, top_margin), wxPoint(width, top_margin + title_height - text_padding)); wxRect version_rect(wxPoint(50,550), wxPoint(300,550)); - memDc.SetFont(m_constant_text.version_font); - memDc.SetTextForeground(StateColor::darkModeColorFor(wxColor(134, 134, 134))); - memDc.DrawLabel(m_constant_text.version, version_rect, wxALIGN_LEFT | wxALIGN_BOTTOM); + //memDc.SetFont(m_constant_text.version_font); + //memDc.SetTextForeground(StateColor::darkModeColorFor(wxColor(0, 0, 0,255))); + //memDc.SetTextForeground(*wxBLACK); + //memDc.SetTextBackground(StateColor::darkModeColorFor(wxColor(0, 0, 0,255))); + //memDc.SetTextBackground(*wxBLACK); + //memDc.DrawLabel(m_constant_text.version, version_rect, wxALIGN_LEFT | wxALIGN_BOTTOM); + //memDc.DrawText(m_constant_text.version, 50, 500); + //version_title.svg + BitmapCache version_cache; + wxBitmap version_bmp = *version_cache.load_svg("version_title", FromDIP(600), FromDIP(600)); + memDc.DrawBitmap(version_bmp, 50, 500, false); + BitmapCache title_cache; + wxBitmap title_bmp = *title_cache.load_svg("ui_title", FromDIP(600), FromDIP(600)); + memDc.DrawBitmap(title_bmp, 50, 350, false); + + BitmapCache config_cache; + wxBitmap config_bmp = *config_cache.load_svg("config_title", FromDIP(550), FromDIP(550)); + memDc.DrawBitmap(config_bmp, 515, 600, false); // draw title and version //int text_padding = FromDIP(3 * m_scale); //memDc.SetFont(m_constant_text.title_font); @@ -402,13 +412,15 @@ public: //#endif // load bitmap for logo BitmapCache bmp_cache; + int logo_margin = FromDIP(72 * m_scale); int logo_size = FromDIP(122 * m_scale); int logo_width = FromDIP(94 * m_scale); wxBitmap logo_bmp = *bmp_cache.load_svg("splash_logo", logo_size, logo_size); int logo_y = top_margin + title_rect.GetHeight() + logo_margin; memDc.DrawBitmap(logo_bmp, 900, 0, true); - + + // calculate position for the dynamic text int text_margin = FromDIP(80 * m_scale); m_action_line_y_position = logo_y + logo_size + text_margin; @@ -419,90 +431,93 @@ public: int width = FromDIP(1000, nullptr); int height = FromDIP(550, nullptr); - /*wxImage image(width, height);111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 - wxBitmap new_bmp(image);2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 - 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 - wxMemoryDC memDC;41234444444444444444444444444444444444344444444444444444555555555555555555555555555555555555555555555555555555555555555555555555555 - memDC.SelectObject(new_bmp);12344444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 - memDC.SetBrush(StateColor::darkModeColorFor(wxColor(0, 157, 244,128)));2341324132444444444444444444444444444444444444444444444444444444444444444442 - memDC.DrawRectangle(-1, -1, width + 2, height + 2);23411111111111111111111111111111111111111111111111111111111111111111111111111111111121341234234 - memDC.DrawBitmap(new_bmp, 0, 0, true);*/ - // StateColor::darkModeColorFor(wxColor(0, 157, 244) - //fangfa1 - /* wxImage image(width, height, true); - unsigned char* alpha = image.GetAlpha(); - if (alpha) { - for (int i = 0; i < width * height; ++i) { - alpha[i] = 0; - } - } + /*wxImage image(width, height); wxBitmap new_bmp(image); wxMemoryDC memDC; memDC.SelectObject(new_bmp); - wxBrush brush(wxColor(254, 254, 254, 128)); - memDC.SetBrush(brush); + memDC.SetBrush(StateColor::darkModeColorFor(*wxWHITE)); memDC.DrawRectangle(-1, -1, width + 2, height + 2); memDC.DrawBitmap(new_bmp, 0, 0, true);*/ - //fangfa2 - //wxBitmap new_bmp(width, height, 32); + //wxImage image(width, height); + //wxBitmap new_bmp(image); //wxMemoryDC memDC; //memDC.SelectObject(new_bmp); - //memDC.SetBackground(*wxTRANSPARENT_BRUSH); - //memDC.Clear(); - //wxGraphicsContext* gc = wxGraphicsContext::Create(memDC); - //if (gc) { - // gc->SetAntialiasMode(wxANTIALIAS_DEFAULT); - // //wxColour semiTransparent(255, 255, 255, 128); - // //gc->SetBrush(semiTransparent); - // gc->DrawRectangle(0, 0, width, height); - // delete gc; - //} - //fangfa3 - /*wxBitmap bitmap(width, height,32); - { - wxMemoryDC dc(bitmap); - dc.SetBackground(*wxTRANSPARENT_BRUSH); - dc.Clear(); - } - { - wxMemoryDC dc(bitmap); - dc.SetBackground(*wxTRANSPARENT_BRUSH); - dc.Clear(); - dc.SetPen(wxPen(wxColour(255, 0, 0, 128))); - dc.SetBrush(wxBrush(wxColour(255, 0, 0, 128))); - dc.DrawRectangle(0, 0, width, height); - }*/ + //memDC.SetBrush(*wxWHITE_BRUSH); + //wxMask* mask = new wxMask(new_bmp, *wxWHITE); // Make cyan areas transparent + //new_bmp.SetMask(mask); + //memDC.DrawRectangle(-1, -1, width + 2, height + 2); + //memDC.DrawBitmap(new_bmp, 0, 0, true); + /* + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 + 44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + 555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555 + 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 + 77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777 + 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 + 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 + 44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + 555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555 + 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 + 77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777 + 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 + 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 + 44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + 555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555 + 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 + 77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777 + 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 + 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 + 44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + 555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555 + 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 + 77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777 + 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 + 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 + 44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + 555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555 + 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 + 77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777 + 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 + 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 + 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 + 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 + 44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444 + 555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555 + 666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666 + 77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777 + 88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 + 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + */ - /* wxBitmap mask(100, 100); - wxMemoryDC maskDC(mask); - maskDC.SetBackground(*wxWHITE_BRUSH); - maskDC.Clear(); - maskDC.SetPen(wxPen(wxColour(0, 0, 0))); - maskDC.SetBrush(wxBrush(wxColour(0, 0, 0))); - maskDC.DrawRectangle(0, 0, width, height); - - bitmap.SetMask(new wxMask(mask, wxColour(0, 0, 0)));*/ - - //wxBitmap bitmap(width, height, 24); - ////wxBitmap bitmap("back.png", wxBITMAP_TYPE_PNG); - //wxMemoryDC memDC; - //memDC.SelectObject(bitmap); - - //memDC.SetBackground(*wxWHITE_BRUSH); - //memDC.Clear(); - //wxImage image = bitmap.ConvertToImage(); - - - //image.SetMask(true); - //image.SetMaskColour(255,255,255); - //bitmap = wxBitmap(image); - //BitmapCache bmp_cache; - //*bmp_cache.load_png - wxBitmap new_bmp("E:\\Code\\Projects\\BambuStudio\\resources\\images\\back1234.png", wxBITMAP_TYPE_PNG); - new_bmp.SetHeight(height); - new_bmp.SetWidth(width); + + //wxBitmap new_bmp("D:\\Code\\Projects\\BambuStudio\\resources\\images\\back123.png", wxBITMAP_TYPE_PNG); + BitmapCache new_cache; + wxBitmap new_bmp = *new_cache.load_png("back123", width, height); + //new_bmp.SetHeight(height); + //new_bmp.SetWidth(width); return new_bmp; } @@ -2783,8 +2798,12 @@ bool GUI_App::on_init_inner() #ifndef __linux__ wxYield(); #endif - //scrn->SetBackgroundColour(wxColor(0,0,0,0)); + //scrn->SetBackgroundColour(wxColor(0,0,0,255)); + //scrn->SetForegroundColour(wxColor(0, 0, 0, 255)); + //scrn->SetForegroundColour(*wxBLACK); scrn->SetText(_L("Loading configuration")+ dots); + //scrn->SetBackgroundColour(wxColor(0,0,0,255)); + //scrn->SetForegroundColour(wxColor(0, 0, 0, 255)); } BOOST_LOG_TRIVIAL(info) << "loading systen presets..."; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp new file mode 100644 index 000000000..9b4394793 --- /dev/null +++ b/src/slic3r/GUI/GUI_App.hpp @@ -0,0 +1,689 @@ +#ifndef slic3r_GUI_App_hpp_ +#define slic3r_GUI_App_hpp_ + +#include +#include +#include "ImGuiWrapper.hpp" +#include "ConfigWizard.hpp" +#include "OpenGLManager.hpp" +#include "libslic3r/Preset.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" +#include "slic3r/GUI/DeviceManager.hpp" +#include "slic3r/GUI/UserNotification.hpp" +#include "slic3r/Utils/NetworkAgent.hpp" +#include "slic3r/GUI/WebViewDialog.hpp" +#include "slic3r/GUI/WebUserLoginDialog.hpp" +#include "slic3r/GUI/BindDialog.hpp" +#include "slic3r/GUI/HMS.hpp" +#include "slic3r/GUI/Jobs/UpgradeNetworkJob.hpp" +#include "slic3r/GUI/HttpServer.hpp" +#include "../Utils/PrintHost.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +//#define BBL_HAS_FIRST_PAGE 1 +#define STUDIO_INACTIVE_TIMEOUT 15*60*1000 +#define LOG_FILES_MAX_NUM 30 +#define TIMEOUT_CONNECT 15 +#define TIMEOUT_RESPONSE 15 + +#define BE_UNACTED_ON 0x00200001 +#define SHOW_BACKGROUND_BITMAP_PIXEL_THRESHOLD 80 +#ifndef _MSW_DARK_MODE + #define _MSW_DARK_MODE 1 +#endif // _MSW_DARK_MODE + +class wxMenuItem; +class wxMenuBar; +class wxTopLevelWindow; +class wxDataViewCtrl; +class wxBookCtrlBase; +// BBS +class Notebook; +struct wxLanguageInfo; + + +namespace Slic3r { + +class AppConfig; +class PresetBundle; +class PresetUpdater; +class ModelObject; +class Model; +class UserManager; +class DeviceManager; +class NetworkAgent; +class TaskManager; + +namespace GUI{ + +class RemovableDriveManager; +class OtherInstanceMessageHandler; +class MainFrame; +class Sidebar; +class ObjectSettings; +class ObjectList; +class ObjectLayers; +class Plater; +class ParamsPanel; +class NotificationManager; +struct GUI_InitParams; +class ParamsDialog; +class HMSQuery; +class ModelMallDialog; +class PingCodeBindDialog; + + +enum FileType +{ + FT_STEP, + FT_STL, + FT_OBJ, + FT_AMF, + FT_3MF, + FT_GCODE_3MF, + FT_GCODE, + FT_MODEL, + FT_PROJECT, + FT_GALLERY, + + FT_INI, + FT_SVG, + + FT_TEX, + + FT_SL1, + + FT_SIZE, +}; + +extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string{}); + +enum ConfigMenuIDs { + //ConfigMenuWizard, + //ConfigMenuSnapshots, + //ConfigMenuTakeSnapshot, + //ConfigMenuUpdate, + //ConfigMenuDesktopIntegration, + ConfigMenuPreferences, + ConfigMenuPrinter, + //ConfigMenuModeSimple, + //ConfigMenuModeAdvanced, + //ConfigMenuLanguage, + //ConfigMenuFlashFirmware, + ConfigMenuCnt, +}; + +enum BambuStudioMenuIDs { + BambuStudioMenuAbout, + BambuStudioMenuPreferences, +}; + +enum CameraMenuIDs { + wxID_CAMERA_PERSPECTIVE, + wxID_CAMERA_ORTHOGONAL, + wxID_CAMERA_COUNT, +}; + + +class Tab; +class ConfigWizard; +class GizmoObjectManipulation; + +static wxString dots("...", wxConvUTF8); + +// Does our wxWidgets version support markup? +#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) + #define SUPPORTS_MARKUP +#endif + + +#define VERSION_LEN 4 +class VersionInfo +{ +public: + std::string version_str; + std::string version_name; + std::string description; + std::string url; + bool force_upgrade{ false }; + int ver_items[VERSION_LEN]; // AA.BB.CC.DD + VersionInfo() { + for (int i = 0; i < VERSION_LEN; i++) { + ver_items[i] = 0; + } + force_upgrade = false; + version_str = ""; + } + + void parse_version_str(std::string str) { + version_str = str; + std::vector items; + boost::split(items, str, boost::is_any_of(".")); + if (items.size() == VERSION_LEN) { + try { + for (int i = 0; i < VERSION_LEN; i++) { + ver_items[i] = stoi(items[i]); + } + } + catch (...) { + ; + } + } + } + static std::string convert_full_version(std::string short_version); + static std::string convert_short_version(std::string full_version); + static std::string get_full_version() { + return convert_full_version(SLIC3R_VERSION); + } + + /* return > 0, need update */ + int compare(std::string ver_str) { + if (version_str.empty()) return -1; + + int ver_target[VERSION_LEN]; + std::vector items; + boost::split(items, ver_str, boost::is_any_of(".")); + if (items.size() == VERSION_LEN) { + try { + for (int i = 0; i < VERSION_LEN; i++) { + ver_target[i] = stoi(items[i]); + if (ver_target[i] < ver_items[i]) { + return 1; + } + else if (ver_target[i] == ver_items[i]) { + continue; + } + else { + return -1; + } + } + } + catch (...) { + return -1; + } + } + return -1; + } +}; + +class GUI_App : public wxApp +{ +public: + + //BBS: remove GCodeViewer as seperate APP logic + enum class EAppMode : unsigned char + { + Editor, + GCodeViewer + }; + +private: + bool m_initialized { false }; + bool m_post_initialized { false }; + bool m_app_conf_exists{ false }; + EAppMode m_app_mode{ EAppMode::Editor }; + bool m_is_recreating_gui{ false }; +#ifdef __linux__ + bool m_opengl_initialized{ false }; +#endif + +//import model from mall + wxString m_download_file_url; + +//#ifdef _WIN32 + wxColour m_color_label_modified; + wxColour m_color_label_sys; + wxColour m_color_label_default; + wxColour m_color_window_default; + wxColour m_color_highlight_label_default; + wxColour m_color_hovered_btn_label; + wxColour m_color_default_btn_label; + wxColour m_color_highlight_default; + wxColour m_color_selected_btn_bg; + bool m_force_colors_update { false }; +//#endif + + wxFont m_small_font; + wxFont m_bold_font; + wxFont m_normal_font; + wxFont m_code_font; + wxFont m_link_font; + + int m_em_unit; // width of a "m"-symbol in pixels for current system font + // Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls + + std::unique_ptr m_wxLocale; + // System language, from locales, owned by wxWidgets. + const wxLanguageInfo *m_language_info_system = nullptr; + // Best translation language, provided by Windows or OSX, owned by wxWidgets. + const wxLanguageInfo *m_language_info_best = nullptr; + + OpenGLManager m_opengl_mgr; + std::unique_ptr m_removable_drive_manager; + + std::unique_ptr m_imgui; + std::unique_ptr m_printhost_job_queue; + std::unique_ptr m_other_instance_message_handler; + std::unique_ptr m_single_instance_checker; + std::string m_instance_hash_string; + size_t m_instance_hash_int; + + //BBS + bool m_is_closing {false}; + Slic3r::DeviceManager* m_device_manager { nullptr }; + Slic3r::UserManager* m_user_manager { nullptr }; + Slic3r::TaskManager* m_task_manager { nullptr }; + NetworkAgent* m_agent { nullptr }; + std::vector need_delete_presets; // store setting ids of preset + std::vector m_create_preset_blocked { false, false, false, false, false, false }; // excceed limit + bool m_networking_compatible { false }; + bool m_networking_need_update { false }; + bool m_networking_cancel_update { false }; + std::shared_ptr m_upgrade_network_job; + + // login widget + ZUserLogin* login_dlg { nullptr }; + + VersionInfo version_info; + VersionInfo privacy_version_info; + static std::string version_display; + HMSQuery *hms_query { nullptr }; + + boost::thread m_sync_update_thread; + std::shared_ptr m_user_sync_token; + bool m_is_dark_mode{ false }; + bool m_adding_script_handler { false }; + bool m_side_popup_status{false}; + bool m_show_http_errpr_msgdlg{false}; + wxString m_info_dialog_content; + HttpServer m_http_server; + + boost::thread m_check_network_thread; +public: + //try again when subscription fails + void on_start_subscribe_again(std::string dev_id); + void check_filaments_in_blacklist(std::string tag_supplier, std::string tag_material, bool& in_blacklist, std::string& action, std::string& info); + std::string get_local_models_path(); + bool OnInit() override; + int OnExit() override; + bool initialized() const { return m_initialized; } + inline bool is_enable_multi_machine() { return this->app_config&& this->app_config->get("enable_multi_machine") == "true"; } + + std::map test_url_state; + + //BBS: remove GCodeViewer as seperate APP logic + explicit GUI_App(); + //explicit GUI_App(EAppMode mode = EAppMode::Editor); + ~GUI_App() override; + + bool get_app_conf_exists() { return m_app_conf_exists; } + void show_message_box(std::string msg) { wxMessageBox(msg); } + EAppMode get_app_mode() const { return m_app_mode; } + Slic3r::DeviceManager* getDeviceManager() { return m_device_manager; } + Slic3r::TaskManager* getTaskManager() { return m_task_manager; } + HMSQuery* get_hms_query() { return hms_query; } + NetworkAgent* getAgent() { return m_agent; } + bool is_editor() const { return m_app_mode == EAppMode::Editor; } + bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } + bool is_recreating_gui() const { return m_is_recreating_gui; } + std::string logo_name() const { return is_editor() ? "BambuStudio" : "BambuStudio-gcodeviewer"; } + wxString get_inf_dialog_contect () {return m_info_dialog_content;}; + + std::vector split_str(std::string src, std::string separator); + // To be called after the GUI is fully built up. + // Process command line parameters cached in this->init_params, + // load configs, STLs etc. + void post_init(); + void shutdown(); + // If formatted for github, plaintext with OpenGL extensions enclosed into
. + // Otherwise HTML formatted for the system info dialog. + static std::string get_gl_info(bool for_github); + wxGLContext* init_glcontext(wxGLCanvas& canvas); + bool init_opengl(); + + void init_download_path(); + static unsigned get_colour_approx_luma(const wxColour& colour); + static bool dark_mode(); + const wxColour get_label_default_clr_system(); + const wxColour get_label_default_clr_modified(); + void init_label_colours(); + void update_label_colours_from_appconfig(); + void update_publish_status(); + bool has_model_mall(); + void update_label_colours(); + // update color mode for window + void UpdateDarkUI(wxWindow *window, bool highlited = false, bool just_font = false); + void UpdateDarkUIWin(wxWindow* win); + void Update_dark_mode_flag(); + // update color mode for whole dialog including all children + void UpdateDlgDarkUI(wxDialog* dlg); + void UpdateFrameDarkUI(wxFrame* dlg); + // update color mode for DataViewControl + void UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited = false); + // update color mode for panel including all static texts controls + void UpdateAllStaticTextDarkUI(wxWindow* parent); + void init_fonts(); + void update_fonts(const MainFrame *main_frame = nullptr); + void set_label_clr_modified(const wxColour& clr); + void set_label_clr_sys(const wxColour& clr); + //update side popup status + bool get_side_menu_popup_status(); + void set_side_menu_popup_status(bool status); + void link_to_network_check(); + + + const wxColour& get_label_clr_modified(){ return m_color_label_modified; } + const wxColour& get_label_clr_sys() { return m_color_label_sys; } + const wxColour& get_label_clr_default() { return m_color_label_default; } + const wxColour& get_window_default_clr(){ return m_color_window_default; } + + // BBS +//#ifdef _WIN32 + const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; } + const wxColour& get_highlight_default_clr() { return m_color_highlight_default; } + const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; } + const wxColour& get_color_selected_btn_bg() { return m_color_selected_btn_bg; } + void force_colors_update(); +#ifdef _MSW_DARK_MODE + void force_menu_update(); +#endif //_MSW_DARK_MODE +//#endif + + const wxFont& small_font() { return m_small_font; } + const wxFont& bold_font() { return m_bold_font; } + const wxFont& normal_font() { return m_normal_font; } + const wxFont& code_font() { return m_code_font; } + const wxFont& link_font() { return m_link_font; } + int em_unit() const { return m_em_unit; } + bool tabs_as_menu() const; + wxSize get_min_size() const; + float toolbar_icon_scale(const bool is_limited = false) const; + void set_auto_toolbar_icon_scale(float scale) const; + void check_printer_presets(); + + void recreate_GUI(const wxString& message); + void system_info(); + void keyboard_shortcuts(); + void load_project(wxWindow *parent, wxString& input_file) const; + void import_model(wxWindow *parent, wxArrayString& input_files) const; + void load_gcode(wxWindow* parent, wxString& input_file) const; + + wxString transition_tridid(int trid_id); + void ShowUserGuide(); + void ShowDownNetPluginDlg(); + void ShowUserLogin(bool show = true); + void ShowOnlyFilament(); + //BBS + void request_login(bool show_user_info = false); + bool check_login(); + void get_login_info(); + bool is_user_login(); + + void request_user_login(int online_login = 0); + void request_user_handle(int online_login = 0); + void request_user_logout(); + int request_user_unbind(std::string dev_id); + std::string handle_web_request(std::string cmd); + void handle_script_message(std::string msg); + void request_model_download(wxString url); + void download_project(std::string project_id); + void request_project_download(std::string project_id); + void request_open_project(std::string project_id); + void request_remove_project(std::string project_id); + + void handle_http_error(unsigned int status, std::string body); + void on_http_error(wxCommandEvent &evt); + void on_set_selected_machine(wxCommandEvent& evt); + void on_update_machine_list(wxCommandEvent& evt); + void on_user_login(wxCommandEvent &evt); + void on_user_login_handle(wxCommandEvent& evt); + void enable_user_preset_folder(bool enable); + + // BBS + bool is_studio_active(); + void reset_to_active(); + bool m_studio_active = true; + std::chrono::system_clock::time_point last_active_point; + + void check_update(bool show_tips, int by_user); + void check_new_version(bool show_tips = false, int by_user = 0); + void request_new_version(int by_user); + void enter_force_upgrade(); + void set_skip_version(bool skip = true); + void no_new_version(); + static std::string format_display_version(); + std::string format_IP(const std::string& ip); + void show_dialog(wxString msg); + void push_notification(wxString msg, wxString title = wxEmptyString, UserNotificationStyle style = UserNotificationStyle::UNS_NORMAL); + void reload_settings(); + void remove_user_presets(); + void sync_preset(Preset* preset); + void start_sync_user_preset(bool with_progress_dlg = false); + void stop_sync_user_preset(); + void start_http_server(); + void stop_http_server(); + void switch_staff_pick(bool on); + + void on_show_check_privacy_dlg(int online_login = 0); + void show_check_privacy_dlg(wxCommandEvent& evt); + void on_check_privacy_update(wxCommandEvent &evt); + bool check_privacy_update(); + void check_privacy_version(int online_login = 0); + void check_track_enable(); + + static bool catch_error(std::function cb, const std::string& err); + + void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); + void update_ui_from_settings(); + + bool switch_language(); + bool load_language(wxString language, bool initial); + + Tab* get_tab(Preset::Type type); + Tab* get_plate_tab(); + Tab* get_model_tab(bool part = false); + Tab* get_layer_tab(); + ConfigOptionMode get_mode(); + std::string get_mode_str(); + void save_mode(const /*ConfigOptionMode*/int mode) ; + void update_mode(); + void update_internal_development(); + void show_ip_address_enter_dialog(wxString title = wxEmptyString); + void show_ip_address_enter_dialog_handler(wxCommandEvent &evt); + bool show_modal_ip_address_enter_dialog(wxString title = wxEmptyString); + + // BBS + //void add_config_menu(wxMenuBar *menu); + //void add_config_menu(wxMenu* menu); + bool has_unsaved_preset_changes() const; + bool has_current_preset_changes() const; + void update_saved_preset_from_current_preset(); + std::vector> get_selected_presets() const; + bool check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice = true, bool use_dont_save_insted_of_discard = false); + void apply_keeped_preset_modifications(); + bool check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr); + bool can_load_project(); + bool check_print_host_queue(); + bool checked_tab(Tab* tab); + //BBS: add preset combox re-active logic + void load_current_presets(bool active_preset_combox = false, bool check_printer_presets = true); + std::vector &get_delete_cache_presets(); + std::vector get_delete_cache_presets_lock(); + void delete_preset_from_cloud(std::string setting_id); + void preset_deleted_from_cloud(std::string setting_id); + + wxString filter_string(wxString str); + wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); } + // Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US". + wxString current_language_code_safe() const; + bool is_localized() const { return m_wxLocale->GetLocale() != "English"; } + + void open_preferences(size_t open_on_tab = 0, const std::string& highlight_option = std::string()); + + virtual bool OnExceptionInMainLoop() override; + // Calls wxLaunchDefaultBrowser if user confirms in dialog. + bool open_browser_with_warning_dialog(const wxString& url, int flags = 0); +#ifdef __APPLE__ + void OSXStoreOpenFiles(const wxArrayString &files); + // wxWidgets override to get an event on open files. + void MacOpenFiles(const wxArrayString &fileNames) override; + void MacOpenURL(const wxString& url) override; +#endif /* __APPLE */ + + Sidebar& sidebar(); + GizmoObjectManipulation *obj_manipul(); + ObjectSettings* obj_settings(); + ObjectList* obj_list(); + ObjectLayers* obj_layers(); + Plater* plater(); + const Plater* plater() const; + ParamsPanel* params_panel(); + ParamsDialog* params_dialog(); + Model& model(); + NotificationManager * notification_manager(); + + + std::string m_mall_model_download_url; + std::string m_mall_model_download_name; + ModelMallDialog* m_mall_publish_dialog{ nullptr }; + PingCodeBindDialog* m_ping_code_binding_dialog{ nullptr }; + + void set_download_model_url(std::string url) {m_mall_model_download_url = url;} + void set_download_model_name(std::string name) {m_mall_model_download_name = name;} + std::string get_download_model_url() {return m_mall_model_download_url;} + std::string get_download_model_name() {return m_mall_model_download_name;} + + void load_url(wxString url); + void open_mall_page_dialog(); + void open_publish_page_dialog(); + void remove_mall_system_dialog(); + void run_script(wxString js); + void run_script_left(wxString js); + bool is_adding_script_handler() { return m_adding_script_handler; } + void set_adding_script_handler(bool status) { m_adding_script_handler = status; } + + char from_hex(char ch); + std::string url_encode(std::string value); + std::string url_decode(std::string value); + + void popup_ping_bind_dialog(); + void remove_ping_bind_dialog(); + + // Parameters extracted from the command line to be passed to GUI after initialization. + GUI_InitParams* init_params { nullptr }; + + AppConfig* app_config{ nullptr }; + PresetBundle* preset_bundle{ nullptr }; + PresetUpdater* preset_updater{ nullptr }; + MainFrame* mainframe{ nullptr }; + Plater* plater_{ nullptr }; + + PresetUpdater* get_preset_updater() { return preset_updater; } + + Notebook* tab_panel() const ; + int extruders_cnt() const; + int extruders_edited_cnt() const; + + // BBS + int filaments_cnt() const; + PrintSequence global_print_sequence() const; + + std::vector tabs_list; + std::vector model_tabs_list; + Tab* plate_tab; + + RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); } + OtherInstanceMessageHandler* other_instance_message_handler() { return m_other_instance_message_handler.get(); } + wxSingleInstanceChecker* single_instance_checker() {return m_single_instance_checker.get();} + + void init_single_instance_checker(const std::string &name, const std::string &path); + void set_instance_hash (const size_t hash) { m_instance_hash_int = hash; m_instance_hash_string = std::to_string(hash); } + std::string get_instance_hash_string () { return m_instance_hash_string; } + size_t get_instance_hash_int () { return m_instance_hash_int; } + + ImGuiWrapper* imgui() { return m_imgui.get(); } + + PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } + + void open_web_page_localized(const std::string &http_address); + bool may_switch_to_SLA_preset(const wxString& caption); + bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME); + void show_desktop_integration_dialog(); + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG + // temporary and debug only -> extract thumbnails from selected gcode and save them as png files + void gcode_thumbnails_debug(); +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + + OpenGLManager& get_opengl_manager() { return m_opengl_mgr; } + GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); } + GLShaderProgram* get_current_shader() { return m_opengl_mgr.get_current_shader(); } + + bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); } + bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); } + int GetSingleChoiceIndex(const wxString& message, const wxString& caption, const wxArrayString& choices, int initialSelection); + +#ifdef __WXMSW__ + // extend is stl/3mf/gcode/step etc + void associate_files(std::wstring extend); + void disassociate_files(std::wstring extend); +#endif // __WXMSW__ + std::string get_plugin_url(std::string name, std::string country_code); + int download_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr); + int install_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr); + std::string get_http_url(std::string country_code, std::string path = {}); + std::string get_model_http_url(std::string country_code); + bool is_compatibility_version(); + bool check_networking_version(); + void cancel_networking_install(); + void restart_networking(); + void check_config_updates_from_updater() { check_updates(false); } + +private: + int updating_bambu_networking(); + bool on_init_inner(); + void copy_network_if_available(); + bool on_init_network(bool try_backup = false); + void init_networking_callbacks(); + void init_app_config(); + void remove_old_networking_plugins(); + //BBS set extra header for http request + std::map get_extra_header(); + void init_http_extra_header(); + void update_http_extra_header(); + bool check_older_app_config(Semver current_version, bool backup); + void copy_older_config(); + void window_pos_save(wxTopLevelWindow* window, const std::string &name); + bool window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false); + void window_pos_sanitize(wxTopLevelWindow* window); + void window_pos_center(wxTopLevelWindow *window); + bool select_language(); + + bool config_wizard_startup(); + void check_updates(const bool verbose); + + bool m_init_app_config_from_older { false }; + bool m_datadir_redefined { false }; + std::string m_older_data_dir_path; + boost::optional m_last_config_version; + std::string m_open_method; +}; + +DECLARE_APP(GUI_App) +wxDECLARE_EVENT(EVT_CONNECT_LAN_MODE_PRINT, wxCommandEvent); + +bool is_support_filament(int extruder_id); +} // namespace GUI +} // Slic3r + +#endif // slic3r_GUI_App_hpp_ diff --git a/src/slic3r/GUI/ParamsPanel.hpp b/src/slic3r/GUI/ParamsPanel.hpp new file mode 100644 index 000000000..83e93cdd5 --- /dev/null +++ b/src/slic3r/GUI/ParamsPanel.hpp @@ -0,0 +1,178 @@ +#ifndef slic3r_params_panel_hpp_ +#define slic3r_params_panel_hpp_ + + +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wxExtensions.hpp" +#include "GUI_Utils.hpp" +#include "Widgets/Button.hpp" + +class SwitchButton; +class StaticBox; + +#define TIPS_DIALOG_BUTTON_SIZE wxSize(FromDIP(60), FromDIP(24)) + +namespace Slic3r { +namespace GUI { + +/////////////////////////////////////////////////////////////////////////// + +class TipsDialog : public DPIDialog +{ +private: + bool m_show_again{false}; + std::string m_app_key; + +public: + TipsDialog(wxWindow *parent, const wxString &title, const wxString &description, std::string app_key = ""); + Button *m_confirm{nullptr}; + Button *m_cancel{nullptr}; + wxPanel *m_top_line{nullptr}; + wxStaticText *m_msg; + +protected: + void on_dpi_changed(const wxRect &suggested_rect) override; + void on_ok(wxMouseEvent &event); + wxBoxSizer *create_item_checkbox(wxString title, wxWindow *parent, wxString tooltip, std::string param); +}; + +/////////////////////////////////////////////////////////////////////////////// +/// Class ParamsPanel +/////////////////////////////////////////////////////////////////////////////// +class ParamsPanel : public wxPanel +{ +#if __WXOSX__ + wxWindow* m_tmp_panel; + int m_size_move = -1; +#endif // __WXOSX__ + + private: + void free_sizers(); + void delete_subwindows(); + void refresh_tabs(); + + protected: + wxBoxSizer* m_top_sizer { nullptr }; + wxBoxSizer* m_left_sizer { nullptr }; + wxBoxSizer* m_mode_sizer { nullptr }; + // // BBS: new layout + StaticBox* m_top_panel{ nullptr }; + ScalableButton* m_process_icon{ nullptr }; + wxStaticText* m_title_label { nullptr }; + SwitchButton* m_mode_region { nullptr }; + ScalableButton *m_tips_arrow{nullptr}; + bool m_tips_arror_blink{false}; + wxStaticText* m_title_view { nullptr }; + SwitchButton* m_mode_view { nullptr }; + //wxBitmapButton* m_search_button { nullptr }; + wxStaticLine* m_staticline_print { nullptr }; + //wxBoxSizer* m_print_sizer { nullptr }; + wxPanel* m_tab_print { nullptr }; + wxPanel* m_tab_print_plate { nullptr }; + wxPanel* m_tab_print_object { nullptr }; + wxStaticLine* m_staticline_print_object { nullptr }; + wxPanel* m_tab_print_part { nullptr }; + wxPanel* m_tab_print_layer { nullptr }; + wxStaticLine* m_staticline_print_part { nullptr }; + wxStaticLine* m_staticline_filament { nullptr }; + //wxBoxSizer* m_filament_sizer { nullptr }; + wxPanel* m_tab_filament { nullptr }; + wxStaticLine* m_staticline_printer { nullptr }; + //wxBoxSizer* m_printer_sizer { nullptr }; + wxPanel* m_tab_printer { nullptr }; + wxStaticLine* m_staticline_config{ nullptr }; + wxPanel* m_tab_config { nullptr }; + //wxStaticLine* m_staticline_buttons { nullptr }; + // BBS: new layout + wxBoxSizer* m_button_sizer { nullptr }; + wxWindow* m_export_to_file { nullptr }; + wxWindow* m_import_from_file { nullptr }; + //wxStaticLine* m_staticline_middle{ nullptr }; + //wxBoxSizer* m_right_sizer { nullptr }; + wxScrolledWindow* m_page_view { nullptr }; + wxBoxSizer* m_page_sizer { nullptr }; + + ScalableButton* m_setting_btn { nullptr }; + ScalableButton* m_search_btn { nullptr }; + ScalableButton* m_compare_btn { nullptr }; + + wxBitmap m_toggle_on_icon; + wxBitmap m_toggle_off_icon; + + wxPanel* m_current_tab { nullptr }; + + bool m_has_object_config { false }; + + struct Highlighter + { + void set_timer_owner(wxEvtHandler *owner, int timerid = wxID_ANY); + void init(std::pair, wxWindow *parent = nullptr); + void blink(); + void invalidate(); + + private: + wxWindow * m_bitmap{nullptr}; + bool * m_show_blink_ptr{nullptr}; + int m_blink_counter{0}; + wxTimer m_timer; + wxWindow * m_parent { nullptr }; + } m_highlighter; + + void OnToggled(wxCommandEvent& event); + + public: + ParamsPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 1800,1080 ), long style = wxTAB_TRAVERSAL, const wxString& type = wxEmptyString ); + ~ParamsPanel(); + + void rebuild_panels(); + void create_layout(); + //clear the right page + void clear_page(); + void OnActivate(); + void set_active_tab(wxPanel*tab); + bool is_active_and_shown_tab(wxPanel*tab); + void update_mode(); + void msw_rescale(); + void switch_to_global(); + void switch_to_object(bool with_tips = false); + + void notify_object_config_changed(); + void switch_to_object_if_has_object_configs(); + + StaticBox* get_top_panel() { return m_top_panel; } + + wxPanel* filament_panel() { return m_tab_filament; } + + wxScrolledWindow* get_paged_view() { return m_page_view;} + wxPanel* get_current_tab() { return m_current_tab; } + +}; + +} // GUI +} // Slic3r + +#endif //slic3r_params_panel_hpp_ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7c7fe618b..c440520e9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -964,6 +964,7 @@ Sidebar::Sidebar(Plater *parent) { p->editing_filament = 0; combobox->switch_to_tab(); + p->combo_config->update(); }); combobox->edit_btn = edit_btn; @@ -6507,6 +6508,16 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) if (flag != flag_is_change) { sidebar->auto_calc_flushing_volumes(idx); } + //wxGetApp().get_tab(Preset::TYPE_CONFIG)->get_combo_box()->Update(); + //wxGetApp().preset_bundle->configs.get_preset_base()->Update(); + //p->combo_config->update(); + wxGetApp().sidebar().update_presets(Preset::TYPE_CONFIG); + //wxGetApp().preset_bundle->configs.Update(); + //TabPresetComboBox* tabCombo = wxGetApp().get_tab(Preset::TYPE_CONFIG)->get_combo_box(); + //tabCombo->update(); + //wxGetApp().get_tab(Preset::TYPE_CONFIG)->get_combo_box()->presets()->reset(false); + wxGetApp().get_tab(Preset::TYPE_CONFIG)->get_combo_box()->update(); + //wxGetApp().get_tab(Preset::TYPE_CONFIG)->load_current_preset(); } bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); // TODO: ? diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp new file mode 100644 index 000000000..14ce5befd --- /dev/null +++ b/src/slic3r/GUI/Plater.hpp @@ -0,0 +1,788 @@ +#ifndef slic3r_Plater_hpp_ +#define slic3r_Plater_hpp_ + +#include +#include +#include + +#include +// BBS +#include + +#include "Selection.hpp" + +#include "libslic3r/enum_bitmask.hpp" +#include "libslic3r/Preset.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "Jobs/Job.hpp" +#include "Search.hpp" +#include "PartPlate.hpp" +#include "GUI_App.hpp" +#include "Jobs/PrintJob.hpp" +#include "Jobs/SendJob.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/PrintBase.hpp" +#include "libslic3r/Calib.hpp" +#include "libslic3r/FlushVolCalc.hpp" + +#define FILAMENT_SYSTEM_COLORS_NUM 16 + +class wxButton; +class ScalableButton; +class wxScrolledWindow; +class wxString; +class ComboBox; +class Button; + +namespace Slic3r { + +class BuildVolume; +class Model; +class ModelObject; +enum class ModelObjectCutAttribute : int; +using ModelObjectCutAttributes = enum_bitmask; +class ModelInstance; +class Print; +class SLAPrint; +//BBS: add partplatelist and SlicingStatusEvent +class PartPlateList; +class SlicingStatusEvent; +enum SLAPrintObjectStep : unsigned int; +enum class ConversionType : int; +class Ams; + +using ModelInstancePtrs = std::vector; + + +namespace UndoRedo { + class Stack; + enum class SnapshotType : unsigned char; + struct Snapshot; +} + +namespace GUI { + +class MainFrame; +class ConfigOptionsGroup; +class ObjectSettings; +class ObjectLayers; +class ObjectList; +class GLCanvas3D; +class Mouse3DController; +class NotificationManager; +class DailyTipsWindow; +struct Camera; +class GLToolbar; +class PlaterPresetComboBox; +class PartPlateList; + +using t_optgroups = std::vector >; + +class Plater; +enum class ActionButtonType : int; + +#define EVT_PUBLISHING_START 1 +#define EVT_PUBLISHING_STOP 2 + +//BBS: add EVT_SLICING_UPDATE declare here +wxDECLARE_EVENT(EVT_SLICING_UPDATE, Slic3r::SlicingStatusEvent); +wxDECLARE_EVENT(EVT_PUBLISH, wxCommandEvent); +wxDECLARE_EVENT(EVT_OPEN_PLATESETTINGSDIALOG, wxCommandEvent); +wxDECLARE_EVENT(EVT_REPAIR_MODEL, wxCommandEvent); +wxDECLARE_EVENT(EVT_FILAMENT_COLOR_CHANGED, wxCommandEvent); +wxDECLARE_EVENT(EVT_INSTALL_PLUGIN_NETWORKING, wxCommandEvent); +wxDECLARE_EVENT(EVT_INSTALL_PLUGIN_HINT, wxCommandEvent); +wxDECLARE_EVENT(EVT_UPDATE_PLUGINS_WHEN_LAUNCH, wxCommandEvent); +wxDECLARE_EVENT(EVT_PREVIEW_ONLY_MODE_HINT, wxCommandEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_COLOR_MODE_CHANGED, SimpleEvent); +wxDECLARE_EVENT(EVT_PRINT_FROM_SDCARD_VIEW, SimpleEvent); +wxDECLARE_EVENT(EVT_CREATE_FILAMENT, SimpleEvent); +wxDECLARE_EVENT(EVT_MODIFY_FILAMENT, SimpleEvent); +wxDECLARE_EVENT(EVT_ADD_FILAMENT, SimpleEvent); +wxDECLARE_EVENT(EVT_DEL_FILAMENT, SimpleEvent); +using ColorEvent = Event; +wxDECLARE_EVENT(EVT_ADD_CUSTOM_FILAMENT, ColorEvent); +const wxString DEFAULT_PROJECT_NAME = "Untitled"; + +class Sidebar : public wxPanel +{ + ConfigOptionMode m_mode; +public: + Sidebar(Plater *parent); + Sidebar(Sidebar &&) = delete; + Sidebar(const Sidebar &) = delete; + Sidebar &operator=(Sidebar &&) = delete; + Sidebar &operator=(const Sidebar &) = delete; + ~Sidebar(); + + void create_printer_preset(); + void init_filament_combo(PlaterPresetComboBox **combo, const int filament_idx); + void remove_unused_filament_combos(const size_t current_extruder_count); + void set_bed_by_curr_bed_type(AppConfig *config); + void update_all_preset_comboboxes(); + //void update_partplate(PartPlateList& list); + void update_presets(Slic3r::Preset::Type preset_type); + //BBS + void update_presets_from_to(Slic3r::Preset::Type preset_type, std::string from, std::string to); + bool set_bed_type(const std::string& bed_type_name); + void save_bed_type_to_config(const std::string &bed_type_name); + bool use_default_bed_type(bool is_bbl_preset = true); + void change_top_border_for_mode_sizer(bool increase_border); + void msw_rescale(); + void sys_color_changed(); + void search(); + void jump_to_option(size_t selected); + void jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category); + // BBS. Add on_filaments_change() method. + void on_filaments_change(size_t num_filaments); + void add_filament(); + void delete_filament(); + void add_custom_filament(wxColour new_col); + // BBS + void on_bed_type_change(BedType bed_type); + void load_ams_list(std::string const & device, MachineObject* obj); + std::map build_filament_ams_list(MachineObject* obj); + void sync_ams_list(); + + ObjectList* obj_list(); + ObjectSettings* obj_settings(); + ObjectLayers* obj_layers(); + wxPanel* scrolled_panel(); + wxPanel* print_panel(); + wxPanel* filament_panel(); + + ConfigOptionsGroup* og_freq_chng_params(const bool is_fff); + wxButton* get_wiping_dialog_button(); + + // BBS + void enable_buttons(bool enable); + void set_btn_label(const ActionButtonType btn_type, const wxString& label) const; + bool show_reslice(bool show) const; + bool show_export(bool show) const; + bool show_send(bool show) const; + bool show_eject(bool show)const; + bool show_export_removable(bool show) const; + bool get_eject_shown() const; + bool is_multifilament(); + void update_mode(); + bool is_collapsed(); + void collapse(bool collapse); + void update_searcher(); + void update_ui_from_settings(); + bool show_object_list(bool show) const; + void finish_param_edit(); + void auto_calc_flushing_volumes(const int modify_id); + void jump_to_object(ObjectDataViewModelNode* item); + void can_search(); +#ifdef _MSW_DARK_MODE + void show_mode_sizer(bool show); +#endif + + std::vector& combos_filament(); + Search::OptionsSearcher& get_searcher(); + std::string& get_search_line(); + void set_is_gcode_file(bool flag); + void update_soft_first_start_state() { m_soft_first_start = false; } + void cancel_update_3d_state() { m_update_3d_state = false; } + bool get_update_3d_state() { return m_update_3d_state; } +private: + struct priv; + std::unique_ptr p; + + wxBoxSizer* m_scrolled_sizer = nullptr; + ComboBox* m_bed_type_list = nullptr; + ScalableButton* connection_btn = nullptr; + ScalableButton* ams_btn = nullptr; + bool m_soft_first_start {true }; + bool m_is_gcode_file{ false }; + bool m_update_3d_state{false}; +}; + +class Plater: public wxPanel +{ +public: + using fs_path = boost::filesystem::path; + + Plater(wxWindow *parent, MainFrame *main_frame); + Plater(Plater &&) = delete; + Plater(const Plater &) = delete; + Plater &operator=(Plater &&) = delete; + Plater &operator=(const Plater &) = delete; + ~Plater() = default; + + bool Show(bool show = true); + + bool is_project_dirty() const; + bool is_presets_dirty() const; + void set_plater_dirty(bool is_dirty); + void update_project_dirty_from_presets(); + int save_project_if_dirty(const wxString& reason); + void reset_project_dirty_after_save(); + void reset_project_dirty_initial_presets(); +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + void render_project_state_debug_window() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + + Sidebar& sidebar(); + const Model& model() const; + Model& model(); + const Print& fff_print() const; + Print& fff_print(); + const SLAPrint& sla_print() const; + SLAPrint& sla_print(); + + int new_project(bool skip_confirm = false, bool silent = false, const wxString &project_name = wxString()); + // BBS: save & backup + void load_project(wxString const & filename = "", wxString const & originfile = "-"); + int save_project(bool saveAs = false); + //BBS download project by project id + void import_model_id(wxString download_info); + void download_project(const wxString& project_id); + void request_model_download(wxString url); + void request_download_project(std::string project_id); + // BBS: check snapshot + bool up_to_date(bool saved, bool backup); + + bool open_3mf_file(const fs::path &file_path); + int get_3mf_file_count(std::vector paths); + void add_file(); + void add_model(bool imperial_units = false, std::string fname = ""); + void import_sl1_archive(); + void extract_config_from_project(); + void load_gcode(); + void load_gcode(const wxString& filename); + void reload_gcode_from_disk(); + void refresh_print(); + + // OrcaSlicer calibration + void calib_pa(const Calib_Params ¶ms); + void calib_flowrate(int pass); + void calib_temp(const Calib_Params ¶ms); + void calib_max_vol_speed(const Calib_Params ¶ms); + void calib_retraction(const Calib_Params ¶ms); + void calib_VFA(const Calib_Params ¶ms); + + //BBS: add only gcode mode + bool only_gcode_mode() { return m_only_gcode; } + void set_only_gcode(bool only_gcode) { m_only_gcode = only_gcode; } + + //BBS: add only gcode mode + bool using_exported_file() { return m_exported_file; } + void set_using_exported_file(bool exported_file) { + m_exported_file = exported_file; + } + + // BBS + wxString get_project_name(); + void update_all_plate_thumbnails(bool force_update = false); + void invalid_all_plate_thumbnails(); + void force_update_all_plate_thumbnails(); + + static wxColour get_next_color_for_filament(); + static wxString get_slice_warning_string(GCodeProcessorResult::SliceWarning& warning); + + // BBS: restore + std::vector load_files(const std::vector& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false); + // To be called when providing a list of files to the GUI slic3r on command line. + std::vector load_files(const std::vector& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false); + // to be called on drag and drop + bool load_files(const wxArrayString& filenames); + + const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; } + + void update(bool conside_update_flag = false, bool force_background_processing_update = false); + //BBS + void object_list_changed(); + void stop_jobs(); + bool is_any_job_running() const; + void select_view(const std::string& direction); + //BBS: add no_slice logic + void select_view_3D(const std::string& name, bool no_slice = true); + + void reload_paint_after_background_process_apply(); + bool is_preview_shown() const; + bool is_preview_loaded() const; + bool is_view3D_shown() const; + + bool are_view3D_labels_shown() const; + void show_view3D_labels(bool show); + + bool is_view3D_overhang_shown() const; + void show_view3D_overhang(bool show); + + bool is_sidebar_collapsed() const; + void collapse_sidebar(bool show); + + // Called after the Preferences dialog is closed and the program settings are saved. + // Update the UI based on the current preferences. + void update_ui_from_settings(); + + //BBS + void select_curr_plate_all(); + void remove_curr_plate_all(); + + void select_all(); + void deselect_all(); + void exit_gizmo(); + void remove(size_t obj_idx); + void reset(bool apply_presets_change = false); + void reset_with_confirm(); + //BBS: return int for various result + int close_with_confirm(std::function second_check = nullptr); // BBS close project + //BBS: trigger a restore project event + void trigger_restore_project(int skip_confirm = 0); + bool delete_object_from_model(size_t obj_idx, bool refresh_immediately = true); // BBS support refresh immediately + void delete_all_objects_from_model(); //BBS delete all objects from model + void set_selected_visible(bool visible); + void remove_selected(); + void increase_instances(size_t num = 1); + void decrease_instances(size_t num = 1); + void set_number_of_copies(/*size_t num*/); + void fill_bed_with_instances(); + bool is_selection_empty() const; + void scale_selection_to_fit_print_volume(); + void convert_unit(ConversionType conv_type); + + // BBS: replace z with plane_points + void cut(size_t obj_idx, size_t instance_idx, std::array plane_points, ModelObjectCutAttributes attributes); + + // BBS: segment model with CGAL + void segment(size_t obj_idx, size_t instance_idx, double smoothing_alpha=0.5, int segment_number=5); + void apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs &cut_objects); + void merge(size_t obj_idx, std::vector &vol_indeces); + + void send_to_printer(bool isall = false); + void export_gcode(bool prefer_removable); + void export_gcode_3mf(bool export_all = false); + void send_gcode_finish(wxString name); + void export_core_3mf(); + static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function notify_func = {}); + void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false); + //BBS: remove amf + //void export_amf(); + //BBS add extra param for exporting 3mf silence + // BBS: backup + int export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path(), SaveStrategy strategy = SaveStrategy::Default, int export_plate_idx = -1, Export3mfProgressFn proFn = nullptr); + + //BBS + void publish_project(); + + void reload_from_disk(); + void replace_with_stl(); + void reload_all_from_disk(); + bool has_toolpaths_to_export() const; + void export_toolpaths_to_obj() const; + void reslice(); + void record_slice_preset(std::string action); + void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false); + void reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages = false); + void reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false); + + void clear_before_change_mesh(int obj_idx); + void changed_mesh(int obj_idx); + + void changed_object(int obj_idx); + void changed_objects(const std::vector& object_idxs); + void schedule_background_process(bool schedule = true); + bool is_background_process_update_scheduled() const; + void suppress_background_process(const bool stop_background_process) ; + /* -1: send current gcode if not specified + * -2: send all gcode to target machine */ + int send_gcode(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); + void send_gcode_legacy(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); + int export_config_3mf(int plate_idx = -1, Export3mfProgressFn proFn = nullptr); + //BBS jump to nonitor after print job finished + void send_calibration_job_finished(wxCommandEvent &evt); + void print_job_finished(wxCommandEvent &evt); + void send_job_finished(wxCommandEvent& evt); + void publish_job_finished(wxCommandEvent& evt); + void open_platesettings_dialog(wxCommandEvent& evt); + void on_change_color_mode(SimpleEvent& evt); + void eject_drive(); + + void take_snapshot(const std::string &snapshot_name); + //void take_snapshot(const wxString &snapshot_name); + void take_snapshot(const std::string &snapshot_name, UndoRedo::SnapshotType snapshot_type); + //void take_snapshot(const wxString &snapshot_name, UndoRedo::SnapshotType snapshot_type); + + void undo(); + void redo(); + void undo_to(int selection); + void redo_to(int selection); + bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); + void undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text); + int update_print_required_data(Slic3r::DynamicPrintConfig config, Slic3r::Model model, Slic3r::PlateDataPtrs plate_data_list, std::string file_name, std::string file_path); + bool search_string_getter(int idx, const char** label, const char** tooltip); + // For the memory statistics. + const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; + void clear_undo_redo_stack_main(); + // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. + void enter_gizmos_stack(); + // BBS: return false if not changed + bool leave_gizmos_stack(); + + void on_filaments_change(size_t extruders_count); + // BBS + void on_bed_type_change(BedType bed_type,bool is_gcode_file = false); + bool update_filament_colors_in_full_config(); + void config_change_notification(const DynamicPrintConfig &config, const std::string& key); + void on_config_change(const DynamicPrintConfig &config); + void force_filament_colors_update(); + void force_print_bed_update(); + // On activating the parent window. + void on_activate(); + std::vector get_extruder_colors_from_plater_config(const GCodeProcessorResult* const result = nullptr) const; + std::vector get_colors_for_color_print(const GCodeProcessorResult* const result = nullptr) const; + + void update_menus(); + // BBS + //void show_action_buttons(const bool is_ready_to_slice) const; + + wxString get_project_filename(const wxString& extension = wxEmptyString) const; + wxString get_export_gcode_filename(const wxString& extension = wxEmptyString, bool only_filename = false, bool export_all = false) const; + void set_project_filename(const wxString& filename); + void update_print_error_info(int code, std::string msg, std::string extra); + + bool is_export_gcode_scheduled() const; + + const Selection& get_selection() const; + int get_selected_object_idx(); + bool is_single_full_object_selection() const; + GLCanvas3D* canvas3D(); + const GLCanvas3D * canvas3D() const; + GLCanvas3D* get_current_canvas3D(bool exclude_preview = false); + GLCanvas3D* get_view3D_canvas3D(); + GLCanvas3D* get_preview_canvas3D(); + GLCanvas3D* get_assmeble_canvas3D(); + wxWindow* get_select_machine_dialog(); + + void arrange(); + void orient(); + void find_new_position(const ModelInstancePtrs &instances); + //BBS: add job state related functions + void set_prepare_state(int state); + int get_prepare_state(); + //BBS: add print job releated functions + void get_print_job_data(PrintPrepareData* data); + int get_send_calibration_finished_event(); + int get_print_finished_event(); + int get_send_finished_event(); + int get_publish_finished_event(); + + void set_current_canvas_as_dirty(); + void unbind_canvas_event_handlers(); + void reset_canvas_volumes(); + + PrinterTechnology printer_technology() const; + const DynamicPrintConfig * config() const; + bool set_printer_technology(PrinterTechnology printer_technology); + + //BBS + void cut_selection_to_clipboard(); + + void copy_selection_to_clipboard(); + void paste_from_clipboard(); + //BBS: add clone logic + void clone_selection(); + void center_selection(); + void search(bool plater_is_active, Preset::Type type, wxWindow *tag, TextInput *etag, wxWindow *stag); + void mirror(Axis axis); + void split_object(); + void split_volume(); + void optimize_rotation(); + // find all empty cells on the plate and won't overlap with exclusion areas + static std::vector get_empty_cells(const Vec2f step); + + //BBS: + void fill_color(int extruder_id); + + //BBS: + void edit_text(); + bool can_edit_text() const; + + bool can_delete() const; + bool can_delete_all() const; + bool can_add_model() const; + bool can_add_plate() const; + bool can_delete_plate() const; + bool can_increase_instances() const; + bool can_decrease_instances() const; + bool can_set_instance_to_object() const; + bool can_fix_through_netfabb() const; + bool can_simplify() const; + bool can_split_to_objects() const; + bool can_split_to_volumes() const; + bool can_arrange() const; + //BBS + bool can_cut_to_clipboard() const; + bool can_layers_editing() const; + bool can_paste_from_clipboard() const; + bool can_copy_to_clipboard() const; + bool can_undo() const; + bool can_redo() const; + bool can_reload_from_disk() const; + bool can_replace_with_stl() const; + bool can_mirror() const; + bool can_split(bool to_objects) const; +#if ENABLE_ENHANCED_PRINT_VOLUME_FIT + bool can_scale_to_print_volume() const; +#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT + + //BBS: + bool can_fillcolor() const; + bool has_assmeble_view() const; + + void msw_rescale(); + void sys_color_changed(); + + // BBS +#if 0 + bool init_view_toolbar(); + void enable_view_toolbar(bool enable); +#endif + + bool init_collapse_toolbar(); + void enable_collapse_toolbar(bool enable); + + const Camera& get_camera() const; + Camera& get_camera(); + + //BBS: partplate list related functions + PartPlateList& get_partplate_list(); + void validate_current_plate(bool& model_fits, bool& validate_error); + //BBS: select the plate by index + int select_plate(int plate_index, bool need_slice = false); + //BBS: update progress result + void apply_background_progress(); + //BBS: select the plate by hover_id + int select_plate_by_hover_id(int hover_id, bool right_click = false, bool isModidyPlateName = false); + //BBS: delete the plate, index= -1 means the current plate + int delete_plate(int plate_index = -1); + //BBS: select the sliced plate by index + int select_sliced_plate(int plate_index); + //BBS: set bed positions + void set_bed_position(Vec2d& pos); + //BBS: is the background process slicing currently + bool is_background_process_slicing() const; + //BBS: update slicing context + void update_slicing_context_to_current_partplate(); + //BBS: show object info + void show_object_info(); + //BBS + bool show_publish_dialog(bool show = true); + //BBS: post process string object exception strings by warning types + void post_process_string_object_exception(StringObjectException &err); + +#if ENABLE_ENVIRONMENT_MAP + void init_environment_texture(); + unsigned int get_environment_texture_id() const; +#endif // ENABLE_ENVIRONMENT_MAP + + const BuildVolume& build_volume() const; + + // BBS + //const GLToolbar& get_view_toolbar() const; + //GLToolbar& get_view_toolbar(); + + const GLToolbar& get_collapse_toolbar() const; + GLToolbar& get_collapse_toolbar(); + + void update_preview_bottom_toolbar(); + void update_preview_moves_slider(); + void enable_preview_moves_slider(bool enable); + +#if 0 + void update_partplate(); +#endif + + void reset_gcode_toolpaths(); + void reset_last_loaded_gcode() { m_last_loaded_gcode = ""; } + + const Mouse3DController& get_mouse3d_controller() const; + Mouse3DController& get_mouse3d_controller(); + + //BBS: add bed exclude area + void set_bed_shape() const; + void set_bed_shape(const Pointfs& shape, const Pointfs& exclude_area, const double printable_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; + + const NotificationManager* get_notification_manager() const; + NotificationManager* get_notification_manager(); + DailyTipsWindow* get_dailytips() const; + //BBS: show message in status bar + void show_status_message(std::string s); + + void init_notification_manager(); + + void bring_instance_forward(); + + bool need_update() const; + void set_need_update(bool need_update); + + // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. + class SuppressSnapshots + { + public: + SuppressSnapshots(Plater *plater) : m_plater(plater) + { + m_plater->suppress_snapshots(); + } + ~SuppressSnapshots() + { + m_plater->allow_snapshots(); + } + private: + Plater *m_plater; + }; + + // RAII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot. + class TakeSnapshot + { + public: + TakeSnapshot(Plater *plater, const std::string &snapshot_name) : m_plater(plater) + { + m_plater->take_snapshot(snapshot_name); + m_plater->suppress_snapshots(); + } + /*TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater) + { + m_plater->take_snapshot(snapshot_name); + m_plater->suppress_snapshots(); + }*/ + TakeSnapshot(Plater* plater, const std::string& snapshot_name, UndoRedo::SnapshotType snapshot_type) : m_plater(plater) + { + m_plater->take_snapshot(snapshot_name, snapshot_type); + m_plater->suppress_snapshots(); + } + /*TakeSnapshot(Plater *plater, const wxString &snapshot_name, UndoRedo::SnapshotType snapshot_type) : m_plater(plater) + { + m_plater->take_snapshot(snapshot_name, snapshot_type); + m_plater->suppress_snapshots(); + }*/ + + ~TakeSnapshot() + { + m_plater->allow_snapshots(); + } + private: + Plater *m_plater; + }; + + // BBS: limit to single snapshot taking by the methods called from inside + // this snapshot. + class SingleSnapshot + { + public: + SingleSnapshot(Plater *plater) : m_plater(plater) + { + m_plater->single_snapshots_enter(this); + } + + ~SingleSnapshot() { m_plater->single_snapshots_leave(this); } + + bool check(bool modify) + { + if (token && (this->modify || !modify)) return false; + token = true; + this->modify = modify; + return true; + } + + private: + Plater *m_plater; + bool token = false; + bool modify = false; + }; + + bool inside_snapshot_capture(); + + void toggle_render_statistic_dialog(); + bool is_render_statistic_dialog_visible() const; + + void toggle_show_wireframe(); + bool is_show_wireframe() const; + void enable_wireframe(bool status); + bool is_wireframe_enabled() const; + + // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu. + bool PopupMenu(wxMenu *menu, const wxPoint& pos = wxDefaultPosition); + bool PopupMenu(wxMenu *menu, int x, int y) { return this->PopupMenu(menu, wxPoint(x, y)); } + + //BBS: add popup logic for table object + bool PopupObjectTable(int object_id, int volume_id, const wxPoint& position); + //BBS: popup selection at default position + bool PopupObjectTableBySelection(); + + // get same Plater/ObjectList menus + wxMenu* plate_menu(); + wxMenu* object_menu(); + wxMenu* part_menu(); + wxMenu* sla_object_menu(); + wxMenu* default_menu(); + wxMenu* instance_menu(); + wxMenu* layer_menu(); + wxMenu* multi_selection_menu(); + wxMenu* assemble_multi_selection_menu(); + int GetPlateIndexByRightMenuInLeftUI(); + void SetPlateIndexByRightMenuInLeftUI(int); + static bool has_illegal_filename_characters(const wxString& name); + static bool has_illegal_filename_characters(const std::string& name); + static void show_illegal_characters_warning(wxWindow* parent); + + std::string get_preview_only_filename() { return m_preview_only_filename; }; + + bool last_arrange_job_is_finished() + { + bool prevRunning = false; + return m_arrange_running.compare_exchange_strong(prevRunning, true); + }; + std::atomic m_arrange_running{false}; + +private: + struct priv; + std::unique_ptr p; + + // Set true during PopupMenu() tracking to suppress immediate error message boxes. + // The error messages are collected to m_tracking_popup_menu_error_message instead and these error messages + // are shown after the pop-up dialog closes. + bool m_tracking_popup_menu = false; + wxString m_tracking_popup_menu_error_message; + + wxString m_last_loaded_gcode; + //BBS: add only gcode mode + bool m_only_gcode { false }; + bool m_exported_file { false }; + bool skip_thumbnail_invalid { false }; + bool m_loading_project {false }; + std::string m_preview_only_filename; + int m_valid_plates_count { 0 }; + + void suppress_snapshots(); + void allow_snapshots(); + // BBS: single snapshot + void single_snapshots_enter(SingleSnapshot *single); + void single_snapshots_leave(SingleSnapshot *single); + // BBS: add project slice related functions + int start_next_slice(); + + void _calib_pa_pattern(const Calib_Params ¶ms); + void _calib_pa_tower(const Calib_Params ¶ms); + void _calib_pa_select_added_objects(); + + friend class SuppressBackgroundProcessingUpdate; +}; + +class SuppressBackgroundProcessingUpdate +{ +public: + SuppressBackgroundProcessingUpdate(); + ~SuppressBackgroundProcessingUpdate(); +private: + bool m_was_scheduled; +}; + +std::vector get_min_flush_volumes(const DynamicPrintConfig& full_config); +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 6567bc959..eb88d7182 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -830,6 +830,7 @@ bool PlaterPresetComboBox::switch_to_tab() return false; } } + //wxGetApp().get_tab(Preset::TYPE_CONFIG)->get_combo_box()->Update(); } /* @@ -969,6 +970,7 @@ void PlaterPresetComboBox::update() bool wide_icons = selected_preset && !selected_preset->is_compatible; std::map nonsys_presets; + std::map temp_presets; //BBS: add project embedded presets logic std::map project_embedded_presets; std::map system_presets; @@ -1056,29 +1058,69 @@ void PlaterPresetComboBox::update() add_ams_filaments(into_u8(selected_user_preset.empty() ? selected_system_preset : selected_user_preset), true); //BBS: add project embedded preset logic - if (!project_embedded_presets.empty()) - { - set_label_marker(Append(separator(L("Project-inside presets")), wxNullBitmap)); - for (std::map::iterator it = project_embedded_presets.begin(); it != project_embedded_presets.end(); ++it) { - SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); - validate_selection(it->first == selected_user_preset); + if (m_type == Preset::TYPE_CONFIG) { + wxString lian_xu = _L("Lian xu"); + bool m_fold = false; + //m_preset_bundle->filament_presets + for (auto& f : m_preset_bundle->filament_presets) { + wxString abc = wxString::FromUTF8(f); + if (m_fold = abc.Contains(lian_xu)) { + break; + } + } + + if (m_fold){ + /* for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + wxString key = it->first; + temp_presets.insert + }*/ + temp_presets.clear(); + for (const auto& pair : nonsys_presets) { + if (m_fold) { + wxString key = pair.first; + if (key.Contains(lian_xu)) { + temp_presets.emplace(pair); + } + } + } + for (std::map::iterator it = temp_presets.begin(); it != temp_presets.end(); ++it) { + SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); + validate_selection(it->first == selected_user_preset); + } + //m_collection->cbegin + } + else { + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); + validate_selection(it->first == selected_user_preset); + } } } - if (!nonsys_presets.empty()) - { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); - validate_selection(it->first == selected_user_preset); + else { + if (!project_embedded_presets.empty()) + { + set_label_marker(Append(separator(L("Project-inside presets")), wxNullBitmap)); + for (std::map::iterator it = project_embedded_presets.begin(); it != project_embedded_presets.end(); ++it) { + SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); + validate_selection(it->first == selected_user_preset); + } } - } - //BBS: move system to the end - if (!system_presets.empty()) - { - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); - for (std::map::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { - SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); - validate_selection(it->first == selected_system_preset); + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); + validate_selection(it->first == selected_user_preset); + } + } + //BBS: move system to the end + if (!system_presets.empty()) + { + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + for (std::map::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { + SetItemTooltip(Append(it->first, *it->second), preset_descriptions[it->first]); + validate_selection(it->first == selected_system_preset); + } } } @@ -1114,8 +1156,8 @@ void PlaterPresetComboBox::update() set_label_marker(Append(separator(L("Add/Remove filaments")), *bmp), LABEL_ITEM_WIZARD_FILAMENTS); else if (m_type == Preset::TYPE_SLA_MATERIAL) set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); - else if (m_type == Preset::TYPE_CONFIG) - set_label_marker(Append(separator(L("Add/Remove filaments")), *bmp), LABEL_ITEM_WIZARD_FILAMENTS); + else if (m_type == Preset::TYPE_CONFIG) {} + //set_label_marker(Append(separator(L("Add/Remove configs")), *bmp), LABEL_ITEM_WIZARD_CONFIGS); else { set_label_marker(Append(separator(L("Select/Remove printers(system presets)")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); set_label_marker(Append(separator(L("Create printer")), *bmp), LABEL_ITEM_WIZARD_ADD_PRINTERS); @@ -1183,6 +1225,7 @@ void TabPresetComboBox::OnSelect(wxCommandEvent &evt) case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break; case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break; case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break; + case LABEL_ITEM_WIZARD_CONFIGS: sp = ConfigWizard::SP_CONFIGS; break; default: break; } if (sp != ConfigWizard::SP_WELCOME) { @@ -1224,6 +1267,7 @@ void TabPresetComboBox::update() const std::deque& presets = m_collection->get_presets(); std::map> nonsys_presets; + std::map> temp_presets; //BBS: add project embedded presets logic std::map> project_embedded_presets; //BBS: move system to the end @@ -1292,41 +1336,88 @@ void TabPresetComboBox::update() add_ams_filaments(into_u8(selected)); //BBS: add project embedded preset logic - if (!project_embedded_presets.empty()) - { - set_label_marker(Append(separator(L("Project-inside presets")), wxNullBitmap)); - for (std::map>::iterator it = project_embedded_presets.begin(); it != project_embedded_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - SetItemTooltip(item_id, preset_descriptions[it->first]); - bool is_enabled = it->second.second; - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); + if (m_type == Preset::TYPE_CONFIG) { + wxString lian_xu = _L("Lian xu"); + bool m_fold = false; + //m_preset_bundle->filament_presets + for (auto& f : m_preset_bundle->filament_presets) { + wxString abc = wxString::FromUTF8(f); + if (m_fold = abc.Contains(lian_xu)) { + break; + } + } + + if (m_fold) { + /* for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + wxString key = it->first; + temp_presets.insert + }*/ + temp_presets.clear(); + for (const auto& pair : nonsys_presets) { + if (m_fold) { + wxString key = pair.first; + if (key.Contains(lian_xu)) { + temp_presets.emplace(pair); + } + } + } + for (std::map>::iterator it = temp_presets.begin(); it != temp_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + SetItemTooltip(item_id, preset_descriptions[it->first]); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } + } + else { + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + SetItemTooltip(item_id, preset_descriptions[it->first]); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } } } - if (!nonsys_presets.empty()) - { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - SetItemTooltip(item_id, preset_descriptions[it->first]); - bool is_enabled = it->second.second; - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); + else { + if (!project_embedded_presets.empty()) + { + set_label_marker(Append(separator(L("Project-inside presets")), wxNullBitmap)); + for (std::map>::iterator it = project_embedded_presets.begin(); it != project_embedded_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + SetItemTooltip(item_id, preset_descriptions[it->first]); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } } - } - //BBS: move system to the end - if (!system_presets.empty()) - { - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); - for (std::map>::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { - int item_id = Append(it->first, *it->second.first); - SetItemTooltip(item_id, preset_descriptions[it->first]); - bool is_enabled = it->second.second; - if (!is_enabled) - set_label_marker(item_id, LABEL_ITEM_DISABLED); - validate_selection(it->first == selected); + if (!nonsys_presets.empty()) + { + set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + SetItemTooltip(item_id, preset_descriptions[it->first]); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } + } + //BBS: move system to the end + if (!system_presets.empty()) + { + set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + for (std::map>::iterator it = system_presets.begin(); it != system_presets.end(); ++it) { + int item_id = Append(it->first, *it->second.first); + SetItemTooltip(item_id, preset_descriptions[it->first]); + bool is_enabled = it->second.second; + if (!is_enabled) + set_label_marker(item_id, LABEL_ITEM_DISABLED); + validate_selection(it->first == selected); + } } } diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp new file mode 100644 index 000000000..5c73efc0c --- /dev/null +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -0,0 +1,257 @@ +#ifndef slic3r_PresetComboBoxes_hpp_ +#define slic3r_PresetComboBoxes_hpp_ + +//#include +#include +#include +#include + +#include "libslic3r/Preset.hpp" +#include "wxExtensions.hpp" +#include "BitmapComboBox.hpp" +#include "Widgets/ComboBox.hpp" +#include "GUI_Utils.hpp" + +class wxString; +class wxTextCtrl; +class wxStaticText; +class ScalableButton; +class wxBoxSizer; +class wxComboBox; +class wxStaticBitmap; + +namespace Slic3r { + +namespace GUI { + +class BitmapCache; + +// --------------------------------- +// *** PresetComboBox *** +// --------------------------------- + +// BitmapComboBox used to presets list on Sidebar and Tabs +class PresetComboBox : public ::ComboBox // BBS +{ + bool m_show_all { false }; +public: + PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr); + ~PresetComboBox(); + + enum LabelItemType { + LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01, + LABEL_ITEM_DISABLED, + LABEL_ITEM_MARKER, + LABEL_ITEM_PHYSICAL_PRINTERS, + LABEL_ITEM_WIZARD_PRINTERS, + LABEL_ITEM_WIZARD_FILAMENTS, + LABEL_ITEM_WIZARD_MATERIALS, + LABEL_ITEM_WIZARD_ADD_PRINTERS, + LABEL_ITEM_WIZARD_CONFIGS, + + LABEL_ITEM_MAX, + }; + + void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); + bool set_printer_technology(PrinterTechnology pt); + + void set_selection_changed_function(std::function sel_changed) { on_selection_changed = sel_changed; } + + bool is_selected_physical_printer(); + + // Return true, if physical printer was selected + // and next internal selection was accomplished + bool selection_is_changed_according_to_physical_printers(); + + void update(std::string select_preset); + // select preset which is selected in PreseBundle + void update_from_bundle(); + + // BBS: ams + void add_ams_filaments(std::string selected, bool alias_name = false); + int selected_ams_filament() const; + + void set_filament_idx(const int extr_idx) { m_filament_idx = extr_idx; } + int get_filament_idx() const { return m_filament_idx; } + + // BBS + wxString get_tooltip(const Preset& preset); + + static wxColor different_color(wxColor const & color); + + virtual wxString get_preset_name(const Preset& preset); + Preset::Type get_type() { return m_type; } + void show_all(bool show_all); + virtual void update(); + virtual void msw_rescale(); + virtual void sys_color_changed(); + virtual void OnSelect(wxCommandEvent& evt); + +protected: + typedef std::size_t Marker; + std::function on_selection_changed { nullptr }; + + Preset::Type m_type; + std::string m_main_bitmap_name; + + PresetBundle* m_preset_bundle {nullptr}; + PresetCollection* m_collection {nullptr}; + + // Caching bitmaps for the all bitmaps, used in preset comboboxes + static BitmapCache& bitmap_cache(); + + // Indicator, that the preset is compatible with the selected printer. + ScalableBitmap m_bitmapCompatible; + // Indicator, that the preset is NOT compatible with the selected printer. + ScalableBitmap m_bitmapIncompatible; + + int m_last_selected; + int m_em_unit; + bool m_suppress_change { true }; + + // BBS: ams + int m_filament_idx = -1; + int m_first_ams_filament = 0; + int m_last_ams_filament = 0; + + // parameters for an icon's drawing + int icon_height; + int norm_icon_width; + int thin_icon_width; + int wide_icon_width; + int space_icon_width; + int thin_space_icon_width; + int wide_space_icon_width; + + PrinterTechnology printer_technology {ptAny}; + + void invalidate_selection(); + void validate_selection(bool predicate = false); + void update_selection(); + + // BBS: ams + int update_ams_color(); + +#ifdef __linux__ + static const char* separator_head() { return "------- "; } + static const char* separator_tail() { return " -------"; } +#else // __linux__ + static const char* separator_head() { return "------ "; } + static const char* separator_tail() { return " ------"; } +#endif // __linux__ + static wxString separator(const std::string& label); + + wxBitmap* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + bool is_compatible = true, bool is_system = false, bool is_single_bar = false, + const std::string& filament_rgb = "", const std::string& extruder_rgb = "", const std::string& material_rgb = ""); + + wxBitmap* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, + bool is_enabled = true, bool is_compatible = true, bool is_system = false); + + wxBitmap *get_bmp(Preset const &preset); + +private: + void fill_width_height(); +}; + + +// --------------------------------- +// *** PlaterPresetComboBox *** +// --------------------------------- + +class PlaterPresetComboBox : public PresetComboBox +{ +public: + PlaterPresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~PlaterPresetComboBox(); + + ScalableButton* edit_btn { nullptr }; + + // BBS + wxButton* clr_picker { nullptr }; + wxColourData m_clrData; + + wxColor get_color() { return m_color; } + + bool switch_to_tab(); + void change_extruder_color(); + void show_add_menu(); + void show_edit_menu(); + + wxString get_preset_name(const Preset& preset) override; + void update() override; + void msw_rescale() override; + void OnSelect(wxCommandEvent& evt) override; + +private: + // BBS + wxColor m_color; +}; + + +// --------------------------------- +// *** TabPresetComboBox *** +// --------------------------------- + +class TabPresetComboBox : public PresetComboBox +{ + bool show_incompatible {false}; + bool m_enable_all {false}; + +public: + TabPresetComboBox(wxWindow *parent, Preset::Type preset_type); + ~TabPresetComboBox() {} + void set_show_incompatible_presets(bool show_incompatible_presets) { + show_incompatible = show_incompatible_presets; + } + + wxString get_preset_name(const Preset& preset) override; + void update() override; + void update_dirty(); + void msw_rescale() override; + void OnSelect(wxCommandEvent& evt) override; + + void set_enable_all(bool enable=true) { m_enable_all = enable; } + + PresetCollection* presets() const { return m_collection; } + Preset::Type type() const { return m_type; } +}; + +// --------------------------------- +// *** CalibrateFilamentComboBox *** +// --------------------------------- + +class CalibrateFilamentComboBox : public PlaterPresetComboBox +{ +public: + CalibrateFilamentComboBox(wxWindow *parent); + ~CalibrateFilamentComboBox(); + + void load_tray(DynamicPrintConfig & config); + + void update() override; + void msw_rescale() override; + void OnSelect(wxCommandEvent &evt) override; + const Preset* get_selected_preset() { return m_selected_preset; } + std::string get_tray_name() { return m_tray_name; } + std::string get_tag_uid() { return m_tag_uid; } + bool is_tray_exist() { return m_filament_exist; } + bool is_compatible_with_printer() { return m_is_compatible; } + +private: + std::string m_tray_name; + std::string m_filament_id; + std::string m_tag_uid; + std::string m_filament_type; + std::string m_filament_color; + bool m_filament_exist{false}; + bool m_is_compatible{true}; + const Preset* m_selected_preset = nullptr; + std::map> m_nonsys_presets; + std::map> m_system_presets; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 225c7ccb9..5078898cd 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2999,72 +2999,72 @@ void TabFilament::build() load_initial_data(); auto page = add_options_page(L("Filament"), "spool"); - //BBS - auto optgroup = page->new_optgroup(L("Basic information"), L"param_information"); - // Set size as all another fields for a better alignment - Option option = optgroup->get_option("filament_type"); - option.opt.width = Field::def_width(); - optgroup->append_single_option_line(option); - optgroup->append_single_option_line("filament_vendor"); - optgroup->append_single_option_line("filament_soluble"); - // BBS - optgroup->append_single_option_line("filament_is_support"); - //optgroup->append_single_option_line("filament_colour"); - optgroup->append_single_option_line("required_nozzle_HRC"); - optgroup->append_single_option_line("default_filament_colour"); - optgroup->append_single_option_line("filament_diameter"); - optgroup->append_single_option_line("filament_flow_ratio"); - optgroup->append_single_option_line("enable_pressure_advance"); - optgroup->append_single_option_line("pressure_advance"); - optgroup->append_single_option_line("filament_density"); - optgroup->append_single_option_line("filament_cost"); - //BBS - optgroup->append_single_option_line("temperature_vitrification"); - Line line = { L("Recommended nozzle temperature"), L("Recommended nozzle temperature range of this filament. 0 means no set") }; - line.append_option(optgroup->get_option("nozzle_temperature_range_low")); - line.append_option(optgroup->get_option("nozzle_temperature_range_high")); - optgroup->append_line(line); + //BBS + auto optgroup = page->new_optgroup(L("Basic information"), L"param_information"); + // Set size as all another fields for a better alignment + Option option = optgroup->get_option("filament_type"); + option.opt.width = Field::def_width(); + optgroup->append_single_option_line(option); + optgroup->append_single_option_line("filament_vendor"); + optgroup->append_single_option_line("filament_soluble"); + // BBS + optgroup->append_single_option_line("filament_is_support"); + //optgroup->append_single_option_line("filament_colour"); + optgroup->append_single_option_line("required_nozzle_HRC"); + optgroup->append_single_option_line("default_filament_colour"); + optgroup->append_single_option_line("filament_diameter"); + optgroup->append_single_option_line("filament_flow_ratio"); + optgroup->append_single_option_line("enable_pressure_advance"); + optgroup->append_single_option_line("pressure_advance"); + optgroup->append_single_option_line("filament_density"); + optgroup->append_single_option_line("filament_cost"); + //BBS + optgroup->append_single_option_line("temperature_vitrification"); + Line line = { L("Recommended nozzle temperature"), L("Recommended nozzle temperature range of this filament. 0 means no set") }; + line.append_option(optgroup->get_option("nozzle_temperature_range_low")); + line.append_option(optgroup->get_option("nozzle_temperature_range_high")); + optgroup->append_line(line); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { - DynamicPrintConfig &filament_config = wxGetApp().preset_bundle->filaments.get_edited_preset().config; + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { + DynamicPrintConfig& filament_config = wxGetApp().preset_bundle->filaments.get_edited_preset().config; - update_dirty(); - if (!m_postpone_update_ui && (opt_key == "nozzle_temperature_range_low" || opt_key == "nozzle_temperature_range_high")) { - m_config_manipulation.check_nozzle_recommended_temperature_range(&filament_config); - } - on_value_change(opt_key, value); + update_dirty(); + if (!m_postpone_update_ui && (opt_key == "nozzle_temperature_range_low" || opt_key == "nozzle_temperature_range_high")) { + m_config_manipulation.check_nozzle_recommended_temperature_range(&filament_config); + } + on_value_change(opt_key, value); }; - optgroup = page->new_optgroup(L("Print temperature"), L"param_temperature"); - optgroup->append_single_option_line("chamber_temperatures","chamber-temperature"); + optgroup = page->new_optgroup(L("Print temperature"), L"param_temperature"); + optgroup->append_single_option_line("chamber_temperatures", "chamber-temperature"); - line = { L("Nozzle"), L("Nozzle temperature when printing") }; - line.append_option(optgroup->get_option("nozzle_temperature_initial_layer")); - line.append_option(optgroup->get_option("nozzle_temperature")); - optgroup->append_line(line); + line = { L("Nozzle"), L("Nozzle temperature when printing") }; + line.append_option(optgroup->get_option("nozzle_temperature_initial_layer")); + line.append_option(optgroup->get_option("nozzle_temperature")); + optgroup->append_line(line); - line = { L("Cool Plate / PLA Plate"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Cool Plate") }; - line.append_option(optgroup->get_option("cool_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("cool_plate_temp")); - optgroup->append_line(line); + line = { L("Cool Plate / PLA Plate"), L("Bed temperature when cool plate is installed. Value 0 means the filament does not support to print on the Cool Plate") }; + line.append_option(optgroup->get_option("cool_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("cool_plate_temp")); + optgroup->append_line(line); - line = { L("Engineering Plate"), L("Bed temperature when engineering plate is installed. Value 0 means the filament does not support to print on the Engineering Plate") }; - line.append_option(optgroup->get_option("eng_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("eng_plate_temp")); - optgroup->append_line(line); + line = { L("Engineering Plate"), L("Bed temperature when engineering plate is installed. Value 0 means the filament does not support to print on the Engineering Plate") }; + line.append_option(optgroup->get_option("eng_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("eng_plate_temp")); + optgroup->append_line(line); - line = {L("Smooth PEI Plate / High Temp Plate"), L("Bed temperature when Smooth PEI Plate/High temperature plate is installed. Value 0 means the filament does not support to print on the Smooth PEI Plate/High Temp Plate") }; - line.append_option(optgroup->get_option("hot_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("hot_plate_temp")); - optgroup->append_line(line); + line = { L("Smooth PEI Plate / High Temp Plate"), L("Bed temperature when Smooth PEI Plate/High temperature plate is installed. Value 0 means the filament does not support to print on the Smooth PEI Plate/High Temp Plate") }; + line.append_option(optgroup->get_option("hot_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("hot_plate_temp")); + optgroup->append_line(line); - line = {L("Textured PEI Plate"), L("Bed temperature when Textured PEI Plate is installed. Value 0 means the filament does not support to print on the Textured PEI Plate")}; - line.append_option(optgroup->get_option("textured_plate_temp_initial_layer")); - line.append_option(optgroup->get_option("textured_plate_temp")); - optgroup->append_line(line); + line = { L("Textured PEI Plate"), L("Bed temperature when Textured PEI Plate is installed. Value 0 means the filament does not support to print on the Textured PEI Plate") }; + line.append_option(optgroup->get_option("textured_plate_temp_initial_layer")); + line.append_option(optgroup->get_option("textured_plate_temp")); + optgroup->append_line(line); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { DynamicPrintConfig& filament_config = wxGetApp().preset_bundle->filaments.get_edited_preset().config; @@ -3094,104 +3094,104 @@ void TabFilament::build() on_value_change(opt_key, value); }; - //BBS - optgroup = page->new_optgroup(L("Volumetric speed limitation"), L"param_volumetric_speed"); - optgroup->append_single_option_line("filament_max_volumetric_speed"); + //BBS + optgroup = page->new_optgroup(L("Volumetric speed limitation"), L"param_volumetric_speed"); + optgroup->append_single_option_line("filament_max_volumetric_speed"); - //line = { "", "" }; - //line.full_width = 1; - //line.widget = [this](wxWindow* parent) { - // return description_line_widget(parent, &m_volumetric_speed_description_line); - //}; - //optgroup->append_line(line); + //line = { "", "" }; + //line.full_width = 1; + //line.widget = [this](wxWindow* parent) { + // return description_line_widget(parent, &m_volumetric_speed_description_line); + //}; + //optgroup->append_line(line); page = add_options_page(L("Cooling"), "empty"); - //line = { "", "" }; - //line.full_width = 1; - //line.widget = [this](wxWindow* parent) { - // return description_line_widget(parent, &m_cooling_description_line); - //}; - //optgroup->append_line(line); - optgroup = page->new_optgroup(L("Cooling for specific layer"), L"param_cooling"); + //line = { "", "" }; + //line.full_width = 1; + //line.widget = [this](wxWindow* parent) { + // return description_line_widget(parent, &m_cooling_description_line); + //}; + //optgroup->append_line(line); + optgroup = page->new_optgroup(L("Cooling for specific layer"), L"param_cooling"); optgroup->append_single_option_line("close_fan_the_first_x_layers", "auto-cooling"); - //optgroup->append_single_option_line("full_fan_speed_layer"); + //optgroup->append_single_option_line("full_fan_speed_layer"); - optgroup = page->new_optgroup(L("Part cooling fan"), L"param_cooling_fan"); - line = { L("Min fan speed threshold"), L("Part cooling fan speed will start to run at min speed when the estimated layer time is no longer than the layer time in setting. When layer time is shorter than threshold, fan speed is interpolated between the minimum and maximum fan speed according to layer printing time") }; - line.label_path = "auto-cooling"; - line.append_option(optgroup->get_option("fan_min_speed")); - line.append_option(optgroup->get_option("fan_cooling_layer_time")); - optgroup->append_line(line); - line = { L("Max fan speed threshold"), L("Part cooling fan speed will be max when the estimated layer time is shorter than the setting value") }; - line.label_path = "auto-cooling"; - line.append_option(optgroup->get_option("fan_max_speed")); - line.append_option(optgroup->get_option("slow_down_layer_time")); - optgroup->append_line(line); - optgroup->append_single_option_line("reduce_fan_stop_start_freq", "auto-cooling"); - optgroup->append_single_option_line("slow_down_for_layer_cooling", "auto-cooling"); - optgroup->append_single_option_line("slow_down_min_speed","auto-cooling"); + optgroup = page->new_optgroup(L("Part cooling fan"), L"param_cooling_fan"); + line = { L("Min fan speed threshold"), L("Part cooling fan speed will start to run at min speed when the estimated layer time is no longer than the layer time in setting. When layer time is shorter than threshold, fan speed is interpolated between the minimum and maximum fan speed according to layer printing time") }; + line.label_path = "auto-cooling"; + line.append_option(optgroup->get_option("fan_min_speed")); + line.append_option(optgroup->get_option("fan_cooling_layer_time")); + optgroup->append_line(line); + line = { L("Max fan speed threshold"), L("Part cooling fan speed will be max when the estimated layer time is shorter than the setting value") }; + line.label_path = "auto-cooling"; + line.append_option(optgroup->get_option("fan_max_speed")); + line.append_option(optgroup->get_option("slow_down_layer_time")); + optgroup->append_line(line); + optgroup->append_single_option_line("reduce_fan_stop_start_freq", "auto-cooling"); + optgroup->append_single_option_line("slow_down_for_layer_cooling", "auto-cooling"); + optgroup->append_single_option_line("slow_down_min_speed", "auto-cooling"); - optgroup->append_single_option_line("enable_overhang_bridge_fan", "auto-cooling"); - optgroup->append_single_option_line("overhang_fan_threshold", "auto-cooling"); - optgroup->append_single_option_line("overhang_fan_speed", "auto-cooling"); + optgroup->append_single_option_line("enable_overhang_bridge_fan", "auto-cooling"); + optgroup->append_single_option_line("overhang_fan_threshold", "auto-cooling"); + optgroup->append_single_option_line("overhang_fan_speed", "auto-cooling"); - optgroup = page->new_optgroup(L("Auxiliary part cooling fan"), L"param_cooling_fan"); - optgroup->append_single_option_line("additional_cooling_fan_speed"); + optgroup = page->new_optgroup(L("Auxiliary part cooling fan"), L"param_cooling_fan"); + optgroup->append_single_option_line("additional_cooling_fan_speed"); - optgroup = page->new_optgroup(L("Exhaust fan"),L"param_cooling_fan"); + optgroup = page->new_optgroup(L("Exhaust fan"), L"param_cooling_fan"); - optgroup->append_single_option_line("activate_air_filtration"); + optgroup->append_single_option_line("activate_air_filtration"); - line = {L("During print"), ""}; - line.append_option(optgroup->get_option("during_print_exhaust_fan_speed")); - optgroup->append_line(line); + line = { L("During print"), "" }; + line.append_option(optgroup->get_option("during_print_exhaust_fan_speed")); + optgroup->append_line(line); - line = {L("Complete print"), ""}; - line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed")); - optgroup->append_line(line); - //BBS - // add_filament_overrides_page(); + line = { L("Complete print"), "" }; + line.append_option(optgroup->get_option("complete_print_exhaust_fan_speed")); + optgroup->append_line(line); + //BBS + add_filament_overrides_page(); #if 0 //page = add_options_page(L("Advanced"), "advanced"); // optgroup = page->new_optgroup(L("Wipe tower parameters")); // optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); #endif - const int gcode_field_height = 15; // 150 - const int notes_field_height = 25; // 250 + const int gcode_field_height = 15; // 150 + const int notes_field_height = 25; // 250 page = add_options_page(L("Advanced"), "advanced"); - optgroup = page->new_optgroup(L("Filament start G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup = page->new_optgroup(L("Filament start G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup, opt_key, value); }; - option = optgroup->get_option("filament_start_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); + option = optgroup->get_option("filament_start_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height;// 150; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(L("Filament end G-code"), L"param_gcode", 0); - optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { - validate_custom_gcode_cb(this, optgroup, opt_key, value); + optgroup = page->new_optgroup(L("Filament end G-code"), L"param_gcode", 0); + optgroup->m_on_change = [this, optgroup](const t_config_option_key& opt_key, const boost::any& value) { + validate_custom_gcode_cb(this, optgroup, opt_key, value); }; - option = optgroup->get_option("filament_end_gcode"); - option.opt.full_width = true; - option.opt.is_code = true; - option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); + option = optgroup->get_option("filament_end_gcode"); + option.opt.full_width = true; + option.opt.is_code = true; + option.opt.height = gcode_field_height;// 150; + optgroup->append_single_option_line(option); page = add_options_page(L("Notes"), "note"); - optgroup = page->new_optgroup(L("Notes"),"note"); - optgroup->label_width = 0; - option = optgroup->get_option("filament_notes"); - option.opt.full_width = true; - option.opt.height = notes_field_height; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Notes"), "note"); + optgroup->label_width = 0; + option = optgroup->get_option("filament_notes"); + option.opt.full_width = true; + option.opt.height = notes_field_height; + optgroup->append_single_option_line(option); - //BBS + //BBS #if 0 //page = add_options_page(L("Dependencies"), "advanced"); // optgroup = page->new_optgroup(L("Profile dependencies")); @@ -4401,19 +4401,19 @@ void TabConfig::reload_config() //this->compatible_widget_reload(m_compatible_prints); Tab::reload_config(); } -void TabConfig::update_description_lines() -{ - Tab::update_description_lines(); - - if (!m_active_page) - return; - - //if (m_active_page->title() == "Cooling" && m_cooling_description_line) - // m_cooling_description_line->SetText(from_u8(PresetHints::cooling_description(m_presets->get_edited_preset()))); - //BBS - //if (m_active_page->title() == "Filament" && m_volumetric_speed_description_line) - // this->update_volumetric_flow_preset_hints(); -} +//void TabConfig::update_description_lines() +//{ +// Tab::update_description_lines(); +// +// if (!m_active_page) +// return; +// +// //if (m_active_page->title() == "Cooling" && m_cooling_description_line) +// // m_cooling_description_line->SetText(from_u8(PresetHints::cooling_description(m_presets->get_edited_preset()))); +// //BBS +// //if (m_active_page->title() == "Filament" && m_volumetric_speed_description_line) +// // this->update_volumetric_flow_preset_hints(); +//} void TabConfig::toggle_options() { if (!m_active_page) return; @@ -4422,49 +4422,211 @@ void TabConfig::toggle_options() { if (m_preset_bundle) { is_BBL_printer = m_preset_bundle->configs.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle); } - - /* const Preset& preset = m_preset_bundle->configs.get_edited_preset(); - bool is_BBL = preset.is_system;*/ - //if (m_active_page->title() == "Quality") { - //toggle_line("printable_area", !is_configed_by_BBL);//all printer can entry and view data - //toggle_option("single_extruder_multi_material", have_multiple_extruders); - //BBS: gcode_flavore of BBL printer can't be edited and changed - //toggle_option("gcode_flavor", !is_BBL_printer); - //toggle_option("thumbnail_size", !is_BBL_printer); - //toggle_option("printer_structure", !is_BBL_printer); - //toggle_option("use_relative_e_distances", !is_BBL_printer); - //toggle_option("support_chamber_temp_control", !is_BBL_printer); - //toggle_option("use_firmware_retraction", !is_BBL_printer); - //toggle_option("support_air_filtration", is_BBL_printer); - //auto flavor = m_config->option>("gcode_flavor")->value; - //bool is_marlin_flavor = flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware; - //// Disable silent mode for non-marlin firmwares. - //toggle_option("silent_mode", is_marlin_flavor); - ////BBS: extruder clearance of BBL printer can't be edited. - //for (auto el : { "extruder_clearance_max_radius", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" }) - // toggle_option(el, !is_BBL_printer); - //} - - /* if (m_active_page->title() == "Strength") { - toggle_line("time_lapse_gcode", m_preset_bundle->configs.get_edited_preset().config.opt_enum("printer_structure") == PrinterStructure::psI3); - toggle_option("thumbnail_size", !is_BBL_printer); + //std::string cde = "1"; + //m_preset_bundle->filament_presets + /* for (auto& f : m_preset_bundle->filament_presets) { + cde = f; }*/ + + //p->combos_filament + //m_preset_bundle->configs.update_dirty(); + //wxString abc = wxString::FromUTF8( m_preset_bundle->configs.get_selected_preset().name); + wxString abc = wxGetApp().get_tab(Preset::TYPE_CONFIG)->get_combo_box()->GetTextLabel(); + //wxString abc = wxString::FromUTF8( m_preset_bundle->configs.get_edited_preset().name); + //InfillPattern bcd = m_preset_bundle->configs.get_edited_preset().config.opt_enum("top_surface_pattern"); + wxString lian_xu = _L("Lian xu"); + if (abc.Contains(lian_xu)) { + BitmapCache concentric_cache; + wxBitmap concentric_bmp = *concentric_cache.load_svg("param_concentric", FromDIP(23), FromDIP(23)); + BitmapCache zig_cache; + wxBitmap zig_bmp = *zig_cache.load_svg("param_zig-zag", FromDIP(23), FromDIP(23)); + BitmapCache monotonic_cache; + wxBitmap monotonic_bmp = *monotonic_cache.load_svg("param_monotonic", FromDIP(23), FromDIP(23)); + BitmapCache monotonicline_cache; + wxBitmap monotonicline_bmp = *monotonicline_cache.load_svg("param_monotonicline", FromDIP(23), FromDIP(23)); + BitmapCache alignedrectilinear_cache; + wxBitmap alignedrectilinear_bmp = *alignedrectilinear_cache.load_svg("param_alignedrectilinear", FromDIP(23), FromDIP(23)); + BitmapCache fiberspiral_cache; + wxBitmap fiberspiral_bmp = *fiberspiral_cache.load_svg("param_fiberspiral", FromDIP(23), FromDIP(23)); + + BitmapCache grid_cache; + wxBitmap grid_bmp = *grid_cache.load_svg("param_grid", FromDIP(23), FromDIP(23)); + BitmapCache line_cache; + wxBitmap line_bmp = *line_cache.load_svg("param_line", FromDIP(23), FromDIP(23)); + //BitmapCache cubic_cache; + //wxBitmap cubic_bmp = *cubic_cache.load_svg("param_cubic", FromDIP(23), FromDIP(23)); + //BitmapCache triangles_cache; + //wxBitmap triangles_bmp = *triangles_cache.load_svg("param_triangles", FromDIP(23), FromDIP(23)); + //BitmapCache tri_cache; + //wxBitmap tri_bmp = *tri_cache.load_svg("param_tri-hexagon", FromDIP(23), FromDIP(23)); + //BitmapCache gyroid_cache; + //wxBitmap gyroid_bmp = *gyroid_cache.load_svg("param_gyroid", FromDIP(23), FromDIP(23)); + BitmapCache honeycomb_cache; + wxBitmap honeycomb_bmp = *honeycomb_cache.load_svg("param_honeycomb", FromDIP(23), FromDIP(23)); + BitmapCache adaptivecubic_cache; + wxBitmap adaptivecubic_bmp = *adaptivecubic_cache.load_svg("param_adaptivecubic", FromDIP(23), FromDIP(23)); + BitmapCache dhoneycomb_cache; + wxBitmap dhoneycomb_bmp = *dhoneycomb_cache.load_svg("param_3dhoneycomb", FromDIP(23), FromDIP(23)); + + + { + Field* field = m_active_page->get_field("top_surface_pattern"); + if (auto choice = dynamic_cast(field)) { + auto& opt = const_cast(field->m_opt); + auto cb = dynamic_cast(choice->window); + auto n = cb->GetValue(); + opt.enum_values.clear(); + opt.enum_labels.clear(); + cb->Clear(); + + opt.enum_values.push_back("concentric"); + opt.enum_values.push_back("zig-zag"); + opt.enum_values.push_back("monotonic"); + opt.enum_values.push_back("monotonicline"); + opt.enum_values.push_back("alignedrectilinear"); + opt.enum_values.push_back("fiberspiral"); + + opt.enum_labels.push_back(L("Concentric")); + cb->Append(_L("Concentric"), concentric_bmp); + opt.enum_labels.push_back(L("Rectilinear")); + cb->Append(_L("Rectilinear"), zig_bmp); + opt.enum_labels.push_back(L("Monotonic")); + cb->Append(_L("Monotonic"), monotonic_bmp); + opt.enum_labels.push_back(L("Monotonic line")); + cb->Append(_L("Monotonic line"), monotonicline_bmp); + opt.enum_labels.push_back(L("Aligned Rectilinear")); + cb->Append(_L("Aligned Rectilinear"), alignedrectilinear_bmp); + opt.enum_labels.push_back(L("Fiber Spiral")); + cb->Append(_L("Fiber Spiral"), fiberspiral_bmp); + + cb->SetValue(n); + } + } + { + Field* field = m_active_page->get_field("bottom_surface_pattern"); + if (auto choice = dynamic_cast(field)) { + auto& opt = const_cast(field->m_opt); + auto cb = dynamic_cast(choice->window); + auto n = cb->GetValue(); + opt.enum_values.clear(); + opt.enum_labels.clear(); + cb->Clear(); + + opt.enum_values.push_back("concentric"); + opt.enum_values.push_back("zig-zag"); + opt.enum_values.push_back("monotonic"); + opt.enum_values.push_back("monotonicline"); + opt.enum_values.push_back("alignedrectilinear"); + opt.enum_values.push_back("fiberspiral"); + + opt.enum_labels.push_back(L("Concentric")); + cb->Append(_L("Concentric"), concentric_bmp); + opt.enum_labels.push_back(L("Rectilinear")); + cb->Append(_L("Rectilinear"), zig_bmp); + opt.enum_labels.push_back(L("Monotonic")); + cb->Append(_L("Monotonic"), monotonic_bmp); + opt.enum_labels.push_back(L("Monotonic line")); + cb->Append(_L("Monotonic line"), monotonicline_bmp); + opt.enum_labels.push_back(L("Aligned Rectilinear")); + cb->Append(_L("Aligned Rectilinear"), alignedrectilinear_bmp); + opt.enum_labels.push_back(L("Fiber Spiral")); + cb->Append(_L("Fiber Spiral"), fiberspiral_bmp); + + cb->SetValue(n); + } + } + { + Field * field = m_active_page->get_field("internal_solid_infill_pattern"); + if (auto choice = dynamic_cast(field)) { + auto& opt = const_cast(field->m_opt); + auto cb = dynamic_cast(choice->window); + auto n = cb->GetValue(); + opt.enum_values.clear(); + opt.enum_labels.clear(); + cb->Clear(); + + opt.enum_values.push_back("concentric"); + opt.enum_values.push_back("zig-zag"); + opt.enum_values.push_back("monotonic"); + opt.enum_values.push_back("monotonicline"); + opt.enum_values.push_back("alignedrectilinear"); + opt.enum_values.push_back("fiberspiral"); + + opt.enum_labels.push_back(L("Concentric")); + cb->Append(_L("Concentric"), concentric_bmp); + opt.enum_labels.push_back(L("Rectilinear")); + cb->Append(_L("Rectilinear"), zig_bmp); + opt.enum_labels.push_back(L("Monotonic")); + cb->Append(_L("Monotonic"), monotonic_bmp); + opt.enum_labels.push_back(L("Monotonic line")); + cb->Append(_L("Monotonic line"), monotonicline_bmp); + opt.enum_labels.push_back(L("Aligned Rectilinear")); + cb->Append(_L("Aligned Rectilinear"), alignedrectilinear_bmp); + opt.enum_labels.push_back(L("Fiber Spiral")); + cb->Append(_L("Fiber Spiral"), fiberspiral_bmp); + + cb->SetValue(n); + } + } + { + Field* field = m_active_page->get_field("sparse_infill_pattern"); + if (auto choice = dynamic_cast(field)) { + auto& opt = const_cast(field->m_opt); + auto cb = dynamic_cast(choice->window); + auto n = cb->GetValue(); + opt.enum_values.clear(); + opt.enum_labels.clear(); + cb->Clear(); + + opt.enum_values.push_back("concentric"); + opt.enum_values.push_back("zig-zag"); + opt.enum_values.push_back("grid"); + opt.enum_values.push_back("line"); + //opt.enum_values.push_back("cubic"); + //opt.enum_values.push_back("triangles"); + //opt.enum_values.push_back("tri-hexagon"); + //opt.enum_values.push_back("gyroid"); + opt.enum_values.push_back("honeycomb"); + opt.enum_values.push_back("adaptivecubic"); + opt.enum_values.push_back("alignedrectilinear"); + opt.enum_values.push_back("3dhoneycomb"); + opt.enum_values.push_back("fiberspiral"); + + opt.enum_labels.push_back(L("Concentric")); + cb->Append(_L("Concentric"), concentric_bmp); + opt.enum_labels.push_back(L("Rectilinear")); + cb->Append(_L("Rectilinear"), zig_bmp); + opt.enum_labels.push_back(L("Grid")); + cb->Append(_L("Grid"), grid_bmp); + opt.enum_labels.push_back(L("Line")); + cb->Append(_L("Line"), line_bmp); + //opt.enum_labels.push_back(L("Cubic")); + //cb->Append(_L("Cubic"), cubic_bmp); + //opt.enum_labels.push_back(L("Triangles")); + //cb->Append(_L("Triangles"), triangles_bmp); + //opt.enum_labels.push_back(L("Tri-hexagon")); + //cb->Append(_L("Tri-hexagon"), tri_bmp); + //opt.enum_labels.push_back(L("Gyroid")); + //cb->Append(_L("Gyroid"), gyroid_bmp); + opt.enum_labels.push_back(L("Honeycomb")); + cb->Append(_L("Honeycomb"), honeycomb_bmp); + opt.enum_labels.push_back(L("Adaptive Cubic")); + cb->Append(_L("Adaptive Cubic"), adaptivecubic_bmp); + opt.enum_labels.push_back(L("Aligned Rectilinear")); + cb->Append(_L("Aligned Rectilinear"), alignedrectilinear_bmp); + opt.enum_labels.push_back(L("3D Honeycomb")); + cb->Append(_L("3D Honeycomb"), dhoneycomb_bmp); + opt.enum_labels.push_back(L("Fiber Spiral")); + cb->Append(_L("Fiber Spiral"), fiberspiral_bmp); + + cb->SetValue(n); + } + } + } } void TabConfig::update() { - - //m_update_cnt++; - //update_description_lines(); - ////BBS: GUI refactor - ////Layout(); - //m_parent->Layout(); - m_update_cnt++; - update_description_lines(); - //BBS: GUI refactor - //Layout(); - m_parent->Layout(); - toggle_options(); m_update_cnt--; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index f92a2c275..dcfc1ac84 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -585,7 +585,7 @@ public: void build() override; void reload_config() override; - void update_description_lines() override; + //void update_description_lines() override; void toggle_options() override; void update() override; void clear_pages() override;