Compare commits

..
Author SHA1 Message Date
Hanif Koh a80d0b49f0 Report Per-Object Slicing Errors in the CLI
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-23 02:13:28 +08:00
6 changed files with 57 additions and 23 deletions
+8 -13
View File
@@ -28,7 +28,6 @@
#include <csignal>
#include <atomic>
#include <new>
#include <optional>
#if defined(__linux__) || defined(__LINUX__)
#include <condition_variable>
@@ -1615,10 +1614,6 @@ int CLI::run(int argc, char **argv)
ConfigOptionBool* allow_rotations_option = m_config.option<ConfigOptionBool>("allow_rotations");
if (allow_rotations_option)
allow_rotations = allow_rotations_option->value;
// Only an explicit --align-to-y-axis overrides the printer-structure default.
std::optional<bool> align_to_y_axis;
if (m_given_option_keys.count("align_to_y_axis") > 0)
align_to_y_axis = m_config.opt_bool("align_to_y_axis");
ConfigOptionBool* skip_modified_gcodes_option = m_config.option<ConfigOptionBool>("skip_modified_gcodes");
if (skip_modified_gcodes_option)
@@ -5303,9 +5298,7 @@ int CLI::run(int argc, char **argv)
arrange_cfg.bed_shrink_x = BED_SHRINK_SEQ_PRINT;
arrange_cfg.bed_shrink_y = BED_SHRINK_SEQ_PRINT;
}
if (align_to_y_axis.has_value()) {
arrange_cfg.align_to_y_axis = *align_to_y_axis;
} else if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
}
@@ -5755,9 +5748,7 @@ int CLI::run(int argc, char **argv)
arrange_cfg.bed_shrink_x = BED_SHRINK_SEQ_PRINT;
arrange_cfg.bed_shrink_y = BED_SHRINK_SEQ_PRINT;
}
if (align_to_y_axis.has_value()) {
arrange_cfg.align_to_y_axis = *align_to_y_axis;
} else if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
if (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
}
@@ -7024,6 +7015,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;
@@ -7952,8 +7949,6 @@ bool CLI::setup(int argc, char **argv)
for (std::string &input_file : m_input_files)
input_file = resolve_cli_input_path(input_file);
m_given_option_keys.insert(opt_order.begin(), opt_order.end());
// Parse actions and transform options.
for (auto const &opt_key : opt_order) {
if (cli_actions_config_def.has(opt_key))
-4
View File
@@ -1,8 +1,6 @@
#ifndef SLIC3R_HPP
#define SLIC3R_HPP
#include <set>
#include "libslic3r/Config.hpp"
#include "libslic3r/Model.hpp"
@@ -115,8 +113,6 @@ private:
std::vector<std::string> m_input_files;
std::vector<std::string> m_actions;
std::vector<std::string> m_transforms;
// Options the user typed; setup() fills the CLI's own options with defaults afterwards.
std::set<std::string> m_given_option_keys;
std::vector<Model> m_models;
bool setup(int argc, char **argv);
+19
View File
@@ -1704,6 +1704,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;
-6
View File
@@ -12347,12 +12347,6 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->tooltip = L("If enabled, Arrange will allow rotation when placing objects.");
def->set_default_value(new ConfigOptionBool(true));
def = this->add("align_to_y_axis", coBool);
def->label = L("Align to Y axis when arranging");
def->tooltip = L("If enabled, Arrange will turn each object so its long side runs along the Y axis before placing it. "
"When not given, it is on for i3 printers and off for the others, as in the GUI.");
def->set_default_value(new ConfigOptionBool(false));
def = this->add("avoid_extrusion_cali_region", coBool);
def->label = L("Avoid extrusion calibrate region when arranging");
def->tooltip = L("If enabled, Arrange will avoid extrusion calibrate region when placing objects.");
+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);
}