PrusaSlicer/src/CLI/ProcessActions.cpp
YuSanka 4be7eeabb4 CLI: Code cleaning and reorganization
+Remove configuration of fhs.hpp on UNIX. Use target_compile_definitions() instead.
2025-01-21 14:40:07 +01:00

367 lines
14 KiB
C++

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <math.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/args.hpp>
#include <boost/nowide/cstdlib.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/filesystem.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/dll/runtime_symbol_info.hpp>
#include "libslic3r/libslic3r.h"
#if !SLIC3R_OPENGL_ES
#include <boost/algorithm/string/split.hpp>
#endif // !SLIC3R_OPENGL_ES
#include "libslic3r/Config.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Preset.hpp"
#include "libslic3r/ProfilesSharingUtils.hpp"
#include <arrange-wrapper/ModelArrange.hpp>
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/OBJ.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/PNGReadWrite.hpp"
#include "libslic3r/MultipleBeds.hpp"
#include "CLI.hpp"
namespace Slic3r::CLI {
static bool has_profile_sharing_action(const Data& cli)
{
return cli.actions_config.has("query-printer-models") || cli.actions_config.has("query-print-filament-profiles");
}
bool has_full_config_from_profiles(const Data& cli)
{
const DynamicPrintConfig& input = cli.input_config;
return !has_profile_sharing_action(cli) &&
(input.has("print-profile") && !input.opt_string("print-profile").empty() ||
input.has("material-profile") && !input.option<ConfigOptionStrings>("material-profile")->values.empty() ||
input.has("printer-profile") && !input.opt_string("printer-profile").empty());
}
bool process_profiles_sharing(const Data& cli)
{
if (!has_profile_sharing_action(cli))
return false;
std::string ret;
if (cli.actions_config.has("query-printer-models")) {
ret = Slic3r::get_json_printer_models(get_printer_technology(cli.overrides_config));
}
else if (cli.actions_config.has("query-print-filament-profiles")) {
if (cli.input_config.has("printer-profile") && !cli.input_config.opt_string("printer-profile").empty()) {
const std::string printer_profile = cli.input_config.opt_string("printer-profile");
ret = Slic3r::get_json_print_filament_profiles(printer_profile);
if (ret.empty()) {
boost::nowide::cerr << "query-print-filament-profiles error: Printer profile '" << printer_profile <<
"' wasn't found among installed printers." << std::endl <<
"Or the request can be wrong." << std::endl;
return true;
}
}
else {
boost::nowide::cerr << "query-print-filament-profiles error: This action requires set 'printer-profile' option" << std::endl;
return true;
}
}
if (ret.empty()) {
boost::nowide::cerr << "Wrong request" << std::endl;
return true;
}
// use --output when available
if (cli.misc_config.has("output")) {
std::string cmdline_param = cli.misc_config.opt_string("output");
// if we were supplied a directory, use it and append our automatically generated filename
boost::filesystem::path cmdline_path(cmdline_param);
boost::filesystem::path proposed_path = boost::filesystem::path(Slic3r::resources_dir()) / "out.json";
if (boost::filesystem::is_directory(cmdline_path))
proposed_path = (cmdline_path / proposed_path.filename());
else if (cmdline_path.extension().empty())
proposed_path = cmdline_path.replace_extension("json");
else
proposed_path = cmdline_path;
const std::string file = proposed_path.string();
boost::nowide::ofstream c;
c.open(file, std::ios::out | std::ios::trunc);
c << ret << std::endl;
c.close();
boost::nowide::cout << "Output for your request is written into " << file << std::endl;
}
else
printf("%s", ret.c_str());
return true;
}
namespace IO {
enum ExportFormat : int {
OBJ,
STL,
// SVG,
TMF,
Gcode
};
}
static std::string output_filepath(const Model& model, IO::ExportFormat format, const std::string& cmdline_param)
{
std::string ext;
switch (format) {
case IO::OBJ: ext = ".obj"; break;
case IO::STL: ext = ".stl"; break;
case IO::TMF: ext = ".3mf"; break;
default: assert(false); break;
};
auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext));
// use --output when available
if (!cmdline_param.empty()) {
// if we were supplied a directory, use it and append our automatically generated filename
boost::filesystem::path cmdline_path(cmdline_param);
if (boost::filesystem::is_directory(cmdline_path))
proposed_path = cmdline_path / proposed_path.filename();
else
proposed_path = cmdline_path;
}
return proposed_path.string();
}
static bool export_models(std::vector<Model>& models, IO::ExportFormat format, const std::string& cmdline_param)
{
for (Model& model : models) {
const std::string path = output_filepath(model, format, cmdline_param);
bool success = false;
switch (format) {
case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break;
case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break;
case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break;
default: assert(false); break;
}
if (success)
std::cout << "File exported to " << path << std::endl;
else {
std::cerr << "File export to " << path << " failed" << std::endl;
return false;
}
}
return true;
}
static std::function<ThumbnailsList(const ThumbnailsParams&)> get_thumbnail_generator_cli(Print& fff_print)
{
if (!fff_print.model().objects.empty() && boost::iends_with(fff_print.model().objects.front()->input_file, ".3mf")) {
std::string filename = fff_print.model().objects.front()->input_file;
return [filename](const ThumbnailsParams&) {
ThumbnailsList list_out;
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, filename))
return list_out;
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
int index = mz_zip_reader_locate_file(&archive, "Metadata/thumbnail.png", nullptr, 0);
if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat))
return list_out;
std::string buffer;
buffer.resize(int(stat.m_uncomp_size));
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0)
return list_out;
close_zip_reader(&archive);
std::vector<unsigned char> data;
unsigned width = 0;
unsigned height = 0;
png::decode_png(buffer, data, width, height);
{
// Flip the image vertically so it matches the convention in Thumbnails generator.
const int row_size = width * 4; // Each pixel is 4 bytes (RGBA)
std::vector<unsigned char> temp_row(row_size);
for (int i = 0; i < height / 2; ++i) {
unsigned char* top_row = &data[i * row_size];
unsigned char* bottom_row = &data[(height - i - 1) * row_size];
std::copy(bottom_row, bottom_row + row_size, temp_row.begin());
std::copy(top_row, top_row + row_size, bottom_row);
std::copy(temp_row.begin(), temp_row.end(), top_row);
}
}
ThumbnailData th;
th.set(width, height);
th.pixels = data;
list_out.push_back(th);
return list_out;
};
}
return [](const ThumbnailsParams&) ->ThumbnailsList { return {}; };
}
bool process_actions(Data& cli, const DynamicPrintConfig& print_config, std::vector<Model>& models)
{
DynamicPrintConfig& actions = cli.actions_config;
DynamicPrintConfig& transform = cli.transform_config;
// doesn't need any aditional input
if (actions.has("help")) {
print_help();
}
if (actions.has("help_fff")) {
print_help(true, ptFFF);
}
if (actions.has("help_sla")) {
print_help(true, ptSLA);
}
if (actions.has("info")) {
if (models.empty()) {
boost::nowide::cerr << "error: cannot show info for empty models." << std::endl;
return 1;
}
// --info works on unrepaired model
for (Model& model : models) {
model.add_default_instances();
model.print_info();
}
}
if (actions.has("save")) {
//FIXME check for mixing the FFF / SLA parameters.
// or better save fff_print_config vs. sla_print_config
print_config.save(actions.opt_string("save"));
}
if (models.empty() && (actions.has("export_stl") || actions.has("export_obj") || actions.has("export_3mf"))) {
boost::nowide::cerr << "error: cannot export empty models." << std::endl;
return 1;
}
const std::string output = cli.misc_config.has("output") ? cli.misc_config.opt_string("output") : "";
if (actions.has("export_stl")) {
for (auto& model : models)
model.add_default_instances();
if (!export_models(models, IO::STL, output))
return 1;
}
if (actions.has("export_obj")) {
for (auto& model : models)
model.add_default_instances();
if (!export_models(models, IO::OBJ, output))
return 1;
}
if (actions.has("export_3mf")) {
if (!export_models(models, IO::TMF, output))
return 1;
}
if (actions.has("slice")) {
PrinterTechnology printer_technology = Preset::printer_technology(print_config);
const Vec2crd gap{ s_multiple_beds.get_bed_gap() };
arr2::ArrangeBed bed = arr2::to_arrange_bed(get_bed_shape(print_config), gap);
arr2::ArrangeSettings arrange_cfg;
arrange_cfg.set_distance_from_objects(min_object_distance(print_config));
for (Model& model : models) {
// If all objects have defined instances, their relative positions will be
// honored when printing (they will be only centered, unless --dont-arrange
// is supplied); if any object has no instances, it will get a default one
// and all instances will be rearranged (unless --dont-arrange is supplied).
if (!transform.has("dont_arrange") || !transform.opt_bool("dont_arrange")) {
if (transform.has("center")) {
Vec2d c = transform.option<ConfigOptionPoint>("center")->value;
arrange_objects(model, arr2::InfiniteBed{ scaled(c) }, arrange_cfg);
}
else
arrange_objects(model, bed, arrange_cfg);
}
Print fff_print;
SLAPrint sla_print;
sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) {
if (s.percent >= 0) { // FIXME: is this sufficient?
printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str());
std::fflush(stdout);
}
});
PrintBase* print = (printer_technology == ptFFF) ? static_cast<PrintBase*>(&fff_print) : static_cast<PrintBase*>(&sla_print);
if (printer_technology == ptFFF) {
for (auto* mo : model.objects)
fff_print.auto_assign_extruders(mo);
}
print->apply(model, print_config);
std::string err = print->validate();
if (!err.empty()) {
boost::nowide::cerr << err << std::endl;
return 1;
}
std::string outfile = output;
if (print->empty())
boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl;
else
try {
std::string outfile_final;
print->process();
if (printer_technology == ptFFF) {
// The outfile is processed by a PlaceholderParser.
outfile = fff_print.export_gcode(outfile, nullptr, get_thumbnail_generator_cli(fff_print));
outfile_final = fff_print.print_statistics().finalize_output_path(outfile);
}
else {
outfile = sla_print.output_filepath(outfile);
// We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata
outfile_final = sla_print.print_statistics().finalize_output_path(outfile);
sla_print.export_print(outfile_final);
}
if (outfile != outfile_final) {
if (Slic3r::rename_file(outfile, outfile_final)) {
boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl;
return false;
}
outfile = outfile_final;
}
// Run the post-processing scripts if defined.
run_post_process_scripts(outfile, fff_print.full_print_config());
boost::nowide::cout << "Slicing result exported to " << outfile << std::endl;
}
catch (const std::exception& ex) {
boost::nowide::cerr << ex.what() << std::endl;
return false;
}
}
}
return true;
}
}