Merge branch 'main' into weilun/speed_dial

This commit is contained in:
Lam Wei Lun
2026-09-10 09:35:43 +08:00
62 changed files with 1845 additions and 414 deletions
+25 -3
View File
@@ -20,6 +20,8 @@
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/GCode/WipeTower.hpp"
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/PrintConfig.hpp"
@@ -919,6 +921,21 @@ int GLVolumeCollection::load_wipe_tower_preview(
GUI::PartPlateList& ppl = GUI::wxGetApp().plater()->get_partplate_list();
std::vector<int> plate_extruders = ppl.get_plate(plate_idx)->get_extruders(true);
TriangleMesh wipe_tower_shell = make_cube(width, depth, height);
// The brim is part of the printed footprint: draw it and fold it into the shell so the
// outside-bed shader and the drag clamp react to the true first-layer extent.
const bool show_brim = brim_width > 0.f;
const float brim_height = 0.2f; // one first layer, visual only
TriangleMesh brim_slab;
if (show_brim) {
// The brim follows the real first-layer outline: a Type2 cone-wall tower's base bulges
// past the body box. The wall type and angle are print settings, the planner a printer one.
const DynamicPrintConfig &print_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config;
const DynamicPrintConfig &printer_cfg = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
const Polygon outline = estimate_wipe_tower_first_layer_outline(print_cfg, resolve_wipe_tower_type(printer_cfg), width, depth, height);
const Polygons brim_outline = offset(outline, scaled(brim_width));
brim_slab = WipeTower::its_make_rib_brim(brim_outline.empty() ? outline : brim_outline.front(), brim_height);
wipe_tower_shell.merge(brim_slab);
}
for (int extruder_id : plate_extruders) {
if (extruder_id <= extruder_colors.size())
colors.push_back(extruder_colors[extruder_id - 1]);
@@ -929,14 +946,19 @@ int GLVolumeCollection::load_wipe_tower_preview(
// Orca: make it transparent
for(auto& color : colors)
color.a(0.66f);
const size_t slab_count = colors.size(); // per-filament body slabs; the brim part comes after
if (show_brim && !colors.empty())
colors.push_back(colors.front());
volumes.emplace_back(new GLWipeTowerVolume(colors));
GLWipeTowerVolume& v = *dynamic_cast<GLWipeTowerVolume*>(volumes.back());
v.model_per_colors.resize(colors.size());
for (int i = 0; i < colors.size(); i++) {
TriangleMesh color_part = make_cube(width, depth / colors.size(), height);
color_part.translate({ 0.f, depth * i / colors.size(), 0. });
for (size_t i = 0; i < slab_count; i++) {
TriangleMesh color_part = make_cube(width, depth / slab_count, height);
color_part.translate({ 0.f, depth * i / slab_count, 0. });
v.model_per_colors[i].init_from(color_part);
}
if (show_brim && !colors.empty())
v.model_per_colors[slab_count].init_from(brim_slab);
v.model.init_from(wipe_tower_shell);
v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(std::make_shared<const TriangleMesh>(wipe_tower_shell));
v.set_convex_hull(wipe_tower_shell);
+1 -1
View File
@@ -406,7 +406,7 @@ void AmsMapingPopup::update_ams_data_multi_machines()
int ams_type = 1;
int nozzle_id = 0;
if (ams_type >= 1 || ams_type <= 3) { // 1:ams 2:ams-lite 3:n3f
if (ams_type >= 1 && ams_type <= 3) { // 1:ams 2:ams-lite 3:n3f
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
auto ams_mapping_item_container = new MappingContainer(nozzle_id == 0 ? m_right_marea_panel : m_left_marea_panel, "AMS-1", 4);
+3 -1
View File
@@ -848,7 +848,9 @@ void BackgroundSlicingProcess::finalize_gcode()
case CopyFileResult::SUCCESS: break; // no error
case CopyFileResult::FAIL_COPY_FILE:
throw Slic3r::ExportError(GUI::format(
_L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"),
m_export_path_on_removable_media ?
_L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%") :
_L("Copying of the temporary G-code to the output G-code failed.\nError message: %1%"),
error_message));
break;
case CopyFileResult::FAIL_FILES_DIFFERENT:
@@ -360,7 +360,7 @@ void CaliPresetCustomRangePanel::create_panel(wxWindow* parent)
int max_decimal_length;
if (i <= 1)
max_decimal_length = 3;
else if (i >= 2)
else
max_decimal_length = 4;
if (decimal_number > max_decimal_length) {
int allowed_length = number.length() - decimal_number + max_decimal_length;
+1 -1
View File
@@ -872,7 +872,7 @@ namespace Slic3r
obj->m_is_online = elem["dev_online"].get<bool>();
if (elem.contains("dev_model_name") && !elem["dev_model_name"].is_null()) {
auto printer_type = elem["dev_model_name"].get<std::string>();
for (const std::pair<std::string, std::vector<std::string>> &pair : device_subseries) {
for (const auto &pair : device_subseries) {
auto it = std::find(pair.second.begin(), pair.second.end(), printer_type);
if (it != pair.second.end())
{
+2 -1
View File
@@ -2711,7 +2711,8 @@ void ColourPicker::set_value(const boost::any& value, bool change_event)
auto field = dynamic_cast<wxColourPickerCtrl*>(window);
#ifdef __WXMSW__
wxColour clr = (clr_str.IsEmpty() || !clr.IsOk()) ? wxTransparentColour : clr_str;
const wxColour parsed_clr(clr_str);
wxColour clr = (clr_str.IsEmpty() || !parsed_clr.IsOk()) ? wxTransparentColour : parsed_clr;
field->SetColour(clr);
draw_bmp_btn(field, clr);
#else
+30 -16
View File
@@ -2190,7 +2190,7 @@ void GLCanvas3D::render(bool only_init)
// Negative coordinate means out of the window, likely because the window was deactivated.
// In that case the tooltip should be hidden.
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0. || has_mouse_capture()) { // ORCA continue to capture mouse pos mid drag
if ((m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) || has_mouse_capture()) { // ORCA continue to capture mouse pos mid drag
if (tooltip.empty())
tooltip = m_layers_editing.get_tooltip(*this);
@@ -2890,23 +2890,37 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
// BBS
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
const Print* print = m_process->fff_print();
const Print* current_print = part_plate->fff_print();
if (!need_wipe_tower && part_plate->get_extruders(true).size() < 2) continue;
if (part_plate->get_objects_on_this_plate().empty()) continue;
float brim_width = print->wipe_tower_data(filaments_count).brim_width;
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 0, false, dynamic_cast<const ConfigOptionBool*>(dconfig.option("enable_wrapping_detection"))->value);
// Body and brim from this plate's own estimate: m_process->fff_print() is the
// selected plate's, so an auto brim drew every tower with that plate's brim.
const WipeTowerFootprint footprint = part_plate->estimate_wipe_tower_footprint(full_config);
// The estimate is also the answer to whether this plate prints a tower;
// deciding it here as well only gave the two room to drift.
if (footprint.depth <= 0.) continue;
float brim_width = float(footprint.brim_width);
Vec3d wipe_tower_size(footprint.width, footprint.depth, footprint.height);
// set_default_wipe_tower_pos_for_plate doesn't rerun when painting changes the
// filament count, so redo its clamp here on every reload — unconditionally: a
// paint-triggered reload can arrive before the background process invalidates
// psWipeTower, so gating on it would skip the clamp exactly when it is needed.
{
Vec3d clamped_pos, clamped_size;
part_plate->estimate_wipe_tower_polygon(full_config, plate_id, clamped_pos, clamped_size);
if (std::abs(x - (float) clamped_pos(0)) > EPSILON || std::abs(y - (float) clamped_pos(1)) > EPSILON) {
x = (float) clamped_pos(0);
y = (float) clamped_pos(1);
ConfigOptionFloat wt_x_opt(x), wt_y_opt(y);
dynamic_cast<ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_id, 0);
dynamic_cast<ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->set_at(&wt_y_opt, plate_id, 0);
}
}
// The stored position is already clamped onto the bed, by
// set_default_wipe_tower_pos_for_plate and again on every drag.
if (!current_print->is_step_done(psWipeTower) || !current_print->wipe_tower_data().wipe_tower_mesh_data) {
// update for wipe tower position
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
@@ -9760,18 +9774,18 @@ void GLCanvas3D::_render_paint_toolbar() const
ImVec2 number_label_size = ImGui::CalcTextSize(std::to_string(i + 1).c_str());
ImGui::SetCursorPosY(cursor_y + text_offset_y);
ImGui::SetCursorPosX(spacing + i * (spacing + button_size.x) + (button_size.x - number_label_size.x) / 2);
ImGui::TextColored(text_color, std::to_string(i + 1).c_str());
ImGui::TextColored(text_color, "%s", std::to_string(i + 1).c_str());
imgui.pop_bold_font();
ImVec2 filament_first_line_label_size = ImGui::CalcTextSize(filament_text_first_line[i].c_str());
ImGui::SetCursorPosY(cursor_y + text_offset_y + number_label_size.y);
ImGui::SetCursorPosX(spacing + i * (spacing + button_size.x) + (button_size.x - filament_first_line_label_size.x) / 2);
ImGui::TextColored(text_color, filament_text_first_line[i].c_str());
ImGui::TextColored(text_color, "%s", filament_text_first_line[i].c_str());
ImVec2 filament_second_line_label_size = ImGui::CalcTextSize(filament_text_second_line[i].c_str());
ImGui::SetCursorPosY(cursor_y + text_offset_y + number_label_size.y + filament_first_line_label_size.y);
ImGui::SetCursorPosX(spacing + i * (spacing + button_size.x) + (button_size.x - filament_second_line_label_size.x) / 2);
ImGui::TextColored(text_color, filament_text_second_line[i].c_str());
ImGui::TextColored(text_color, "%s", filament_text_second_line[i].c_str());
}
if (ImGui::GetWindowWidth() == constraint_window_width) {
@@ -9998,9 +10012,9 @@ void GLCanvas3D::_render_assemble_info() const
double size1 = m_selection.get_bounding_box().size()(1);
double size2 = m_selection.get_bounding_box().size()(2);
if (!m_selection.is_empty()) {
ImGui::Text(_L("Volume:").ToUTF8()); ImGui::SameLine(caption_max);
ImGui::Text("%s", _L("Volume:").ToUTF8().data()); ImGui::SameLine(caption_max);
ImGui::Text("%.2f", size0 * size1 * size2);
ImGui::Text(_L("Size:").ToUTF8()); ImGui::SameLine(caption_max);
ImGui::Text("%s", _L("Size:").ToUTF8().data()); ImGui::SameLine(caption_max);
ImGui::Text("%.2f x %.2f x %.2f", size0, size1, size2);
}
imgui->end();
+1 -1
View File
@@ -6808,7 +6808,7 @@ bool GUI_App::check_preset_parent_available(const std::pair<std::string, std::ma
void GUI_App::add_pending_vendor_preset(const std::pair<std::string, std::map<std::string, std::string>>& preset_data)
{
Preset::Type type;
Preset::Type type = Preset::Type::TYPE_INVALID;
if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_PRINT_TYPE)
type = Preset::Type::TYPE_PRINT;
else if (preset_data.second.at(BBL_JSON_KEY_TYPE) == PRESET_IOT_PRINTER_TYPE)
+2 -2
View File
@@ -102,7 +102,7 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std
result = ReadFile(handlesrc, buff, size, &dwRead, NULL);
if (!result) {
DWORD errCode = GetLastError();
error_message = "Error: " + errCode;
error_message = "Error: " + std::to_string(errCode);
ret = FAIL_COPY_FILE;
goto __finished;
}
@@ -110,7 +110,7 @@ CopyFileResult copy_file_gui(const std::string &from, const std::string &to, std
result = WriteFile(handledst,buff,size,&dwWrite,NULL);
if (!result) {
DWORD errCode = GetLastError();
error_message = "Error: " + errCode;
error_message = "Error: " + std::to_string(errCode);
ret = FAIL_COPY_FILE;
goto __finished;
}
+1 -1
View File
@@ -342,7 +342,7 @@ bool GLGizmoBrimEars::on_mouse(const wxMouseEvent& mouse_event)
// concludes that the event was not intended for it, it should return false.
bool GLGizmoBrimEars::gizmo_event(SLAGizmoEventType action, const Vec2d &mouse_position, bool shift_down, bool alt_down, bool control_down)
{
if (action != SLAGizmoEventType::MouseWheelDown || action != SLAGizmoEventType::MouseWheelUp || action != SLAGizmoEventType::Moving) {
if (action != SLAGizmoEventType::MouseWheelDown && action != SLAGizmoEventType::MouseWheelUp && action != SLAGizmoEventType::Moving) {
apply_radius_change();
}
+1 -1
View File
@@ -122,7 +122,7 @@ bool GLGizmoFdmSupports::on_init()
{ctrl + _L("Mouse wheel"), _L("Gap area")}
};
memset(&m_print_instance, 0, sizeof(m_print_instance));
m_print_instance = PrintInstance();
return true;
}
+2 -2
View File
@@ -343,12 +343,12 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(10,20));
if (is_worker_running) { // apply or preview
// draw progress bar
std::string progress_text = GUI::format("%1%", std::to_string(progress)) + "%%";
std::string progress_text = GUI::format("%1%", std::to_string(progress)) + "%";
ImVec2 progress_size(bottom_left_width - space_size, 0.0f);
ImGui::BBLProgressBar2(progress / 100., progress_size);
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
ImGui::TextColored(ImVec4(0.42f, 0.42f, 0.42f, 1.00f), progress_text.c_str());
ImGui::TextColored(ImVec4(0.42f, 0.42f, 0.42f, 1.00f), "%s", progress_text.c_str());
ImGui::SameLine(bottom_left_width + slider_width + m_imgui->scaled(1.0f));
} else {
ImGui::Dummy(ImVec2(bottom_left_width - space_size, -1));
+1 -2
View File
@@ -265,8 +265,7 @@ arrangement::ArrangePolygon estimate_wipe_tower_info(int plate_index, std::set<i
int extruder_size = extruder_ids.size();
Vec3d wipe_tower_size, wipe_tower_pos;
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
auto arrange_poly = ppl.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(full_config, plate_index, wipe_tower_pos, wipe_tower_size, nozzle_nums, extruder_size);
auto arrange_poly = ppl.get_plate(plate_index_valid)->estimate_wipe_tower_polygon(full_config, plate_index, wipe_tower_pos, wipe_tower_size, extruder_size);
arrange_poly.bed_idx = plate_index;
return arrange_poly;
}
+1 -1
View File
@@ -71,7 +71,7 @@ MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const w
auto ip = str.find(' ', ik);
if (ip == wxString::npos) ip = str.Length();
auto v = str.Mid(ik, ip - ik);
if (k == "T:" && v.Length() == 8) {
if (strcmp(k, "T:") == 0 && v.Length() == 8) {
long h = 0,m = 0,s = 0;
v.Left(2).ToLong(&h);
v.Mid(3, 2).ToLong(&m);
+1 -1
View File
@@ -498,7 +498,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
static ImVec2 last_win_size(0.0f, 0.0f);
bool shown = true;
if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse || ImGuiWindowFlags_NoTitleBar)) {
if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar)) {
if (shown) {
ImVec2 win_size = ImGui::GetWindowSize();
if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) {
+123 -117
View File
@@ -1,5 +1,6 @@
#include <cstddef>
#include <algorithm>
#include <limits>
#include <numeric>
#include <vector>
#include <string>
@@ -20,6 +21,7 @@
#include "libslic3r/libslic3r.h"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Geometry.hpp"
@@ -1531,8 +1533,23 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
if (check_objects_empty_and_gcode3mf(plate_extruders)) {
return plate_extruders;
}
// if 3mf file
const DynamicPrintConfig& glb_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
return get_extruders(conside_custom_gcode, wxGetApp().preset_bundle->prints.get_edited_preset().config, wxGetApp().preset_bundle->project_config);
}
// The plate's filaments, with the global keys read from the given configs rather than the
// application's presets: the wipe tower estimate is also called under the CLI, which has no
// application object. get_extruders(bool) passes the edited presets; a full config serves both.
std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode, const DynamicPrintConfig& glb_config, const DynamicPrintConfig& project_config) const
{
std::vector<int> plate_extruders;
// A plate from a sliced .gcode.3mf holds no objects, so report the filaments the G-code
// used. check_objects_empty_and_gcode3mf does this for get_extruders(bool), but reaches
// the plater, which the CLI has none of; slice_filaments_info is only filled for such a plate.
if (m_model->objects.empty()) {
for (const FilamentInfo &info : slice_filaments_info)
plate_extruders.push_back(info.id + 1);
return plate_extruders;
}
int glb_support_intf_extr = glb_config.opt_int("support_interface_filament");
int glb_support_extr = glb_config.opt_int("support_filament");
int glb_outer_wall_extr = glb_config.opt_int("outer_wall_filament_id");
@@ -1549,7 +1566,9 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
glb_support |= glb_config.opt_int("raft_layers") > 0;
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
if (!contain_instance_totally(obj_idx, 0))
// Any instance on the plate counts, as PrintApply does: after an arrange, instance 0
// can sit on a different plate.
if (!contain_any_instance_totally(obj_idx))
continue;
ModelObject* mo = m_model->objects[obj_idx];
@@ -1662,7 +1681,7 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
if (conside_custom_gcode) {
//BBS
int nums_extruders = 0;
if (const ConfigOptionStrings *color_option = dynamic_cast<const ConfigOptionStrings *>(wxGetApp().preset_bundle->project_config.option("filament_colour"))) {
if (const ConfigOptionStrings *color_option = dynamic_cast<const ConfigOptionStrings *>(project_config.option("filament_colour"))) {
nums_extruders = color_option->values.size();
if (m_model->plates_custom_gcodes.find(m_plate_index) != m_model->plates_custom_gcodes.end()) {
for (auto item : m_model->plates_custom_gcodes.at(m_plate_index).gcodes) {
@@ -1681,9 +1700,8 @@ std::vector<int> PartPlate::get_extruders(bool conside_custom_gcode) const
// is never loaded into a tray, so callers (AMS mapping, filament checks) must see the
// physical filaments it resolves to instead.
{
auto& project_config = wxGetApp().preset_bundle->project_config;
auto* is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
auto* comp_strs_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
const auto* is_mixed_opt = project_config.option<ConfigOptionBools>("filament_is_mixed");
const auto* comp_strs_opt = project_config.option<ConfigOptionStrings>("filament_mixed_components");
if (is_mixed_opt && comp_strs_opt && has_any_mixed_filament(is_mixed_opt->values)) {
std::vector<unsigned int> ext_0based;
for (int e : plate_extruders)
@@ -2311,113 +2329,97 @@ bool PartPlate::check_compatible_of_nozzle_and_filament(const DynamicPrintConfig
return wipe_tower_size;
}*/
Vec3d PartPlate::estimate_wipe_tower_size(const DynamicPrintConfig & config, const double w, const double wipe_volume, int extruder_count, int plate_extruder_size, bool use_global_objects, bool enable_wrapping_detection) const
WipeTowerFootprint PartPlate::estimate_wipe_tower_footprint(const DynamicPrintConfig &config, int plate_extruder_size, bool use_global_objects) const
{
Vec3d wipe_tower_size;
double layer_height = 0.08f; // hard code layer height
double max_height = 0.f;
wipe_tower_size.setZero();
// The CLI calls this too, so the plate's filaments are derived from the passed config:
// get_extruders(bool) reads the same keys off wxGetApp()'s presets, which the CLI has none of.
// An explicit count is a floor: init-time and arrange estimates size an empty plate for that
// many generic filaments, the lowest ids not already on the plate.
std::vector<int> plate_extruders = get_extruders(true, config, config);
for (int id = 1; int(plate_extruders.size()) < plate_extruder_size; ++id)
if (std::find(plate_extruders.begin(), plate_extruders.end(), id) == plate_extruders.end())
plate_extruders.push_back(id);
// The wipe tower filament joins the tool ordering even when unused (Print::extruders), so
// validation counts it - but only where there is a tower to join, which is the
// has_wipe_tower() half of that guard.
const ConfigOption *wipe_tower_filament_opt = config.option("wipe_tower_filament");
const ConfigOption *enable_prime_tower_opt = config.option("enable_prime_tower");
const int wipe_tower_filament = wipe_tower_filament_opt != nullptr ? wipe_tower_filament_opt->getInt() : 0;
if (enable_prime_tower_opt != nullptr && enable_prime_tower_opt->getBool() && plate_extruders.size() > 1 && wipe_tower_filament > 0 &&
std::find(plate_extruders.begin(), plate_extruders.end(), wipe_tower_filament) == plate_extruders.end())
plate_extruders.push_back(wipe_tower_filament);
if (plate_extruders.empty())
return WipeTowerFootprint();
const ConfigOption* layer_height_opt = config.option("layer_height");
if (layer_height_opt)
layer_height = layer_height_opt->getFloat();
// empty plate
if (plate_extruder_size == 0)
{
std::vector<int> plate_extruders = get_extruders(true);
plate_extruder_size = plate_extruders.size();
}
if (plate_extruder_size == 0)
return wipe_tower_size;
for (int obj_idx = 0; obj_idx < m_model->objects.size(); obj_idx++) {
if (!use_global_objects && !contain_instance_totally(obj_idx, 0))
// Tallest object on this plate and the thinnest layer it is sliced at, resolved per object
// as PrintObject resolves them (override, else preset) and over this plate's objects only -
// seeding from the global value, or folding in an off-plate override, diverges from Print.
const ConfigOption *layer_height_opt = config.option("layer_height");
const double global_layer_height = layer_height_opt != nullptr ? layer_height_opt->getFloat() : 0.08;
double max_height = 0.;
double layer_height = std::numeric_limits<double>::max();
for (int obj_idx = 0; obj_idx < int(m_model->objects.size()); ++obj_idx) {
const ModelObject *object = m_model->objects[obj_idx];
if (!use_global_objects && !contain_any_instance_totally(obj_idx))
continue;
BoundingBoxf3 bbox = m_model->objects[obj_idx]->bounding_box_exact();
max_height = std::max(bbox.size().z(), max_height);
}
wipe_tower_size(2) = max_height;
//const DynamicPrintConfig &dconfig = wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto timelapse_type = config.option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
bool need_wipe_tower = (timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false) | enable_wrapping_detection;
double extra_spacing = config.option("prime_tower_infill_gap")->getFloat() / 100.;
const ConfigOptionEnum<WipeTowerWallType>* use_rib_wall_opt = config.option<ConfigOptionEnum<WipeTowerWallType>>("wipe_tower_wall_type");
bool use_rib_wall = use_rib_wall_opt ? use_rib_wall_opt->value == WipeTowerWallType::wtwRib: false;
double rib_width = config.option("wipe_tower_rib_width")->getFloat();
double depth;
double filament_change_volume=0.;
{
std::vector<double> filament_change_lengths;
auto filament_change_lengths_opt = m_print->config().option<ConfigOptionFloats>("filament_change_length");
if (filament_change_lengths_opt) filament_change_lengths = filament_change_lengths_opt->values;
double length = filament_change_lengths.empty() ? 0 : *std::max_element(filament_change_lengths.begin(), filament_change_lengths.end());
double diameter = 1.75;
std::vector<double> diameters;
auto filament_diameter_opt = m_print->config().option<ConfigOptionFloats>("filament_diameter");
if (filament_diameter_opt) diameters = filament_diameter_opt->values;
diameter = diameters.empty() ? diameter : *std::max_element(diameters.begin(), diameters.end());
filament_change_volume = length * PI * diameter * diameter / 4.;
}
double volume = wipe_volume * (extruder_count == 2 ? plate_extruder_size : (plate_extruder_size - 1));
if (extruder_count == 2) volume += filament_change_volume * (int) (plate_extruder_size / 2);
// Read from the passed plate config — m_print may not have been applied yet
// (fresh plates, CLI), in which case its PrintConfig still holds defaults.
const auto *purge_opt = config.option<ConfigOptionBool>("purge_in_prime_tower");
const auto *semm_opt = config.option<ConfigOptionBool>("single_extruder_multi_material");
const bool semm_flush = purge_opt && purge_opt->value && semm_opt && semm_opt->value;
if (semm_flush) volume = WipeTower2::estimate_semm_flush_volume(config, plate_extruder_size);
if (use_rib_wall) {
depth = std::sqrt(volume / layer_height * extra_spacing);
if (need_wipe_tower || plate_extruder_size > 1) {
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
double volume_depth = depth;
depth = std::max((double) min_wipe_tower_depth, depth);
rib_width = std::min(rib_width, depth / 2);
depth = rib_width / std::sqrt(2) + std::max(depth + m_print->config().wipe_tower_extra_rib_length.value, volume_depth);
wipe_tower_size(0) = wipe_tower_size(1) = depth;
// Per instance, to match PrintObject::size(); the union over instances differs once
// they are rotated apart. The cached convex hull has the mesh's z extent and is cheap
// enough for every scene reload.
for (int inst_idx = 0; inst_idx < int(object->instances.size()); ++inst_idx) {
if (!use_global_objects && !contain_instance_totally(obj_idx, inst_idx))
continue;
max_height = std::max(max_height, object->instance_convex_hull_bounding_box(inst_idx, true).size().z());
}
const ConfigOption *object_layer_height = object->config.option("layer_height");
layer_height = std::min(layer_height, object_layer_height != nullptr ? object_layer_height->getFloat() : global_layer_height);
}
else {
depth = volume / (layer_height * w);
// The flush volumes already hold the spacing between wipes.
if (!semm_flush) depth *= extra_spacing;
if (need_wipe_tower || depth > EPSILON) {
float min_wipe_tower_depth = WipeTower::get_limit_depth_by_height(max_height);
depth = std::max((double)min_wipe_tower_depth, depth);
}
wipe_tower_size(0) = w;
wipe_tower_size(1) = depth;
}
if (layer_height == std::numeric_limits<double>::max())
layer_height = global_layer_height;
return wipe_tower_size;
std::vector<unsigned int> filament_ids;
for (int id : plate_extruders)
if (id > 0)
filament_ids.push_back(static_cast<unsigned int>(id - 1));
return Slic3r::estimate_wipe_tower_footprint(config, resolve_wipe_tower_type(config), filament_ids, layer_height, max_height);
}
arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const DynamicPrintConfig& config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int extruder_count, int plate_extruder_size, bool use_global_objects) const
arrangement::ArrangePolygon PartPlate::estimate_wipe_tower_polygon(const DynamicPrintConfig& config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int plate_extruder_size, bool use_global_objects) const
{
float x = dynamic_cast<const ConfigOptionFloats*>(config.option("wipe_tower_x"))->get_at(plate_index);
float y = dynamic_cast<const ConfigOptionFloats*>(config.option("wipe_tower_y"))->get_at(plate_index);
float w = dynamic_cast<const ConfigOptionFloat*>(config.option("prime_tower_width"))->value;
//float a = dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value;
float v = dynamic_cast<const ConfigOptionFloat*>(config.option("prime_volume"))->value;
float tower_brim_width = dynamic_cast<const ConfigOptionFloat*>(config.option("prime_tower_brim_width"))->value;
const ConfigOptionBool * wrapping_opt = dynamic_cast<const ConfigOptionBool *>(config.option("enable_wrapping_detection"));
bool enable_wrapping = (wrapping_opt != nullptr) && wrapping_opt->value;
wt_size = estimate_wipe_tower_size(config, w, v, extruder_count, plate_extruder_size, use_global_objects, enable_wrapping);
const WipeTowerFootprint footprint = estimate_wipe_tower_footprint(config, plate_extruder_size, use_global_objects);
wt_size = Vec3d(footprint.width, footprint.depth, footprint.height);
int plate_width=m_width, plate_depth=m_depth;
w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
float w = wt_size(0); // effective width; differs from prime_tower_width when the rib wall squares the tower
float depth = wt_size(1);
float margin = WIPE_TOWER_MARGIN + tower_brim_width, wp_brim_width = 0.f;
const ConfigOption* wipe_tower_brim_width_opt = config.option("prime_tower_brim_width");
if (wipe_tower_brim_width_opt) {
wp_brim_width = wipe_tower_brim_width_opt->getFloat();
if (wp_brim_width < 0) wp_brim_width = WipeTower::get_auto_brim_by_height((float) wt_size.z());
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: wp_brim_width %1%") % wp_brim_width;
}
x = std::clamp(x, margin, (float)plate_width - w - margin - wp_brim_width);
y = std::clamp(y, margin, (float)plate_depth - depth - margin - wp_brim_width);
// Resolved brim, not the raw option: "Auto" (-1) would yield a margin of 0 and let the
// clamp put the brim off the bed. Matches set_default_wipe_tower_pos_for_plate.
float wp_brim_width = float(footprint.brim_width);
// A Type2 stabilization cone bulges past the body box like a brim does - fold its worst-axis
// bulge into the same margin.
const BoundingBox outline = get_extents(estimate_wipe_tower_first_layer_outline(config, resolve_wipe_tower_type(config), w, depth, wt_size.z()));
wp_brim_width += float(std::max({0., unscaled(outline.max.x()) - w, unscaled(outline.max.y()) - depth, -unscaled(outline.min.x()), -unscaled(outline.min.y())}));
// A position valid by WIPE_TOWER_MARGIN is the user's choice and stays untouched; an
// invalid one is re-placed with the comfort margin (falling back to the validity bounds
// on cramped plates). std::clamp is UB if lo > hi, so keep every hi >= lo.
const float margin = WIPE_TOWER_MARGIN + wp_brim_width;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("arrange wipe_tower: wp_brim_width %1%") % wp_brim_width;
const float x_hi = std::max(margin, (float) plate_width - w - margin);
const float y_hi = std::max(margin, (float) plate_depth - depth - margin);
const float margin_c = (float) WIPE_TOWER_AUTO_MARGIN + wp_brim_width;
float x_lo_c = margin_c, x_hi_c = (float) plate_width - w - margin_c;
if (x_lo_c > x_hi_c) { x_lo_c = margin; x_hi_c = x_hi; }
float y_lo_c = margin_c, y_hi_c = (float) plate_depth - depth - margin_c;
if (y_lo_c > y_hi_c) { y_lo_c = margin; y_hi_c = y_hi; }
// Drag clamps reach this limit through the volume's bounding box (post-slice: the real
// mesh, a couple of mm inside this reserved estimate), so a drop can land slightly out
// of bounds — snap it onto the bound; only far-out positions get the comfort re-place.
const float tol = 5.f;
if (x < margin - tol || x > x_hi + tol) x = std::clamp(x, x_lo_c, x_hi_c);
else x = std::clamp(x, margin, x_hi);
if (y < margin - tol || y > y_hi + tol) y = std::clamp(y, y_lo_c, y_hi_c);
else y = std::clamp(y, margin, y_hi);
wt_pos(0) = x;
wt_pos(1) = y;
wt_pos(2) = 0.f;
@@ -2755,6 +2757,20 @@ bool PartPlate::contain_instance_totally(int obj_id, int instance_id) const
return result;
}
//judge whether any of the object's instances is totally included in plate or not
bool PartPlate::contain_any_instance_totally(int obj_id) const
{
if (obj_id < 0 || obj_id >= int(m_model->objects.size()))
return false;
const ModelObject *object = m_model->objects[obj_id];
for (int instance_id = 0; instance_id < int(object->instances.size()); ++instance_id)
if (contain_instance_totally(obj_id, instance_id))
return true;
return false;
}
//check whether instance is outside the plate or not
bool PartPlate::check_outside(int obj_id, int instance_id, BoundingBoxf3* bounding_box)
{
@@ -4488,26 +4504,16 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
}
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
float w = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_tower_width"))->value;
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
bool enable_wrapping = false;
const ConfigOptionBool *wrapping_opt = dynamic_cast<const ConfigOptionBool *>(full_config.option("enable_wrapping_detection"));
if (wrapping_opt) enable_wrapping = wrapping_opt->value;
int nozzle_nums = wxGetApp().preset_bundle->get_printer_extruder_count();
Vec3d wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, init_pos ? 2 : 0, false, enable_wrapping);
WipeTowerFootprint footprint = part_plate->estimate_wipe_tower_footprint(full_config, init_pos ? 2 : 0);
if (!init_pos && (is_approx(wipe_tower_size(0), 0.0) || is_approx(wipe_tower_size(1), 0.0))) {
wipe_tower_size = part_plate->estimate_wipe_tower_size(full_config, w, v, nozzle_nums, 2, false, enable_wrapping);
if (!init_pos && (is_approx(footprint.width, 0.0) || is_approx(footprint.depth, 0.0))) {
footprint = part_plate->estimate_wipe_tower_footprint(full_config, 2);
}
Vec3d wipe_tower_size(footprint.width, footprint.depth, footprint.height);
// Compute brim-aware margin: brim extends outward from tower position
float brim_width = 0.f;
const ConfigOptionFloat *brim_opt = full_config.option<ConfigOptionFloat>("prime_tower_brim_width");
if (brim_opt) {
brim_width = brim_opt->value;
if (brim_width < 0) brim_width = WipeTower::get_auto_brim_by_height((float) wipe_tower_size.z());
}
const float margin = WIPE_TOWER_MARGIN + brim_width;
// Brim-aware margin: the brim extends outward from the tower position.
const float brim_width = float(footprint.brim_width);
const float margin = WIPE_TOWER_AUTO_MARGIN + brim_width;
// clamp wipe tower position within plate boundaries
{
+10 -2
View File
@@ -11,6 +11,7 @@
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/Format/bbs_3mf.hpp"
#include "libslic3r/Slicing.hpp"
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
#include "libslic3r/Arrange.hpp"
#include "Plater.hpp"
#include "libslic3r/Model.hpp"
@@ -339,11 +340,16 @@ public:
Vec3d get_origin() { return m_origin; }
//Vec3d calculate_wipe_tower_size(const DynamicPrintConfig &config, const double w, const double wipe_volume, int plate_extruder_size = 0, bool use_global_objects = false) const;
Vec3d estimate_wipe_tower_size(const DynamicPrintConfig & config, const double w, const double wipe_volume, int extruder_count = 1, int plate_extruder_size = 0, bool use_global_objects = false, bool enable_wrapping_detection = false) const;
arrangement::ArrangePolygon estimate_wipe_tower_polygon(const DynamicPrintConfig & config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int extruder_count = 1, int plate_extruder_size = 0, bool use_global_objects = false) const;
// plate_extruder_size: a floor on the filaments purged on the plate; its own are always
// counted, so 0 sizes for exactly those.
// use_global_objects skips the containment test, which the CLI needs before objects are
// assigned to plates - the layer height is then the project's thinnest, which over-reserves.
WipeTowerFootprint estimate_wipe_tower_footprint(const DynamicPrintConfig & config, int plate_extruder_size = 0, bool use_global_objects = false) const;
arrangement::ArrangePolygon estimate_wipe_tower_polygon(const DynamicPrintConfig & config, int plate_index, Vec3d& wt_pos, Vec3d& wt_size, int plate_extruder_size = 0, bool use_global_objects = false) const;
bool check_objects_empty_and_gcode3mf(std::vector<int> &result) const;
// get used filaments from config, 1 based idx
std::vector<int> get_extruders(bool conside_custom_gcode = false) const;
std::vector<int> get_extruders(bool conside_custom_gcode, const DynamicPrintConfig& glb_config, const DynamicPrintConfig& project_config) const;
std::vector<int> get_extruders_under_cli(bool conside_custom_gcode, DynamicPrintConfig& full_config) const;
std::vector<int> get_extruders_without_support(bool conside_custom_gcode = false) const;
// get used filaments from gcode result, 1 based idx
@@ -366,6 +372,8 @@ public:
bool contain_instance_totally(ModelObject* object, int instance_id) const;
//judge whether instance is totally included in plate or not
bool contain_instance_totally(int obj_id, int instance_id) const;
//judge whether any of the object's instances is totally included in plate or not
bool contain_any_instance_totally(int obj_id) const;
//judge whether the plate's origin is at the left of instance or not
bool is_left_top_of(int obj_id, int instance_id);
+2 -2
View File
@@ -3667,8 +3667,8 @@ void Sidebar::update_presets(Preset::Type preset_type)
// so extruders without an explicit sub-nozzle count never offer Hybrid. A nullable-int
// nil is INT_MAX (> 1) and would otherwise falsely pass the gate, so exclude it too.
if (boost::algorithm::contains(extruder_variants->values[index], type + " " + nozzle_volumes_def->enum_labels[i]) ||
extruder_max_nozzle_count->get_at(index) > 1 && extruder_max_nozzle_count->get_at(index) != ConfigOptionIntsNullable::nil_value() &&
nozzle_volumes_def->enum_keys_map->at(nozzle_volumes_def->enum_values[i]) == nvtHybrid) {
(extruder_max_nozzle_count->get_at(index) > 1 && extruder_max_nozzle_count->get_at(index) != ConfigOptionIntsNullable::nil_value() &&
nozzle_volumes_def->enum_keys_map->at(nozzle_volumes_def->enum_values[i]) == nvtHybrid)) {
if (nozzle_volumes_def->enum_keys_map->at(nozzle_volumes_def->enum_values[i]) == NozzleVolumeType::nvtHighFlow &&(diameter == "0.2" ||
is_skip_high_flow_printer(printer_model)))
continue;
+1 -1
View File
@@ -3073,7 +3073,7 @@ static bool _HasExt(const std::vector<FilamentInfo> &ams_mapping_result) {
};
for (const auto &info : ams_mapping_result) {
if (info.ams_id == VIRTUAL_AMS_MAIN_ID_STR || info.ams_id == VIRTUAL_AMS_DEPUTY_ID_STR && !info.ams_id.empty()) {
if (info.ams_id == VIRTUAL_AMS_MAIN_ID_STR || (info.ams_id == VIRTUAL_AMS_DEPUTY_ID_STR && !info.ams_id.empty())) {
return true;
}
}
+3 -4
View File
@@ -1273,10 +1273,9 @@ void Selection::translate(const Vec3d &displacement, TransformationType transfor
const Polygons bed_polys{wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->get_shared_printable_polygon()};
Vec3d tower_origin = m_cache.volumes_data[i].get_volume_position();
Vec3d actual_displacement = displacement;
bool show_read_wipe_tower = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx)->fff_print()->is_step_done(psWipeTower);
float brim_width = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("prime_tower_brim_width");
const double margin = show_read_wipe_tower ? WIPE_TOWER_MARGIN : brim_width + 0.5; // 0.5 is the line width of wipe tower
// Both preview volumes carry the brim in their bounding box, and the release
// clamp holds it WIPE_TOWER_MARGIN inside — same margin, so drops don't snap.
const double margin = WIPE_TOWER_MARGIN;
actual_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() *
m_cache.volumes_data[i].get_instance_mirror_matrix())
+2 -2
View File
@@ -678,7 +678,7 @@ SyncAmsInfoDialog::SyncAmsInfoDialog(wxWindow *parent, SyncInfo &info) :
wxBoxSizer *loading_Sizer = new wxBoxSizer(wxHORIZONTAL);
m_gif_ctrl = new wxAnimationCtrl(m_loading_page, wxID_ANY, wxNullAnimation, wxDefaultPosition, wxDefaultSize, wxAC_DEFAULT_STYLE);
auto gif_path = Slic3r::var("loading.gif").c_str();
const wxString gif_path = from_u8(Slic3r::var("loading.gif"));
if (m_gif_ctrl->LoadFile(gif_path)){
m_gif_ctrl->SetSize(m_gif_ctrl->GetAnimation().GetSize());
m_gif_ctrl->Play();
@@ -1547,7 +1547,7 @@ bool SyncAmsInfoDialog::is_nozzle_type_match(DevExtderSystem data, wxString &err
auto sai_nz_pt = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle);
if (target_machine_nozzle_id == DEPUTY_EXTRUDER_ID) {
pos = _L(DevPrinterConfigUtil::get_toolhead_display_name(sai_nz_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase));
} else if ((target_machine_nozzle_id == MAIN_EXTRUDER_ID)) {
} else if (target_machine_nozzle_id == MAIN_EXTRUDER_ID) {
pos = _L(DevPrinterConfigUtil::get_toolhead_display_name(sai_nz_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase));
}
+2 -2
View File
@@ -1281,8 +1281,8 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig&
}
auto opt_vector = dynamic_cast<const ConfigOptionVectorBase *>(option);
if (option->is_scalar() && config.option(opt_key)->is_nil() ||
option->is_vector() && opt_vector && opt_idx >= 0 && opt_idx < opt_vector->size() && opt_vector->is_nil(opt_idx))
if ((option->is_scalar() && config.option(opt_key)->is_nil()) ||
(option->is_vector() && opt_vector && opt_idx >= 0 && opt_idx < opt_vector->size() && opt_vector->is_nil(opt_idx)))
return _L("N/A");
wxString out;
+4 -4
View File
@@ -254,10 +254,8 @@ int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_o
bool cb_cancel = false;
if (self->progressfn) {
double speed;
double speed = 0.;
curl_easy_getinfo(self->curl, CURLINFO_SPEED_UPLOAD, &speed);
if (speed > 0.01)
speed = speed;
Progress progress(dltotal, dlnow, ultotal, ulnow, self->buffer, speed);
self->progressfn(progress, cb_cancel);
}
@@ -323,8 +321,10 @@ void Http::priv::form_add_file(const char *name, const fs::path &path, const cha
// We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
// and so we use CURLFORM_STREAM with boost ifstream to read the file.
std::string filename_str;
if (filename == nullptr) {
filename = path.string().c_str();
filename_str = path.string();
filename = filename_str.c_str();
}
form_files.emplace_back(path, offset, length);
+1 -1
View File
@@ -39,7 +39,7 @@ std::string find_closest_color_preset_by_vendor_and_type(const PresetCollection&
std::string p_color = p.config.opt_string("default_filament_colour", 0u);
unsigned int p_color_value;
if (!p_color.empty()) {
unsigned int hash_pos = p_color.find("#");
size_t hash_pos = p_color.find("#");
p_color_value = std::stoul(p_color.substr(hash_pos != std::string::npos ? hash_pos + 1 : 0), nullptr, 16);
} else {
// Default to black if no color specified in profile. Assume other profiles might be a closer color match.