ENH: new wiping dialog

1.Enhance performance and ui display

jira: None

Signed-off-by: xun.zhang <xun.zhang@bambulab.com>
Change-Id: If1cde4d1a17437ee5485dd51adff4c3444774ffd
(cherry picked from commit 9c28c320d680d69b16d4895fda1bdcab9ad5b18d)
This commit is contained in:
xun.zhang 2025-02-25 15:26:10 +08:00 committed by Noisyfox
parent 6895cbcc58
commit 5c406ef00a
5 changed files with 881 additions and 778 deletions

View file

@ -0,0 +1,645 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
html, body {
margin: 0px;
padding: 0px;
background: #f5f5f5;
font-family: sans-serif;
}
.container {
background: #fff;
padding: 20px;
padding-bottom: 10px;
border: 1px solid #ccc;
max-width:fit-content(1000px);
margin: 0 auto;
}
.tip-panel {
background: #eeeeee;
padding: 10px;
margin-bottom: 10px;
font-size: 12px;
}
.scroll-container {
max-width: 100%;
max-height: 500px;
overflow: auto;
border: 1px solid #ccc;
position: relative;
margin: 0px;
}
table {
border-collapse: collapse;
width: 100%;
}
th, td {
border: 1px solid #ccc;
text-align: center;
padding: 2px;
position: relative;
width: 40px;
height: 25px;
}
thead th {
position: sticky;
top: 0;
background: #eee;
z-index: 10;
}
tbody th {
position: sticky;
left: 0;
background: #eee;
z-index: 9;
}
tbody tr:nth-child(even) {
background: #ffffff;
}
tbody tr:nth-child(odd) {
background: #eeeeee;
}
tbody td:first-child,
thead th:first-child {
position: sticky;
top: 0;
left: 0;
background: #eee;
z-index: 11;
}
.icon-button {
display: inline-block;
width: 16px;
height: 16px;
border: 1px solid #999;
text-align: center;
line-height: 15px;
color: #fff;
font-weight: bold;
font-size: 12px;
border-radius: 4px;
cursor: default;
user-select: none;
padding: 1px;
}
input[type="number"].table-input {
width: 30px;
height: 20px;
text-align: center;
-moz-appearance: textfield;
appearance: textfield;
border: none;
background-color: inherit;
}
input[type="number"].table-input::-webkit-inner-spin-button,
input[type="number"].table-input::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.warning-text {
color: red;
font-size: 12px;
}
.normal-text {
color: black;
font-size: 12px;
}
.icon-gap {
width: 10px;
display: inline-block;
}
.btn {
display: inline-block;
padding: 6px 9px;
border: 2px solid transparent;
border-radius: 12px;
font-size: 12px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.btn-ok {
color: #fff;
background-color: #21a452;
}
.btn-ok:hover {
background-color: #3dcb73;
}
.btn-cancel {
color: black;
background-color: white;
border: 1px solid black;
}
.btn-cancel:hover {
background-color: #eeeeee;
}
select {
padding: 6px 9px;
border: 1px solid #dbdbdb;
border-radius: 3px;
font-size: 12px;
cursor: pointer;
appearance: auto;
background-color: white;
color: black;
}
select option {
background-color: white;
color: black;
}
select:hover,
select:focus {
outline: none;
border-color: #00ae42;
background-color: #edfaf2;
}
.button-container {
display: flex;
justify-content: center;
gap: 10px;
margin: 10px;
}
/* 暗色模式样式 */
body.dark-mode {
background-color: #2d2d31;
color: #e0e0e0;
}
body.dark-mode .container {
background: inherit;
border-color: #2d2d31;
}
body.dark-mode .tip-panel {
background: #4c4c55;
color: #ccc;
}
body.dark-mode .scroll-container {
border-color: #4c4c55;
}
body.dark-mode table {
background: inherit;
border-color: #4c4c55;
}
body.dark-mode th, body.dark-mode td {
background: inherit;
border-color: #555;
color: #e0e0e0;
}
body.dark-mode thead th{
position: sticky;
z-index: 10
}
body.dark-mode tbody th{
position: sticky;
z-index: 9
}
body.dark-mode tbody tr:nth-child(even) {
background: #2d2d31;
}
body.dark-mode tbody tr:nth-child(odd) {
background: #4c4c55;
}
body.dark-mode tbody td:first-child,
body.dark-mode thead th:first-child {
position: sticky;
z-index: 11;
}
body.dark-mode .btn-ok {
background-color: #3a8f44;
}
body.dark-mode .btn-dark {
background-color: #34495e;
}
body.dark-mode select {
background-color: #2d2d31;
color: white;
border-color: #4c4c55;
}
body.dark-mode .warning-text {
color: #f44336;
}
body.dark-mode .normal-text {
color: #e0e0e0;
}
body.dark-mode .icon-button {
background-color: #4c4c55;
}
</style>
</head>
<body>
<div class="container">
<div class="tip-panel" id="auto_flush_tip">
Studio would re-calculate your flushing volumes every time the filament
color changed or filaments changed. You could disable the auto-calculate
in Bambu Studio &gt; Preferences.
</div>
<div style="margin-bottom: 10px; ">
<button
class="btn btn-ok"
onclick="calcFlushingVolumes()"
id="calc_btn"
>
Re-Calculate
</button>
<select
id="extruders"
onchange="handleExtruderSelect(document.getElementById('extruders').value)"
>
<option value="left" id="extruder_label_0">Left Nozzle</option>
<option value="right" id="extruder_label_1">Right Nozzle</option>
</select>
</div>
<div class="scroll-container">
<table id="flushTable">
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div style="margin-top: 10px;">
<div id="volume_desp_panel" class="normal-text">
Flushing volume (mm³) for each filament pair.
</div>
<div
id="volume_range_panel"
class="normal-text"
style="margin-top: 5px;"
>
Suggestion: Flushing Volume in range [50, 999]
</div>
<div style="margin-top: 8px;">
<label
for="multiplierInput"
id="multiplier_label"
style="font-size: 12px;"
>Multiplier</label
>
<input
type="number"
step="0.1"
id="multiplierInput"
style="width:70px;"
value="1.00"
oninput="onMultiplierChange()"
/>
</div>
<div
style="margin-top: 5px; margin-bottom: 5px; color: #666; font-size: 12px;"
id="multiplier_range_panel"
>
The multiplier should be in range [0.50, 3.00].
</div>
</div>
<div class="button-container" style="padding: 0px; margin: 0px;">
<button class="btn btn-ok" id="ok_btn" style="width: 60px; height: 30px; font-size: 12px; text-align: center;" onclick="storeData()">
Save
</button>
<button class="btn btn-cancel" id="cancel_btn" style="width: 60px; height: 30px; font-size: 12px; text-align: center;" onclick="quit()">
Cancel
</button>
</div>
</div>
<script>
let m_number_of_filaments // 材料数量
let m_number_of_extruders // 喷嘴数量
let m_colours // 颜色
let m_display_matrix // 显示的矩阵
let m_raw_matrix // 原始数据
let m_flush_multipiers // 冲刷系数
let m_cell_inputs = []; //显示的内容
let m_curr_extruder_id = 0
let m_min_flush_volumes = []
let m_max_flush_volumes = []
let m_min_flush_multiplier = 0.50
let m_max_flush_multiplier = 3
function storeData() {
var data = JSON.stringify({
msg: 'storeData',
number_of_extruders: m_number_of_extruders,
raw_matrix: m_raw_matrix,
flush_multiplier: m_flush_multipiers
})
window.wipingDialog.postMessage(data);
}
function quit() {
var data = JSON.stringify({
msg: 'quit'
})
window.wipingDialog.postMessage(data);
}
function calcFlushingVolumes() {
var data = JSON.stringify({
msg: 'updateMatrix',
extruder_id: m_curr_extruder_id
})
window.wipingDialog.postMessage(data);
}
function updateTable(dataMatrix, extruder_id) {
for (let i = 0; i < m_number_of_filaments; i++) {
for (let j = 0; j < m_number_of_filaments; j++) {
var newValue
var index = m_number_of_filaments * i + j
newValue = (i == j ? 0 : rawToDislay(dataMatrix[index] ,m_flush_multipiers[extruder_id]))
newValue = limitDisplayVal(newValue,extruder_id)
m_cell_inputs[i][j].value = newValue;
m_display_matrix[index] = newValue;
m_raw_matrix[extruder_id][index] = dataMatrix[index]
}
}
updateWarningTexts();
}
// const virtual_data = {
// filament_colors: [
// '#123456', '#7890AB', '#CDEF01', '#F1A2B3', '#4D8B72', '#D9E1F2', '#B76C8C', '#A9C3D3',
// '#FF5733', '#33FF57', '#5733FF', '#FF33FF', '#FFFF33', '#33FFFF', '#FF5733', '#3333FF'
// ],
// extruder_num: 2,
// flush_volume_matrixs: [
// Array.from({ length: 256 }, () => Math.floor(Math.random() * 1000)),
// Array.from({ length: 256 }, () => Math.floor(Math.random() * 1000))
// ],
// flush_multiplier:[1,1]
// }
window.addEventListener("DOMContentLoaded",function(){
var data = JSON.stringify({
msg: 'init',
})
window.wipingDialog.postMessage(data);
});
function buildText(data) {
document.getElementById('volume_desp_panel').innerText = data.volume_desp_panel
document.getElementById('volume_range_panel').innerText = data.volume_range_panel
document.getElementById('multiplier_range_panel').innerText = data.multiplier_range_panel
document.getElementById('auto_flush_tip').innerText = data.auto_flush_tip
document.getElementById('calc_btn').innerText = data.calc_btn_panel
document.getElementById('extruder_label_0').innerText = data.extruder_label_0
document.getElementById('extruder_label_1').innerText = data.extruder_label_1
document.getElementById('multiplier_label').innerText = data.multiplier_label
document.getElementById('ok_btn').innerText = data.ok_btn_label
document.getElementById('cancel_btn').innerText = data.cancel_btn_label
updateVolumeRange(m_min_flush_volumes[m_curr_extruder_id],m_max_flush_volumes[m_curr_extruder_id])
updateMultiplierRange(m_min_flush_multiplier,m_max_flush_multiplier)
}
// 计算亮度
function getLuminance(color) {
const hex = color.replace(/^#/, '');
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
}
function updateVolumeRange(min_volume,max_volume){
const panel = document.getElementById('volume_range_panel');
panel.innerText = panel.innerText.replace(/\[.*\]/, `[${min_volume}, ${max_volume}]`);
}
function updateMultiplierRange(min_multiplier,max_multiplier){
const panel = document.getElementById('multiplier_range_panel');
panel.innerText = panel.innerText.replace(/\[.*\]/, `[${min_multiplier}, ${max_multiplier}]`);
}
function updateFlushMultiplier(extruder_id){
document.getElementById("multiplierInput").value = m_flush_multipiers[extruder_id]
}
function rawToDislay(val, ratio){
return Math.round(val * ratio)
}
function displayToRaw(val, ratio){
if(ratio==0)
return 0
else
return Math.round(val / ratio)
}
function limitDisplayVal(val,extruder_id){
if (isNaN(val))
return 0
if (val < 0)
return 0
if (val > m_max_flush_volumes[extruder_id])
return m_max_flush_volumes[extruder_id];
return val
}
function buildTable(data) {
m_colours = data.filament_colors
m_number_of_extruders = data.extruder_num
m_number_of_filaments = data.filament_colors.length
m_display_matrix = data.flush_volume_matrixs[0].slice();
m_raw_matrix = data.flush_volume_matrixs.map(function(arr) {
return arr.slice();
});
m_flush_multipiers = data.flush_multiplier.slice()
m_max_flush_volumes = data.max_flush_volumes
m_min_flush_volumes = data.min_flush_volumes
m_min_flush_multiplier = data.min_flush_multiplier
m_max_flush_multiplier = data.max_flush_multiplier
if(data.is_dark_mode == true)
document.body.classList.add('dark-mode');
updateFlushMultiplier(m_curr_extruder_id)
const selectElement = document.getElementById('extruders');
if (m_number_of_extruders > 1) {
selectElement.style.display = 'inline-block';
} else {
selectElement.style.display = 'none';
}
var thead = document.querySelector("#flushTable thead tr");
var tbody = document.querySelector("#flushTable tbody");
thead.innerHTML = "";
tbody.innerHTML = "";
for (let j = 0; j < m_number_of_filaments; j++) {
if (j == 0) {
var tag = document.createElement("th")
tag.innerHTML = "<div style='font-size:12px'>from/to</div>"
thead.appendChild(tag)
}
let th = document.createElement("th");
const luminance = getLuminance(m_colours[j]);
const textColor = luminance > 0.5 ? "black" : "white";
th.innerHTML = `<div class="icon-button" style="font-size:10px;background:${m_colours[j]}; color:${textColor};">${j + 1}</div>`;
thead.appendChild(th);
}
for (let i = 0; i < m_number_of_filaments; i++) {
let tr = document.createElement("tr");
let rowHeader = document.createElement("th");
const luminance = getLuminance(m_colours[i]);
const textColor = luminance > 0.5 ? "black" : "white";
rowHeader.innerHTML = `<div class="icon-button" style="font-size:10px; background:${m_colours[i]};color:${textColor};">${i + 1}</div>`;
tr.appendChild(rowHeader);
m_cell_inputs[i] = [];
for (let j = 0; j < m_number_of_filaments; j++) {
let td = document.createElement("td");
td.style.overflow = "hidden";
td.style.textOverflow = "ellipsis";
let displayVal = rawToDislay(m_raw_matrix[m_curr_extruder_id][m_number_of_filaments * i + j], m_flush_multipiers[m_curr_extruder_id])
displayVal = limitDisplayVal(displayVal, m_curr_extruder_id)
m_display_matrix[m_number_of_filaments*i + j] = displayVal
let readonly = (i === j);
let input = document.createElement("input");
input.className = "table-input"
input.type = "number";
input.value = readonly ? 0 : displayVal;
if (readonly) {
input.readOnly = true;
}
input.addEventListener("input", (e) => onCellInput(i, j, e));
m_cell_inputs[i][j] = input;
td.appendChild(input);
tr.appendChild(td);
}
tbody.appendChild(tr);
}
updateWarningTexts();
}
function onCellInput(i, j, event) {
const input = event.target;
let val = parseInt(input.value, 10);
val = limitDisplayVal(val, m_curr_extruder_id);
input.value = val;
if (i !== j) {
var index = m_number_of_filaments*i+j
m_raw_matrix[m_curr_extruder_id][index] = displayToRaw(val, m_flush_multipiers[m_curr_extruder_id])
m_display_matrix[index] = val;
}
updateWarningTexts();
}
function handleExtruderSelect(extruder) {
m_curr_extruder_id = extruder == 'left' ? 0 : 1
updateTable(m_raw_matrix[m_curr_extruder_id], m_curr_extruder_id)
updateVolumeRange(m_min_flush_volumes[m_curr_extruder_id],m_max_flush_volumes[m_curr_extruder_id])
updateFlushMultiplier(m_min_flush_multiplier[m_curr_extruder_id],m_max_flush_multiplier[m_curr_extruder_id])
updateFlushMultiplier(m_curr_extruder_id)
}
function onMultiplierChange() {
let val = parseFloat(document.getElementById("multiplierInput").value);
if (isNaN(val)) val = 1.0;
if(val<m_min_flush_multiplier){
val = m_min_flush_multiplier
document.getElementById("multiplierInput").value = m_min_flush_multiplier;
}
else if(val>m_max_flush_multiplier){
val = m_max_flush_multiplier
document.getElementById("multiplierInput").value = m_max_flush_multiplier;
}
m_flush_multipiers[m_curr_extruder_id] = val;
for (let i = 0; i < m_number_of_filaments; i++) {
for (let j = 0; j < m_number_of_filaments; j++) {
if (i === j) continue;
var index = i * m_number_of_filaments + j;
let displayVal = rawToDislay(m_raw_matrix[m_curr_extruder_id][index] ,m_flush_multipiers[m_curr_extruder_id])
displayVal = limitDisplayVal(displayVal, m_curr_extruder_id)
m_cell_inputs[i][j].value = displayVal;
m_display_matrix[index] = displayVal;
}
}
updateWarningTexts();
}
function updateWarningTexts() {
let hasException = false;
for (let i = 0; i < m_number_of_filaments; i++) {
for (let j = 0; j < m_number_of_filaments; j++) {
if (i === j) continue;
const input = m_cell_inputs[i][j];
let val = parseInt(input.value, 10);
if (isNaN(val)) val = 0;
if (val < m_min_flush_volumes[m_curr_extruder_id] || val > m_max_flush_volumes[m_curr_extruder_id]) {
input.style.color = "red";
hasException = true;
} else {
input.style.color = "black";
}
}
}
const rangeLabel = document.getElementById("volume_range_panel");
if (hasException) {
rangeLabel.classList.remove("normal-text");
rangeLabel.classList.add("warning-text");
} else {
rangeLabel.classList.remove("warning-text");
rangeLabel.classList.add("normal-text");
}
}
</script>
</body>
</html>

View file

@ -1719,22 +1719,13 @@ Sidebar::Sidebar(Plater *parent)
extra_flush_volumes[nozzle_id] = get_min_flush_volumes(full_config, nozzle_id);
}
std::vector<float> flush_multiplier;
ConfigOptionFloats *flush_multi_opt = project_config.option<ConfigOptionFloats>("flush_multiplier");
if (flush_multi_opt)
flush_multiplier = cast<float>(flush_multi_opt->values);
flush_multiplier.resize(nozzle_nums, 1.0f);
WipingDialog dlg(parent, cast<float>(init_matrix), cast<float>(init_extruders), extruder_colours, extra_flush_volumes, flush_multiplier, nozzle_nums);
if (dlg.ShowModal() == wxID_OK) {
std::vector<float> matrix = dlg.get_matrix();
std::vector<float> extruders = dlg.get_extruders();
std::vector<float> flush_multipliers = dlg.get_flush_multiplier_vector();
WipingDialog dlg(static_cast<wxWindow *>(wxGetApp().mainframe),extra_flush_volumes);
dlg.ShowModal();
if (dlg.GetSubmitFlag()) {
auto matrix = dlg.GetFlattenMatrix();
auto flush_multipliers = dlg.GetMultipliers();
(project_config.option<ConfigOptionFloats>("flush_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
(project_config.option<ConfigOptionFloats>("flush_volumes_vector"))->values = std::vector<double>(extruders.begin(), extruders.end());
(project_config.option<ConfigOptionFloats>("flush_multiplier"))->values = std::vector<double>(flush_multipliers.begin(), flush_multipliers.end());
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
wxGetApp().plater()->update_project_dirty_from_presets();

View file

@ -7,7 +7,6 @@
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "WipeTowerDialog.hpp"
#include "Search.hpp"
#include "OG_CustomCtrl.hpp"
@ -49,6 +48,7 @@
#include "Search.hpp"
#include "BedShapeDialog.hpp"
#include "libslic3r/GCode/Thumbnails.hpp"
#include "WipeTowerDialog.hpp"
#include "BedShapeDialog.hpp"
// #include "BonjourDialog.hpp"

View file

@ -1,19 +1,15 @@
#include <algorithm>
#include <sstream>
#include <wx/sizer.h>
#include "libslic3r/FlushVolCalc.hpp"
#include "WipeTowerDialog.hpp"
#include "BitmapCache.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "MsgDialog.hpp"
#include "format.hpp"
#include "libslic3r/Color.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/StaticLine.hpp"
#include "Widgets/DialogButtons.hpp"
#include "slic3r/Utils/ColorSpaceConvert.hpp"
#include "MainFrame.hpp"
#include "libslic3r/Config.hpp"
#include "Widgets/Label.hpp"
@ -21,28 +17,6 @@ using namespace Slic3r;
using namespace Slic3r::GUI;
int scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit() / 10; }
int ITEM_WIDTH() { return scale(30); }
static const wxColour g_text_color = wxColour(107, 107, 107, 255);
#undef ICON_SIZE
#define ICON_SIZE wxSize(FromDIP(16), FromDIP(16))
#define TABLE_BORDER FromDIP(28)
#define HEADER_VERT_PADDING FromDIP(12)
#define HEADER_BEG_PADDING FromDIP(30)
#define ICON_GAP FromDIP(44)
#define HEADER_END_PADDING FromDIP(24)
#define ROW_VERT_PADDING FromDIP(6)
#define ROW_BEG_PADDING FromDIP(20)
#define EDIT_BOXES_GAP FromDIP(30)
#define ROW_END_PADDING FromDIP(21)
#define BTN_SIZE wxSize(FromDIP(58), FromDIP(24))
#define BTN_GAP FromDIP(20)
#define TEXT_BEG_PADDING FromDIP(30)
#define MAX_FLUSH_VALUE 2147483520
#define MIN_WIPING_DIALOG_WIDTH FromDIP(300)
#define TIP_MESSAGES_PADDING FromDIP(8)
static void update_ui(wxWindow* window)
{
@ -220,530 +194,212 @@ std::string RammingPanel::get_parameters()
return stream.str();
}
#ifdef _WIN32
#define style wxSP_ARROW_KEYS | wxBORDER_SIMPLE
#else
#define style wxSP_ARROW_KEYS
#endif
static const float g_min_flush_multiplier = 0.f;
static const float g_max_flush_multiplier = 3.f;
wxBoxSizer* WipingDialog::create_btn_sizer(long flags)
{
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
btn_sizer->AddStretchSpacer();
if (flags & wxRESET) {
Button *calc_btn = new Button(this, _L("Auto-Calc"));
calc_btn->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
calc_btn->SetFocus();
calc_btn->SetId(wxID_RESET);
btn_sizer->Add(calc_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(ButtonProps::ChoiceButtonGap()));
m_button_list[wxRESET] = calc_btn;
static std::vector<float> MatrixFlatten(const WipingDialog::VolumeMatrix& matrix) {
std::vector<float> vec;
for (auto row_elems : matrix) {
for (auto elem : row_elems)
vec.emplace_back(elem);
}
if (flags & wxOK) {
Button* ok_btn = new Button(this, _L("OK"));
ok_btn->SetStyle(ButtonStyle::Confirm, ButtonType::Choice);
ok_btn->SetFocus();
ok_btn->SetId(wxID_OK);
btn_sizer->Add(ok_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(ButtonProps::ChoiceButtonGap()));
m_button_list[wxOK] = ok_btn;
}
if (flags & wxCANCEL) {
Button* cancel_btn = new Button(this, _L("Cancel"));
cancel_btn->SetStyle(ButtonStyle::Regular, ButtonType::Choice);
cancel_btn->SetId(wxID_CANCEL);
btn_sizer->Add(cancel_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(ButtonProps::ChoiceButtonGap()));
m_button_list[wxCANCEL] = cancel_btn;
}
return btn_sizer;
return vec;
}
wxBoxSizer* WipingPanel::create_calc_btn_sizer(wxWindow* parent) {
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
wxString WipingDialog::BuildTableObjStr()
{
auto full_config = wxGetApp().preset_bundle->full_config();
auto filament_colors = full_config.option<ConfigOptionStrings>("filament_colour")->values;
auto flush_multiplier = full_config.option<ConfigOptionFloats>("flush_multiplier")->values;
int nozzle_num = full_config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values.size();
auto raw_matrix_data = full_config.option<ConfigOptionFloats>("flush_volumes_matrix")->values;
std::vector<std::vector<double>> flush_matrixs;
for (int idx = 0; idx < nozzle_num; ++idx) {
flush_matrixs.emplace_back(get_flush_volumes_matrix(raw_matrix_data, idx, nozzle_num));
}
flush_multiplier.resize(nozzle_num, 1);
Button* calc_btn = new Button(parent, _L("Re-calculate"));
calc_btn->SetStyle(ButtonStyle::Confirm, ButtonType::Window);
calc_btn->SetFocus();
btn_sizer->Add(calc_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, FromDIP(ButtonProps::WindowButtonGap()));
calc_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { calc_flushing_volumes(); });
m_raw_matrixs = flush_matrixs;
m_flush_multipliers = flush_multiplier;
if (m_nozzle_nums == 2) {
ComboBox *extruder_combo = new ComboBox(parent, wxID_ANY, "", wxDefaultPosition, wxSize(FromDIP(130), FromDIP(24)), 0, nullptr, wxCB_READONLY);
extruder_combo->AppendString(_L("Left extruder"));
extruder_combo->AppendString(_L("Right extruder"));
extruder_combo->SetSelection(0);
extruder_combo->Bind(wxEVT_COMBOBOX, &WipingPanel::on_select_extruder, this);
btn_sizer->Add(extruder_combo);
json obj;
obj["flush_multiplier"] = flush_multiplier;
obj["extruder_num"] = nozzle_num;
obj["filament_colors"] = filament_colors;
obj["flush_volume_matrixs"] = json::array();
obj["min_flush_volumes"] = json::array();
obj["max_flush_volumes"] = json::array();
obj["min_flush_multiplier"] = g_min_flush_multiplier;
obj["max_flush_multiplier"] = g_max_flush_multiplier;
obj["is_dark_mode"] = wxGetApp().dark_mode();
for (const auto& vec : flush_matrixs) {
obj["flush_volume_matrixs"].push_back(vec);
}
return btn_sizer;
for (int idx = 0; idx < nozzle_num; ++idx) {
obj["min_flush_volumes"].push_back(*min_element(m_extra_flush_volume[idx].begin(), m_extra_flush_volume[idx].end()));
obj["max_flush_volumes"].push_back(m_max_flush_volume);
}
auto obj_str = obj.dump();
return obj_str;
}
void WipingDialog::on_dpi_changed(const wxRect &suggested_rect)
wxString WipingDialog::BuildTextObjStr()
{
for (auto button_item : m_button_list)
{
if (button_item.first == wxRESET)
{
button_item.second->Rescale();
}
wxString auto_flush_tip = _L("Studio would re-calculate your flushing volumes everytime the filaments color changed or filaments changed. You could disable the auto-calculate in Bambu Studio > Preferences");
wxString volume_desp_panel = _L("Flushing volume (mm³) for each filament pair.");
wxString volume_range_panel = wxString::Format(_L("Suggestion: Flushing Volume in range [%d, %d]"), 0, 700);
wxString multiplier_range_panel = wxString::Format(_L("The multiplier should be in range [%.2f, %.2f]."), 0, 3);
wxString calc_btn_panel = _L("Re-calculate");
wxString extruder_label_0 = _L("Left extruder");
wxString extruder_label_1 = _L("Right extruder");
wxString multiplier_label = _L("Multiplier");
wxString ok_btn_label = _L("OK");
wxString cancel_btn_label = _L("Cancel");
wxString text_obj = "{";
text_obj += wxString::Format("\"volume_desp_panel\":\"%s\",", volume_desp_panel);
text_obj += wxString::Format("\"volume_range_panel\":\"%s\",", volume_range_panel);
text_obj += wxString::Format("\"multiplier_range_panel\":\"%s\",", multiplier_range_panel);
text_obj += wxString::Format("\"calc_btn_panel\":\"%s\",", calc_btn_panel);
text_obj += wxString::Format("\"extruder_label_0\":\"%s\",", extruder_label_0);
text_obj += wxString::Format("\"extruder_label_1\":\"%s\",", extruder_label_1);
text_obj += wxString::Format("\"multiplier_label\":\"%s\",", multiplier_label);
text_obj += wxString::Format("\"ok_btn_label\":\"%s\",", ok_btn_label);
text_obj += wxString::Format("\"cancel_btn_label\":\"%s\",", cancel_btn_label);
text_obj += wxString::Format("\"auto_flush_tip\":\"%s\"", auto_flush_tip);
text_obj += "}";
return text_obj;
}
WipingDialog::WipingDialog(wxWindow* parent, const std::vector<std::vector<int>>& extra_flush_volume,const int max_flush_volume) :
wxDialog(parent, wxID_ANY, _(L("Flushing volumes for filament change")),
wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE ),
m_extra_flush_volume(extra_flush_volume),
m_max_flush_volume(max_flush_volume)
{
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
this->SetSizer(main_sizer);
auto filament_count= wxGetApp().preset_bundle->project_config.option<ConfigOptionStrings>("filament_colour")->values.size();
wxSize extra_size = { FromDIP(100),FromDIP(235) };
if (filament_count <= 2)
extra_size.y += FromDIP(16) * 3 + FromDIP(32);
else if (filament_count == 3)
extra_size.y += FromDIP(16) * 3;
else if (4 <= filament_count && filament_count <= 8)
extra_size.y += FromDIP(16) * 2;
else
extra_size.y += FromDIP(16);
wxSize max_scroll_size = { FromDIP(1000),FromDIP(500) };
wxSize estimate_size = { (int)(filament_count + 1) * FromDIP(60),(int)(filament_count + 1) * FromDIP(30)+FromDIP(2)};
wxSize scroll_size ={ std::min(max_scroll_size.x,estimate_size.x),std::min(max_scroll_size.y,estimate_size.y) };
wxSize appied_size = scroll_size + extra_size;
m_webview = wxWebView::New(this, wxID_ANY,
wxEmptyString,
wxDefaultPosition,
appied_size,
wxWebViewBackendDefault,
wxNO_BORDER);
m_webview->AddScriptMessageHandler("wipingDialog");
main_sizer->Add(m_webview, 1);
fs::path filepath = fs::path(resources_dir()) / "web/flush/WipingDialog.html";
wxString filepath_str = wxString::FromUTF8(filepath.string());
wxFileName fn(filepath_str);
if(fn.FileExists()) {
wxString url = "file:///" + fn.GetFullPath();
m_webview->LoadURL(url);
} else {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< "WipingDialog.html not exist";
}
m_panel_wiping->msw_rescale();
this->Refresh();
};
// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode:
WipingDialog::WipingDialog(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, const std::vector<std::string>& extruder_colours,
const std::vector<std::vector<int>>&extra_flush_volume, const std::vector<float>& flush_multiplier, size_t nozzle_nums)
: GUI::DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY,
_(L("Flushing volumes for filament change")),
wxDefaultPosition,
wxDefaultSize,
wxDEFAULT_DIALOG_STYLE /* | wxRESIZE_BORDER*/)
{
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
this->SetBackgroundColour(*wxWHITE);
this->SetMinSize(wxSize(MIN_WIPING_DIALOG_WIDTH, -1));
m_panel_wiping = new WipingPanel(this, matrix, extruders, 0, extruder_colours, nullptr, extra_flush_volume, flush_multiplier, nozzle_nums);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_line_top, 0, wxEXPAND, 0);
// set min sizer width according to extruders count
auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH());
sizer_width = sizer_width > MIN_WIPING_DIALOG_WIDTH ? sizer_width : MIN_WIPING_DIALOG_WIDTH;
main_sizer->SetMinSize(wxSize(sizer_width, -1));
main_sizer->Add(m_panel_wiping, 1, wxEXPAND | wxALL, 0);
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
main_sizer->Add(dlg_btns, 0, wxEXPAND);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
dlg_btns->GetOK()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { // if OK button is clicked..
m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values
m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...()
EndModal(wxID_OK);
}, wxID_OK);
dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxCANCEL); });
/*
if (this->FindWindowById(wxID_RESET, this)) {
this->FindWindowById(wxID_RESET, this)->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { m_panel_wiping->calc_flushing_volumes(); });
}
*/
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); });
this->Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& e) {
if (e.GetKeyCode() == WXK_ESCAPE) {
if (this->IsModal())
this->EndModal(wxID_CANCEL);
else
this->Close();
}
else
e.Skip();
});
main_sizer->Fit(this);
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
}
void WipingPanel::create_panels(wxWindow* parent, const int num) {
for (size_t i = 0; i < num; i++)
{
wxPanel* panel = new wxPanel(parent);
panel->SetBackgroundColour(i % 2 == 0 ? *wxWHITE : wxColour(238, 238, 238));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
panel->SetSizer(sizer);
//m_webview->Bind(wxEVT_WEBVIEW_NAVIGATED, [this](auto& evt) {
// auto table_obj_str = BuildTableObjStr();
// auto text_obj_str = BuildTextObjStr();
// CallAfter([table_obj_str, text_obj_str, this] {
// wxString script = wxString::Format("buildTable(%s);buildText(%s)", table_obj_str, text_obj_str);
// m_webview->RunScript(script);
// });
// });
wxButton* icon = new wxButton(panel, wxID_ANY, {}, wxDefaultPosition, ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW);
icon->SetBitmap(*get_extruder_color_icon(m_colours[i].GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(i + 1), FromDIP(16), FromDIP(16)));
icon->SetCanFocus(false);
icon_list2.push_back(icon);
sizer->AddSpacer(ROW_BEG_PADDING);
sizer->Add(icon, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, ROW_VERT_PADDING);
for (int j = 0; j < num; ++j) {
edit_boxes[j][i]->Reparent(panel);
edit_boxes[j][i]->SetBackgroundColour(panel->GetBackgroundColour());
edit_boxes[j][i]->SetFont(::Label::Body_13);
sizer->AddSpacer(EDIT_BOXES_GAP);
sizer->Add(edit_boxes[j][i], 0, wxALIGN_CENTER_VERTICAL, 0);
}
sizer->AddSpacer(ROW_END_PADDING);
m_sizer_advanced->Add(panel, 0, wxRIGHT | wxLEFT | wxEXPAND, TABLE_BORDER);
panel->Layout();
}
}
// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers)
WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders,
size_t cur_extruder_id,
const std::vector<std::string>& extruder_colours, Button* calc_button,
const std::vector<std::vector<int>>& extra_flush_volume, const std::vector<float>& flush_multiplier, size_t nozzle_nums)
: wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/)
, m_flush_multiplier(flush_multiplier)
, m_matrix(matrix)
, m_cur_extruder_id(cur_extruder_id)
, m_min_flush_volume(extra_flush_volume)
, m_max_flush_volume(Slic3r::g_max_flush_volume)
, m_nozzle_nums(nozzle_nums)
{
m_number_of_extruders = (unsigned int)extruder_colours.size();
generate_display_matrix();
for (const std::string& color : extruder_colours) {
Slic3r::ColorRGB rgb;
Slic3r::decode_color(color, rgb);
m_colours.push_back(wxColor(rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar()));
}
auto sizer_width = (int)((sqrt(m_display_matrix.size())) * ITEM_WIDTH() + (sqrt(m_display_matrix.size()) + 1) * HEADER_BEG_PADDING);
sizer_width = sizer_width > MIN_WIPING_DIALOG_WIDTH ? sizer_width : MIN_WIPING_DIALOG_WIDTH;
// Create two switched panels with their own sizers
m_sizer_simple = new wxBoxSizer(wxVERTICAL);
m_sizer_advanced = new wxBoxSizer(wxVERTICAL);
m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_page_simple->SetSizer(m_sizer_simple);
m_page_advanced->SetSizer(m_sizer_advanced);
m_page_advanced->SetBackgroundColour(*wxWHITE);
update_ui(m_page_simple);
update_ui(m_page_advanced);
auto gridsizer_simple = new wxGridSizer(3, 5, 10);
m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders + 1, 5, 1);
// First create controls for advanced mode and assign them to m_page_advanced:
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
edit_boxes.push_back(std::vector<wxTextCtrl*>(0));
for (unsigned int j = 0; j < m_number_of_extruders; ++j) {
#ifdef _WIN32
wxTextCtrl* text = new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), wxTE_CENTER | wxBORDER_NONE | wxTE_PROCESS_ENTER);
update_ui(text);
edit_boxes.back().push_back(text);
#else
edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1)));
#endif
if (i == j) {
edit_boxes[i][j]->SetValue(wxString("0"));
edit_boxes[i][j]->SetEditable(false);
edit_boxes[i][j]->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent&) {});
edit_boxes[i][j]->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent&) {});
}
else {
edit_boxes[i][j]->SetValue(wxString("") << int(m_display_matrix[m_number_of_extruders * j + i] * m_flush_multiplier[m_cur_extruder_id]));
edit_boxes[i][j]->Bind(wxEVT_TEXT, [this, i, j](wxCommandEvent& e) {
wxString str = edit_boxes[i][j]->GetValue();
int value = wxAtoi(str);
if (value > MAX_FLUSH_VALUE) {
str = wxString::Format(("%d"), MAX_FLUSH_VALUE);
edit_boxes[i][j]->SetValue(str);
}
else if (value < 0) {
edit_boxes[i][j]->SetValue(wxString("0"));
}
m_webview->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
std::string message = evt.GetString().ToStdString();
try {
json j = json::parse(message);
if (j["msg"].get<std::string>() == "init") {
auto table_obj_str = BuildTableObjStr();
auto text_obj_str = BuildTextObjStr();
CallAfter([table_obj_str, text_obj_str, this] {
wxString script = wxString::Format("buildTable(%s);buildText(%s)", table_obj_str, text_obj_str);
m_webview->RunScript(script);
});
auto on_apply_text_modify = [this, i, j](wxEvent &e) {
wxString str = edit_boxes[i][j]->GetValue();
int value = wxAtoi(str);
m_display_matrix[m_number_of_extruders * j + i] = value / get_flush_multiplier();
this->update_warning_texts();
e.Skip();
};
edit_boxes[i][j]->Bind(wxEVT_TEXT_ENTER, on_apply_text_modify);
edit_boxes[i][j]->Bind(wxEVT_KILL_FOCUS, on_apply_text_modify);
}
else if (j["msg"].get<std::string>() == "updateMatrix") {
int extruder_id = j["extruder_id"].get<int>();
auto new_matrix = CalcFlushingVolumes(extruder_id);
json obj;
obj["flush_volume_matrix"] = MatrixFlatten(new_matrix);
obj["extruder_id"] = extruder_id;
CallAfter([obj, this] {
std::string flush_volume_matrix_str = obj["flush_volume_matrix"].dump();
std::string extruder_id_str = std::to_string(obj["extruder_id"].get<int>());
wxString script = wxString::Format("updateTable(%s,%s)", flush_volume_matrix_str, extruder_id_str);
m_webview->RunScript(script);
});
}
else if (j["msg"].get<std::string>() == "storeData") {
int extruder_num = j["number_of_extruders"].get<int>();
std::vector<std::vector<double>> store_matrixs;
for (auto iter = j["raw_matrix"].begin(); iter != j["raw_matrix"].end(); ++iter) {
store_matrixs.emplace_back((*iter).get<std::vector<double>>());
}
std::vector<double>store_multipliers = j["flush_multiplier"].get<std::vector<double>>();
this->StoreFlushData(extruder_num, store_matrixs, store_multipliers);
m_submit_flag = true;
this->Close();
}
else if (j["msg"].get<std::string>() == "quit") {
this->Close();
}
}
}
catch (...) {
// BBS
m_sizer_advanced->AddSpacer(FromDIP(10));
auto tip_message_panel = new wxPanel(m_page_advanced, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
tip_message_panel->SetBackgroundColour(wxColour(238, 238, 238));
auto message_sizer = new wxBoxSizer(wxVERTICAL);
tip_message_panel->SetSizer(message_sizer);
{
wxString message = _L("Orca would re-calculate your flushing volumes every time the filaments color changed. You could disable the auto-calculate in Orca Slicer > Preferences");
m_tip_message_label = new Label(tip_message_panel, wxEmptyString);
wxClientDC dc(tip_message_panel);
wxString multiline_message;
m_tip_message_label->split_lines(dc, sizer_width, message, multiline_message);
m_tip_message_label->SetLabel(multiline_message);
m_tip_message_label->SetFont(Label::Body_13);
message_sizer->Add(m_tip_message_label, 0, wxEXPAND | wxALL, TIP_MESSAGES_PADDING);
}
m_sizer_advanced->Add(tip_message_panel, 0, wxEXPAND | wxRIGHT | wxLEFT, TABLE_BORDER);
bool is_show = wxGetApp().app_config->get("auto_calculate_flush") != "disabled";
tip_message_panel->Show(is_show);
m_sizer_advanced->AddSpacer(FromDIP(10));
auto calc_btn_sizer = create_calc_btn_sizer(m_page_advanced);
m_sizer_advanced->Add(calc_btn_sizer, 0, wxEXPAND | wxLEFT, FromDIP(30));
//m_sizer_advanced->AddSpacer(FromDIP(10));
m_sizer_advanced->AddSpacer(FromDIP(5));
header_line_panel = new wxPanel(m_page_advanced, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
header_line_panel->SetBackgroundColour(wxColour(238, 238, 238));
auto header_line_sizer = new wxBoxSizer(wxHORIZONTAL);
header_line_panel->SetSizer(header_line_sizer);
header_line_sizer->AddSpacer(HEADER_BEG_PADDING);
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
wxButton* icon = new wxButton(header_line_panel, wxID_ANY, {}, wxDefaultPosition, ICON_SIZE, wxBORDER_NONE | wxBU_AUTODRAW);
icon->SetBitmap(*get_extruder_color_icon(m_colours[i].GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(i + 1), FromDIP(16), FromDIP(16)));
icon->SetCanFocus(false);
icon_list1.push_back(icon);
header_line_sizer->AddSpacer(ICON_GAP);
header_line_sizer->Add(icon, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, HEADER_VERT_PADDING);
}
header_line_sizer->AddSpacer(HEADER_END_PADDING);
m_sizer_advanced->Add(header_line_panel, 0, wxEXPAND | wxRIGHT | wxLEFT, TABLE_BORDER);
create_panels(m_page_advanced, m_number_of_extruders);
//m_sizer_advanced->AddSpacer(BTN_SIZE.y);
// BBS: for tunning flush volumes
{
auto multi_desc_label = new wxStaticText(m_page_advanced, wxID_ANY, _(L("Flushing volume (mm³) for each filament pair.")), wxDefaultPosition, wxDefaultSize, 0);
multi_desc_label->SetForegroundColour(g_text_color);
m_sizer_advanced->Add(multi_desc_label, 0, wxEXPAND | wxLEFT, TEXT_BEG_PADDING);
wxString min_flush_str = wxString::Format(_L("Suggestion: Flushing Volume in range [%d, %d]"),*std::min_element(m_min_flush_volume[m_cur_extruder_id].begin(), m_min_flush_volume[m_cur_extruder_id].end()), m_max_flush_volume);
m_min_flush_label = new wxStaticText(m_page_advanced, wxID_ANY, min_flush_str, wxDefaultPosition, wxDefaultSize, 0);
m_min_flush_label->SetForegroundColour(g_text_color);
m_sizer_advanced->Add(m_min_flush_label, 0, wxEXPAND | wxLEFT, TEXT_BEG_PADDING);
auto on_apply_text_modify = [this](wxEvent& e) {
wxString str = m_flush_multiplier_ebox->GetValue();
float multiplier = wxAtof(str);
if (multiplier < g_min_flush_multiplier || multiplier > g_max_flush_multiplier) {
str = wxString::Format(("%.2f"), multiplier < g_min_flush_multiplier ? g_min_flush_multiplier : g_max_flush_multiplier);
m_flush_multiplier_ebox->SetValue(str);
m_flush_multiplier[m_cur_extruder_id] = wxAtof(m_flush_multiplier_ebox->GetValue());
MessageDialog dlg(nullptr,
wxString::Format(_L("The multiplier should be in range [%.2f, %.2f]."), g_min_flush_multiplier, g_max_flush_multiplier),
_L("Warning"), wxICON_WARNING | wxOK);
dlg.ShowModal();
}
else {
m_flush_multiplier[m_cur_extruder_id] = multiplier;
}
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
for (unsigned int j = 0; j < m_number_of_extruders; ++j) {
edit_boxes[i][j]->SetValue(to_string(int(m_display_matrix[m_number_of_extruders * j + i] * multiplier)));
}
}
this->update_warning_texts();
e.Skip();
};
m_sizer_advanced->AddSpacer(10);
wxBoxSizer* param_sizer = new wxBoxSizer(wxHORIZONTAL);
wxStaticText* flush_multiplier_title = new wxStaticText(m_page_advanced, wxID_ANY, _L("Multiplier"));
param_sizer->Add(flush_multiplier_title, 0, wxALIGN_CENTER | wxALL, 0);
param_sizer->AddSpacer(FromDIP(5));
m_flush_multiplier_ebox = new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(50), -1), wxTE_PROCESS_ENTER);
m_flush_multiplier_ebox->SetValue(wxString::Format(("%.2f"), m_flush_multiplier[m_cur_extruder_id]));
m_flush_multiplier_ebox->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
param_sizer->Add(m_flush_multiplier_ebox, 0, wxALIGN_CENTER | wxALL, 0);
param_sizer->AddStretchSpacer(1);
m_sizer_advanced->Add(param_sizer, 0, wxEXPAND | wxLEFT, TEXT_BEG_PADDING);
m_flush_multiplier_ebox->Bind(wxEVT_TEXT_ENTER, on_apply_text_modify);
m_flush_multiplier_ebox->Bind(wxEVT_KILL_FOCUS, on_apply_text_modify);
m_flush_multiplier_ebox->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this](wxCommandEvent&) {
wxString str = m_flush_multiplier_ebox->GetValue();
float multiplier = wxAtof(str);
if (multiplier < g_min_flush_multiplier || multiplier > g_max_flush_multiplier) {
str = wxString::Format(("%.2f"), multiplier < g_min_flush_multiplier ? g_min_flush_multiplier : g_max_flush_multiplier);
m_flush_multiplier_ebox->SetValue(str);
m_flush_multiplier[m_cur_extruder_id] = wxAtof(m_flush_multiplier_ebox->GetValue());
}
else {
m_flush_multiplier[m_cur_extruder_id] = multiplier;
}
m_flush_multiplier_ebox->SetInsertionPointEnd();
}
});
}
this->update_warning_texts();
m_page_advanced->Hide();
// Now the same for simple mode:
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
auto add_spin_ctrl = [this](std::vector<wxSpinCtrl*>& vec, float initial)
{
wxSpinCtrl* spin_ctrl = new wxSpinCtrl(m_page_simple, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), style | wxALIGN_RIGHT, 0, 300, (int)initial);
update_ui(spin_ctrl);
vec.push_back(spin_ctrl);
#ifdef __WXOSX__
// On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
// when it was changed from the text control, so the on_change callback
// gets the old one, and on_kill_focus resets the control to the old value.
// As a workaround, we get the new value from $event->GetString and store
// here temporarily so that we can return it from get_value()
spin_ctrl->Bind(wxEVT_TEXT, ([spin_ctrl](wxCommandEvent e)
{
long value;
const bool parsed = e.GetString().ToLong(&value);
int tmp_value = parsed && value >= INT_MIN && value <= INT_MAX ? (int)value : INT_MIN;
// Forcibly set the input value for SpinControl, since the value
// inserted from the keyboard or clipboard is not updated under OSX
if (tmp_value != INT_MIN) {
spin_ctrl->SetValue(tmp_value);
// But in SetValue() is executed m_text_ctrl->SelectAll(), so
// discard this selection and set insertion point to the end of string
spin_ctrl->GetText()->SetInsertionPointEnd();
}
}), spin_ctrl->GetId());
#endif
};
for (unsigned int i=0;i<m_number_of_extruders;++i) {
add_spin_ctrl(m_old, extruders[2 * i]);
add_spin_ctrl(m_new, extruders[2 * i+1]);
auto hsizer = new wxBoxSizer(wxHORIZONTAL);
wxWindow* w = new wxWindow(m_page_simple, wxID_ANY, wxDefaultPosition, ICON_SIZE, wxBORDER_SIMPLE);
w->SetCanFocus(false);
w->SetBackgroundColour(m_colours[i]);
hsizer->Add(w, wxALIGN_CENTER_VERTICAL);
hsizer->AddSpacer(10);
hsizer->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Filament #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(hsizer, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(m_old.back(),0);
gridsizer_simple->Add(m_new.back(),0);
}
m_sizer = new wxBoxSizer(wxVERTICAL);
m_sizer->Add(m_page_simple, 0, wxEXPAND, 0);
m_sizer->Add(m_page_advanced, 0, wxEXPAND, 0);
m_sizer->SetSizeHints(this);
SetSizer(m_sizer);
this->Layout();
toggle_advanced(); // to show/hide what is appropriate
header_line_panel->Bind(wxEVT_PAINT, [this](wxPaintEvent&) {
wxPaintDC dc(header_line_panel);
wxString from_text = _L("From");
wxString to_text = _L("To");
wxSize from_text_size = dc.GetTextExtent(from_text);
wxSize to_text_size = dc.GetTextExtent(to_text);
int base_y = (header_line_panel->GetSize().y - from_text_size.y - to_text_size.y) / 2;
int vol_width = ROW_BEG_PADDING + EDIT_BOXES_GAP / 2 + ICON_SIZE.x;
int base_x = (vol_width - from_text_size.x - to_text_size.x) / 2;
// draw from text
int x = base_x;
int y = base_y + to_text_size.y;
dc.DrawText(from_text, x, y);
// draw to text
x = base_x + from_text_size.x;
y = base_y;
dc.DrawText(to_text, x, y);
// draw a line
int p1_x = base_x + from_text_size.x - to_text_size.y;
int p1_y = base_y;
int p2_x = base_x + from_text_size.x + from_text_size.y;
int p2_y = base_y + from_text_size.y + to_text_size.y;
dc.SetPen(wxPen(wxColour(172, 172, 172, 1)));
dc.DrawLine(p1_x, p1_y, p2_x, p2_y);
});
}
int WipingPanel::calc_flushing_volume(const wxColour& from_, const wxColour& to_ ,int min_flush_volume)
{
Slic3r::FlushVolCalculator calculator(min_flush_volume, m_max_flush_volume);
return calculator.calc_flush_vol(from_.Alpha(), from_.Red(), from_.Green(), from_.Blue(), to_.Alpha(), to_.Red(), to_.Green(), to_.Blue());
int WipingDialog::CalcFlushingVolume(const wxColour& from, const wxColour& to, int min_flush_volume)
{
Slic3r::FlushVolCalculator calculator(min_flush_volume, Slic3r::g_max_flush_volume);
return calculator.calc_flush_vol(from.Alpha(), from.Red(), from.Green(), from.Blue(), to.Alpha(), to.Red(), to.Green(), to.Blue());
}
void WipingPanel::update_warning_texts()
WipingDialog::VolumeMatrix WipingDialog::CalcFlushingVolumes(int extruder_id)
{
static const wxColour g_warning_color = *wxRED;
static const wxColour g_normal_color = *wxBLACK;
auto& preset_bundle = wxGetApp().preset_bundle;
auto full_config = preset_bundle->full_config();
auto& ams_multi_color_filament = preset_bundle->ams_multi_color_filment;
wxString multi_str = m_flush_multiplier_ebox->GetValue();
float multiplier = wxAtof(multi_str);
bool has_exception_flush = false;
for (int i = 0; i < edit_boxes.size(); i++) {
auto& box_vec = edit_boxes[i];
for (int j = 0; j < box_vec.size(); j++) {
if (i == j)
continue;
auto text_box = box_vec[j];
wxString str = text_box->GetValue();
int actual_volume = wxAtoi(str);
if (actual_volume < m_min_flush_volume[m_cur_extruder_id][i] || actual_volume > m_max_flush_volume) {
if (text_box->GetForegroundColour() != g_warning_color) {
text_box->SetForegroundColour(g_warning_color);
text_box->Refresh();
}
has_exception_flush = true;
}
else {
if (text_box->GetForegroundColour() != g_normal_color) {
text_box->SetForegroundColour(StateColor::darkModeColorFor(g_normal_color));
text_box->Refresh();
}
}
}
}
if (has_exception_flush && m_min_flush_label->GetForegroundColour() != g_warning_color) {
m_min_flush_label->SetForegroundColour(g_warning_color);
m_min_flush_label->Refresh();
}
else if (!has_exception_flush && m_min_flush_label->GetForegroundColour() != g_text_color) {
m_min_flush_label->SetForegroundColour(g_text_color);
m_min_flush_label->Refresh();
}
}
void WipingPanel::calc_flushing_volumes()
{
auto& ams_multi_color_filament = wxGetApp().preset_bundle->ams_multi_color_filment;
std::vector<std::string> filament_color_strs = full_config.option<ConfigOptionStrings>("filament_colour")->values;
std::vector<std::vector<wxColour>> multi_colors;
std::vector<wxColour> filament_colors;
for (auto color_str : filament_color_strs)
filament_colors.emplace_back(color_str);
// Support for multi-color filament
for (int i = 0; i < m_colours.size(); ++i) {
for (int i = 0; i < filament_colors.size(); ++i) {
std::vector<wxColour> single_filament;
if (i < ams_multi_color_filament.size()) {
if (!ams_multi_color_filament[i].empty()) {
@ -755,169 +411,64 @@ void WipingPanel::calc_flushing_volumes()
continue;
}
}
single_filament.push_back(wxColour(m_colours[i]));
single_filament.push_back(wxColour(filament_colors[i]));
multi_colors.push_back(single_filament);
}
VolumeMatrix matrix;
for (int from_idx = 0; from_idx < multi_colors.size(); ++from_idx) {
bool is_from_support = is_support_filament(from_idx);
matrix.emplace_back();
for (int to_idx = 0; to_idx < multi_colors.size(); ++to_idx) {
bool is_to_support = is_support_filament(to_idx);
if (from_idx == to_idx) {
edit_boxes[to_idx][from_idx]->SetValue(std::to_string(0));
matrix.back().emplace_back(0);
continue;
}
bool is_to_support = is_support_filament(to_idx);
int flushing_volume = 0;
if (is_to_support) {
flushing_volume = Slic3r::g_flush_volume_to_support;
}
else {
int flushing_volume = 0;
if (is_to_support) {
flushing_volume = Slic3r::g_flush_volume_to_support;
}
else {
for (int i = 0; i < multi_colors[from_idx].size(); ++i) {
const wxColour& from = multi_colors[from_idx][i];
for (int j = 0; j < multi_colors[to_idx].size(); ++j) {
const wxColour& to = multi_colors[to_idx][j];
int volume = calc_flushing_volume(from, to, m_min_flush_volume[m_cur_extruder_id][from_idx]);
flushing_volume = std::max(flushing_volume, volume);
}
}
if (is_from_support) {
flushing_volume = std::max(Slic3r::g_min_flush_volume_from_support, flushing_volume);
for (int i = 0; i < multi_colors[from_idx].size(); ++i) {
const wxColour& from = multi_colors[from_idx][i];
for (int j = 0; j < multi_colors[to_idx].size(); ++j) {
const wxColour& to = multi_colors[to_idx][j];
int volume = CalcFlushingVolume(from, to, m_extra_flush_volume[extruder_id][from_idx]);
flushing_volume = std::max(flushing_volume, volume);
}
}
m_display_matrix[m_number_of_extruders * from_idx + to_idx] = flushing_volume;
flushing_volume = int(flushing_volume * get_flush_multiplier());
edit_boxes[to_idx][from_idx]->SetValue(std::to_string(flushing_volume));
if (is_from_support) {
flushing_volume = std::max(Slic3r::g_min_flush_volume_from_support, flushing_volume);
}
}
matrix.back().emplace_back(flushing_volume);
}
}
this->update_warning_texts();
return matrix;
}
void WipingPanel::msw_rescale()
void WipingDialog::StoreFlushData(int extruder_num, const std::vector<std::vector<double>>& flush_volume_vecs, const std::vector<double>&flush_multipliers)
{
for (unsigned int i = 0; i < icon_list1.size(); ++i) {
auto bitmap = *get_extruder_color_icon(m_colours[i].GetAsString(wxC2S_HTML_SYNTAX).ToStdString(), std::to_string(i + 1), FromDIP(16), FromDIP(16));
icon_list1[i]->SetBitmap(bitmap);
icon_list2[i]->SetBitmap(bitmap);
}
m_flush_multipliers = flush_multipliers;
m_raw_matrixs = flush_volume_vecs;
}
// Reads values from the (advanced) wiping matrix:
std::vector<float> WipingPanel::read_matrix_values() {
if (!m_advanced)
fill_in_matrix();
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
for (unsigned int j=0;j<m_number_of_extruders;++j) {
double val = 0.;
float flush_multipler = get_flush_multiplier();
if (flush_multipler == 0) {
output.push_back(0.);
}
else {
edit_boxes[j][i]->GetValue().ToDouble(&val);
output.push_back((float) val / get_flush_multiplier());
}
}
}
m_display_matrix = output;
back_matrix();
return m_matrix;
}
// Reads values from simple mode to save them for next time:
std::vector<float> WipingPanel::read_extruders_values() {
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
output.push_back(m_old[i]->GetValue());
output.push_back(m_new[i]->GetValue());
}
return output;
}
// This updates the "advanced" matrix based on values from "simple" mode
void WipingPanel::fill_in_matrix() {
for (unsigned i=0;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
edit_boxes[j][i]->SetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()));
}
}
}
void WipingPanel::generate_display_matrix()
std::vector<double> WipingDialog::GetFlattenMatrix()const
{
m_display_matrix = get_flush_volumes_matrix(m_matrix, m_cur_extruder_id, m_nozzle_nums);
std::vector<double> ret;
for (auto& matrix : m_raw_matrixs) {
ret.insert(ret.end(), matrix.begin(), matrix.end());
}
return ret;
}
void WipingPanel::back_matrix()
std::vector<double> WipingDialog::GetMultipliers()const
{
set_flush_volumes_matrix(m_matrix, m_display_matrix, m_cur_extruder_id, m_nozzle_nums);
}
void WipingPanel::update_table()
{
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
for (unsigned int j = 0; j < m_number_of_extruders; ++j) {
if (i == j) {
edit_boxes[i][j]->SetValue(wxString("0"));
edit_boxes[i][j]->SetEditable(false);
} else {
edit_boxes[i][j]->SetValue(wxString("") << int(m_display_matrix[m_number_of_extruders * j + i] * m_flush_multiplier[m_cur_extruder_id]));
}
}
}
wxString str = wxString::Format(("%.2f"), m_flush_multiplier[m_cur_extruder_id]);
m_flush_multiplier_ebox->SetValue(str);
update_warning_texts();
}
void WipingPanel::on_select_extruder(wxCommandEvent &evt)
{
ComboBox *comboBox = dynamic_cast<ComboBox *>(evt.GetEventObject());
if (comboBox) {
int selection = comboBox->GetSelection();
if (m_cur_extruder_id != selection) {
read_matrix_values();
m_cur_extruder_id = selection;
generate_display_matrix();
update_table();
}
}
}
// Function to check if simple and advanced settings are matching
bool WipingPanel::advanced_matches_simple() {
for (unsigned i=0;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
if (edit_boxes[j][i]->GetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())))
return false;
}
}
return true;
}
// Switches the dialog from simple to advanced mode and vice versa
void WipingPanel::toggle_advanced(bool user_action) {
if (user_action)
m_advanced = !m_advanced; // user demands a change -> toggle
else {
// BBS: show advanced mode by default
//m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate
m_advanced = true;
}
(m_advanced ? m_page_advanced : m_page_simple)->Show();
(!m_advanced ? m_page_advanced : m_page_simple)->Hide();
if (m_advanced)
if (user_action) fill_in_matrix(); // otherwise keep values loaded from config
m_sizer->Layout();
Refresh();
return m_flush_multipliers;
}

