diff --git a/include/MeshGroup.h b/include/MeshGroup.h index 232bf08662..6f20523f4a 100644 --- a/include/MeshGroup.h +++ b/include/MeshGroup.h @@ -18,9 +18,21 @@ class Matrix4x3D; * One MeshGroup is a whole which is printed at once. * Generally there is one single MeshGroup, though when using one-at-a-time printing, multiple MeshGroups are processed consecutively. */ -class MeshGroup : public NoCopy +class MeshGroup { public: + MeshGroup() = default; + + ~MeshGroup() = default; + + MeshGroup(MeshGroup&& other) noexcept = default; + + MeshGroup& operator=(MeshGroup&& other) noexcept = default; + + /* Copying a MeshGroup is not allowed */ + MeshGroup(const MeshGroup& other) = delete; + MeshGroup& operator=(const MeshGroup& other) = delete; + std::vector meshes; Settings settings; diff --git a/include/communication/CommandLine.h b/include/communication/CommandLine.h index 47b27f6586..55c742871a 100644 --- a/include/communication/CommandLine.h +++ b/include/communication/CommandLine.h @@ -5,8 +5,10 @@ #define COMMANDLINE_H #include +#include #include //Loading JSON documents to get settings from them. #include //To store the command line arguments. +#include #include //To store the command line arguments. #include "Communication.h" //The class we're implementing. @@ -15,6 +17,9 @@ namespace cura { class Settings; +using setting_map = std::unordered_map; +using container_setting_map = std::unordered_map; + /* * \brief When slicing via the command line, interprets the command line * arguments to initiate a slice. @@ -212,6 +217,20 @@ class CommandLine : public Communication * \return The first definition file that matches the definition ID. */ static std::string findDefinitionFile(const std::string& definition_id, const std::vector& search_directories); + + /* + * \brief Read the resolved JSON values from a file. + * \param element The path to the file to read the JSON values from. + * \return The resolved JSON values. + */ + static std::optional readResolvedJsonValues(const std::filesystem::path& json_filename); + + /* + * \brief Read the resolved JSON values from a document. + * \param document The document to read the JSON values from. + * \return The resolved JSON values. + */ + static std::optional readResolvedJsonValues(const rapidjson::Document& document); }; } // namespace cura diff --git a/include/geometry/Shape.h b/include/geometry/Shape.h index 46091d49a3..aaa373f4e1 100644 --- a/include/geometry/Shape.h +++ b/include/geometry/Shape.h @@ -5,6 +5,7 @@ #define GEOMETRY_SHAPE_H #include "geometry/LinesSet.h" +#include "geometry/Polygon.h" #include "settings/types/Angle.h" namespace cura diff --git a/src/Application.cpp b/src/Application.cpp index 14f5a1bf7d..cb0f17b297 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -131,6 +131,7 @@ void Application::printHelp() const fmt::print(" -p\n\tLog progress information.\n"); fmt::print(" -d Add definition search paths seperated by a `:` (Unix) or `;` (Windows)\n"); fmt::print(" -j\n\tLoad settings.def.json file to register all settings and their defaults.\n"); + fmt::print(" -r\n\tLoad a json file containing resolved setting values.\n"); fmt::print(" -s =\n\tSet a setting to a value for the last supplied object, \n\textruder train, or general settings.\n"); fmt::print(" -l \n\tLoad an STL model. \n"); fmt::print(" -g\n\tSwitch setting focus to the current mesh group only.\n\tUsed for one-at-a-time printing.\n"); diff --git a/src/MeshGroup.cpp b/src/MeshGroup.cpp index f71b2f7bd5..91fae7ae5c 100644 --- a/src/MeshGroup.cpp +++ b/src/MeshGroup.cpp @@ -293,6 +293,8 @@ bool loadMeshIntoMeshGroup(MeshGroup* meshgroup, const char* filename, const Mat spdlog::info("loading '{}' took {:03.3f} seconds", filename, load_timer.restart()); return true; } + spdlog::warn("loading '{}' failed", filename); + return false; } spdlog::warn("Unable to recognize the extension of the file. Currently only .stl and .STL are supported."); return false; diff --git a/src/communication/CommandLine.cpp b/src/communication/CommandLine.cpp index de88960fbf..12e04adc13 100644 --- a/src/communication/CommandLine.cpp +++ b/src/communication/CommandLine.cpp @@ -8,12 +8,16 @@ #include #include //To check if files exist. #include //For std::accumulate. +#include #include //Loading JSON documents to get settings from them. #include #include #include #include +#include +#include #include +#include #include #include @@ -22,6 +26,7 @@ #include "Application.h" //To get the extruders for material estimates. #include "ExtruderTrain.h" #include "FffProcessor.h" //To start a slice and get time estimates. +#include "MeshGroup.h" #include "Slice.h" #include "utils/Matrix4x3D.h" //For the mesh_rotation_matrix setting. #include "utils/format/filesystem_path.h" @@ -354,6 +359,127 @@ void CommandLine::sliceNext() last_settings->add(key, value); break; } + case 'r': + { + /* + * read in resolved values from a json file. The json format of the file resolved settings is the following: + * + * ``` + * { + * "global": [SETTINGS], + * "extruder.0": [SETTINGS], + * "extruder.1": [SETTINGS], + * "model.stl": [SETTINGS] + * } + * ``` + * where `[SETTINGS]` follow the schema + * ``` + * { + * [key: string]: bool | string | number | number[] | number[][] + * } + * ``` + * There can be any number of extruders (denoted with `extruder.n`) and any number of models (denoted with `[modelname].stl`). + * The key of the model values will also be the filename of the relevant model, when running CuraEngine with this option the + * model file with that same name _must_ be in the same folder as the resolved settings json. + */ + + argument_index++; + if (argument_index >= arguments_.size()) + { + spdlog::error("Missing setting name and value with -r argument."); + exit(1); + } + argument = arguments_[argument_index]; + const auto settings = readResolvedJsonValues(std::filesystem::path{ argument }); + + if (! settings.has_value()) + { + spdlog::error("Failed to load JSON file: {}", argument); + exit(1); + } + + constexpr std::string_view global_identifier = "global"; + constexpr std::string_view extruder_identifier = "extruder."; + constexpr std::string_view model_identifier = "model."; + constexpr std::string_view limit_to_extruder_identifier = "limit_to_extruder"; + + // Split the settings into global, extruder and model settings. This is needed since the order in which the settings are applied is important. + // first global settings, then extruder settings, then model settings. The order of these stacks is not enforced in the JSON files. + std::unordered_map global_settings; + container_setting_map extruder_settings; + container_setting_map model_settings; + std::unordered_map limit_to_extruder; + + for (const auto& [key, values] : settings.value()) + { + if (key == global_identifier) + { + global_settings = values; + } + else if (key.starts_with(extruder_identifier)) + { + extruder_settings[key] = values; + } + else if (key == limit_to_extruder_identifier) + { + limit_to_extruder = values; + } + else + { + model_settings[key] = values; + } + } + + for (const auto& [setting_key, setting_value] : global_settings) + { + slice.scene.settings.add(setting_key, setting_value); + } + + for (const auto& [key, values] : extruder_settings) + { + const auto extruder_nr = std::stoi(key.substr(extruder_identifier.size())); + while (slice.scene.extruders.size() <= static_cast(extruder_nr)) + { + slice.scene.extruders.emplace_back(extruder_nr, &slice.scene.settings); + } + for (const auto& [setting_key, setting_value] : values) + { + slice.scene.extruders[extruder_nr].settings_.add(setting_key, setting_value); + } + } + + for (const auto& [key, values] : model_settings) + { + const auto& model_name = key; + + cura::MeshGroup mesh_group; + for (const auto& [setting_key, setting_value] : values) + { + mesh_group.settings.add(setting_key, setting_value); + } + + const auto transformation = mesh_group.settings.get("mesh_rotation_matrix"); + const auto extruder_nr = mesh_group.settings.get("extruder_nr"); + + if (! loadMeshIntoMeshGroup(&mesh_group, model_name.c_str(), transformation, slice.scene.extruders[extruder_nr].settings_)) + { + spdlog::error("Failed to load model: {}. (error number {})", model_name, errno); + exit(1); + } + + slice.scene.mesh_groups.push_back(std::move(mesh_group)); + } + for (const auto& [key, value] : limit_to_extruder) + { + const auto extruder_nr = std::stoi(value.substr(extruder_identifier.size())); + if (extruder_nr >= 0) + { + slice.scene.limit_to_extruder[key] = &slice.scene.extruders[extruder_nr]; + } + } + + break; + } default: { spdlog::error("Unknown option: -{}", argument[1]); @@ -586,6 +712,55 @@ void CommandLine::loadJSONSettings(const rapidjson::Value& element, Settings& se } } +std::optional CommandLine::readResolvedJsonValues(const std::filesystem::path& json_filename) +{ + std::ifstream file(json_filename, std::ios::binary); + if (! file) + { + spdlog::error("Couldn't open JSON file: {}", json_filename); + return std::nullopt; + } + + std::vector read_buffer(std::istreambuf_iterator(file), {}); + rapidjson::MemoryStream memory_stream(read_buffer.data(), read_buffer.size()); + + rapidjson::Document json_document; + json_document.ParseStream(memory_stream); + if (json_document.HasParseError()) + { + spdlog::error("Error parsing JSON (offset {}): {}", json_document.GetErrorOffset(), GetParseError_En(json_document.GetParseError())); + return std::nullopt; + } + + return readResolvedJsonValues(json_document); +} + +std::optional CommandLine::readResolvedJsonValues(const rapidjson::Document& document) +{ + if (! document.IsObject()) + { + return std::nullopt; + } + + container_setting_map result; + for (rapidjson::Value::ConstMemberIterator resolved_key = document.MemberBegin(); resolved_key != document.MemberEnd(); resolved_key++) + { + std::unordered_map values; + for (rapidjson::Value::ConstMemberIterator resolved_value = resolved_key->value.MemberBegin(); resolved_value != resolved_key->value.MemberEnd(); resolved_value++) + { + std::string value_string; + if (! jsonValue2Str(resolved_value->value, value_string)) + { + spdlog::warn("Unrecognized data type in JSON setting {}", resolved_value->name.GetString()); + continue; + } + values.emplace(resolved_value->name.GetString(), value_string); + } + result.emplace(resolved_key->name.GetString(), std::move(values)); + } + return result; +} + std::string CommandLine::findDefinitionFile(const std::string& definition_id, const std::vector& search_directories) { for (const auto& search_directory : search_directories)