Compare commits

..
Author SHA1 Message Date
SoftFever 6d02a945e0 Speed up the Linux incremental rebuild (optimizes packaging step) (#16244)
## Speeds up OrcaSlicer incremental rebuild on Linux

Profiled `build_linux_image.sh`: 96 s, of which 51 s in the dependency
audit.

**`appimage_is_elf_file()`** ran `file` and `grep` per candidate. An
AppDir holds ~9.6k of them, 4.8k being the bundled Python runtime and
none of them ELF: ~19k processes, 14 s. Reads the four-byte magic
instead. Checked against the old result on 4000 files, no disagreement.

**The dependency walk** popped its queue with `"${queue[@]:1}"`, which
rebuilds the whole array each time. At ~4.8k entries that was 22 s of
copying an array around. Uses a read index.

Audit still passes. `shellcheck` v0.11.0, the version CI uses, is clean.

## Notes
The 96 s -> 12.7 s. Measured on a 32-core / 48 GB machine, but the audit
is a serial bash loop, so cores and RAM is not the bottleneck. On slower
hardware the saving should be larger

## Images
<img width="1987" height="782" alt="Screenshot_20261007_092516"
src="https://github.com/user-attachments/assets/96fcb917-38e5-49e7-8cbe-b37be1a2f23a"
/>
<img width="1807" height="742" alt="Screenshot_20261007_092621"
src="https://github.com/user-attachments/assets/4a65ffae-4d59-41f0-a1c7-ee5b49f3c4be"
/>
2026-10-07 19:07:22 +08:00
SoftFever 01afd3b5b9 Use multiswitch for extruders instead new tabs on printer settings & publish dialog (#16162)
# CHANGES / TESTS
• Uses "Extruders" as tab name if it has multiple or it uses "Extruder" for single ones
• Keeps selected extruder while switching between "Extruders" and "Motion ability" tab
• Revert functions are working
• New extruders generated with values so they will shown in "Unchanged values" dialog if you try to change preset while its edited. revert functions not works properly without this
• BBL printers visible as Left / Right while normal printers visible as T1 / T2. i think there should be a separate option for how many toolheads and how many extruders it has. we might see 4 nozzles on same toolhead if one brand is brave enough :)
• Sidebar and other sections updates itself properly


<img width="751" height="173" alt="Screenshot-20261005173918" src="https://github.com/user-attachments/assets/3be0a6bd-84bd-4d15-822c-ed34acd98a9f" />
<img width="768" height="184" alt="Screenshot-20261005173905" src="https://github.com/user-attachments/assets/46164926-e385-4482-9fd1-41325eb9f61d" />
<img width="755" height="289" alt="Screenshot-20261005175318" src="https://github.com/user-attachments/assets/4550fa51-4c71-4a0a-b15e-9ca82dd0f1ad" />

# FIXES
• Extruders count on parameters section not updated when extruder count changed on printer settings. fixed on this PR
<img width="800" height="478" alt="Screenshot-20261005174702" src="https://github.com/user-attachments/assets/734e53df-f23f-4f83-8f87-2ecfeb6c162c" />

• New extruders gets randomly modifed parameters. fixed on this PR
<img width="871" height="87" alt="Screenshot-20261005174840" src="https://github.com/user-attachments/assets/254dae2a-fcfa-44ed-b3b0-038faf019abb" />

• Changed parameters not triggers revert / modified on extruder tabs. fixed on this PR

• Multi switch on motion ability tab not updated on extruder count change. fixed on this PR
<img width="813" height="184" alt="Screenshot-20261005174929" src="https://github.com/user-attachments/assets/f4f33f8f-b1bd-4a30-b91b-6ff632d08c2a" />
2026-10-07 19:05:37 +08:00
SoftFever b805f2ffd6 Fill a new extruder's machine limits the same way as its other settings 2026-10-07 18:42:09 +08:00
SoftFever 6b2b200f50 Merge branch 'main' into pr/yw4z/16162 2026-10-07 18:40:37 +08:00
yw4z a38e6c61f2 Show dummy image for printers without cover on printer selectiondialog (#16235)
init
2026-10-07 12:57:28 +03:00
SoftFever cc3769453f Merge branch 'main' into pr/yw4z/16162 2026-10-07 17:25:29 +08:00
ExPikaPaka bd0ef35b90 Speed up the Linux image build
Profiling build_linux_image.sh: 96 s, of which 51 s in the dependency audit.

appimage_is_elf_file() ran file(1) and grep per candidate. An AppDir holds ~9.6k
of them, 4.8k being the bundled Python runtime, none of them ELF: ~19k processes
for 14 s. Read the four-byte magic instead; checked against the old result on
4000 files, no disagreement.

The dependency walk popped its queue with "${queue[@]:1}", which rebuilds the
whole array each time. At ~4.8k entries that was 22 s of copying. Use a read
index.

96 s -> 12.7 s. The audit still passes.
2026-10-07 09:25:19 +02:00
SoftFever b0da039eae Merge branch 'main' into pr/yw4z/16162 2026-10-07 14:44:59 +08:00
SoftFever ba5ecc5470 Edit the selected extruder's machine limits on the Motion ability page 2026-10-07 13:41:42 +08:00
SoftFever 82349e1ba4 Give each extruder its own machine limits 2026-10-07 13:41:42 +08:00
SoftFever c740ddf0a6 Publish each extruder's own retraction and Z-hop values 2026-10-07 01:45:24 +08:00
SoftFever d068468bbb Simplify the unified Extruder page and its Publish dialog switch 2026-10-07 01:40:10 +08:00
SoftFever 67e29f69e7 Merge branch 'main' into exturuder-tab-multi-switch 2026-10-07 01:38:10 +08:00
SoftFever 919f507cca Merge branch 'main' into exturuder-tab-multi-switch 2026-10-06 20:04:09 +08:00
SoftFever 26329e7147 Show Left/Right Extruder or Extruder N on the extruder switch 2026-10-06 20:03:07 +08:00
SoftFever e47fdcaef7 Merge branch 'main' into exturuder-tab-multi-switch 2026-10-06 17:56:06 +08:00
yw4z 26be825234 init 2026-10-05 17:37:52 +03:00
60 changed files with 1134 additions and 140 deletions
@@ -105,11 +105,12 @@ Rules:
`single_extruder_multi_material` off, and in the app when the printer tab loads a printer with a
different number of extruders, the pair is rebuilt from `extruder_variant_list` (one
`Direct Drive Standard` per extruder when the list is absent) and the variant arrays are resized to
the rebuilt pair, padded with their first value or cut. The resize skips the `machine_max_*` limits
and `hotend_heating_rate` / `hotend_cooling_rate`: they keep their width, and an extruder beyond it
reads their first value, so extruder 2 and up of a list-less printer take extruder 1's normal limit
as their silent one too. With the three in agreement that changes nothing; a pair written without
the list is replaced. A listed variant the pair lacks is a menu choice that reads variant index 0.
the rebuilt pair, padded with their first value or cut. The `machine_max_*` limits are padded the
same way, so extruder 2 and up of a list-less printer take extruder 1's normal limit as their
silent one too. The resize skips `hotend_heating_rate` / `hotend_cooling_rate`: they keep their
width, and an extruder beyond it reads their first value. With the three in agreement that changes
nothing; a pair written without the list is replaced. A listed variant the pair lacks is a menu
choice that reads variant index 0.
- The pair without `extruder_variant_list` slices, but the sidebar offers no variant switch and
the app cannot add variants to a list-less process: nothing is lost while every extruder
has exactly one variant, and every further variant is unreachable.
+1 -1
View File
@@ -311,7 +311,7 @@ function CreatePrinterBlock(OneModel)
return '<div class="PrinterBlock" onClick="ChooseModel(\''+vendor+'\',\''+OneModel['model']+'\')">'+
' <div class="PImg">'+
' <img class="ModelThumbnail" src="' + OneModel['cover'] + '" />'+
' <img class="ModelThumbnail" src="' + OneModel['cover'] + '" onerror="this.onerror=null;this.src=\'../img/printer-dummy.png\';"/>'+
' </div>'+
' <div class="PrinterInfoMark">?</div>'+
' <div class="PrinterInfo">'+
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+7 -1
View File
@@ -23,7 +23,13 @@ appimage_is_host_library() {
}
appimage_is_elf_file() {
file -b "$1" 2>/dev/null | grep -q '^ELF '
# Read the four-byte ELF magic rather than asking file(1): this runs once per candidate, and an
# AppDir holds thousands of them (the bundled Python runtime alone is ~5k files, none of them
# ELF). Two processes per call made this ~14 s of a ~95 s image build on its own.
local magic
[[ -f "$1" && -r "$1" ]] || return 1
IFS= read -r -n 4 -d '' magic < "$1" 2>/dev/null
[[ $magic == $'\177ELF' ]]
}
appimage_list_direct_dependencies() {
@@ -118,9 +118,13 @@ bundle_dependency_closure() {
# (scripts/check_appimage_libs.sh).
local -a search_dirs=("$dst_dir")
while [ ${#queue[@]} -gt 0 ]; do
target="${queue[0]}"
queue=("${queue[@]:1}")
# Walked with a read index rather than by reslicing the array: "${queue[@]:1}" rebuilds the whole
# queue on every pop, so draining it costs O(n^2). At the ~5k entries this bundle reaches that was
# ~22 s of the image build spent copying an array around.
local head=0
while [ "$head" -lt ${#queue[@]} ]; do
target="${queue[$head]}"
head=$((head + 1))
if [ ! -e "$target" ] || ! appimage_is_elf_file "$target"; then
continue
+1
View File
@@ -83,6 +83,7 @@
#include <cmath>
#include <cctype>
#include <cstdio>
#include <functional>
#include <gp_XY.hxx>
#include <initializer_list>
#include <math.h>
+3
View File
@@ -261,6 +261,8 @@ set(lisbslic3r_sources
GCode/PreciseSeam.cpp
GCode/PreciseSeam.hpp
GCode/PreciseSeamInternal.hpp
#GCodeSender.cpp
#GCodeSender.hpp
GCode/SmallAreaInfillFlowCompensator.cpp
GCode/SmallAreaInfillFlowCompensator.hpp
GCode/SpiralVase.cpp
@@ -445,6 +447,7 @@ set(lisbslic3r_sources
SLA/SupportTreeBuildsteps.hpp
SLA/SupportTree.cpp
SLA/SupportTree.hpp
#SLA/SupportTreeIGL.cpp
SLA/SupportTreeMesher.cpp
SLA/SupportTreeMesher.hpp
SlicesToTriangleMesh.cpp
+1
View File
@@ -2589,6 +2589,7 @@ void priv::create_face_types(FaceTypeMap &map,
}
#include <CGAL/Polygon_mesh_processing/clip.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper)
{
CutMesh& tm = cut.mesh;
+1
View File
@@ -15,6 +15,7 @@
#include "Emboss.hpp"
#include <optional>
#include <stdio.h>
#include <numeric>
#include <cstdlib>
#include <boost/nowide/convert.hpp>
#include <boost/log/trivial.hpp>
+1
View File
@@ -55,6 +55,7 @@
#include "TDataStd_Name.hxx"
#include "BRepBuilderAPI_Transform.hxx"
#include "TopExp_Explorer.hxx"
#include "TopExp_Explorer.hxx"
#include "BRep_Tool.hxx"
#include "BRepTools.hxx"
#include <IMeshTools_Parameters.hxx>
+1
View File
@@ -43,6 +43,7 @@
#include "Print.hpp"
#include "Utils.hpp"
#include "ClipperUtils.hpp"
#include "libslic3r.h"
#include "LocalesUtils.hpp"
#include "libslic3r/format.hpp"
#include "Time.hpp"
+2
View File
@@ -27,6 +27,8 @@
#include "../PrintConfig.hpp"
#include "../Utils.hpp"
#include "Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h"
#include <boost/log/trivial.hpp>
#include <string>
+580
View File
@@ -0,0 +1,580 @@
#include "GCodeSender.hpp"
#include <iostream>
#include <istream>
#include <string>
#include <thread>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
#if defined(__APPLE__) || defined(__OpenBSD__)
#include <termios.h>
#endif
#ifdef __APPLE__
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#endif
#ifdef __linux__
#include <sys/ioctl.h>
#include <fcntl.h>
#include "/usr/include/asm-generic/ioctls.h"
/* The following definitions are kindly borrowed from:
/usr/include/asm-generic/termbits.h
Unfortunately we cannot just include that one because
it would redefine the "struct termios" already defined
the <termios.h> already included by Boost.ASIO. */
#define K_NCCS 19
struct termios2 {
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[K_NCCS];
speed_t c_ispeed;
speed_t c_ospeed;
};
#define BOTHER CBAUDEX
#endif
//#define DEBUG_SERIAL
#ifdef DEBUG_SERIAL
#include <cstdlib>
#include <fstream>
std::fstream fs;
#endif
#define KEEP_SENT 20
namespace Slic3r {
GCodeSender::GCodeSender()
: io(), serial(io), can_send(false), sent(0), open(false), error(false),
connected(false), queue_paused(false)
{
#ifdef DEBUG_SERIAL
std::srand(std::time(nullptr));
#endif
}
GCodeSender::~GCodeSender()
{
this->disconnect();
}
bool
GCodeSender::connect(std::string devname, unsigned int baud_rate)
{
this->disconnect();
this->set_error_status(false);
try {
this->serial.open(devname);
this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::odd));
this->serial.set_option(boost::asio::serial_port_base::character_size(boost::asio::serial_port_base::character_size(8)));
this->serial.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
this->serial.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
this->set_baud_rate(baud_rate);
this->serial.close();
this->serial.open(devname);
this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
// set baud rate again because set_option overwrote it
this->set_baud_rate(baud_rate);
this->open = true;
this->reset();
} catch (boost::system::system_error &) {
this->set_error_status(true);
return false;
}
// a reset firmware expect line numbers to start again from 1
this->sent = 0;
this->last_sent.clear();
/* Initialize debugger */
#ifdef DEBUG_SERIAL
fs.open("serial.txt", std::fstream::out | std::fstream::trunc);
#endif
// this gives some work to the io_service before it is started
// (post() runs the supplied function in its thread)
this->io.post(boost::bind(&GCodeSender::do_read, this));
// start reading in the background thread
boost::thread t(boost::bind(&boost::asio::io_service::run, &this->io));
this->background_thread.swap(t);
// always send a M105 to check for connection because firmware might be silent on connect
//FIXME Vojtech: This is being sent too early, leading to line number synchronization issues,
// from which the GCodeSender never recovers.
// this->send("M105", true);
return true;
}
void
GCodeSender::set_baud_rate(unsigned int baud_rate)
{
try {
// This does not support speeds > 115200
this->serial.set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
} catch (boost::system::system_error &) {
boost::asio::serial_port::native_handle_type handle = this->serial.native_handle();
#if __APPLE__
termios ios;
::tcgetattr(handle, &ios);
::cfsetspeed(&ios, baud_rate);
speed_t newSpeed = baud_rate;
ioctl(handle, IOSSIOSPEED, &newSpeed);
::tcsetattr(handle, TCSANOW, &ios);
#elif __linux__
termios2 ios;
if (ioctl(handle, TCGETS2, &ios))
printf("Error in TCGETS2: %s\n", strerror(errno));
ios.c_ispeed = ios.c_ospeed = baud_rate;
ios.c_cflag &= ~CBAUD;
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
ios.c_cc[VTIME] = 1;
if (ioctl(handle, TCSETS2, &ios))
printf("Error in TCSETS2: %s\n", strerror(errno));
#elif __OpenBSD__
struct termios ios;
::tcgetattr(handle, &ios);
::cfsetspeed(&ios, baud_rate);
if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0)
printf("Failed to set baud rate: %s\n", strerror(errno));
#else
//throw Slic3r::InvalidArgument("OS does not currently support custom bauds");
#endif
}
}
void
GCodeSender::disconnect()
{
if (!this->open) return;
this->open = false;
this->connected = false;
this->io.post(boost::bind(&GCodeSender::do_close, this));
this->background_thread.join();
this->io.reset();
/*
if (this->error_status()) {
throw(boost::system::system_error(boost::system::error_code(),
"Error while closing the device"));
}
*/
#ifdef DEBUG_SERIAL
fs << "DISCONNECTED" << std::endl << std::flush;
fs.close();
#endif
}
bool
GCodeSender::is_connected() const
{
return this->connected;
}
bool
GCodeSender::wait_connected(unsigned int timeout) const
{
using namespace boost::posix_time;
ptime t0 = second_clock::local_time() + seconds(timeout);
while (!this->connected) {
if (second_clock::local_time() > t0) return false;
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
}
return true;
}
size_t
GCodeSender::queue_size() const
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
return this->queue.size();
}
void
GCodeSender::pause_queue()
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->queue_paused = true;
}
void
GCodeSender::resume_queue()
{
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->queue_paused = false;
}
this->send();
}
void
GCodeSender::purge_queue(bool priority)
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
if (priority) {
// clear priority queue
std::list<std::string> empty;
std::swap(this->priqueue, empty);
} else {
// clear queue
std::queue<std::string> empty;
std::swap(this->queue, empty);
this->queue_paused = false;
}
}
// purge log and return its contents
std::vector<std::string>
GCodeSender::purge_log()
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
std::vector<std::string> retval;
retval.reserve(this->log.size());
while (!this->log.empty()) {
retval.push_back(this->log.front());
this->log.pop();
}
return retval;
}
std::string
GCodeSender::getT() const
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
return this->T;
}
std::string
GCodeSender::getB() const
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
return this->B;
}
void
GCodeSender::do_close()
{
this->set_error_status(false);
boost::system::error_code ec;
this->serial.cancel(ec);
if (ec) this->set_error_status(true);
this->serial.close(ec);
if (ec) this->set_error_status(true);
}
void
GCodeSender::set_error_status(bool e)
{
boost::lock_guard<boost::mutex> l(this->error_mutex);
this->error = e;
}
bool
GCodeSender::error_status() const
{
boost::lock_guard<boost::mutex> l(this->error_mutex);
return this->error;
}
void
GCodeSender::do_read()
{
// read one line
boost::asio::async_read_until(
this->serial,
this->read_buffer,
'\n',
boost::bind(
&GCodeSender::on_read,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
void
GCodeSender::on_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
this->set_error_status(false);
if (error) {
#ifdef __APPLE__
if (error.value() == 45) {
// OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html
this->do_read();
return;
}
#endif
// printf("ERROR: [%d] %s\n", error.value(), error.message().c_str());
// error can be true even because the serial port was closed.
// In this case it is not a real error, so ignore.
if (this->open) {
this->do_close();
this->set_error_status(true);
}
return;
}
std::istream is(&this->read_buffer);
std::string line;
std::getline(is, line);
if (!line.empty()) {
#ifdef DEBUG_SERIAL
fs << "<< " << line << std::endl << std::flush;
#endif
// note that line might contain \r at its end
// parse incoming line
if (!this->connected
&& (boost::starts_with(line, "start")
|| boost::starts_with(line, "Grbl ")
|| boost::starts_with(line, "ok")
|| boost::contains(line, "T:"))) {
this->connected = true;
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->can_send = true;
}
this->send();
} else if (boost::starts_with(line, "ok")) {
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->can_send = true;
}
this->send();
} else if (boost::istarts_with(line, "resend") // Marlin uses "Resend: "
|| boost::istarts_with(line, "rs")) {
// extract the first number from line
boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit());
size_t toresend = boost::lexical_cast<size_t>(line.substr(0, line.find_first_not_of("0123456789")));
#ifdef DEBUG_SERIAL
fs << "!! line num out of sync: toresend = " << toresend << ", sent = " << sent << ", last_sent.size = " << last_sent.size() << std::endl;
#endif
if (toresend > this->sent - this->last_sent.size() && toresend <= this->sent) {
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
const auto lines_to_resend = this->sent - toresend + 1;
#ifdef DEBUG_SERIAL
fs << "!! resending " << lines_to_resend << " lines" << std::endl;
#endif
// move the unsent lines to priqueue
this->priqueue.insert(
this->priqueue.begin(), // insert at the beginning
this->last_sent.begin() + this->last_sent.size() - lines_to_resend,
this->last_sent.end()
);
// we can empty last_sent because it's not useful anymore
this->last_sent.clear();
// start resending with the requested line number
this->sent = toresend - 1;
this->can_send = true;
}
this->send();
} else {
printf("Cannot resend %zu (oldest we have is %zu)\n", toresend, this->sent - this->last_sent.size());
}
} else if (boost::starts_with(line, "wait")) {
// ignore
} else {
// push any other line into the log
boost::lock_guard<boost::mutex> l(this->log_mutex);
this->log.push(line);
}
// parse temperature info
{
size_t pos = line.find("T:");
if (pos != std::string::npos && line.size() > pos + 2) {
// we got temperature info
boost::lock_guard<boost::mutex> l(this->log_mutex);
this->T = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
pos = line.find("B:");
if (pos != std::string::npos && line.size() > pos + 2) {
// we got bed temperature info
this->B = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
}
}
}
}
this->do_read();
}
void
GCodeSender::send(const std::vector<std::string> &lines, bool priority)
{
// append lines to queue
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
for (std::vector<std::string>::const_iterator line = lines.begin(); line != lines.end(); ++line) {
if (priority) {
this->priqueue.push_back(*line);
} else {
this->queue.push(*line);
}
}
}
this->send();
}
void
GCodeSender::send(const std::string &line, bool priority)
{
// append line to queue
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
if (priority) {
this->priqueue.push_back(line);
} else {
this->queue.push(line);
}
}
this->send();
}
void
GCodeSender::send()
{
this->io.post(boost::bind(&GCodeSender::do_send, this));
}
void
GCodeSender::do_send()
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
// printer is not connected or we're still waiting for the previous ack
if (!this->can_send) return;
std::string line;
while (!this->priqueue.empty() || (!this->queue.empty() && !this->queue_paused)) {
if (!this->priqueue.empty()) {
line = this->priqueue.front();
this->priqueue.pop_front();
} else {
line = this->queue.front();
this->queue.pop();
}
// strip comments
size_t comment_pos = line.find_first_of(';');
if (comment_pos != std::string::npos)
line.erase(comment_pos, std::string::npos);
boost::algorithm::trim(line);
// if line is not empty, send it
if (!line.empty()) break;
// if line is empty, process next item in queue
}
if (line.empty()) return;
// compute full line
++ this->sent;
#ifndef DEBUG_SERIAL
const auto line_num = this->sent;
#else
// In DEBUG_SERIAL mode, test line re-synchronization by sending bad line number 1/4 of the time
const auto line_num = std::rand() < RAND_MAX/4 ? 0 : this->sent;
#endif
std::string full_line = "N" + boost::lexical_cast<std::string>(line_num) + " " + line;
// calculate checksum
int cs = 0;
for (std::string::const_iterator it = full_line.begin(); it != full_line.end(); ++it)
cs = cs ^ *it;
// write line to device
full_line += "*";
full_line += boost::lexical_cast<std::string>(cs);
full_line += "\n";
#ifdef DEBUG_SERIAL
fs << ">> " << full_line << std::flush;
#endif
this->last_sent.push_back(line);
this->can_send = false;
while (this->last_sent.size() > KEEP_SENT) {
this->last_sent.pop_front();
}
// we can't supply boost::asio::buffer(full_line) to async_write() because full_line is on the
// stack and the buffer would lose its underlying storage causing memory corruption
std::ostream os(&this->write_buffer);
os << full_line;
boost::asio::async_write(this->serial, this->write_buffer, boost::bind(&GCodeSender::on_write, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void
GCodeSender::on_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
this->set_error_status(false);
if (error) {
if (this->open) {
this->do_close();
this->set_error_status(true);
}
return;
}
this->do_send();
}
void
GCodeSender::set_DTR(bool on)
{
#if defined(_WIN32) && !defined(__SYMBIAN32__)
boost::asio::serial_port_service::native_handle_type handle = this->serial.native_handle();
if (on)
EscapeCommFunction(handle, SETDTR);
else
EscapeCommFunction(handle, CLRDTR);
#else
int fd = this->serial.native_handle();
int status;
ioctl(fd, TIOCMGET, &status);
if (on)
status |= TIOCM_DTR;
else
status &= ~TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
#endif
}
void
GCodeSender::reset()
{
set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
set_DTR(true);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
} // namespace Slic3r
+81
View File
@@ -0,0 +1,81 @@
#ifndef slic3r_GCodeSender_hpp_
#define slic3r_GCodeSender_hpp_
#include "libslic3r.h"
#include <queue>
#include <boost/core/noncopyable.hpp>
#include <cstddef>
#include <boost/asio/io_service.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/asio/streambuf.hpp>
#include <list>
#include <deque>
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <boost/bind/bind.hpp>
#include <boost/thread.hpp>
namespace Slic3r {
namespace asio = boost::asio;
class GCodeSender : private boost::noncopyable {
public:
GCodeSender();
~GCodeSender();
bool connect(std::string devname, unsigned int baud_rate);
void send(const std::vector<std::string> &lines, bool priority = false);
void send(const std::string &s, bool priority = false);
void disconnect();
bool error_status() const;
bool is_connected() const;
bool wait_connected(unsigned int timeout = 3) const;
size_t queue_size() const;
void pause_queue();
void resume_queue();
void purge_queue(bool priority = false);
std::vector<std::string> purge_log();
std::string getT() const;
std::string getB() const;
void set_DTR(bool on);
void reset();
private:
asio::io_service io;
asio::serial_port serial;
boost::thread background_thread;
boost::asio::streambuf read_buffer, write_buffer;
bool open; // whether the serial socket is connected
bool connected; // whether the printer is online
bool error;
mutable boost::mutex error_mutex;
// this mutex guards queue, priqueue, can_send, queue_paused, sent, last_sent
mutable boost::mutex queue_mutex;
std::queue<std::string> queue;
std::list<std::string> priqueue;
bool can_send;
bool queue_paused;
size_t sent;
std::deque<std::string> last_sent;
// this mutex guards log, T, B
mutable boost::mutex log_mutex;
std::queue<std::string> log;
std::string T, B;
void set_baud_rate(unsigned int baud_rate);
void set_error_status(bool e);
void do_send();
void on_write(const boost::system::error_code& error, size_t bytes_transferred);
void do_close();
void do_read();
void on_read(const boost::system::error_code& error, size_t bytes_transferred);
void send();
};
} // namespace Slic3r
#endif /* slic3r_GCodeSender_hpp_ */
+1
View File
@@ -1,6 +1,7 @@
#include <boost/log/trivial.hpp>
#include "MedialAxis.hpp"
#include <boost/log/trivial.hpp>
#include <boost/polygon/polygon.hpp>
#include <cassert>
#include <cmath>
+1
View File
@@ -22,6 +22,7 @@
#include "Format/AssimpImport.hpp"
#include "ClipperUtils.hpp"
#include "Exception.hpp"
#include "Model.hpp"
#include "ModelArrange.hpp"
#include "Arrange.hpp"
#include "Geometry.hpp"
+1
View File
@@ -18,6 +18,7 @@
#include "TriangleMesh.hpp"
#include "CustomGCode.hpp"
#include "calib.hpp"
#include "enum_bitmask.hpp"
#include "TextConfiguration.hpp"
#include "EmbossShape.hpp"
#include "TriangleSelector.hpp"
+7
View File
@@ -10030,6 +10030,13 @@ static void extend_extruder_variant(DynamicPrintConfig& config, const unsigned i
printer_extruder_variant_opt->values.insert(printer_extruder_variant_opt->values.end(), variants_list.begin(), variants_list.end());
}
}
// 3. Size the machine limits to the rebuilt variants, padded with their first value like the other variant keys.
// They are not extruder option keys, so the resize loop in set_num_extruders skips them.
const auto &defaults = FullPrintConfig::defaults();
for (const std::string &key : printer_options_with_variant_2)
if (auto *opt = config.option<ConfigOptionFloats>(key))
opt->resize(config.get_parameter_size(key, num_extruders), defaults.option(key));
}
void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders)
+1
View File
@@ -44,6 +44,7 @@
#include "TDataStd_Name.hxx"
#include "BRepBuilderAPI_Transform.hxx"
#include "TopExp_Explorer.hxx"
#include "TopExp_Explorer.hxx"
#include "BRep_Tool.hxx"
#include "Font_BRepFont.hxx"
#include "Font_BRepTextBuilder.hxx"
+2
View File
@@ -25,6 +25,8 @@
#include <initializer_list>
#include <string_view>
#include <regex>
#include <string_view>
#include <algorithm>
#include <boost/system/error_code.hpp>
#include <boost/algorithm/string.hpp>
+2
View File
@@ -17,6 +17,7 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -29,6 +30,7 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include "GUI_Utils.hpp"
+3
View File
@@ -19,17 +19,20 @@
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/dataview.h>
#include <wx/panel.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/statbox.h>
#include <wx/tglbtn.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <map>
+3
View File
@@ -28,6 +28,7 @@
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/dataview.h>
#include <wx/panel.h>
@@ -35,12 +36,14 @@
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/gbsizer.h>
#include <wx/statbox.h>
#include <wx/tglbtn.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/webrequest.h>
#include <map>
#include <vector>
+1
View File
@@ -8,6 +8,7 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include "Jobs/ProgressIndicator.hpp"
+1
View File
@@ -10,6 +10,7 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include <wx/string.h>
#include "Jobs/ProgressIndicator.hpp"
+1
View File
@@ -9,6 +9,7 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include <wx/hyperlink.h>
#include "Jobs/ProgressIndicator.hpp"
+1
View File
@@ -9,6 +9,7 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include <wx/hyperlink.h>
#include "Jobs/ProgressIndicator.hpp"
@@ -18,6 +18,7 @@
#include <wx/string.h>
#include <wx/toplevel.h>
#include <wx/timer.h>
#include "Widgets/Button.hpp"
#include "CapsuleButton.hpp"
namespace Slic3r::GUI { struct IntEvent; }
+1
View File
@@ -46,6 +46,7 @@
#include "libslic3r/Model.hpp"
#include "libslic3r/Polygon.hpp"
#include "MainFrame.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "Jobs/BoostThreadWorker.hpp"
#include "Jobs/PlaterWorker.hpp"
+1
View File
@@ -29,6 +29,7 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/Bonjour.hpp"
#include "slic3r/GUI/Widgets/DialogButtons.hpp"
+2
View File
@@ -11,6 +11,7 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -23,6 +24,7 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include "GUI_Utils.hpp"
+1
View File
@@ -1,3 +1,4 @@
// #include "libslic3r/GCodeSender.hpp"
#include "ConfigManipulation.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
@@ -15,6 +15,7 @@
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <wx/string.h>
#include <wx/colour.h>
+1
View File
@@ -11,6 +11,7 @@
#include "GUI_App.hpp"
#include "DeviceErrorDialog.hpp"
#include "Plater.hpp"
#include "GUI_App.hpp"
#include "ReleaseNote.hpp"
#include <string>
#include <boost/log/trivial.hpp>
+1
View File
@@ -46,6 +46,7 @@
#include "nanosvg/nanosvgrast.h"
#include "libslic3r/Utils.hpp"
#include "GUI_App.hpp"
#include <boost/log/trivial.hpp>
#include <wx/dcgraph.h>
#include <wx/dcmemory.h>
+1
View File
@@ -50,6 +50,7 @@
#include "AboutDialog.hpp"
#include "MsgDialog.hpp"
#include "Plater.hpp"
#include "format.hpp"
#include "WebUserLoginDialog.hpp"
+2
View File
@@ -172,10 +172,12 @@
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/I18N.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/InstanceLock.hpp"
#include "libslic3r/Thread.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/Utils.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include "slic3r/plugin/host/PluginHostUi.hpp"
#include "slic3r/plugin/PythonInterpreter.hpp"
+1
View File
@@ -11,6 +11,7 @@
#include "libslic3r/Config.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <cstdint>
+1
View File
@@ -66,6 +66,7 @@
#include <nanosvg/nanosvg.h>
#include <nanosvg/nanosvgrast.h>
#include "OpenGLManager.hpp"
#include "GUI_App.hpp"
namespace Slic3r {
namespace GUI {
+1
View File
@@ -113,6 +113,7 @@
#include "MsgDialog.hpp"
#include "Notebook.hpp"
#include "GUI_Factories.hpp"
#include "GUI_ObjectList.hpp"
#include "NotificationManager.hpp"
#include "MarkdownTip.hpp"
#include "NetworkTestDialog.hpp"
+1
View File
@@ -21,6 +21,7 @@
#include "wx/evtloop.h"
#include "MainFrame.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
using json = nlohmann::json;
+1
View File
@@ -45,6 +45,7 @@
#include "MainFrame.hpp"
#include "MediaPlayCtrl.h"
#include "MediaFilePanel.h"
#include "Plater.hpp"
#include "BindDialog.hpp"
#include "DeviceCore/DevManager.h"
+3
View File
@@ -23,6 +23,7 @@
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/grid.h>
#include <wx/dataview.h>
#include <wx/panel.h>
@@ -30,12 +31,14 @@
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/gbsizer.h>
#include <wx/statbox.h>
#include <wx/tglbtn.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/webrequest.h>
#include <map>
#include <vector>
+1
View File
@@ -4,6 +4,7 @@
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "libslic3r_version.h"
#include "slic3r/Utils/Http.hpp"
#include "libslic3r/AppConfig.hpp"
+1
View File
@@ -30,6 +30,7 @@
#include "MainFrame.hpp"
#include <miniz.h>
#include <OrcaCloudServiceAgent.hpp>
#include <wx/event.h>
#include <wx/utils.h>
using json = nlohmann::json;
+1
View File
@@ -79,6 +79,7 @@
#include "libslic3r_version.h"
#include "wxExtensions.hpp"
#include <nlohmann/json.hpp>
#include "libslic3r/Preset.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
+1
View File
@@ -4,6 +4,7 @@
#include <memory>
#include <string>
#include <functional>
#include <string>
#include "Jobs/ProgressIndicator.hpp"
+58 -25
View File
@@ -14,6 +14,7 @@
#include "Widgets/DialogButtons.hpp"
#include "Widgets/StaticLine.hpp"
#include "Widgets/StateColor.hpp"
#include "Widgets/SwitchButton.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Preset.hpp"
@@ -864,34 +865,25 @@ void PublishSettingsDialog::build_option_model()
};
// --- Phase 1: printer per-extruder retraction settings (first, mirroring the sidebar's
// Printer group), from the printer tab's "Extruder"/"Extruder N" pages. One inner tab per
// extruder (e.g. "Left Extruder"/"Right Extruder" via Tab::translate_category), each holding
// that extruder's Retraction and Z-Hop rows with per-extruder "#N" values.
// Printer group), from the printer tab's "Extruder" page. One inner tab per extruder (named
// as on the printer tab's switch, e.g. "Left Extruder"/"Right Extruder" via
// Tab::translate_category), each holding that extruder's Retraction and Z-Hop rows with
// per-extruder "#N" values.
{
size_t g = section_group_for(Section::Printer);
std::set<std::string> printer_added;
for (Tab* tab : wxGetApp().tabs_list) {
if (tab->m_type != Preset::TYPE_PRINTER)
// The page's controls edit the extruder chosen on the printer tab's switch, so its
// option list is read once per extruder.
auto* printer_tab = dynamic_cast<TabPrinter*>(tab);
const Page* page = printer_tab ? printer_tab->extruder_page() : nullptr;
if (page == nullptr)
continue;
for (const PageShp& page : tab->m_pages) {
if (!page->title().StartsWith("Extruder"))
continue;
// The extruder index of this page: its options are appended with the same
// "#N" opt_index (opt.second.second), so derive the tab's index from the first
// allowlisted option; skip the page when none is found (defensive).
int extruder_idx = -1;
for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) {
if (optgroup->title != "Retraction" && optgroup->title != "Z-Hop")
continue;
for (const auto& opt : optgroup->opt_map())
if (extruder_idx < 0)
extruder_idx = opt.second.second;
if (extruder_idx >= 0)
break;
}
if (extruder_idx < 0)
continue;
const wxString page_title = Tab::translate_category(page->title(), tab->m_type);
const size_t extruders_count = printer_tab->m_extruders_count;
for (size_t extruder_idx = 0; extruder_idx < extruders_count; ++extruder_idx) {
const wxString page_title = Tab::translate_category(extruders_count > 1 ? wxString::Format("Extruder %d", int(extruder_idx + 1)) : wxString("Extruder"), tab->m_type);
// Retraction and Z-Hop values are stored per variant column, not per extruder.
const int variant_index = printer_tab->extruder_variant_index(int(extruder_idx));
for (const ConfigOptionsGroupShp& optgroup : page->m_optgroups) {
// Allowlist on the untranslated optgroup title; the "Retraction when
// switching material" group is intentionally skipped.
@@ -899,17 +891,17 @@ void PublishSettingsDialog::build_option_model()
continue;
const wxString subcategory = _(optgroup->title);
for (const auto& opt : optgroup->opt_map()) {
const std::string& opt_id = opt.first;
const std::string& pure_key = opt.second.first;
// Rows are keyed by the full per-extruder "#N" opt_id so each extruder
// tab publishes its own value; GetPublishedKeys() emits the checked rows
// as-is.
const std::string opt_id = pure_key + "#" + std::to_string(variant_index);
if (!printer_added.insert(opt_id).second)
continue;
wxString label, value, unit;
if (!option_text(opt_id, pure_key, label, value, unit))
continue;
size_t cat_index = category_index_for(page_title, Section::Printer, g, size_t(extruder_idx));
size_t cat_index = category_index_for(page_title, Section::Printer, g, extruder_idx);
size_t sub_index = subcategory_index_for(cat_index, subcategory, optgroup->icon);
add_row_ui(opt_id, label, value, unit, cat_index, sub_index);
}
@@ -1098,6 +1090,10 @@ void PublishSettingsDialog::build_option_model()
for (SectionGroup& section : m_sections)
if (!section.categories.empty())
section.tabs->SelectItem(0);
// Orca: the Printer section shows its extruders on the same switch as the printer tab's Extruder page.
for (size_t s = 0; s < m_sections.size(); ++s)
if (m_sections[s].kind == Section::Printer && m_sections[s].categories.size() > 1)
setup_variant_switch(s);
if (!m_sections.empty()) {
m_outer_tabs->SelectItem(0);
show_outer_page(0);
@@ -1630,6 +1626,8 @@ void PublishSettingsDialog::show_inner_page(size_t section_index, int inner_inde
section.selected_mixed = -1;
}
section.selected_inner = inner_index;
if (section.variant_switch != nullptr)
section.variant_switch->SetSelection(inner_index); // fires its event, which ignores the shown page
Category& category = m_categories[section.categories[inner_index]];
category.page->Show();
category.scroll->FitInside();
@@ -1639,6 +1637,32 @@ void PublishSettingsDialog::show_inner_page(size_t section_index, int inner_inde
section.page_host_sizer->Layout();
}
void PublishSettingsDialog::setup_variant_switch(size_t section_index)
{
SectionGroup& section = m_sections[section_index];
std::vector<wxString> titles;
for (size_t category : section.categories)
titles.push_back(m_categories[category].title);
section.variant_switch = new MultiSwitchButton(section.page);
section.variant_switch->SetFitToOptions();
section.variant_switch->SetOptions(titles);
section.variant_switch->SetSelection(section.selected_inner);
section.variant_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this, section_index](wxCommandEvent& evt) {
evt.Skip();
// The hidden tab strip stays the selection model; its event shows the page.
SectionGroup& sec = m_sections[section_index];
if (evt.GetInt() != sec.selected_inner)
sec.tabs->SelectItem(evt.GetInt());
});
// The switch takes the place of the tab strip, centered like on the printer tab.
wxSizer* page_sizer = section.page->GetSizer();
page_sizer->Insert(1, section.variant_switch, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(4));
section.tabs->Hide();
section.page->Layout();
}
void PublishSettingsDialog::show_mixed_page(size_t section_index, int mixed_index)
{
if (section_index >= m_sections.size())
@@ -2184,6 +2208,13 @@ void PublishSettingsDialog::refresh_tab_indicators()
for (size_t i = 0; i < section.categories.size(); ++i) {
const bool on = category_has_selection(m_categories[section.categories[i]]);
section.tabs->SetItemIndicator(static_cast<unsigned int>(i), on);
// The switch has no indicator dot; mark its option text instead.
if (section.variant_switch != nullptr) {
const wxString& title = m_categories[section.categories[i]].title;
const wxString text = on ? title + wxString(" ") + wxString(wxUniChar(0x2022)) : title;
if (section.variant_switch->GetOptionText(static_cast<unsigned int>(i)) != text)
section.variant_switch->SetOptionText(static_cast<unsigned int>(i), text);
}
any = any || on;
}
if (section.mixed_tabs != nullptr)
@@ -2248,6 +2279,8 @@ void PublishSettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
section.tabs->Rescale();
if (section.mixed_tabs != nullptr)
section.mixed_tabs->Rescale();
if (section.variant_switch != nullptr)
section.variant_switch->Rescale();
}
// Refresh the per-row Color chips at the new DPI (they carry the slot number too).
+7
View File
@@ -37,6 +37,7 @@ namespace Slic3r { class DynamicPrintConfig; }
class TextInput;
class StaticLine;
class CheckBox;
class MultiSwitchButton;
namespace Slic3r { namespace GUI {
@@ -195,6 +196,10 @@ private:
ScalableBitmap icon_bmp; // tab icon next to the title; rescaled on DPI change
wxPanel* page{nullptr};
TabCtrl* tabs{nullptr};
// Printer section with several extruders: the extruder switch shown instead of `tabs`, the
// same MultiSwitchButton (and option names) as the printer tab's Extruder page. One option
// per entry of `categories`, named by its title; `tabs` stays as the hidden selection model.
MultiSwitchButton* variant_switch{nullptr};
// Second tab strip, below the main one, listing only the mixed-color filament slots.
// Present on the Material section only (null elsewhere).
TabCtrl* mixed_tabs{nullptr};
@@ -277,6 +282,8 @@ private:
bool row_is_visible(const Row& row) const;
void apply_visibility();
void bind_tab_events();
// Replaces a section's inner tab strip with a variant switch (see SectionGroup::variant_switch).
void setup_variant_switch(size_t section_index);
TabCtrl* m_outer_tabs{nullptr};
wxPanel* m_outer_host{nullptr};
+3
View File
@@ -19,6 +19,7 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -31,8 +32,10 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include <wx/event.h>
#include <wx/hyperlink.h>
#include <wx/richtext/richtextctrl.h>
#include "AmsMappingPopup.hpp"
+2
View File
@@ -25,6 +25,7 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -37,6 +38,7 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include <wx/srchctrl.h>
+2
View File
@@ -16,6 +16,7 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -28,6 +29,7 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include <wx/srchctrl.h>
+2
View File
@@ -20,6 +20,7 @@
#include <wx/dataview.h>
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/dataview.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
@@ -32,6 +33,7 @@
#include <wx/dialog.h>
#include <wx/popupwin.h>
#include <wx/spinctrl.h>
#include <wx/artprov.h>
#include <wx/wrapsizer.h>
#include <wx/srchctrl.h>
+244 -104
View File
@@ -1,3 +1,4 @@
// #include "libslic3r/GCodeSender.hpp"
//#include "slic3r/Utils/Serial.hpp"
#include "Tab.hpp"
#include "PresetHints.hpp"
@@ -101,6 +102,8 @@
#include "Widgets/MultiNozzleSync.hpp"
#include "Widgets/SwitchButton.hpp"
#include "Widgets/TabCtrl.hpp"
#include "Widgets/ComboBox.hpp"
#include "Search.hpp"
#include "BedShapeDialog.hpp"
#include "libslic3r/GCode/Thumbnails.hpp"
#include "WipeTowerDialog.hpp"
@@ -808,6 +811,12 @@ wxString Tab::translate_category(const wxString& title, Preset::Type preset_type
}
return _("Extruder") + title.SubString(8, title.Last());
}
// Orca: one "Extruder" page serves all extruders; name it "Extruders" when there are several.
if (preset_type == Preset::TYPE_PRINTER && title == "Extruder") {
auto preset = wxGetApp().preset_bundle;
if (preset && preset->get_printer_extruder_count() > 1)
return _("Extruders");
}
return _(title);
}
@@ -1150,6 +1159,38 @@ std::string Tab::options_list_storage_key(const std::string& opt_key) const
return (serialized || is_plugin_field) ? opt_key : opt_key + "#0";
}
// Orca: deep_diff() flags every vector entry at or past the reference vector's length as changed,
// whatever its value (e.g. the values of an extruder added by raising the extruder count). A vector
// grows by copying its first entry (ConfigOptionVector::resize), so such an entry only counts as
// changed when it differs from the reference's first entry, as before Orca's deep_diff() change.
// The change of the count itself shows on "extruders_count".
static void drop_unchanged_added_entries(std::vector<std::string> &options, const DynamicPrintConfig &current, const Preset *reference)
{
if (reference == nullptr)
return;
// deep_diff() lists a key's entries one after another, so serialize each key's vectors once.
std::string serialized_key;
std::vector<std::string> cur_values;
std::string ref_first;
options.erase(std::remove_if(options.begin(), options.end(), [&](const std::string &opt) {
const auto pos = opt.find('#');
if (pos == std::string::npos)
return false;
const std::string key = opt.substr(0, pos);
const size_t idx = size_t(std::atoi(opt.c_str() + pos + 1));
auto ref = dynamic_cast<const ConfigOptionVectorBase *>(reference->config.option(key));
auto cur = dynamic_cast<const ConfigOptionVectorBase *>(current.option(key));
if (ref == nullptr || cur == nullptr || idx < ref->size() || ref->size() == 0 || idx >= cur->size())
return false;
if (key != serialized_key) {
serialized_key = key;
cur_values = cur->vserialize();
ref_first = ref->vserialize().front();
}
return cur_values[idx] == ref_first;
}), options.end());
}
void Tab::update_all_extruder_options_status()
{
if (!m_extruder_switch && !m_variant_combo) {
@@ -1180,6 +1221,8 @@ void Tab::update_all_extruder_options_status()
auto dirty_options = m_presets->current_dirty_options(true);
auto nonsys_options = m_presets->current_different_from_parent_options(true);
if (m_type == Preset::TYPE_PRINTER)
update_custom_dirty(dirty_options, nonsys_options);
auto filter_extruder_options = [](const std::vector<std::string>& options) {
std::vector<std::string> filtered_options;
for (const auto& opt : options) {
@@ -1228,7 +1271,7 @@ void Tab::update_extruder_switch_colors()
if (m_active_page) {
if (m_active_page->title() == "Speed" || m_active_page->title() == "Motion ability" || m_active_page->title() == "Filament" ||
m_active_page->title() == "Setting Overrides" || m_active_page->title() == "Multimaterial") {
m_active_page->title() == "Setting Overrides" || m_active_page->title() == "Multimaterial" || is_printer_extruder_page(m_active_page)) {
for (auto page_ptr : m_pages) {
if (page_ptr.get() == m_active_page) {
pages_to_check.push_back(page_ptr);
@@ -1259,8 +1302,8 @@ void Tab::update_extruder_switch_colors()
void Tab::check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector<PageShp>& pages_to_check)
{
int config_index = index;
int extruder_id = index;
if (m_type == Preset::TYPE_PRINT || m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_MODEL) {
int extruder_id;
NozzleVolumeType nozzle_type;
parse_extruder_selection(index, extruder_id, nozzle_type);
@@ -1293,6 +1336,10 @@ void Tab::check_extruder_options_status(int index, bool &sys_extruder, bool &mod
}
std::string target_opt_key = base_opt_key + "#" + std::to_string(config_index * stride);
// Orca: on the printer's Extruder page only per-variant options use the variant column,
// the others (nozzle_diameter, extruder_offset, ...) are indexed by the extruder.
if (is_printer_extruder_page(page.get()) && printer_options_with_variant_1.count(base_opt_key) == 0)
target_opt_key = base_opt_key + "#" + std::to_string(extruder_id);
auto status_iter = m_all_extruder_options_status.find(target_opt_key);
if (status_iter != m_all_extruder_options_status.end()) {
@@ -1333,18 +1380,6 @@ void TabPrinter::init_options_list()
Tab::init_options_list();
if (m_printer_technology == ptFFF)
m_options_list.emplace("extruders_count", m_opt_status_value);
for (size_t i = 1; i < m_extruders_count; ++i) {
wxString target_title = wxString::Format("Extruder %d", int(i + 1));
for (auto &page : m_pages) {
if (page->title() == target_title) {
for (auto group : page->m_optgroups) {
for (auto &opt : group->opt_map())
m_options_list.emplace(opt.first, m_opt_status_value);
}
break;
}
}
}
}
void TabPrinter::msw_rescale()
@@ -1434,7 +1469,7 @@ void Tab::update_changed_tree_ui()
get_sys_and_mod_flags("compatible_printers", sys_page, modified_page);
}
}
if (page->title() == "Speed" || page->title() == "Motion ability" || page->title() == "Filament" || page->title() == "Setting Overrides" || page->title() == "Multimaterial") {
if (page->title() == "Speed" || page->title() == "Motion ability" || page->title() == "Filament" || page->title() == "Setting Overrides" || page->title() == "Multimaterial" || is_printer_extruder_page(page.get())) {
auto options = generate_extruder_options();
for (size_t switch_index = 0; switch_index < options.size(); ++switch_index) {
std::vector<PageShp> pages_to_check = { page };
@@ -1537,6 +1572,10 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/)
m_postpone_update_ui = false;
// Orca: the restored config may have another extruder count than the tab shows.
if (auto printer_tab = dynamic_cast<TabPrinter *>(this))
printer_tab->sync_extruders_count();
// When all values are rolled, then we have to update whole tab in respect to the reverted values
update();
if (m_active_page)
@@ -1874,6 +1913,17 @@ static wxString pad_combo_value_for_config(const DynamicPrintConfig &config)
return config.opt_bool("pad_enable") ? (config.opt_bool("pad_around_object") ? _("Around object") : _("Below object")) : _("None");
}
// Rebuilds the variant switch of every tab that has one, e.g. after the extruder count or a nozzle volume type changed.
static void update_all_extruder_variants(int extruder_idx = -1)
{
for (auto tab : wxGetApp().tabs_list)
tab->update_extruder_variants(extruder_idx);
if (auto tab = wxGetApp().plate_tab)
tab->update_extruder_variants(extruder_idx);
for (auto tab : wxGetApp().model_tabs_list)
tab->update_extruder_variants(extruder_idx);
}
void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
{
// Orca:
@@ -2316,15 +2366,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
if (opt_key.find("nozzle_volume_type") != std::string::npos) {
int extruder_idx = std::atoi(opt_key.substr(opt_key.find_last_of('#') + 1).c_str());
for (auto tab : wxGetApp().tabs_list) {
tab->update_extruder_variants(extruder_idx);
}
if (auto tab = wxGetApp().plate_tab) {
tab->update_extruder_variants(extruder_idx);
}
for (auto tab : wxGetApp().model_tabs_list) {
tab->update_extruder_variants(extruder_idx);
}
update_all_extruder_variants(extruder_idx);
if (wxGetApp().app_config->get("auto_calculate_flush") == "all") {
wxGetApp().plater()->sidebar().auto_calc_flushing_volumes(-1,extruder_idx);
}
@@ -5682,9 +5724,13 @@ if (is_marlin_flavor)
size_t extruders_count = size_t(boost::any_cast<int>(v));
wxTheApp->CallAfter([this, opt_key, value, extruders_count]() {
if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") {
const size_t old_extruders_count = m_extruders_count;
extruders_count_changed(extruders_count);
init_options_list(); // m_options_list should be updated before UI updating
update_dirty();
// Orca: the variant switches (here, Process in the sidebar, ...) list one option per extruder.
if (m_extruders_count != old_extruders_count)
update_all_extruder_variants();
if (opt_key == "single_extruder_multi_material") { // the single_extruder_multimaterial was added to force pages
on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped
@@ -5694,6 +5740,7 @@ if (is_marlin_flavor)
// Orca: we use a different logic here. If SEMM is enabled, we set extruder count to 1.
#if 1
extruders_count_changed(1);
update_all_extruder_variants();
#else
std::vector<double> nozzle_diameters =
@@ -5765,14 +5812,13 @@ if (is_marlin_flavor)
m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page);
}
// Orca: build missed extruder pages
for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) {
const wxString& page_name = (m_extruders_count > 1) ? wxString::Format("Extruder %d", int(extruder_idx + 1)) : wxString::Format("Extruder");
//# build page
//const wxString& page_name = wxString::Format("Extruder %d", int(extruder_idx + 1));
auto page = add_options_page(page_name, "custom-gcode_extruder", true); // ORCA: icon only visible on placeholders
m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page);
// Orca: a single "Extruder" page serves all extruders. Its controls are created once for extruder 0;
// switch_excluder() re-targets them to the extruder selected on the variant switch (m_extruder_switch),
// which the printer tab shows on this page too.
if (extruder_page() == nullptr && m_extruders_count > 0) {
const size_t extruder_idx = 0;
auto page = add_options_page(L("Extruder"), "custom-gcode_extruder", true); // ORCA: icon only visible on placeholders
m_pages.insert(m_pages.begin() + n_before_extruders, page);
auto optgroup = page->new_optgroup(L("Basic information"), L"param_information", -1, true);
optgroup->append_single_option_line("nozzle_diameter", "printer_extruder_basic_information#nozzle-diameter", extruder_idx);
@@ -5785,8 +5831,10 @@ if (is_marlin_flavor)
option.opt.full_width = true;
optgroup->append_single_option_line(option, "printer_extruder_basic_information#extruder-offset-position");
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
optgroup->m_on_change = [this](const t_config_option_key& opt_key, boost::any value)
{
// The page edits the extruder selected on the variant switch.
const size_t extruder_idx = size_t(get_current_active_extruder());
bool is_SEMM = m_config->opt_bool("single_extruder_multi_material");
if (is_SEMM && m_extruders_count > 1 && boost::starts_with(opt_key, "nozzle_diameter"))
{
@@ -5881,22 +5929,22 @@ if (is_marlin_flavor)
//optgroup->append_line(line);
#endif
}
// BBS. No extra extruder page for single physical extruder machine
// # remove extra pages
auto &first_extruder_title = const_cast<wxString &>(m_pages[n_before_extruders]->title());
if (m_extruders_count < m_extruders_count_old) {
m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count,
m_pages.begin() + n_before_extruders + m_extruders_count_old);
if (m_extruders_count == 1)
first_extruder_title = wxString::Format("Extruder");
} else if (m_extruders_count_old == 1) {
first_extruder_title = wxString::Format("Extruder %d", 1);
}
auto & index = wxGetApp().sidebar().settings_index();
for (auto &group : m_pages[n_before_extruders]->m_optgroups) {
group->set_config_category_and_type(first_extruder_title, m_type);
for (auto &opt : group->opt_map())
index.add_key(opt.first + "#0", m_type, group->title, first_extruder_title, group->icon);
// The page holds only the "#0" controls, so register every extruder's options with the search
// index under "Extruder N"; a search hit selects that extruder (TabPrinter::activate_option()).
if (Page *page = extruder_page()) {
auto &index = wxGetApp().sidebar().settings_index();
for (auto &group : page->m_optgroups) {
group->set_config_category_and_type(page->title(), m_type);
for (auto &opt : group->opt_map()) {
if (opt.second.second < 0)
continue;
for (size_t i = 0; i < m_extruders_count; ++i) {
const wxString category = m_extruders_count > 1 ? wxString::Format("Extruder %d", int(i + 1)) : wxString("Extruder");
index.add_key(opt.second.first + "#" + std::to_string(i), m_type, group->title, category, group->icon);
}
}
}
}
Thaw();
@@ -6177,12 +6225,6 @@ void TabPrinter::toggle_options()
return;
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto extruders = m_config->option<ConfigOptionEnumsGeneric>("extruder_type");
auto get_index_for_extruder =
[this, &extruders](int extruder_id, int stride = 1) {
return m_config->get_index_for_extruder(extruder_id + 1, "printer_extruder_id",
ExtruderType(extruders->values[extruder_id]), get_actual_nozzle_volume_type(extruder_id), "printer_extruder_variant", stride);
};
//BBS: whether the preset is Bambu Lab printer
bool is_BBL_printer = false;
@@ -6257,14 +6299,13 @@ void TabPrinter::toggle_options()
toggle_option("tool_change_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
toggle_option("wait_for_temp_on_wipe_tower", !bSEMM && supports_wipe_tower_2 && extruders_count > 1);
}
wxString extruder_number;
long val = 1;
if ( m_active_page->title().IsSameAs(L("Extruder")) ||
(m_active_page->title().StartsWith("Extruder ", &extruder_number) && extruder_number.ToLong(&val) &&
val > 0 && (size_t)val <= m_extruders_count))
if (m_active_page->title() == L("Extruder") && m_extruders_count > 0)
{
size_t i = size_t(val - 1);
int variant_index = get_index_for_extruder(i);
// Orca: the single Extruder page edits the extruder selected on the variant switch; its
// controls carry field index 0 (i), the values are read for `extruder`.
const size_t i = 0;
const size_t extruder = std::min<size_t>(size_t(get_current_active_extruder()), m_extruders_count - 1);
const int variant_index = extruder_variant_index(int(extruder));
bool have_retract_length = m_config->opt_float("retraction_length", variant_index) > 0;
toggle_option("extruder_printable_area", false, i); // disable
@@ -6291,7 +6332,7 @@ void TabPrinter::toggle_options()
vec.resize(0);
vec = {"retract_lift_above", "retract_lift_below", "retract_lift_enforce"};
for (auto el : vec)
toggle_option(el, retraction && (m_config->opt_float("z_hop", i) > 0), i);
toggle_option(el, retraction && (m_config->opt_float("z_hop", variant_index) > 0), i);
// some options only apply when not using firmware retraction
vec.resize(0);
@@ -6344,7 +6385,7 @@ void TabPrinter::toggle_options()
toggle_option("long_retractions_when_cut", !use_firmware_retraction && m_config->opt_int("enable_long_retraction_when_cut"), i);
toggle_line("retraction_distances_when_cut", m_config->opt_bool("long_retractions_when_cut", variant_index), i);
toggle_option("travel_slope", m_config->opt_enum("z_hop_types", i) != ZHopType::zhtNormal, i);
toggle_option("travel_slope", m_config->opt_enum("z_hop_types", variant_index) != ZHopType::zhtNormal, i);
}
if (m_active_page->title() == L("Motion ability")) {
@@ -6418,12 +6459,16 @@ void TabPrinter::on_value_change(const std::string& opt_key, const boost::any& v
return;
const int pos = opt_key.find("#");
// Orca: fields of the single Extruder page keep index 0 but edit the selected extruder.
const int data_idx = pos > 0 ? extruder_page_data_index(opt_key) : -1;
if (pos > 0) {
std::string temp_str = opt_key;
boost::erase_head(temp_str, pos + 1);
int orig_opt_idx = static_cast<size_t>(atoi(temp_str.c_str()));
int opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0;
if (data_idx >= 0)
opt_idx = data_idx;
std::string opt_key_pure = opt_key;
boost::erase_tail(opt_key_pure, opt_key_pure.size() - pos);
@@ -6458,7 +6503,12 @@ void TabPrinter::on_value_change(const std::string& opt_key, const boost::any& v
}
}
Tab::on_value_change(opt_key, value);
// Orca: report Extruder page changes as "key#<extruder>", as the former "Extruder N" pages did,
// so per-extruder handling in Tab::on_value_change() sees the extruder that was edited.
if (data_idx >= 0)
Tab::on_value_change(opt_key.substr(0, pos) + "#" + std::to_string(get_current_active_extruder()), value);
else
Tab::on_value_change(opt_key, value);
}
void TabPrinter::update()
@@ -7390,12 +7440,7 @@ bool Tab::tree_sel_change_delayed(wxCommandEvent& event)
// update_undo_buttons();
this->OnActivate();
m_parent->set_active_tab(this);
if (m_variant_sizer) {
wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder "));
if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown());
GetParent()->Layout();
}
update_variant_sizer_visibility();
m_page_view->Thaw();
return false;
@@ -7406,12 +7451,7 @@ bool Tab::tree_sel_change_delayed(wxCommandEvent& event)
return false;
m_active_page = page;
if (m_variant_sizer) {
wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder"));
if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown());
GetParent()->Layout();
}
update_variant_sizer_visibility();
auto throw_if_canceled = std::function<void()>([this](){
#ifdef WIN32
@@ -8237,9 +8277,11 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
m_actual_nozzle_volumes.resize(extruder_nums, NozzleVolumeType::nvtStandard);
for (int i = 0; i < extruder_nums; i++) m_actual_nozzle_volumes[i] = (NozzleVolumeType)nozzle_volumes->values[i];
// Orca: a non-Bambu dual-nozzle printer has two extruders but a single variant column, so
// the nozzle switch and sync button have nothing to act on. Only enable with real variants.
if (extruder_nums >= 2 && m_preset_bundle->support_different_extruders()) {
// Orca: when every extruder uses the same variant (e.g. a non-Bambu dual-nozzle printer), the
// switch has no nozzle variants to select and nothing to sync. The printer tab still enables it
// to choose the extruder its Extruder and Motion ability pages edit.
m_extruder_switch_variants = extruder_nums >= 2 && m_preset_bundle->support_different_extruders();
if (m_extruder_switch_variants || (m_type == Preset::TYPE_PRINTER && extruder_nums >= 2)) {
auto options = generate_extruder_options();
m_extruder_switch->SetOptions(options);
@@ -8278,12 +8320,8 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
if (m_type == Preset::TYPE_PRINT) {
update_pages_with_multi_variant();
}
if (m_variant_sizer) {
wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
m_main_sizer->Show(m_variant_sizer, variant_ctrl->IsThisEnabled() && m_active_page && !m_active_page->m_opt_id_map.empty() && !m_active_page->title().StartsWith("Extruder "));
if (m_extruder_sync) m_extruder_sync->Show(variant_ctrl->IsShown());
GetParent()->Layout();
}
update_extruder_switch_colors();
update_variant_sizer_visibility();
}
// The variant switch tags are the narrowest place a volume type is named, so they abbreviate it;
@@ -8358,12 +8396,21 @@ std::vector<wxString> Tab::generate_extruder_options()
return options;
}
// Orca: the printer tab has one tag per extruder, named as its former "Extruder N" pages were.
// parse_extruder_selection() counts a hybrid extruder as two tags; that still lines up because
// only the last extruder can be hybrid.
if (m_type == Preset::TYPE_PRINTER) {
for (int i = 0; i < extruder_nums; ++i)
options.push_back(translate_category(wxString::Format("Extruder %d", i + 1), m_type));
return options;
}
std::string pt = m_preset_bundle->printers.get_edited_preset().get_printer_type(m_preset_bundle);
// Orca: the main/deputy toolhead names describe a dual-nozzle printer, where extruder 0 is the
// left (deputy) and extruder 1 the right (main) nozzle. From three extruders on the tools are
// interchangeable, so name them by index instead of repeating one side.
// Orca: the main/deputy toolhead names describe a Bambu dual-nozzle printer, where extruder 0 is
// the left (deputy) and extruder 1 the right (main) nozzle. Other printers number their tools.
const bool toolhead_names = extruder_nums == 2 && m_preset_bundle->is_bbl_vendor();
for (int i = 0; i < extruder_nums; ++i) {
wxString extruder_name = extruder_nums > 2 ? wxString::Format("T%d", i + 1) :
wxString extruder_name = !toolhead_names ? wxString::Format("T%d", i + 1) :
_L(DevPrinterConfigUtil::get_toolhead_display_name(
pt, (i == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID,
ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true));
@@ -8380,6 +8427,71 @@ std::vector<wxString> Tab::generate_extruder_options()
return options;
}
Page *TabPrinter::extruder_page() const
{
for (const PageShp &page : m_pages)
if (page->title() == "Extruder")
return page.get();
return nullptr;
}
int TabPrinter::extruder_page_data_index(const std::string &field_id) const
{
if (Page *page = extruder_page())
for (const auto &group : page->m_optgroups)
if (auto it = group->opt_map().find(field_id); it != group->opt_map().end())
return it->second.second;
return -1;
}
int TabPrinter::extruder_variant_index(int extruder)
{
const auto *extruders = m_config->option<ConfigOptionEnumsGeneric>("extruder_type");
const int index = extruder < int(extruders->size()) ?
m_config->get_index_for_extruder(extruder + 1, "printer_extruder_id", ExtruderType(extruders->values[extruder]),
get_actual_nozzle_volume_type(extruder), "printer_extruder_variant") :
-1;
return index < 0 ? extruder : index;
}
void TabPrinter::update_custom_dirty(std::vector<std::string> &dirty_options, std::vector<std::string> &nonsys_options)
{
drop_unchanged_added_entries(dirty_options, *m_config, &m_presets->get_selected_preset());
drop_unchanged_added_entries(nonsys_options, *m_config, m_presets->get_selected_preset_parent());
}
void TabPrinter::sync_extruders_count()
{
if (m_printer_technology != ptFFF)
return;
const auto *nozzle_diameter = m_config->option<ConfigOptionFloats>("nozzle_diameter");
if (nozzle_diameter == nullptr || nozzle_diameter->size() == m_extruders_count)
return;
extruders_count_changed(nozzle_diameter->size());
init_options_list();
update_all_extruder_variants();
}
void TabPrinter::activate_option(const std::string &opt_key, const wxString &category)
{
wxString number;
long n = 0;
const bool numbered = category.StartsWith("Extruder ", &number) && number.ToLong(&n);
if (extruder_page() == nullptr || (!numbered && category != "Extruder")) {
Tab::activate_option(opt_key, category);
return;
}
// Selecting fires the switch's event, which re-targets the page (switch_excluder()).
const int extruder_idx = (n >= 1 && n <= long(m_extruders_count)) ? int(n - 1) : 0;
if (m_extruder_switch && m_extruder_switch->IsThisEnabled() && extruder_idx != get_current_active_extruder())
m_extruder_switch->SetSelection(calculate_selection_index_for_extruder(extruder_idx, get_actual_nozzle_volume_type(extruder_idx)));
// The page's controls are created for index 0.
const auto pos = opt_key.find('#');
Tab::activate_option(pos == std::string::npos ? opt_key : opt_key.substr(0, pos) + "#0", "Extruder");
}
NozzleVolumeType Tab::get_actual_nozzle_volume_type(int extruder_id)
{
int extruder_count = m_preset_bundle->get_printer_extruder_count();
@@ -8456,18 +8568,44 @@ bool Tab::get_extruder_sync_enable_state(int extruder_id)
return false;
}
bool Tab::variant_switch_active() const
{
if (m_extruder_switch)
return m_extruder_switch->IsThisEnabled();
return m_variant_combo && m_variant_combo->IsThisEnabled();
}
void Tab::update_variant_sizer_visibility()
{
if (!m_variant_sizer)
return;
const bool show = variant_switch_active() && m_active_page && !m_active_page->m_opt_id_map.empty();
m_main_sizer->Show(m_variant_sizer, show);
if (m_extruder_sync) {
m_extruder_sync->Show(show);
// Orca: copying between extruders is offered only between nozzle variants, and not on the Extruder page.
m_extruder_sync->Enable(m_extruder_switch_variants && !is_printer_extruder_page(m_active_page) &&
get_extruder_sync_enable_state(get_current_active_extruder()));
}
GetParent()->Layout();
}
void Tab::switch_excluder(int extruder_id, bool reload)
{
Preset & printer_preset = m_preset_bundle->printers.get_edited_preset();
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto extruders = printer_preset.config.option<ConfigOptionEnumsGeneric>("extruder_type");
// Orca: the printer tab re-targets its pages to the extruder selected on the switch. Resolved before
// the range check below, which keeps the extruder_type read in get_index_for_extruder in range.
if (m_type == Preset::TYPE_PRINTER && extruder_id == -1)
extruder_id = get_current_active_extruder();
if (!m_variant_combo && (extruder_id >= (int)nozzle_volumes->size() || extruder_id >= (int)extruders->size()))
extruder_id = 0;
if (m_extruder_switch) {
int current_extruder = get_current_active_extruder();
bool sync_enable = get_extruder_sync_enable_state(current_extruder);
m_extruder_sync->Enable(m_extruder_switch->IsThisEnabled() && sync_enable);
m_extruder_sync->Enable(m_extruder_switch_variants && sync_enable && !is_printer_extruder_page(m_active_page));
m_extruder_sync->Show();
if (m_type != Preset::TYPE_PRINTER) {
if (extruder_id == -1)
@@ -8494,24 +8632,26 @@ void Tab::switch_excluder(int extruder_id, bool reload)
return;
if (m_extruder_switch) m_extruder_switch->SetClientData(reinterpret_cast<void*>(static_cast<std::uintptr_t>(index)));
if (m_variant_combo) m_variant_combo->SetClientData(reinterpret_cast<void *>(static_cast<std::uintptr_t>(index)));
wxWindow *variant_ctrl = m_extruder_switch ? (wxWindow *) m_extruder_switch : m_variant_combo;
for (auto page : m_pages) {
bool is_extruder = false;
int page_index = index;
if (m_type == Preset::TYPE_PRINTER) {
if (page->title().StartsWith("Extruder")) {
int extruder_id2 = std::atoi(page->title().Mid(9).ToUTF8()) - 1;
if (extruder_id >= 0 && extruder_id2 != extruder_id)
continue;
if (extruder_id2 > 0)
index = get_index_for_extruder(extruder_id2);
if (page->title() == "Extruder")
is_extruder = true;
} else if (page->title().StartsWith("Motion ability")) {
index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id, 2);
}
else if (page->title().StartsWith("Motion ability"))
page_index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id, 2);
}
page->m_opt_id_map.clear();
for (auto group : page->m_optgroups) {
for (auto &opt : group->opt_map()) {
if (is_extruder && opt.second.second >= 0) {
// Per-variant options use the variant column, the others (nozzle_diameter,
// extruder_offset, ...) are sized by the extruder count and use the extruder.
const int idx = printer_options_with_variant_1.count(opt.second.first) > 0 ? page_index : extruder_id;
const_cast<int &>(opt.second.second) = idx;
page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(idx), opt.first});
continue;
}
auto iter = std::find(printer_extruder_options.begin(), printer_extruder_options.end(), opt.second.first);
if (iter != printer_extruder_options.end()) {
page->m_opt_id_map.insert({opt.first, opt.first});
@@ -8519,9 +8659,9 @@ void Tab::switch_excluder(int extruder_id, bool reload)
}
if (opt.second.second >= 0) {
const_cast<int &>(opt.second.second) = index;
page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(index), opt.first});
group->draw_multi_extruder = !is_extruder && variant_ctrl->IsThisEnabled();
const_cast<int &>(opt.second.second) = page_index;
page->m_opt_id_map.insert({opt.second.first + "#" + std::to_string(page_index), opt.first});
group->draw_multi_extruder = variant_switch_active();
}
}
}
+31 -1
View File
@@ -331,6 +331,10 @@ public:
MultiSwitchButton * m_variant_combo = nullptr;
ScalableButton *m_extruder_sync = nullptr;
wxPanel * m_extruder_sync_box = nullptr;
// Orca: whether m_extruder_switch switches nozzle variants (it then offers sync between them).
// The printer tab also enables the switch for printers without variants, to choose the extruder
// its Extruder and Motion ability pages edit.
bool m_extruder_switch_variants = false;
std::vector<NozzleVolumeType> m_actual_nozzle_volumes;
public:
@@ -387,6 +391,13 @@ public:
void update_changed_tree_ui();
void update_undo_buttons();
void update_extruder_switch_colors();
// Whether the variant switch (m_extruder_switch / m_variant_combo) is enabled: on the printer tab for
// any multi-extruder printer, on the other tabs when it switches nozzle variants.
bool variant_switch_active() const;
// Orca: whether `page` is the printer tab's single "Extruder" page, which edits the extruder selected on the switch.
bool is_printer_extruder_page(const Page* page) const { return m_type == Preset::TYPE_PRINTER && page && page->title() == "Extruder"; }
// Shows the variant switch row on pages with options that follow it.
void update_variant_sizer_visibility();
void update_all_extruder_options_status();
void check_extruder_options_status(int index, bool &sys_extruder, bool &modified_extruder, const std::vector<PageShp>& pages_to_check);
@@ -444,7 +455,7 @@ public:
virtual void on_value_change(const std::string& opt_key, const boost::any& value);
void update_wiping_button_visibility();
void activate_option(const std::string& opt_key, const wxString& category);
virtual void activate_option(const std::string& opt_key, const wxString& category);
void apply_searcher();
void cache_config_diff(const std::vector<std::string>& selected_options, const DynamicPrintConfig* config = nullptr);
void apply_config_from_cache();
@@ -710,6 +721,25 @@ public:
void cache_extruder_cnt(const DynamicPrintConfig* config = nullptr);
bool apply_extruder_cnt_from_cache();
void refresh_printer_agent_dropdown() const;
// Orca: a single "Extruder" page for all extruders. Its controls are created once (index 0) and
// switch_excluder() re-targets them to the extruder selected on m_extruder_switch.
Page* extruder_page() const;
// Config index an "Extruder" page field (e.g. "retraction_length#0") currently edits, -1 if not on that page.
int extruder_page_data_index(const std::string& field_id) const;
// Config index of an extruder's per-variant options (Retraction, Z-Hop, ...): its variant column
// for the nozzle selected on the switch, or the extruder index on a printer without variants.
int extruder_variant_index(int extruder);
// After the config was restored (roll back): follow its extruder count, if it differs.
void sync_extruders_count();
// Search jump to "Extruder N" / "key#N": selects extruder N on the switch, then activates the
// page's own field on the "Extruder" page.
void activate_option(const std::string& opt_key, const wxString& category) override;
protected:
// Orca: values of extruders added by raising the extruder count have no saved / system value to
// revert to; the change is shown on "extruders_count", not on each of their parameters.
void update_custom_dirty(std::vector<std::string> &dirty_options, std::vector<std::string> &nonsys_options) override;
};
class TabSLAMaterial : public Tab
+2
View File
@@ -20,6 +20,8 @@
#include "../GUI/DeviceCore/DevConfig.h"
#include "../GUI/DeviceCore/DevExtruderSystem.h"
#include "../GUI/DeviceCore/DevManager.h"
#include "../GUI/DeviceCore/DevStorage.h"
#include "libslic3r/FlushVolCalc.hpp"
#include "../GUI/Plater.hpp"
#include <memory>
#include "slic3r/GUI/Jobs/Worker.hpp"
+3
View File
@@ -365,6 +365,7 @@ void StyleManager::init_trunc_names(float max_width) {
#include "slic3r/GUI/Plater.hpp"
// for get DPI
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
@@ -553,6 +554,8 @@ bool StyleManager::set_wx_font(const wxFont &wx_font, std::unique_ptr<FontFile>
return true;
}
#include <libslic3r/AppConfig.hpp>
#include "WxFontUtils.hpp"
#include "fast_float/fast_float.h"
// StylesSerializable
+1
View File
@@ -48,6 +48,7 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/UpdateDialogs.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
@@ -800,6 +800,43 @@ TEST_CASE("A per-variant filament option read with a single value gives it to ev
REQUIRE(config.option<ConfigOptionFloats>("pressure_advance")->values == std::vector<double>({0.021, 0.021, 0.021}));
}
// Machine limits (printer_options_with_variant_2) hold a (normal, silent) pair per printer variant, so the
// printer Tab's Motion ability page can edit each extruder's own limits.
TEST_CASE("set_num_extruders gives every printer variant its own pair of machine limits", "[Config]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
auto speed_x = [&config]() { return config.option<ConfigOptionFloats>("machine_max_speed_x")->values; };
SECTION("a new extruder's pair is padded with the first value, like the other variant keys") {
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = {500., 200.};
config.set_num_extruders(3);
REQUIRE(config.option<ConfigOptionStrings>("printer_extruder_variant")->size() == 3);
REQUIRE(speed_x() == std::vector<double>({500., 200., 500., 500., 500., 500.}));
}
SECTION("per-extruder pairs are kept, and removing an extruder removes its pair") {
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = {500., 200., 400., 150., 300., 100.};
config.set_num_extruders(3);
REQUIRE(speed_x() == std::vector<double>({500., 200., 400., 150., 300., 100.}));
config.set_num_extruders(2);
REQUIRE(speed_x() == std::vector<double>({500., 200., 400., 150.}));
}
SECTION("a printer with nozzle variants gets a pair per variant") {
// 2 extruders x 2 variants each = 4 variants
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
"Direct Drive Standard,Direct Drive High Flow"};
const std::vector<double> per_variant = {500., 200., 510., 210., 520., 220., 530., 230.};
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = per_variant;
config.set_num_extruders(2);
REQUIRE(speed_x() == per_variant);
config.option<ConfigOptionFloats>("machine_max_speed_x")->values = {500., 200.};
config.set_num_extruders(2);
REQUIRE(speed_x() == std::vector<double>({500., 200., 500., 500., 500., 500., 500., 500.}));
}
}
// The device drying options hold several values per filament, as many as each filament preset gives.
TEST_CASE("The device drying options are rebuilt as each filament's values in slot order", "[Config]")
{
@@ -24,6 +24,7 @@
#include "test_utils.hpp"
#include <algorithm>
#include <iostream>
#include <initializer_list>
#include <vector>