View file

@ -1,20 +1,11 @@
#ifndef _WIPE_TOWER_DIALOG_H_
#define _WIPE_TOWER_DIALOG_H_
#include "GUI_Utils.hpp"
#include <wx/sizer.h>
#include <wx/spinctrl.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include <wx/msgdlg.h>
#include <wx/webview.h>
#include "Widgets/SpinInput.hpp"
#include "RammingChart.hpp"
class Button;
class Label;
class RammingPanel : public wxPanel {
@ -45,106 +36,31 @@ private:
std::string m_output_data;
};
class WipingPanel : public wxPanel {
public:
// BBS
WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, size_t cur_extruder_id,
const std::vector<std::string>& extruder_colours, Button* calc_button,
const std::vector<std::vector<int>>& extra_flush_volume, const std::vector<float>& flush_multiplier, size_t nozzle_nums);
std::vector<float> read_matrix_values();
std::vector<float> read_extruders_values();
void toggle_advanced(bool user_action = false);
void create_panels(wxWindow* parent, const int num);
void calc_flushing_volumes();
void msw_rescale();
wxBoxSizer* create_calc_btn_sizer(wxWindow* parent);
float get_flush_multiplier()
{
if (m_flush_multiplier_ebox == nullptr)
return 1.f;
return wxAtof(m_flush_multiplier_ebox->GetValue());
}
std::vector<float> get_flush_multiplier_vector() { return m_flush_multiplier; }
private:
void on_select_extruder(wxCommandEvent &evt);
void generate_display_matrix(); // generate display_matrix frem matrix
void back_matrix();
void update_table(); // if matrix is modified update the table
void fill_in_matrix();
bool advanced_matches_simple();
int calc_flushing_volume(const wxColour& from, const wxColour& to,int min_flush_volume);
void update_warning_texts();
std::vector<wxSpinCtrl*> m_old;
std::vector<wxSpinCtrl*> m_new;
std::vector<std::vector<wxTextCtrl*>> edit_boxes;
std::vector<wxColour> m_colours;
unsigned int m_number_of_extruders = 0;
bool m_advanced = false;
wxPanel* m_page_simple = nullptr;
wxPanel* m_page_advanced = nullptr;
wxPanel* header_line_panel = nullptr;
wxBoxSizer* m_sizer = nullptr;
wxBoxSizer* m_sizer_simple = nullptr;
wxBoxSizer* m_sizer_advanced = nullptr;
wxGridSizer* m_gridsizer_advanced = nullptr;
wxButton* m_widget_button = nullptr;
Label* m_tip_message_label = nullptr;
std::vector<wxButton *> icon_list1;
std::vector<wxButton *> icon_list2;
const std::vector<std::vector<int>> m_min_flush_volume;
const int m_max_flush_volume;
wxTextCtrl* m_flush_multiplier_ebox = nullptr;
wxStaticText* m_min_flush_label = nullptr;
std::vector<float> m_flush_multiplier;
std::vector<float> m_matrix;
std::vector<float> m_display_matrix;
size_t m_cur_extruder_id;
size_t m_nozzle_nums;
};
class WipingDialog : public Slic3r::GUI::DPIDialog
class WipingDialog : public wxDialog
{
public:
WipingDialog(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, const std::vector<std::string>& extruder_colours,
const std::vector<std::vector<int>>&extra_flush_volume, const std::vector<float>& flush_multiplier, size_t nozzle_nums);
std::vector<float> get_matrix() const { return m_output_matrix; }
std::vector<float> get_extruders() const { return m_output_extruders; }
wxBoxSizer* create_btn_sizer(long flags);
using VolumeMatrix = std::vector<std::vector<double>>;
float get_flush_multiplier()
{
if (m_panel_wiping == nullptr)
return 1.f;
return m_panel_wiping->get_flush_multiplier();
}
std::vector<float> get_flush_multiplier_vector() const {
if (m_panel_wiping == nullptr)
return {1.f, 1.f};
return m_panel_wiping->get_flush_multiplier_vector();
}
void on_dpi_changed(const wxRect &suggested_rect) override;
WipingDialog(wxWindow* parent,const std::vector<std::vector<int>>& extra_flush_volume, const int max_flush_volume = Slic3r::g_max_flush_volume);
VolumeMatrix CalcFlushingVolumes(int extruder_id);
std::vector<double> GetFlattenMatrix()const;
std::vector<double> GetMultipliers()const;
bool GetSubmitFlag() const { return m_submit_flag; }
private:
WipingPanel* m_panel_wiping = nullptr;
std::vector<float> m_output_matrix;
std::vector<float> m_output_extruders;
std::unordered_map<int, Button *> m_button_list;
int CalcFlushingVolume(const wxColour& from_, const wxColour& to_, int min_flush_volume);
wxString BuildTableObjStr();
wxString BuildTextObjStr();
void StoreFlushData(int extruder_num, const std::vector<std::vector<double>>& flush_volume_vecs, const std::vector<double>& flush_multipliers);
wxWebView* m_webview;
std::vector<std::vector<int>> m_extra_flush_volume;
int m_max_flush_volume;
VolumeMatrix m_raw_matrixs;
std::vector<double> m_flush_multipliers;
bool m_submit_flag{ false };
};
#endif // _WIPE_TOWER_DIALOG_H_