printrequest implementation:

printrequest reader WIP
Co-authored-by: David Kocik <kocikdav@gmail.com>

transformation matrix loading

Loading filaments from printRequest

Conversion of string to double

Reading PrintRequest when material not installed

empty tree check + allow printrequest for mac

PrintRequest: Set print profile if current doesnt have compatible sla material

followup fix of c24477cd10d92de00874504be434872d49a20ec9

Fix compile error on gcc
This commit is contained in:
Lukas Matena 2024-01-04 21:18:04 +01:00
parent 9e2ecb4607
commit 9c0cb3b47c
9 changed files with 273 additions and 8 deletions

View File

@ -149,6 +149,8 @@ set(SLIC3R_SOURCES
Format/SVG.cpp
Format/SLAArchiveFormatRegistry.hpp
Format/SLAArchiveFormatRegistry.cpp
Format/PrintRequest.cpp
Format/PrintRequest.cpp
GCode/ThumbnailData.cpp
GCode/ThumbnailData.hpp
GCode/Thumbnails.cpp

View File

@ -0,0 +1,161 @@
#include "PrintRequest.hpp"
#include <boost/property_tree/xml_parser.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem.hpp>
#include <fast_float/fast_float.h>
#include "libslic3r/Exception.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/Format/STL.hpp"
//#include "slic3r/GUI/format.hpp"
namespace Slic3r {
namespace pt = boost::property_tree;
namespace {
void read_file(const char* input_file, pt::ptree& tree)
{
boost::filesystem::path path(input_file);
boost::nowide::ifstream ifs(path.string());
try {
pt::read_xml(ifs, tree);
}
catch (const boost::property_tree::xml_parser::xml_parser_error& err) {
throw Slic3r::RuntimeError("Failed reading PrintRequest file. File format is corrupt.");
}
}
void read_tree(const boost::property_tree::ptree::value_type& section, boost::filesystem::path& model_path, std::string& material, std::string& material_color, std::vector<std::string>& transformation_matrix)
{
for (const auto& data : section.second) {
if (data.first == "Path") {
model_path = boost::filesystem::path(data.second.data());
}
else if (data.first == "Material") {
material = data.second.data();
}
else if (data.first == "MaterialColor") {
material_color = data.second.data();
}
else if (data.first == "TransformationMatrix") {
transformation_matrix.reserve(16);
for (const auto& element : data.second) {
transformation_matrix.emplace_back(element.second.data());
}
}
}
}
bool fill_model(Model* model, const boost::filesystem::path& model_path, const std::string& material, const std::vector<std::string>& transformation_matrix)
{
if (!boost::filesystem::exists(model_path))
throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path doesn't exists. " + model_path.string());
if (!boost::algorithm::iends_with(model_path.string(), ".stl"))
throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path is not stl file. " + model_path.string());
bool result = load_stl(model_path.string().c_str(), model);
if (!material.empty()) {
model->objects.back()->volumes.front()->set_material_id(material);
}
return result;
}
void add_instance(Model* model, const boost::filesystem::path& model_path, const std::vector<std::string>& transformation_matrix)
{
if (transformation_matrix.size() >= 16) {
auto string_to_double = [model_path](const std::string& from) -> double {
double ret_val;
auto answer = fast_float::from_chars(from.data(), from.data() + from.size(), ret_val);
if (answer.ec != std::errc())
throw Slic3r::RuntimeError("Failed reading PrintRequest file. Couldn't parse transformation matrix. " + model_path.string());
return ret_val;
};
Vec3d offset_vector;
Slic3r::Transform3d matrix;
try
{
offset_vector = Slic3r::Vec3d(string_to_double(transformation_matrix[3]), string_to_double(transformation_matrix[7]), string_to_double(transformation_matrix[11]));
// PrintRequest is row-major 4x4, Slic3r::Transform3d (Eigen) is column-major by default 3x3
matrix(0, 0) = string_to_double(transformation_matrix[0]);
matrix(1, 0) = string_to_double(transformation_matrix[1]);
matrix(2, 0) = string_to_double(transformation_matrix[2]);
matrix(0, 1) = string_to_double(transformation_matrix[4]);
matrix(1, 1) = string_to_double(transformation_matrix[5]);
matrix(2, 1) = string_to_double(transformation_matrix[6]);
matrix(0, 2) = string_to_double(transformation_matrix[8]);
matrix(1, 2) = string_to_double(transformation_matrix[9]);
matrix(2, 2) = string_to_double(transformation_matrix[10]);
}
catch (const Slic3r::RuntimeError& e) {
throw e;
}
ModelObject* object = model->objects.back();
Slic3r::Geometry::Transformation transformation(matrix);
transformation.set_offset(offset_vector);
object->add_instance(transformation);
}
}
}
bool load_printRequest(const char* input_file, Model* model)
{
pt::ptree tree;
try
{
read_file(input_file, tree);
}
catch (const std::exception& e)
{
throw e;
}
bool result = true;
for (const auto& section0 : tree) {
if (section0.first != "PrintRequest")
continue;
if (section0.second.empty())
continue;
for (const auto& section1 : section0.second) {
if (section1.first != "Files")
continue;
if (section1.second.empty())
continue;
for (const auto& section2 : section1.second) {
if (section2.first != "File")
continue;
if (section2.second.empty())
continue;
boost::filesystem::path model_path;
std::string material;
std::string material_color;
std::vector<std::string> transformation_matrix;
try
{
read_tree(section2, model_path, material, material_color, transformation_matrix);
result = result && fill_model(model, model_path, material, transformation_matrix);
if (!result)
return false;
add_instance(model, model_path, transformation_matrix);
}
catch (const std::exception& e)
{
throw e;
}
}
}
}
return true;
}
} // namespace Slic3r

View File

@ -0,0 +1,12 @@
#ifndef slic3r_Format_PrintRequest_hpp_
#define slic3r_Format_PrintRequest_hpp_
namespace Slic3r {
class Model;
bool load_printRequest(const char* input_file, Model* model);
} //namespace Slic3r
#endif

View File

@ -28,6 +28,7 @@
#include "Format/3mf.hpp"
#include "Format/STEP.hpp"
#include "Format/SVG.hpp"
#include "Format/PrintRequest.hpp"
#include <float.h>
@ -140,6 +141,8 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false);
else if (boost::algorithm::iends_with(input_file, ".svg"))
result = load_svg(input_file, model);
else if (boost::ends_with(input_file, ".printRequest"))
result = load_printRequest(input_file.c_str(), &model);
else
throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip).");
@ -148,9 +151,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
if (model.objects.empty())
throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
for (ModelObject *o : model.objects)
o->input_file = input_file;
if (!boost::ends_with(input_file, ".printRequest"))
for (ModelObject *o : model.objects)
o->input_file = input_file;
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();

