Compare commits

...
Author SHA1 Message Date
HanifKoh ddf9b85169 Reserve the Prime Tower When the CLI Arranges a Project (#15837)
The global arrange branch, taken by --arrange with all plates selected, only
reserved the prime tower when filament ids had been given on the command
line for STL input. A project carries its filament use per plate and its own
tower positions, but that set was empty for it, so the tower was never an
obstacle: the arranged pile was centred over it and the slice then failed on
a G-code path conflict.

When no filament ids were given, count the filaments each plate uses and
reserve a tower on every plate that needs one, keeping the project's tower
position instead of resetting it to the default. Only a tower the slicer will
print is reserved: the prime tower must be enabled, and a by-object print
gets none unless a smooth timelapse needs it, as the per-plate arrange
decides. Overflow beds are sized for the busiest plate. The STL route is
unchanged.
2026-09-25 08:39:52 +08:00
HanifKoh af52da061f Report Per-Object Slicing Errors in the CLI (#15834)
G-code generation collects errors raised per object, such as an empty
first layer, into one SlicingErrors exception whose own message is just
"Errors". The CLI's generic handler printed that word and recorded the
generic slicing error text, so a headless caller had nothing to act on.

Let Print render the per-object messages with each object's name, and have
the CLI catch SlicingErrors ahead of the generic handler, print that text
and record it as the result's error string. The exit code is unchanged. A
unit test lifts a cube off the bed and checks the message names the object.
2026-09-25 08:37:28 +08:00
Kris Austin 87a5d20d4c fix(gtk): crash at startup when opening a project with Home as the start page (#15876)
Since #15811 the settings page is built when its tab is shown, so on a
Home start that opens a project the Quality page is built on screen. Its
flow-compensation-model field is a multiline text view that GTK maps as
it is created and that is hidden, because compensation is off, before
GTK first allocates it. The loading dialog's wxWindowDisabler then
desensitizes the frame, and GTK crashes in
gtk_text_layout_cursors_changed() on that text view.

On GTK the page view is now hidden while a page is built, so a control
that starts hidden is never realized and GTK realizes it when it is
shown. The deferred build is unchanged.
2026-09-24 17:37:46 -03:00
5 changed files with 88 additions and 5 deletions
+31 -5
View File
@@ -5527,12 +5527,28 @@ int CLI::run(int argc, char **argv)
//add the virtual object into unselect list if has
partplate_list.preprocess_exclude_areas(unselected, enable_wrapping_detect);
if (used_filament_set.size() > 0)
// Filament ids given on the command line size the tower for STL input. A project
// records its filament use per plate, so count there and keep its tower positions.
const int plate_count = partplate_list.get_plate_count();
const bool from_project = used_filament_set.empty();
std::vector<int> plate_filament_counts(plate_count, static_cast<int>(used_filament_set.size()));
if (from_project)
for (int plate_index = 0; plate_index < plate_count; ++plate_index)
plate_filament_counts[plate_index] = static_cast<int>(partplate_list.get_plate(plate_index)->get_extruders_under_cli(true, m_print_config).size());
// A project only gets a tower the slicer will print: the prime tower enabled, and not
// a by-object print unless a smooth timelapse needs it, as the per-plate arrange decides.
const bool project_tower_allowed = m_print_config.option<ConfigOptionBool>("enable_prime_tower", true)->value &&
(is_smooth_timelapse || !arrange_cfg.is_seq_print);
const auto plate_needs_wipe_tower = [from_project, project_tower_allowed, is_smooth_timelapse](int filament_count) {
if (!from_project)
return filament_count > 0;
return project_tower_allowed && (filament_count > 1 || (filament_count > 0 && is_smooth_timelapse));
};
const int max_filament_count = plate_count > 0 ? *std::max_element(plate_filament_counts.begin(), plate_filament_counts.end()) : 0;
if (plate_needs_wipe_tower(max_filament_count))
{
//prepare the wipe tower
int plate_count = partplate_list.get_plate_count();
int extruder_size = used_filament_set.size();
auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure");
// This margin only pre-adjusts the default away from the near edges;
// estimate_wipe_tower_polygon below computes the real clamped position.
@@ -5568,7 +5584,11 @@ int CLI::run(int argc, char **argv)
for (int bedid = 0; bedid < MAX_PLATE_COUNT; bedid++) {
int plate_index_valid = std::min(bedid, plate_count - 1);
if (bedid < plate_count) {
// Overflow beds may receive objects from any plate, so size them for the busiest one.
const int extruder_size = bedid < plate_count ? plate_filament_counts[bedid] : max_filament_count;
if (!plate_needs_wipe_tower(extruder_size))
continue;
if (bedid < plate_count && !from_project) {
wipe_x_option->set_at(&wt_x_opt, plate_index_valid, 0);
wipe_y_option->set_at(&wt_y_opt, plate_index_valid, 0);
}
@@ -7024,6 +7044,12 @@ int CLI::run(int argc, char **argv)
}
}
sliced_info.sliced_plates.push_back(sliced_plate_info);
} catch (const Slic3r::SlicingErrors &exs) {
const std::string message = print_fff ? print_fff->slicing_errors_message(exs) : std::string(exs.what());
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate " << index+1 << ": " << message;
boost::nowide::cerr << message << std::endl;
record_exit_reson(outfile_dir, CLI_SLICING_ERROR, index+1, message, sliced_info);
flush_and_exit(CLI_SLICING_ERROR);
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "found slicing or export error for partplate "<<index+1 << std::endl;
boost::nowide::cerr << ex.what() << std::endl;
+19
View File
@@ -1705,6 +1705,25 @@ StringObjectException Print::check_multi_filament_valid(const Print& print)
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
//BBS: refine seq-print validation logic
// The exception's own message is just "Errors"; the detail is in the per-object errors,
// whose object id is the PrintObject's.
std::string Print::slicing_errors_message(const SlicingErrors &errors) const
{
std::string message;
for (const SlicingError &error : errors.errors_) {
std::string object_name;
for (const PrintObject *object : m_objects)
if (object->id().id == error.objectId()) {
object_name = object->model_object()->name;
break;
}
if (!message.empty())
message += "\n";
message += object_name.empty() ? std::string(error.what()) : object_name + ": " + error.what();
}
return message;
}
StringObjectException Print::validate(std::vector<StringObjectException> *warnings, Polygons* collison_polygons, std::vector<std::pair<Polygon, float>>* height_polygons) const
{
auto add_warning = [warnings](StringObjectException w) {
+4
View File
@@ -30,6 +30,8 @@
namespace Slic3r {
class SlicingErrors;
class GCode;
class Layer;
class ModelObject;
@@ -967,6 +969,8 @@ public:
// Returns an empty string if valid, otherwise returns an error message.
StringObjectException validate(std::vector<StringObjectException> *warnings = nullptr, Polygons* collison_polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr) const override;
// The per-object messages of a SlicingErrors, each prefixed with its object's name.
std::string slicing_errors_message(const SlicingErrors &errors) const;
double skirt_first_layer_height() const;
Flow brim_flow() const;
Flow skirt_flow() const;
+8
View File
@@ -7184,6 +7184,14 @@ void Tab::activate_selected_page(std::function<void()> throw_if_canceled)
if (!m_active_page)
return;
#ifdef __WXGTK__
// Builds the page off screen, since GTK crashes when it desensitizes a multiline text view
// that was built on screen and hidden before its first size allocation.
const bool hide_view = m_active_page->build_pending() && m_page_view->IsShown();
if (hide_view)
m_page_view->Hide();
ScopeGuard show_view([this, hide_view] { if (hide_view) m_page_view->Show(); });
#endif
m_active_page->activate(m_mode, throw_if_canceled);
update_changed_ui();
update_description_lines();
+26
View File
@@ -505,3 +505,29 @@ TEST_CASE("Sequential printing publishes the nozzle group result", "[Print][Mult
CHECK(gcode.find("; SEQ-ND-OK") != std::string::npos);
}
}
TEST_CASE("Slicing errors are reported per object with the object's name", "[Print]")
{
Print print;
Model model;
init_print({Slic3r::Test::cube(20.)}, print, model);
// Lift the cube off the bed: its first layer is empty, which G-code export reports per object.
ModelObject *object = model.objects.front();
object->name = "floating cube";
object->instances.front()->set_offset(object->instances.front()->get_offset() + Vec3d(0., 0., 2.));
print.apply(model, DynamicPrintConfig::full_print_config());
print.set_status_silent();
ScopedTemporaryFile temp(".gcode");
std::string message;
try {
print.process();
print.export_gcode(temp.string(), nullptr, nullptr);
FAIL("slicing did not report the empty first layer");
} catch (const SlicingErrors &errors) {
REQUIRE(errors.errors_.size() == 1);
message = print.slicing_errors_message(errors);
}
CHECK(message.rfind("floating cube: ", 0) == 0);
CHECK(message.find("empty first layer") != std::string::npos);
}