mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-06 07:11:10 +00:00
Merge upstream/main into belt-printer
Brings belt-printer up to main4b4a261787. Resolutions: - G-code header (#15897, #15915): main moved the header, config and thumbnail block later in _do_export; write_belt_header() moves with it, still after the thumbnails and outside the BTT_TFT gate. - _extrude: first-layer acceleration keeps the per-path first-layer plane test with main's cached nozzle index (#16028); main's set_speed out-param form (#16108) everywhere else. - GCodeWriter (#16108): the arc-to-polyline fallback for machine mappings that cannot express G2/G3 now runs in the out-param extrude_arc_to_xy, which is the overload GCode calls, and appends to the caller's string. - GCodeProcessorResult: the belt fields join main's forwarding assign. - Clipper2 (#15969): belt arrange helpers take Slic3r::Point; the tree support join types lose their ClipperLib qualifier. - CLI arrange (#15837): belt printers still reserve no wipe tower. - Wipe tower options (#15841): the two new sparse-layer toggles are hidden for belt printers like the rest of the tower options. - Keyboard shortcuts (#15706): main's registry replaces the old key switch; the belt view toggle is re-registered in the next commit. - Print::process: the belt purge-plan undo runs before main's SliceStarted event. - scripts/filament_id_snapshot.json: deleted on main (a77209af8f). - Includes and appended tests: union of both sides.
This commit is contained in:
+476
-193
@@ -20,6 +20,50 @@
|
||||
#endif /* SLIC3R_GUI */
|
||||
#endif /* WIN32 */
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "libslic3r/PrintBase.hpp"
|
||||
#include "slic3r/Utils/json_diff.hpp"
|
||||
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
||||
#include <cerrno>
|
||||
#include <utility>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
#include <fstream>
|
||||
#include <exception>
|
||||
#include <set>
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
#include <algorithm>
|
||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||
#include "libslic3r/Format/AssembleList.hpp"
|
||||
#include <cstdlib>
|
||||
#include <stdlib.h>
|
||||
#include <stdexcept>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
#include "libslic3r/Semver.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
|
||||
#include <functional>
|
||||
#include "libslic3r/Arrange.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/GLShader.hpp"
|
||||
#include <cassert>
|
||||
#include <boost/filesystem/exception.hpp>
|
||||
#include <signal.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
@@ -28,6 +72,7 @@
|
||||
#include <csignal>
|
||||
#include <atomic>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
|
||||
#if defined(__linux__) || defined(__LINUX__)
|
||||
#include <condition_variable>
|
||||
@@ -73,6 +118,7 @@ using namespace nlohmann;
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "libslic3r/BlacklistedLibraryCheck.hpp"
|
||||
#include "libslic3r/FlushVolCalc.hpp"
|
||||
#include "libslic3r/LayOnFace.hpp"
|
||||
|
||||
#include "libslic3r/Orient.hpp"
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
@@ -85,6 +131,8 @@ using namespace nlohmann;
|
||||
#ifdef WIN32
|
||||
#include "dev-utils/BaseException.h"
|
||||
#endif
|
||||
#include "slic3r/Utils/MeshInspect.hpp"
|
||||
#include "slic3r/Utils/PaintCLI.hpp"
|
||||
#include "slic3r/GUI/PartPlate.hpp"
|
||||
#include "slic3r/GUI/BitmapCache.hpp"
|
||||
#include "slic3r/GUI/OpenGLManager.hpp"
|
||||
@@ -94,6 +142,8 @@ using namespace nlohmann;
|
||||
#include "slic3r/GUI/GuiColor.hpp"
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#if __has_include(<X11/Xlib.h>)
|
||||
#include <X11/Xlib.h>
|
||||
@@ -189,6 +239,9 @@ typedef struct _sliced_info {
|
||||
int wall_loops{0};
|
||||
std::vector<std::string> upward_machines;
|
||||
std::vector<std::string> downward_machines;
|
||||
// Structured slicing warnings for result.json, and whether --strict was on.
|
||||
nlohmann::json warnings = nlohmann::json::array();
|
||||
bool strict_mode {false};
|
||||
}sliced_info_t;
|
||||
std::vector<PrintBase::SlicingStatus> g_slicing_warnings;
|
||||
|
||||
@@ -424,6 +477,21 @@ static PrinterTechnology get_printer_technology(const DynamicConfig &config)
|
||||
return(ret);}
|
||||
#endif
|
||||
|
||||
// Records a structured slicing warning so a CI or scripted consumer can branch on
|
||||
// a stable `class` string instead of matching stderr. Warnings are kept on the
|
||||
// run's sliced_info and emitted as the top-level "warnings" array of result.json;
|
||||
// a non-empty array does not by itself mean the run failed. Under --strict a
|
||||
// NON_CRITICAL warning additionally ends the run non-zero.
|
||||
//
|
||||
// result.json is written on Linux only (see the guard in record_exit_reson), so
|
||||
// neither "warnings" nor "strict_mode" reaches Windows or macOS.
|
||||
static void cli_record_warning(sliced_info_t &sliced_info, const std::string &cls,
|
||||
nlohmann::json details = nlohmann::json::object())
|
||||
{
|
||||
details["class"] = cls;
|
||||
sliced_info.warnings.push_back(std::move(details));
|
||||
}
|
||||
|
||||
void record_exit_reson(std::string outputdir, int code, int plate_id, std::string error_message, sliced_info_t& sliced_info, std::map<std::string, std::string> key_values = std::map<std::string, std::string>())
|
||||
{
|
||||
#if defined(__linux__) || defined(__LINUX__)
|
||||
@@ -462,6 +530,9 @@ void record_exit_reson(std::string outputdir, int code, int plate_id, std::strin
|
||||
for (auto& iter: key_values)
|
||||
j[iter.first] = iter.second;
|
||||
|
||||
j["warnings"] = sliced_info.warnings;
|
||||
j["strict_mode"] = sliced_info.strict_mode;
|
||||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(result_file, std::ios::out | std::ios::trunc);
|
||||
c << j.dump(1, '\t') << std::endl;
|
||||
@@ -617,162 +688,6 @@ static void load_default_gcodes_to_config(DynamicPrintConfig& config, Preset::Ty
|
||||
}
|
||||
}
|
||||
|
||||
static int load_assemble_plate_list(std::string config_file, std::vector<assemble_plate_info_t> &assemble_plate_info_list)
|
||||
{
|
||||
int ret = 0;
|
||||
boost::filesystem::path directory_path(config_file);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1% enter, file %2%")%__FUNCTION__ % config_file;
|
||||
if (!fs::exists(directory_path)) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("directory %1% not exist.")%config_file;
|
||||
return CLI_FILE_NOTFOUND;
|
||||
}
|
||||
|
||||
try {
|
||||
json root_json;
|
||||
boost::nowide::ifstream ifs(config_file);
|
||||
ifs >> root_json;
|
||||
ifs.close();
|
||||
|
||||
int plate_count = root_json[JSON_ASSEMPLE_PLATES].size();
|
||||
if ((plate_count <= 0) || (plate_count > MAX_PLATE_COUNT)) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": invalid plate count %1%")%plate_count;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
assemble_plate_info_list.resize(plate_count);
|
||||
|
||||
for (int plate_index = 0; plate_index < plate_count; plate_index++)
|
||||
{
|
||||
assemble_plate_info_t &assemble_plate = assemble_plate_info_list[plate_index];
|
||||
const json& plate_json = root_json[JSON_ASSEMPLE_PLATES][plate_index];
|
||||
assemble_plate.plate_name = plate_json[JSON_ASSEMPLE_PLATE_NAME];
|
||||
assemble_plate.need_arrange = plate_json[JSON_ASSEMPLE_PLATE_NEED_ARRANGE];
|
||||
|
||||
if (plate_json.contains(JSON_ASSEMPLE_PLATE_PARAMS)) {
|
||||
assemble_plate.plate_params = plate_json[JSON_ASSEMPLE_PLATE_PARAMS].get<std::map<std::string, std::string>>();
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, has %2% plate params") % (plate_index + 1) % assemble_plate.plate_params.size();
|
||||
}
|
||||
|
||||
int object_count = plate_json[JSON_ASSEMPLE_OBJECTS].size();
|
||||
if (object_count <= 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< boost::format(": invalid object count %1% in plate %2%")%object_count %(plate_index+1);
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
|
||||
assemble_plate.assemble_obj_list.resize(object_count);
|
||||
for (int object_index = 0; object_index < object_count; object_index++)
|
||||
{
|
||||
assemble_object_info_t& assemble_object = assemble_plate.assemble_obj_list[object_index];
|
||||
const json& object_json = plate_json[JSON_ASSEMPLE_OBJECTS][object_index];
|
||||
|
||||
assemble_object.path = object_json[JSON_ASSEMPLE_OBJECT_PATH];
|
||||
assemble_object.count = object_json[JSON_ASSEMPLE_OBJECT_COUNT];
|
||||
|
||||
if (assemble_object.count <= 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": invalid object clone count %1% in plate %2% Object %3%") % assemble_object.count % (plate_index + 1) % assemble_object.path;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
|
||||
assemble_object.filaments = object_json.at(JSON_ASSEMPLE_OBJECT_FILAMENTS).get<std::vector<int>>();
|
||||
if ((assemble_object.filaments.size() > 0) && (assemble_object.filaments.size() != assemble_object.count) && (assemble_object.filaments.size() != 1))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s filaments count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.filaments.size() % assemble_object.count;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
|
||||
if (object_json.contains(JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX)) {
|
||||
assemble_object.assemble_index = object_json[JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX].get<std::vector<int>>();
|
||||
if ((assemble_object.assemble_index.size() > 0) && (assemble_object.assemble_index.size() != assemble_object.count) && (assemble_object.assemble_index.size() != 1))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s assemble_index count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.assemble_index.size() % assemble_object.count;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_X)) {
|
||||
assemble_object.pos_x = object_json[JSON_ASSEMPLE_OBJECT_POS_X].get<std::vector<float>>();
|
||||
if ((assemble_object.pos_x.size() > 0) && (assemble_object.pos_x.size() != assemble_object.count) && (assemble_object.pos_x.size() != 1))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_x count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_x.size() % assemble_object.count;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
}
|
||||
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_Y)) {
|
||||
assemble_object.pos_y = object_json[JSON_ASSEMPLE_OBJECT_POS_Y].get<std::vector<float>>();
|
||||
if ((assemble_object.pos_y.size() > 0) && (assemble_object.pos_y.size() != assemble_object.count) && (assemble_object.pos_y.size() != 1))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_y count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_y.size() % assemble_object.count;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
}
|
||||
if (object_json.contains(JSON_ASSEMPLE_OBJECT_POS_Z)) {
|
||||
assemble_object.pos_z = object_json[JSON_ASSEMPLE_OBJECT_POS_Z].get<std::vector<float>>();
|
||||
if ((assemble_object.pos_z.size() > 0) && (assemble_object.pos_z.size() != assemble_object.count) && (assemble_object.pos_z.size() != 1))
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": object %1%'s pos_z count %2% not equal to clone count %3%, also not equal to 1") % assemble_object.path % assemble_object.pos_z.size() % assemble_object.count;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
}
|
||||
if (object_json.contains(JSON_ASSEMPLE_OBJECT_PRINT_PARAMS)) {
|
||||
assemble_object.print_params = object_json[JSON_ASSEMPLE_OBJECT_PRINT_PARAMS].get<std::map<std::string, std::string>>();
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, object %2% has %3% print params") % (plate_index + 1) %assemble_object.path % assemble_object.print_params.size();
|
||||
}
|
||||
if (object_json.contains(JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES)) {
|
||||
json height_range_json = object_json[JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES];
|
||||
int range_count = height_range_json.size();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, object %2% has %3% height ranges") % (plate_index + 1) %assemble_object.path % range_count;
|
||||
|
||||
assemble_object.height_ranges.resize(range_count);
|
||||
for (int range_index = 0; range_index < range_count; range_index++)
|
||||
{
|
||||
height_range_info_t& height_range = assemble_object.height_ranges[range_index];
|
||||
height_range.min_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MIN_Z];
|
||||
height_range.max_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MAX_Z];
|
||||
height_range.range_params = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_RANGE_PARAMS].get<std::map<std::string, std::string>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (plate_json.contains(JSON_ASSEMPLE_ASSEMBLE_PARAMS)) {
|
||||
json assemble_params_json = plate_json[JSON_ASSEMPLE_ASSEMBLE_PARAMS];
|
||||
int assemble_count = assemble_params_json.size();
|
||||
for (int i = 0; i < assemble_count; i++)
|
||||
{
|
||||
assembled_param_info_t assembled_param;
|
||||
int assemble_index = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_ASSEMBLE_INDEX];
|
||||
if (assemble_params_json[i].contains(JSON_ASSEMPLE_OBJECT_PRINT_PARAMS)) {
|
||||
assembled_param.print_params = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_PRINT_PARAMS].get<std::map<std::string, std::string>>();
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, assemble object %2% has %3% print params") % (plate_index + 1) %i % assembled_param.print_params.size();
|
||||
}
|
||||
if (assemble_params_json[i].contains(JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES)) {
|
||||
json height_range_json = assemble_params_json[i][JSON_ASSEMPLE_OBJECT_HEIGHT_RANGES];
|
||||
int range_count = height_range_json.size();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, assemble object %2% has %3% height ranges") % (plate_index + 1) %i % range_count;
|
||||
|
||||
assembled_param.height_ranges.resize(range_count);
|
||||
for (int range_index = 0; range_index < range_count; range_index++)
|
||||
{
|
||||
height_range_info_t& height_range = assembled_param.height_ranges[range_index];
|
||||
height_range.min_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MIN_Z];
|
||||
height_range.max_z = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_MAX_Z];
|
||||
height_range.range_params = height_range_json[range_index][JSON_ASSEMPLE_OBJECT_RANGE_PARAMS].get<std::map<std::string, std::string>>();
|
||||
}
|
||||
}
|
||||
assemble_plate.assembled_param_list.emplace(assemble_index, std::move(assembled_param));
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Plate %1%, has %2% plate params") % (plate_index + 1) % assemble_plate.plate_params.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse file "<<config_file<<" got a generic exception, reason = " << err.what();
|
||||
ret = CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void merge_or_add_object(assemble_plate_info_t& assemble_plate_info, Model &model, int assemble_index, std::map<int, ModelObject*> &merged_objects, ModelObject *ori_object)
|
||||
{
|
||||
if (assemble_index > 0) {
|
||||
@@ -1381,12 +1296,87 @@ int CLI::run(int argc, char **argv)
|
||||
bool need_skip = (skip_objects.size() > 0)?true:false;
|
||||
long long global_begin_time = 0, global_current_time;
|
||||
sliced_info_t sliced_info;
|
||||
// Read up front so result.json reports it for early failures too.
|
||||
sliced_info.strict_mode = m_config.opt_bool("strict");
|
||||
// --no-check skips the check behind the only NON_CRITICAL warning --strict acts on
|
||||
// (support needed but disabled), from the point it appears among the actions. The pair
|
||||
// would make --strict a no-op or depend on argument order, so refuse it.
|
||||
if (sliced_info.strict_mode && m_config.opt_bool("no_check")) {
|
||||
boost::nowide::cerr << "--strict cannot be combined with --no-check" << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
std::map<std::string, std::string> record_key_values;
|
||||
|
||||
ConfigOptionBool* downward_check_option = m_config.option<ConfigOptionBool>("downward_check");
|
||||
if (downward_check_option)
|
||||
downward_check = downward_check_option->value;
|
||||
|
||||
// --inspect-mesh prints its JSON and exits, so any action that does work of its
|
||||
// own (slicing, exporting) would be skipped without notice. Reject those up front;
|
||||
// only options that merely tune how the input is loaded may come along.
|
||||
if (std::find(m_actions.begin(), m_actions.end(), "inspect_mesh") != m_actions.end()) {
|
||||
static const std::set<std::string> inspect_compatible = { "inspect_mesh", "uptodate", "load_defaultfila", "min_save",
|
||||
"mtcpp", "mstpp", "no_check", "normative_check", "pipe" };
|
||||
for (const std::string &action : m_actions) {
|
||||
if (inspect_compatible.count(action) == 0) {
|
||||
std::string flag = action;
|
||||
std::replace(flag.begin(), flag.end(), '_', '-');
|
||||
boost::nowide::cerr << "--inspect-mesh cannot be combined with --" << flag << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
}
|
||||
// Without input there is nothing to inspect; fail rather than print nothing and exit 0.
|
||||
if (m_input_files.empty() && m_config.opt_string("load_assemble_list").empty()) {
|
||||
boost::nowide::cerr << "--inspect-mesh needs an input file or --load-assemble-list" << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
}
|
||||
|
||||
// --inspect-paint prints its JSON and exits, so any action that does work of its
|
||||
// own (slicing, exporting) would be skipped without notice. Reject those up front;
|
||||
// only options that merely tune how the input is loaded may come along.
|
||||
if (std::find(m_actions.begin(), m_actions.end(), "inspect_paint") != m_actions.end()) {
|
||||
static const std::set<std::string> inspect_compatible = { "inspect_paint", "uptodate", "load_defaultfila", "min_save",
|
||||
"mtcpp", "mstpp", "no_check", "normative_check", "pipe" };
|
||||
for (const std::string &action : m_actions) {
|
||||
if (inspect_compatible.count(action) == 0) {
|
||||
std::string flag = action;
|
||||
std::replace(flag.begin(), flag.end(), '_', '-');
|
||||
boost::nowide::cerr << "--inspect-paint cannot be combined with --" << flag << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
}
|
||||
// Without input there is nothing to inspect; fail rather than print nothing and exit 0.
|
||||
if (m_input_files.empty() && m_config.opt_string("load_assemble_list").empty()) {
|
||||
boost::nowide::cerr << "--inspect-paint needs an input file or --load-assemble-list" << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
}
|
||||
|
||||
// --export-settings - writes its JSON to stdout, so reject every action or transform that may write there
|
||||
// too (--info, --help, --orient, slicing and exporting). The allowed ones do nothing when nothing is
|
||||
// sliced or exported.
|
||||
if (std::find(m_actions.begin(), m_actions.end(), "export_settings") != m_actions.end() && m_config.opt_string("export_settings") == "-") {
|
||||
static const std::set<std::string> stdout_compatible = { "export_settings", "uptodate", "load_defaultfila", "min_save",
|
||||
"mtcpp", "mstpp", "no_check", "normative_check", "pipe" };
|
||||
for (const std::vector<std::string> *opt_keys : { &m_actions, &m_transforms }) {
|
||||
for (const std::string &opt_key : *opt_keys) {
|
||||
if (stdout_compatible.count(opt_key) == 0) {
|
||||
std::string flag = opt_key;
|
||||
std::replace(flag.begin(), flag.end(), '_', '-');
|
||||
boost::nowide::cerr << "--export-settings - cannot be combined with --" << flag << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool start_gui = m_actions.empty() && !downward_check;
|
||||
if (start_gui) {
|
||||
BOOST_LOG_TRIVIAL(info) << "no action, start gui directly" << std::endl;
|
||||
@@ -1515,6 +1505,10 @@ 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)
|
||||
@@ -1759,6 +1753,14 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("current_printer_name %1%, current_process_name %2%")%current_printer_name %current_process_name;
|
||||
ConfigOptionStrings* option_strings = config.option<ConfigOptionStrings>("inherits_group");
|
||||
// One entry for the process, one per filament and one for the printer. A group of another
|
||||
// length still has the process first and the printer last; one too short for that is ignored.
|
||||
if (option_strings && option_strings->values.size() != current_filaments_name.size() + 2) {
|
||||
boost::nowide::cerr << "Warning: inherits_group has " << option_strings->values.size() << " entries, expected "
|
||||
<< current_filaments_name.size() + 2 << " for " << current_filaments_name.size() << " filaments" << std::endl;
|
||||
if (option_strings->values.size() < 2)
|
||||
option_strings = nullptr;
|
||||
}
|
||||
if (option_strings) {
|
||||
current_inherits_group = option_strings->values;
|
||||
size_t size = current_inherits_group.size();
|
||||
@@ -1780,14 +1782,11 @@ int CLI::run(int argc, char **argv)
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("inherits of process valid, current_process_system_name is %1%") %current_process_system_name;
|
||||
}
|
||||
|
||||
current_filaments_system_name.resize(size - 2);
|
||||
for (int index = 1; index < (size - 1); index++) {
|
||||
if (current_inherits_group[index].empty()) {
|
||||
current_filaments_system_name[index-1] = current_filaments_name[index-1];
|
||||
}
|
||||
else {
|
||||
// A filament without an entry of its own counts as a system preset.
|
||||
current_filaments_system_name = current_filaments_name;
|
||||
for (size_t index = 1; index < size - 1 && index <= current_filaments_name.size(); index++) {
|
||||
if (!current_inherits_group[index].empty())
|
||||
current_filaments_system_name[index-1] = current_inherits_group[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -1809,10 +1808,8 @@ int CLI::run(int argc, char **argv)
|
||||
old_printable_width = static_cast<int>(old_printable_bbox.size().x());
|
||||
old_printable_depth = static_cast<int>(old_printable_bbox.size().y());
|
||||
}
|
||||
// A BBS-style 3mf without Metadata/project_settings.config has no
|
||||
// printable_height (found by fuzzing: this was a silent segfault).
|
||||
if (const auto *ph = config.option<ConfigOptionFloat>("printable_height"))
|
||||
old_printable_height = (int) ph->value;
|
||||
if (config.option<ConfigOptionFloat>("printable_height"))
|
||||
old_printable_height = (int)(config.opt_float("printable_height"));
|
||||
|
||||
if (config.option<ConfigOptionFloat>("extruder_clearance_height_to_rod"))
|
||||
old_height_to_rod = config.opt_float("extruder_clearance_height_to_rod");
|
||||
@@ -1922,7 +1919,12 @@ int CLI::run(int argc, char **argv)
|
||||
//parse the json and assemble object here
|
||||
Model model;
|
||||
|
||||
int ret = load_assemble_plate_list(load_assemble_list, assemble_plate_info_list);
|
||||
AssembleListResult list_result = load_assemble_plate_list(load_assemble_list, assemble_plate_info_list, MAX_PLATE_COUNT);
|
||||
int ret = CLI_SUCCESS;
|
||||
if (list_result == AssembleListResult::FileNotFound)
|
||||
ret = CLI_FILE_NOTFOUND;
|
||||
else if (list_result == AssembleListResult::ConfigError)
|
||||
ret = CLI_CONFIG_FILE_ERROR;
|
||||
if (ret) {
|
||||
record_exit_reson(outfile_dir, ret, 0, cli_errors[ret], sliced_info);
|
||||
flush_and_exit(ret);
|
||||
@@ -2013,19 +2015,24 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
};
|
||||
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
// One resolver for the whole run, so presets from the same vendor tree share its load.
|
||||
std::unique_ptr<PresetBundle> system_preset_resolver;
|
||||
auto ensure_system_preset_resolver = [&system_preset_resolver]() -> PresetBundle & {
|
||||
if (!system_preset_resolver)
|
||||
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||
return *system_preset_resolver;
|
||||
};
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, &ensure_system_preset_resolver](const std::string &file, DynamicPrintConfig &config,
|
||||
std::string &config_type, const std::string &config_from,
|
||||
bool probe_type, std::string &error) {
|
||||
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
|
||||
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
|
||||
return true;
|
||||
|
||||
std::unique_ptr<PresetBundle> source_bundle;
|
||||
PresetBundle *bundle = nullptr;
|
||||
bool allow_source_manifest = false;
|
||||
if (config_from == "system") {
|
||||
source_bundle = std::make_unique<PresetBundle>();
|
||||
bundle = source_bundle.get();
|
||||
bundle = &ensure_system_preset_resolver();
|
||||
allow_source_manifest = true;
|
||||
} else {
|
||||
bundle = ensure_cli_preset_bundle(error);
|
||||
@@ -2673,6 +2680,37 @@ int CLI::run(int argc, char **argv)
|
||||
fetch_compatible_values = true;
|
||||
}
|
||||
|
||||
// Refresh every project filament no loaded filament replaces from its current system preset, as the GUI
|
||||
// does when it loads the project; the filament merge below keeps the keys the project lists as changed.
|
||||
// Entries stay in slot order, which the merge's variant bookkeeping relies on.
|
||||
std::vector<bool> load_filaments_refresh(load_filaments_config.size(), false);
|
||||
if (is_bbl_3mf && new_printer_name.empty()) {
|
||||
const ConfigOptionStrings *project_filament_ids = m_print_config.option<ConfigOptionStrings>("filament_ids");
|
||||
for (size_t index = 0; index < current_filaments_system_name.size(); index++) {
|
||||
const int slot = static_cast<int>(index) + 1;
|
||||
if (std::find(load_filaments_index.begin(), load_filaments_index.end(), slot) != load_filaments_index.end())
|
||||
continue;
|
||||
std::string system_name = current_filaments_system_name[index];
|
||||
if (system_name.empty())
|
||||
continue;
|
||||
PresetBundle::convert_filament_preset_name(current_printer_name, system_name);
|
||||
DynamicPrintConfig config;
|
||||
std::string error;
|
||||
if (!ensure_system_preset_resolver().resolve_system_preset(config, Preset::TYPE_FILAMENT, system_name, config_substitution_rule, error)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system filament preset '%1%' not resolved (%2%); filament %3% keeps its values") % system_name % error % slot;
|
||||
continue;
|
||||
}
|
||||
const size_t at = std::upper_bound(load_filaments_index.begin(), load_filaments_index.end(), slot) - load_filaments_index.begin();
|
||||
load_filaments_id.insert(load_filaments_id.begin() + at,
|
||||
project_filament_ids != nullptr && index < project_filament_ids->size() ? project_filament_ids->values[index] : std::string());
|
||||
load_filaments_name.insert(load_filaments_name.begin() + at, system_name);
|
||||
load_filaments_config.insert(load_filaments_config.begin() + at, std::move(config));
|
||||
load_filaments_index.insert(load_filaments_index.begin() + at, slot);
|
||||
load_filaments_inherit.insert(load_filaments_inherit.begin() + at, system_name);
|
||||
load_filaments_refresh.insert(load_filaments_refresh.begin() + at, true);
|
||||
}
|
||||
}
|
||||
|
||||
//fetch upward_compatible_machine
|
||||
if (fetch_upward_values) {
|
||||
if (!current_printer_system_name.empty()) {
|
||||
@@ -3017,6 +3055,44 @@ int CLI::run(int argc, char **argv)
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Load the project's printer and process settings as the GUI loads its presets: over the default preset,
|
||||
// with every key the project does not list as changed, including keys saved before an option existed,
|
||||
// taken from its current system preset.
|
||||
auto load_project_preset = [this, &ensure_system_preset_resolver, ¤t_different_settings, filament_count](const std::string &system_name, Preset::Type type) {
|
||||
if (system_name.empty())
|
||||
return;
|
||||
// Preset bookkeeping the CLI keeps in its own groups, e.g. inherits_group and print_compatible_printers.
|
||||
static const std::set<std::string> bookkeeping_keys = {"inherits", "compatible_printers", "compatible_prints", "compatible_printers_condition",
|
||||
"compatible_prints_condition", "print_settings_id", "printer_settings_id"};
|
||||
const size_t index = type == Preset::TYPE_PRINTER ? filament_count + 1 : 0;
|
||||
PresetBundle &resolver = ensure_system_preset_resolver();
|
||||
DynamicPrintConfig system_config;
|
||||
t_config_option_keys keys;
|
||||
const DynamicPrintConfig config = Preset::load_external_config(type,
|
||||
type == Preset::TYPE_PRINTER ? resolver.printers.default_preset_for(m_print_config).config : resolver.prints.default_preset().config,
|
||||
m_print_config, PresetBundle::project_different_keys(index < current_different_settings.size() ? current_different_settings[index] : std::string()),
|
||||
[&](const std::string &) -> DynamicPrintConfig * {
|
||||
std::string error;
|
||||
if (resolver.resolve_system_preset(system_config, type, system_name, config_substitution_rule, error))
|
||||
return &system_config;
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("CLI: system preset '%1%' not resolved (%2%); the project keeps its values") % system_name % error;
|
||||
return nullptr;
|
||||
}, &keys);
|
||||
for (const std::string &key : keys) {
|
||||
const ConfigOption *opt = config.option(key);
|
||||
const ConfigOption *old = m_print_config.option(key);
|
||||
if (bookkeeping_keys.count(key) != 0 || opt == nullptr || (old != nullptr && *old == *opt))
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("CLI: %1% from '%2%': %3% -> %4%") % key % system_name % (old ? old->serialize() : std::string("(missing)")) % opt->serialize();
|
||||
m_print_config.set_key_value(key, opt->clone());
|
||||
}
|
||||
};
|
||||
// The --uptodate path refreshes the project from its own system configs.
|
||||
if (new_printer_name.empty() && load_machine_config.empty())
|
||||
load_project_preset(current_printer_system_name, Preset::TYPE_PRINTER);
|
||||
if (new_process_name.empty() && load_process_config.empty())
|
||||
load_project_preset(current_process_system_name, Preset::TYPE_PRINT);
|
||||
|
||||
std::vector<std::string>& different_settings = m_print_config.option<ConfigOptionStrings>("different_settings_to_system", true)->values;
|
||||
std::vector<std::string>& inherits_group = m_print_config.option<ConfigOptionStrings>("inherits_group", true)->values;
|
||||
inherits_group.resize(filament_count + 2, std::string());
|
||||
@@ -3325,7 +3401,7 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
|
||||
//set the filament settings into print config
|
||||
if ((load_filament_count > 0) || (up_config_to_date))
|
||||
if ((load_filament_count > 0) || (up_config_to_date) || !load_filaments_config.empty())
|
||||
{
|
||||
//std::vector<int> filament_variant_count(filament_count, 1);
|
||||
std::vector<int> old_start_indice(filament_count, 0);
|
||||
@@ -3378,9 +3454,19 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
new_variant_counts = old_variant_counts;
|
||||
//filament_variant_count = old_variant_counts;
|
||||
//ORCA: lay the per-variant options out one value per variant of the current filaments before each
|
||||
// loaded filament replaces its own variants, including an option only a loaded filament
|
||||
// defines, which otherwise starts as a single default value and never reaches the others.
|
||||
for (const DynamicPrintConfig &config : load_filaments_config)
|
||||
for (const std::string &opt_key : filament_options_with_variant)
|
||||
if (opt_key != "filament_extruder_variant" && config.has(opt_key))
|
||||
m_print_config.option(opt_key, true);
|
||||
normalize_filament_values_to_variants(m_print_config);
|
||||
for (int index = 0; index < load_filaments_config.size(); index++) {
|
||||
DynamicPrintConfig& config = load_filaments_config[index];
|
||||
int filament_index = load_filaments_index[index];
|
||||
// A filament given with --load-filaments replaces the slot; a refreshed one keeps the project's changed keys.
|
||||
const bool loaded = load_filament_count > 0 && !load_filaments_refresh[index];
|
||||
std::vector<std::string> different_keys;
|
||||
|
||||
//ORCA: diff before load_default_gcodes_to_config, the way the process and machine
|
||||
@@ -3390,12 +3476,12 @@ int CLI::run(int argc, char **argv)
|
||||
// compared" to "compared as empty against the parent" and land in the column
|
||||
// as an override the user never made.
|
||||
std::string filament_different_settings;
|
||||
if (load_filament_count > 0)
|
||||
if (loaded)
|
||||
filament_different_settings = cli_different_settings(config, load_filaments_inherit[index], Preset::TYPE_FILAMENT);
|
||||
|
||||
load_default_gcodes_to_config(config, Preset::TYPE_FILAMENT);
|
||||
|
||||
if (load_filament_count > 0) {
|
||||
if (loaded) {
|
||||
ConfigOptionStrings *opt_filament_settings = static_cast<ConfigOptionStrings *> (m_print_config.option("filament_settings_id", true));
|
||||
std::string& filament_name = load_filaments_name[index];
|
||||
ConfigOptionString* filament_name_setting = new ConfigOptionString(filament_name);
|
||||
@@ -3471,7 +3557,7 @@ int CLI::run(int argc, char **argv)
|
||||
flush_and_exit(CLI_CONFIG_FILE_ERROR);
|
||||
}
|
||||
|
||||
if ((load_filament_count == 0) && !different_keys_set.empty())
|
||||
if (!loaded && !different_keys_set.empty())
|
||||
{
|
||||
std::set<std::string>::iterator iter = different_keys_set.find(opt_key);
|
||||
if ( iter != different_keys_set.end()) {
|
||||
@@ -3573,6 +3659,25 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
//ORCA: a per-variant option the loaded filament does not define keeps the values of the
|
||||
// variants the filament already had, and a variant new to it takes its first one's.
|
||||
const int old_start = old_start_indice[filament_index - 1];
|
||||
std::vector<int> kept_variant_indice = new_variant_indice;
|
||||
for (int &i : kept_variant_indice)
|
||||
if (i < 0)
|
||||
i = old_start;
|
||||
for (const std::string &opt_key : filament_options_with_variant) {
|
||||
if (config.has(opt_key))
|
||||
continue;
|
||||
auto *opt_vec_dst = dynamic_cast<ConfigOptionVectorBase *>(m_print_config.option(opt_key));
|
||||
if (opt_vec_dst == nullptr || opt_vec_dst->size() < size_t(old_start + old_variant_count))
|
||||
continue;
|
||||
// set_with_restore_2() pads its source in place
|
||||
std::unique_ptr<ConfigOption> old_values(opt_vec_dst->clone());
|
||||
opt_vec_dst->set_with_restore_2(static_cast<ConfigOptionVectorBase *>(old_values.get()), kept_variant_indice, old_start,
|
||||
old_variant_count, true);
|
||||
}
|
||||
|
||||
//update the old index
|
||||
if (old_variant_count != new_variant_count)
|
||||
{
|
||||
@@ -4007,6 +4112,9 @@ int CLI::run(int argc, char **argv)
|
||||
if (printer_technology == ptFFF) {
|
||||
fff_print_config.apply(m_print_config, true);
|
||||
m_print_config.apply(fff_print_config, true);
|
||||
//ORCA: an option no preset or project defines has just come in as its single default value, and a
|
||||
// command line override may hold one value per filament.
|
||||
normalize_filament_values_to_variants(m_print_config);
|
||||
} else {
|
||||
boost::nowide::cerr << "invalid printer_technology " << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PRINTER_TECH, 0, cli_errors[CLI_INVALID_PRINTER_TECH], sliced_info);
|
||||
@@ -4663,6 +4771,11 @@ int CLI::run(int argc, char **argv)
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
if (m_models.empty()) {
|
||||
boost::nowide::cerr << "Invalid params: --assemble needs at least one input model." << std::endl;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
Model m;
|
||||
ModelObject* new_object = m.add_object();
|
||||
new_object->name = _u8L("Assembly");
|
||||
@@ -4825,6 +4938,64 @@ int CLI::run(int argc, char **argv)
|
||||
for (auto &o : model.objects)
|
||||
// this affects volumes:
|
||||
o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y);
|
||||
} else if (opt_key == "ground_largest_face" || opt_key == "ground_face_normal" || opt_key == "ground_face_point") {
|
||||
// Each instance is laid on one of its lay-on-face planes, which are computed from the current part
|
||||
// transformations, so the rotations given before this option are respected. A direction or point is in
|
||||
// object coordinates, so it names the same face for every instance of an object.
|
||||
std::function<int(const std::vector<LayOnFacePlane>&, const Transform3d&)> pick;
|
||||
if (opt_key == "ground_largest_face") {
|
||||
if (m_config.opt_bool(opt_key))
|
||||
pick = [](const std::vector<LayOnFacePlane>& planes, const Transform3d&) { return find_largest_plane(planes); };
|
||||
} else {
|
||||
// Only options given on the command line reach this loop, so an empty value is malformed input too.
|
||||
const std::string& value = m_config.opt_string(opt_key);
|
||||
Vec3d v;
|
||||
int consumed = 0;
|
||||
if (sscanf(value.c_str(), "%lf,%lf,%lf%n", &v.x(), &v.y(), &v.z(), &consumed) != 3 || consumed != int(value.size()) ||
|
||||
!v.allFinite() || (opt_key == "ground_face_normal" && v.norm() < EPSILON)) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Invalid params: %1% expects three comma-separated numbers, got \"%2%\"") % opt_key % value;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
if (opt_key == "ground_face_normal")
|
||||
pick = [v](const std::vector<LayOnFacePlane>& planes, const Transform3d&) { return find_plane_by_normal(planes, v); };
|
||||
else
|
||||
pick = [v](const std::vector<LayOnFacePlane>& planes, const Transform3d& inst_matrix) {
|
||||
return find_plane_at_point(planes, inst_matrix, v, 0.01);
|
||||
};
|
||||
}
|
||||
if (pick) {
|
||||
size_t laid = 0, missed = 0;
|
||||
for (auto& model : m_models) {
|
||||
model.add_default_instances();
|
||||
for (ModelObject* o : model.objects)
|
||||
for (size_t i = 0; i < o->instances.size(); ++i) {
|
||||
const Transform3d inst_matrix = o->instances[i]->get_matrix_no_offset();
|
||||
const std::vector<LayOnFacePlane> planes = lay_on_face_planes(*o, inst_matrix);
|
||||
if (planes.empty()) {
|
||||
// Small or smooth parts (e.g. a sphere) have no face to rest on; the gizmo offers none either.
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: object %2% has no face large enough to lay on, left as it is") % opt_key % o->name;
|
||||
continue;
|
||||
}
|
||||
const int idx = pick(planes, inst_matrix);
|
||||
if (idx < 0) {
|
||||
// Only a point can miss: with several objects it usually belongs to one of them.
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: no face of object %2% contains the point, left as it is") % opt_key % o->name;
|
||||
++missed;
|
||||
continue;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: object %2% instance %3% laid on the %4% mm2 face with normal %5%")
|
||||
% opt_key % o->name % i % planes[idx].area % planes[idx].normal.transpose();
|
||||
lay_on_face(*o, i, planes[idx].normal);
|
||||
++laid;
|
||||
}
|
||||
}
|
||||
if (laid == 0 && missed > 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Invalid params: %1%: no face of any object contains the point") % opt_key;
|
||||
record_exit_reson(outfile_dir, CLI_INVALID_PARAMS, 0, cli_errors[CLI_INVALID_PARAMS], sliced_info);
|
||||
flush_and_exit(CLI_INVALID_PARAMS);
|
||||
}
|
||||
}
|
||||
} else if (opt_key == "scale") {
|
||||
float ratio = m_config.opt_float(opt_key);
|
||||
if (ratio <= 0.f) {
|
||||
@@ -5175,7 +5346,9 @@ 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 (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
|
||||
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")) {
|
||||
arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
|
||||
}
|
||||
|
||||
@@ -5384,7 +5557,7 @@ int CLI::run(int argc, char **argv)
|
||||
//skip this object due to be locked in plate
|
||||
ap.itemid = locked_aps.size();
|
||||
locked_aps.emplace_back(ap);
|
||||
boost::nowide::cout <<__FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, instance_id %2%") % oidx % inst_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5397,12 +5570,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 (!is_belt_printer && 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 (!is_belt_printer && 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.
|
||||
@@ -5438,7 +5627,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);
|
||||
}
|
||||
@@ -5523,7 +5716,7 @@ int CLI::run(int argc, char **argv)
|
||||
float w = dynamic_cast<const ConfigOptionFloat *>(m_print_config.option("prime_tower_width"))->value;
|
||||
float a = dynamic_cast<const ConfigOptionFloat *>(m_print_config.option("wipe_tower_rotation_angle"))->value;
|
||||
float v = dynamic_cast<const ConfigOptionFloat *>(m_print_config.option("prime_volume"))->value;
|
||||
unsigned int filaments_cnt = plate_data_src[plate_to_slice-1]->slice_filaments_info.size();
|
||||
unsigned int filaments_cnt = (plate_data_src.size() >= static_cast<size_t>(plate_to_slice)) ? plate_data_src[plate_to_slice-1]->slice_filaments_info.size() : 0;
|
||||
if ((filaments_cnt == 0) || need_skip)
|
||||
{
|
||||
// slice filaments info invalid
|
||||
@@ -5625,7 +5818,9 @@ 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 (auto printer_structure_opt = m_print_config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
|
||||
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")) {
|
||||
arrange_cfg.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
|
||||
}
|
||||
|
||||
@@ -5973,13 +6168,64 @@ int CLI::run(int argc, char **argv)
|
||||
//FIXME check for mixing the FFF / SLA parameters.
|
||||
// or better save fff_print_config vs. sla_print_config
|
||||
//m_print_config.save(m_config.opt_string("save"));
|
||||
m_print_config.save_to_json(m_config.opt_string(opt_key), std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION));
|
||||
const std::string &settings_file = m_config.opt_string(opt_key);
|
||||
if (settings_file == "-")
|
||||
m_print_config.save_to_json(boost::nowide::cout, "project_settings", "project", SoftFever_VERSION, /*replace_invalid_utf8=*/true);
|
||||
else
|
||||
m_print_config.save_to_json(settings_file, std::string("project_settings"), std::string("project"), std::string(SoftFever_VERSION));
|
||||
} else if (opt_key == "info") {
|
||||
// --info works on unrepaired model
|
||||
for (Model &model : m_models) {
|
||||
model.add_default_instances();
|
||||
model.print_info();
|
||||
}
|
||||
} else if (opt_key == "inspect_mesh") {
|
||||
// Machine-readable alternative to --info. Registered as an action so it satisfies the
|
||||
// "needs an action" check and bypasses the GUI fallback, then exits once the JSON is out.
|
||||
for (Model &model : m_models) {
|
||||
model.add_default_instances();
|
||||
Slic3r::MeshInspect::inspect_to_json(model, m_input_files, boost::nowide::cout);
|
||||
}
|
||||
boost::nowide::cout.flush();
|
||||
// Conflicting actions were rejected before loading. Finish like the end of run().
|
||||
// flush_and_exit() is not usable here: it prints "found error ..." to stdout,
|
||||
// which would corrupt the JSON.
|
||||
#if defined(__linux__) || defined(__LINUX__)
|
||||
if (g_cli_callback_mgr.is_started()) {
|
||||
PrintBase::SlicingStatus slicing_status{100, "All done, Success"};
|
||||
cli_status_callback(slicing_status);
|
||||
}
|
||||
g_cli_callback_mgr.stop();
|
||||
#endif
|
||||
for (Model &m : m_models)
|
||||
m.remove_backup_path_if_exist();
|
||||
record_exit_reson(outfile_dir, CLI_SUCCESS, plate_to_slice, cli_errors[CLI_SUCCESS], sliced_info);
|
||||
boost::nowide::cerr.flush();
|
||||
return CLI_SUCCESS;
|
||||
} else if (opt_key == "inspect_paint") {
|
||||
// --inspect-paint — read the per-facet enforcer/blocker/extruder/
|
||||
// fuzzy state from the loaded model and emit a JSON summary.
|
||||
// Machine-readable alternative to opening the paint gizmos.
|
||||
for (Model &model : m_models) {
|
||||
model.add_default_instances();
|
||||
Slic3r::PaintCLI::inspect_to_json(model, m_input_files, boost::nowide::cout);
|
||||
}
|
||||
boost::nowide::cout.flush();
|
||||
// The tooltip promises "then exit"; conflicting actions were rejected before
|
||||
// loading. Finish like the end of run(). flush_and_exit() is not usable here:
|
||||
// it prints "found error ..." to stdout, which would corrupt the JSON.
|
||||
#if defined(__linux__) || defined(__LINUX__)
|
||||
if (g_cli_callback_mgr.is_started()) {
|
||||
PrintBase::SlicingStatus slicing_status{100, "All done, Success"};
|
||||
cli_status_callback(slicing_status);
|
||||
}
|
||||
g_cli_callback_mgr.stop();
|
||||
#endif
|
||||
for (Model &m : m_models)
|
||||
m.remove_backup_path_if_exist();
|
||||
record_exit_reson(outfile_dir, CLI_SUCCESS, plate_to_slice, cli_errors[CLI_SUCCESS], sliced_info);
|
||||
boost::nowide::cerr.flush();
|
||||
return CLI_SUCCESS;
|
||||
} else if (opt_key == "uptodate") {
|
||||
//already processed before
|
||||
} else if (opt_key == "min_save") {
|
||||
@@ -6020,6 +6266,8 @@ int CLI::run(int argc, char **argv)
|
||||
export_3mf_file = m_config.opt_string(opt_key);
|
||||
}else if(opt_key=="no_check"){
|
||||
no_check = m_config.opt_bool(opt_key);
|
||||
}else if(opt_key=="strict"){
|
||||
//already read into sliced_info at the start of run()
|
||||
//} else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") {
|
||||
} else if (opt_key == "normative_check") {
|
||||
//already processed before
|
||||
@@ -6728,6 +6976,15 @@ int CLI::run(int argc, char **argv)
|
||||
|
||||
if (status.warning_level == PrintStateBase::WarningLevel::NON_CRITICAL) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "plate "<< index+1<< ": found NON_CRITICAL slicing warnings: "<<status.text <<std::endl;
|
||||
// Always record for AI/CI consumers; under --strict, elevate to a
|
||||
// non-zero exit so scripted pipelines don't ship a "warning OK" slice.
|
||||
cli_record_warning(sliced_info, "slicing_warning_non_critical",
|
||||
nlohmann::json{{"plate_id", index+1}, {"text", status.text}});
|
||||
if (sliced_info.strict_mode) {
|
||||
sliced_info.sliced_plates.push_back(sliced_plate_info);
|
||||
record_exit_reson(outfile_dir, CLI_SLICING_ERROR, index+1, cli_errors[CLI_SLICING_ERROR], sliced_info);
|
||||
flush_and_exit(CLI_SLICING_ERROR);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("plate %1%: found slicing warnings: %2%, no_check=%3%")%(index+1) %status.text %no_check;
|
||||
@@ -6830,6 +7087,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;
|
||||
@@ -7070,9 +7333,9 @@ int CLI::run(int argc, char **argv)
|
||||
colors_out[color_idx] = ColorRGBA(float(rgb_color[0]) / 255.f, float(rgb_color[1]) / 255.f, float(rgb_color[2]) / 255.f, float(rgb_color[3]) / 255.f);
|
||||
}
|
||||
|
||||
int gl_major, gl_minor, gl_verbos;
|
||||
glfwGetVersion(&gl_major, &gl_minor, &gl_verbos);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("opengl version %1%.%2%.%3%")%gl_major %gl_minor %gl_verbos;
|
||||
int glfw_major, glfw_minor, glfw_revision;
|
||||
glfwGetVersion(&glfw_major, &glfw_minor, &glfw_revision);
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("GLFW version %1%.%2%.%3%") % glfw_major % glfw_minor % glfw_revision;
|
||||
|
||||
bool thumbnail_opengl_ready = false;
|
||||
glfwSetErrorCallback(glfw_callback);
|
||||
@@ -7084,8 +7347,9 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "glfwInit Success."<< std::endl;
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, gl_major);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, gl_minor);
|
||||
// Request OrcaSlicer's minimum OpenGL version, independently of the GLFW library version.
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_RED_BITS, 8);
|
||||
glfwWindowHint(GLFW_GREEN_BITS, 8);
|
||||
glfwWindowHint(GLFW_BLUE_BITS, 8);
|
||||
@@ -7101,6 +7365,16 @@ int CLI::run(int argc, char **argv)
|
||||
#endif
|
||||
|
||||
GLFWwindow* window = glfwCreateWindow(640, 480, "base_window", NULL, NULL);
|
||||
#ifndef __WXMAC__
|
||||
if (window == NULL) {
|
||||
// Some drivers (e.g. older Mesa) only expose compatibility profile 3.0; take whatever they offer.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to create OpenGL 3.3 compatibility context, retrying with driver default" << std::endl;
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
|
||||
window = glfwCreateWindow(640, 480, "base_window", NULL, NULL);
|
||||
}
|
||||
#endif
|
||||
if (window == NULL)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to create GLFW window; skipping thumbnail rendering for CLI export" << std::endl;
|
||||
@@ -7751,6 +8025,15 @@ bool CLI::setup(int argc, char **argv)
|
||||
this->print_help();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Orca: resolve here, while the process is still in the directory the user invoked it from.
|
||||
// GUI_App's constructor moves the working directory to <data_dir>/log, long before the GUI
|
||||
// opens these files in post_init(), and a relative path would then resolve against that.
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user