View File

@ -1213,6 +1213,20 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string&
return alias;
}
const std::string& PresetCollection::get_preset_name_by_alias_invisible(const std::string& alias) const
{
for (
// Find the 1st profile name with the alias.
auto it = Slic3r::lower_bound_by_predicate(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [&alias](auto& l) { return l.first < alias; });
// Continue over all profile names with the same alias.
it != m_map_alias_to_profile_name.end() && it->first == alias; ++it)
if (auto it_preset = this->find_preset_internal(it->second);
it_preset != m_presets.end() && it_preset->name == it->second &&
it_preset->is_compatible)
return it_preset->name;
return alias;
}
const std::string* PresetCollection::get_preset_name_renamed(const std::string &old_name) const
{
auto it_renamed = m_map_system_profile_renamed.find(old_name);
@ -1478,13 +1492,13 @@ Preset& PresetCollection::select_preset(size_t idx)
return m_presets[idx];
}
bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force)
bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force, bool force_invisible /*= false*/)
{
std::string name = Preset::remove_suffix_modified(name_w_suffix);
// 1) Try to find the preset by its name.
auto it = this->find_preset_internal(name);
size_t idx = 0;
if (it != m_presets.end() && it->name == name && it->is_visible)
if (it != m_presets.end() && it->name == name && (force_invisible || it->is_visible))
// Preset found by its name and it is visible.
idx = it - m_presets.begin();
else {

View File

@ -410,6 +410,7 @@ public:
PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); }
const std::string& get_preset_name_by_alias(const std::string& alias) const;
const std::string& get_preset_name_by_alias_invisible(const std::string& alias) const;
const std::string* get_preset_name_renamed(const std::string &old_name) const;
// used to update preset_choice from Tab
@ -528,7 +529,8 @@ public:
// Select a profile by its name. Return true if the selection changed.
// Without force, the selection is only updated if the index changes.
// With force, the changes are reverted if the new index is the same as the old index.
bool select_preset_by_name(const std::string &name, bool force);
// With force_invisible, force preset selection even it's invisible.
bool select_preset_by_name(const std::string &name, bool force, bool force_invisible = false);
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string path_from_name(const std::string &new_name) const;

View File

@ -496,6 +496,20 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p
return presets.get_preset_name_by_alias(alias);
}
const std::string& PresetBundle::get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const
{
// there are not aliases for Printers profiles
if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID)
return alias;
const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints :
preset_type == Preset::TYPE_SLA_PRINT ? sla_prints :
preset_type == Preset::TYPE_FILAMENT ? filaments :
sla_materials;
return presets.get_preset_name_by_alias_invisible(alias);
}
void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type,
const std::vector<std::string>& unselected_options)
{

View File

@ -182,6 +182,7 @@ public:
void load_installed_printers(const AppConfig &config);
const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias, int extruder_id = -1);
const std::string& get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const;
// Save current preset of a provided type under a new name. If the name is different from the old one,
// Unselected option would be reverted to the beginning values

View File

@ -300,6 +300,7 @@ struct Plater::priv
static const std::regex pattern_any_amf;
static const std::regex pattern_prusa;
static const std::regex pattern_zip;
static const std::regex pattern_printRequest;
priv(Plater *q, MainFrame *main_frame);
~priv();
@ -597,6 +598,7 @@ const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::ica
const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase);
const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase);
const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase);
const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase);
Plater::priv::priv(Plater *q, MainFrame *main_frame)
: q(q)
@ -1198,6 +1200,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf);
const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf);
const bool type_prusa = std::regex_match(path.string(), pattern_prusa);
const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest);
Slic3r::Model model;
bool is_project_file = type_prusa;
@ -1370,7 +1373,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
convert_model_if(model, answer_convert_from_imperial_units == wxID_YES);
}
if (model.looks_like_multipart_object()) {
if (!type_printRequest && model.looks_like_multipart_object()) {
if (answer_consider_as_multi_part_objects == wxOK_DEFAULT) {
RichMessageDialog dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n"
@ -1409,6 +1412,58 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
if (!model_object->instances.empty())
model_object->ensure_on_bed(is_project_file);
if (type_printRequest) {
for (ModelInstance* obj_instance : model_object->instances) {
obj_instance->set_offset(obj_instance->get_offset() + Slic3r::to_3d(this->bed.build_volume().bed_center(), -model_object->origin_translation(2)));
}
}
}
if (type_printRequest) {
assert(model.materials.size());
for (const auto& material : model.materials) {
std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL,
Preset::remove_suffix_modified(material.first));
Preset* prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false);
if (!prst) { //did not find compatible profile
// try find alias of material comaptible with another print profile - if exists, use the print profile
auto& prints = wxGetApp().preset_bundle->sla_prints;
std::string edited_print_name = prints.get_edited_preset().name;
bool found = false;
for (auto it = prints.begin(); it != prints.end(); ++it)
{
if (it->name != edited_print_name) {
BOOST_LOG_TRIVIAL(error) << it->name;
wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(it->name, false);
preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL,
Preset::remove_suffix_modified(material.first));
prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false);
if (prst) {
found = true;
break;
}
}
}
if (!found) {
// return to original print profile
wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(edited_print_name, false);
std::string notif_text = into_u8(_L("Material preset was not loaded:"));
notif_text += "\n - " + preset_name;
q->get_notification_manager()->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::PrintInfoNotificationLevel, notif_text);
break;
}
}
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle->sla_materials.get_selected_preset_name() != preset_name) {
preset_bundle->sla_materials.select_preset_by_name(preset_name, false, true);
preset_bundle->tmp_installed_presets = { preset_name };
q->notify_about_installed_presets();
wxGetApp().load_current_presets(false);// For this case we shouldn't check printer_presets
}
break;
}
}
if (one_by_one) {
@ -4814,7 +4869,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*=false*/)
{
const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp|zip)", std::regex::icase);
const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp|zip|printRequest)", std::regex::icase);
const std::regex pattern_gcode_drop(".*[.](gcode|g|bgcode|bgc)", std::regex::icase);
std::vector<fs::path> paths;