Merge remote-tracking branch 'origin/updating' into new_main_page_ui

This commit is contained in:
YuSanka 2018-05-02 08:42:30 +02:00
commit 2cab573a02
95 changed files with 7513 additions and 1736 deletions

View file

@ -170,10 +170,14 @@ add_library(libslic3r STATIC
)
add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/GUI/AboutDialog.cpp
${LIBDIR}/slic3r/GUI/AboutDialog.hpp
${LIBDIR}/slic3r/GUI/AppConfig.cpp
${LIBDIR}/slic3r/GUI/AppConfig.hpp
${LIBDIR}/slic3r/GUI/BitmapCache.cpp
${LIBDIR}/slic3r/GUI/BitmapCache.hpp
${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.cpp
${LIBDIR}/slic3r/GUI/ConfigSnapshotDialog.hpp
${LIBDIR}/slic3r/GUI/3DScene.cpp
${LIBDIR}/slic3r/GUI/3DScene.hpp
${LIBDIR}/slic3r/GUI/GLShader.cpp
@ -216,12 +220,22 @@ add_library(libslic3r_gui STATIC
${LIBDIR}/slic3r/Config/Version.hpp
${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp
${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp
${LIBDIR}/slic3r/GUI/ConfigWizard.cpp
${LIBDIR}/slic3r/GUI/ConfigWizard.hpp
${LIBDIR}/slic3r/GUI/MsgDialog.cpp
${LIBDIR}/slic3r/GUI/MsgDialog.hpp
${LIBDIR}/slic3r/GUI/UpdateDialogs.cpp
${LIBDIR}/slic3r/GUI/UpdateDialogs.hpp
${LIBDIR}/slic3r/Utils/Http.cpp
${LIBDIR}/slic3r/Utils/Http.hpp
${LIBDIR}/slic3r/Utils/OctoPrint.cpp
${LIBDIR}/slic3r/Utils/OctoPrint.hpp
${LIBDIR}/slic3r/Utils/Bonjour.cpp
${LIBDIR}/slic3r/Utils/Bonjour.hpp
${LIBDIR}/slic3r/Utils/PresetUpdater.cpp
${LIBDIR}/slic3r/Utils/PresetUpdater.hpp
${LIBDIR}/slic3r/Utils/Time.cpp
${LIBDIR}/slic3r/Utils/Time.hpp
)
add_library(admesh STATIC
@ -367,6 +381,7 @@ set(XS_XSP_FILES
${XSP_DIR}/SurfaceCollection.xsp
${XSP_DIR}/TriangleMesh.xsp
${XSP_DIR}/Utils_OctoPrint.xsp
${XSP_DIR}/Utils_PresetUpdater.xsp
${XSP_DIR}/XS.xsp
)
foreach (file ${XS_XSP_FILES})
@ -412,7 +427,7 @@ if(APPLE)
# Ignore undefined symbols of the perl interpreter, they will be found in the caller image.
target_link_libraries(XS "-undefined dynamic_lookup")
endif()
target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri)
target_link_libraries(XS libslic3r libslic3r_gui admesh miniz clipper nowide polypartition poly2tri semver)
if(SLIC3R_PROFILE)
target_link_libraries(XS Shiny)
endif()
@ -539,13 +554,13 @@ if (SLIC3R_PRUSACONTROL)
set(wxWidgets_UseAlienWx 1)
if (wxWidgets_UseAlienWx)
set(AlienWx_DEBUG 1)
find_package(AlienWx REQUIRED COMPONENTS base core adv)
find_package(AlienWx REQUIRED COMPONENTS base core adv html)
include_directories(${AlienWx_INCLUDE_DIRS})
#add_compile_options(${AlienWx_CXX_FLAGS})
add_definitions(${AlienWx_DEFINITIONS})
set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES})
else ()
find_package(wxWidgets REQUIRED COMPONENTS base core adv)
find_package(wxWidgets REQUIRED COMPONENTS base core adv html)
include(${wxWidgets_USE_FILE})
endif ()
add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS)
@ -625,6 +640,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:XS>" "${PERL_LOCAL_LIB_DIR}/auto/Slic3r/XS/"
COMMAND ${CMAKE_COMMAND} -E make_directory "${PERL_LOCAL_LIB_DIR}/Slic3r/"
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/xs/lib/Slic3r/XS.pm" "${PERL_LOCAL_LIB_DIR}/Slic3r/"
COMMENT "Installing XS.pm and XS.{so,dll,bundle} into the local-lib directory ..."
)
if(APPLE)
add_custom_command(

View file

@ -12,7 +12,7 @@ our $VERSION = '0.01';
BEGIN {
if ($^O eq 'MSWin32') {
eval "use Wx";
# eval "use Wx::Html";
eval "use Wx::Html";
eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code)
}
}

View file

@ -659,6 +659,7 @@ public:
ConfigOptionPoints() : ConfigOptionVector<Pointf>() {}
explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector<Pointf>(n, value) {}
explicit ConfigOptionPoints(std::initializer_list<Pointf> il) : ConfigOptionVector<Pointf>(std::move(il)) {}
explicit ConfigOptionPoints(const std::vector<Pointf> &values) : ConfigOptionVector<Pointf>(values) {}
static ConfigOptionType static_type() { return coPoints; }
ConfigOptionType type() const override { return static_type(); }

View file

@ -4,6 +4,7 @@
#include "libslic3r.h"
#include <string>
#include <boost/filesystem/path.hpp>
#include <stdexcept>
namespace Slic3r {
@ -15,6 +16,9 @@ public:
file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) :
std::runtime_error(format_what(msg, file, line)),
m_message(msg), m_filename(file), m_line(line) {}
file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) :
std::runtime_error(format_what(msg, file.string(), line)),
m_message(msg), m_filename(file.string()), m_line(line) {}
// gcc 3.4.2 complains about lack of throw specifier on compiler
// generated dtor
~file_parser_error() throw() {}

View file

@ -16,6 +16,12 @@
#include <Eigen/Dense>
#include <miniz/miniz_zip.h>
// VERSION NUMBERS
// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
const unsigned int VERSION_3MF = 1;
const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
const std::string MODEL_FOLDER = "3D/";
const std::string MODEL_EXTENSION = ".model";
const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA
@ -23,6 +29,7 @@ const std::string CONTENT_TYPES_FILE = "[Content_Types].xml";
const std::string RELATIONSHIPS_FILE = "_rels/.rels";
const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config";
const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
const char* MODEL_TAG = "model";
const char* RESOURCES_TAG = "resources";
@ -36,9 +43,9 @@ const char* COMPONENTS_TAG = "components";
const char* COMPONENT_TAG = "component";
const char* BUILD_TAG = "build";
const char* ITEM_TAG = "item";
const char* METADATA_TAG = "metadata";
const char* CONFIG_TAG = "config";
const char* METADATA_TAG = "metadata";
const char* VOLUME_TAG = "volume";
const char* UNIT_ATTR = "unit";
@ -315,6 +322,10 @@ namespace Slic3r {
typedef std::vector<Instance> InstancesList;
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
typedef std::map<int, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
// Version of the 3mf file
unsigned int m_version;
XML_Parser m_xml_parser;
Model* m_model;
@ -326,6 +337,9 @@ namespace Slic3r {
IdToGeometryMap m_geometries;
CurrentConfig m_curr_config;
IdToMetadataMap m_objects_metadata;
IdToLayerHeightsProfileMap m_layer_heights_profiles;
std::string m_curr_metadata_name;
std::string m_curr_characters;
public:
_3MF_Importer();
@ -339,12 +353,14 @@ namespace Slic3r {
bool _load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
bool _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename);
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
// handlers to parse the .model file
void _handle_start_model_xml_element(const char* name, const char** attributes);
void _handle_end_model_xml_element(const char* name);
void _handle_model_xml_characters(const XML_Char* s, int len);
// handlers to parse the MODEL_CONFIG_FILE file
void _handle_start_config_xml_element(const char* name, const char** attributes);
@ -386,6 +402,9 @@ namespace Slic3r {
bool _handle_start_item(const char** attributes, unsigned int num_attributes);
bool _handle_end_item();
bool _handle_start_metadata(const char** attributes, unsigned int num_attributes);
bool _handle_end_metadata();
bool _create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter);
void _apply_transform(ModelInstance& instance, const Matrix4x4& matrix);
@ -407,6 +426,7 @@ namespace Slic3r {
// callbacks to parse the .model file
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name);
static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len);
// callbacks to parse the MODEL_CONFIG_FILE file
static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes);
@ -414,9 +434,12 @@ namespace Slic3r {
};
_3MF_Importer::_3MF_Importer()
: m_xml_parser(nullptr)
: m_version(0)
, m_xml_parser(nullptr)
, m_model(nullptr)
, m_unit_factor(1.0f)
, m_curr_metadata_name("")
, m_curr_characters("")
{
}
@ -427,6 +450,7 @@ namespace Slic3r {
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle)
{
m_version = 0;
m_model = &model;
m_unit_factor = 1.0f;
m_curr_object.reset();
@ -437,6 +461,9 @@ namespace Slic3r {
m_curr_config.object_id = -1;
m_curr_config.volume_id = -1;
m_objects_metadata.clear();
m_layer_heights_profiles.clear();
m_curr_metadata_name.clear();
m_curr_characters.clear();
clear_errors();
return _load_model_from_file(filename, model, bundle);
@ -472,6 +499,8 @@ namespace Slic3r {
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
@ -489,15 +518,26 @@ namespace Slic3r {
return false;
}
}
}
}
// we then loop again the entries to read other files stored in the archive
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
std::string name(stat.m_filename);
std::replace(name.begin(), name.end(), '\\', '/');
if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE))
{
// extract slic3r lazer heights profile file
_extract_layer_heights_profile_config_from_archive(archive, stat);
}
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
{
// extract slic3r print config file
if (!_extract_print_config_from_archive(archive, stat, bundle, filename))
{
mz_zip_reader_end(&archive);
add_error("Archive does not contain a valid print config");
return false;
}
_extract_print_config_from_archive(archive, stat, bundle, filename);
}
else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE))
{
@ -526,6 +566,13 @@ namespace Slic3r {
return false;
}
IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.first);
if (obj_layer_heights_profile != m_layer_heights_profiles.end())
{
object.second->layer_height_profile = obj_layer_heights_profile->second;
object.second->layer_height_profile_valid = true;
}
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
if (obj_metadata != m_objects_metadata.end())
{
@ -583,6 +630,7 @@ namespace Slic3r {
XML_SetUserData(m_xml_parser, (void*)this);
XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element);
XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters);
void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size);
if (parser_buffer == nullptr)
@ -609,23 +657,90 @@ namespace Slic3r {
return true;
}
bool _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename)
void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename)
{
if (stat.m_uncomp_size > 0)
{
std::vector<char> buffer((size_t)stat.m_uncomp_size + 1, 0);
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0)
{
add_error("Error while reading config data to buffer");
return false;
return;
}
buffer.back() = '\0';
bundle.load_config_string(buffer.data(), archive_filename.c_str());
}
}
return true;
void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
if (stat.m_uncomp_size > 0)
{
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0)
{
add_error("Error while reading layer heights profile data to buffer");
return;
}
if (buffer.back() == '\n')
buffer.pop_back();
std::vector<std::string> objects;
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
for (const std::string& object : objects)
{
std::vector<std::string> object_data;
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
if (object_data.size() != 2)
{
add_error("Error while reading object data");
continue;
}
std::vector<std::string> object_data_id;
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
if (object_data_id.size() != 2)
{
add_error("Error while reading object id");
continue;
}
int object_id = std::atoi(object_data_id[1].c_str());
if (object_id == 0)
{
add_error("Found invalid object id");
continue;
}
IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id);
if (object_item != m_layer_heights_profiles.end())
{
add_error("Found duplicated layer heights profile");
continue;
}
std::vector<std::string> object_data_profile;
boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off);
if ((object_data_profile.size() <= 4) || (object_data_profile.size() % 2 != 0))
{
add_error("Found invalid layer heights profile");
continue;
}
std::vector<coordf_t> profile;
profile.reserve(object_data_profile.size());
for (const std::string& value : object_data_profile)
{
profile.push_back((coordf_t)std::atof(value.c_str()));
}
m_layer_heights_profiles.insert(IdToLayerHeightsProfileMap::value_type(object_id, profile));
}
}
}
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
@ -705,6 +820,8 @@ namespace Slic3r {
res = _handle_start_build(attributes, num_attributes);
else if (::strcmp(ITEM_TAG, name) == 0)
res = _handle_start_item(attributes, num_attributes);
else if (::strcmp(METADATA_TAG, name) == 0)
res = _handle_start_metadata(attributes, num_attributes);
if (!res)
_stop_xml_parser();
@ -741,11 +858,18 @@ namespace Slic3r {
res = _handle_end_build();
else if (::strcmp(ITEM_TAG, name) == 0)
res = _handle_end_item();
else if (::strcmp(METADATA_TAG, name) == 0)
res = _handle_end_metadata();
if (!res)
_stop_xml_parser();
}
void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len)
{
m_curr_characters.append(s, len);
}
void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes)
{
if (m_xml_parser == nullptr)
@ -1052,6 +1176,25 @@ namespace Slic3r {
return true;
}
bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes)
{
m_curr_characters.clear();
std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR);
if (!name.empty())
m_curr_metadata_name = name;
return true;
}
bool _3MF_Importer::_handle_end_metadata()
{
if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION)
m_version = (unsigned int)atoi(m_curr_characters.c_str());
return true;
}
bool _3MF_Importer::_create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter)
{
static const unsigned int MAX_RECURSIONS = 10;
@ -1358,6 +1501,13 @@ namespace Slic3r {
importer->_handle_end_model_xml_element(name);
}
void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len)
{
_3MF_Importer* importer = (_3MF_Importer*)userData;
if (importer != nullptr)
importer->_handle_model_xml_characters(s, len);
}
void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes)
{
_3MF_Importer* importer = (_3MF_Importer*)userData;
@ -1429,6 +1579,7 @@ namespace Slic3r {
bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets);
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items);
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model);
};
@ -1477,6 +1628,14 @@ namespace Slic3r {
return false;
}
// adds layer height profile file
if (!_add_layer_height_profile_file_to_archive(archive, model))
{
mz_zip_writer_end(&archive);
boost::filesystem::remove(filename);
return false;
}
// adds slic3r print config file
if (export_print_config)
{
@ -1552,7 +1711,8 @@ namespace Slic3r {
{
std::stringstream stream;
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">\n";
stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n";
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n";
stream << " <" << RESOURCES_TAG << ">\n";
BuildItemsList build_items;
@ -1736,6 +1896,44 @@ namespace Slic3r {
return true;
}
bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
{
std::string out = "";
char buffer[1024];
unsigned int count = 0;
for (const ModelObject* object : model.objects)
{
++count;
std::vector<double> layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector<double>();
if ((layer_height_profile.size() >= 4) && ((layer_height_profile.size() % 2) == 0))
{
sprintf(buffer, "object_id=%d|", count);
out += buffer;
// Store the layer height profile as a single semicolon separated list.
for (size_t i = 0; i < layer_height_profile.size(); ++i)
{
sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]);
out += buffer;
}
out += "\n";
}
}
if (!out.empty())
{
if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
{
add_error("Unable to add layer heights profile file to archive");
return false;
}
}
return true;
}
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print)
{
char buffer[1024];
@ -1744,10 +1942,13 @@ namespace Slic3r {
GCode::append_full_config(print, out);
if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
if (!out.empty())
{
add_error("Unable to add print config file to archive");
return false;
if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
{
add_error("Unable to add print config file to archive");
return false;
}
}
return true;
@ -1832,10 +2033,7 @@ namespace Slic3r {
_3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, *bundle);
if (!res)
importer.log_errors();
importer.log_errors();
return res;
}

View file

@ -24,6 +24,12 @@
#include <assert.h>
// VERSION NUMBERS
// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them.
// 1 : Introduction of amf versioning. No other change in data saved into amf files.
const unsigned int VERSION_AMF = 1;
const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
namespace Slic3r
@ -32,6 +38,7 @@ namespace Slic3r
struct AMFParserContext
{
AMFParserContext(XML_Parser parser, const std::string& archive_filename, PresetBundle* preset_bundle, Model *model) :
m_version(0),
m_parser(parser),
m_model(*model),
m_object(nullptr),
@ -137,6 +144,8 @@ struct AMFParserContext
std::vector<Instance> instances;
};
// Version of the amf file
unsigned int m_version;
// Current Expat XML parser instance.
XML_Parser m_parser;
// Model to receive objects extracted from an AMF file.
@ -360,9 +369,9 @@ void AMFParserContext::endElement(const char * /* name */)
case NODE_TYPE_VERTEX:
assert(m_object);
// Parse the vertex data
m_object_vertices.emplace_back(atof(m_value[0].c_str()));
m_object_vertices.emplace_back(atof(m_value[1].c_str()));
m_object_vertices.emplace_back(atof(m_value[2].c_str()));
m_object_vertices.emplace_back((float)atof(m_value[0].c_str()));
m_object_vertices.emplace_back((float)atof(m_value[1].c_str()));
m_object_vertices.emplace_back((float)atof(m_value[2].c_str()));
m_value[0].clear();
m_value[1].clear();
m_value[2].clear();
@ -462,6 +471,10 @@ void AMFParserContext::endElement(const char * /* name */)
if (m_volume && m_value[0] == "name")
m_volume->name = std::move(m_value[1]);
}
else if (strncmp(m_value[0].c_str(), SLIC3RPE_AMF_VERSION, strlen(SLIC3RPE_AMF_VERSION)) == 0) {
m_version = (unsigned int)atoi(m_value[1].c_str());
}
m_value[0].clear();
m_value[1].clear();
break;
@ -543,46 +556,8 @@ bool load_amf_file(const char *path, PresetBundle* bundle, Model *model)
return result;
}
// Load an AMF archive into a provided model.
bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model)
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, const char* path, PresetBundle* bundle, Model* model, unsigned int& version)
{
if ((path == nullptr) || (model == nullptr))
return false;
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
mz_bool res = mz_zip_reader_init_file(&archive, path, 0);
if (res == 0)
{
printf("Unable to init zip reader\n");
return false;
}
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
if (num_entries != 1)
{
printf("Found invalid number of entries\n");
mz_zip_reader_end(&archive);
return false;
}
mz_zip_archive_file_stat stat;
res = mz_zip_reader_file_stat(&archive, 0, &stat);
if (res == 0)
{
printf("Unable to extract entry statistics\n");
mz_zip_reader_end(&archive);
return false;
}
if (!boost::iends_with(stat.m_filename, ".amf"))
{
printf("Found invalid internal filename\n");
mz_zip_reader_end(&archive);
return false;
}
if (stat.m_uncomp_size == 0)
{
printf("Found invalid size\n");
@ -610,7 +585,7 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model)
return false;
}
res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0);
if (res == 0)
{
printf("Error while reading model data to buffer\n");
@ -627,6 +602,62 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model)
ctx.endDocument();
version = ctx.m_version;
return true;
}
// Load an AMF archive into a provided model.
bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model)
{
if ((path == nullptr) || (model == nullptr))
return false;
unsigned int version = 0;
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
mz_bool res = mz_zip_reader_init_file(&archive, path, 0);
if (res == 0)
{
printf("Unable to init zip reader\n");
return false;
}
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
if (boost::iends_with(stat.m_filename, ".amf"))
{
if (!extract_model_from_archive(archive, stat, path, bundle, model, version))
{
mz_zip_reader_end(&archive);
printf("Archive does not contain a valid model");
return false;
}
break;
}
}
}
#if 0 // forward compatibility
// we then loop again the entries to read other files stored in the archive
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
// add code to extract the file
}
}
#endif // forward compatibility
mz_zip_reader_end(&archive);
return true;
}
@ -664,6 +695,7 @@ bool store_amf(const char *path, Model *model, Print* print, bool export_print_c
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<amf unit=\"millimeter\">\n";
stream << "<metadata type=\"cad\">Slic3r " << SLIC3R_VERSION << "</metadata>\n";
stream << "<metadata type=\"" << SLIC3RPE_AMF_VERSION << "\">" << VERSION_AMF << "</metadata>\n";
if (export_print_config)
{

View file

@ -718,10 +718,10 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
Helper::store_polyline(polyline, data, z, preview_data);
// updates preview ranges data
preview_data.ranges.height.set_from(height_range);
preview_data.ranges.width.set_from(width_range);
preview_data.ranges.feedrate.set_from(feedrate_range);
preview_data.ranges.volumetric_rate.set_from(volumetric_rate_range);
preview_data.ranges.height.update_from(height_range);
preview_data.ranges.width.update_from(width_range);
preview_data.ranges.feedrate.update_from(feedrate_range);
preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range);
}
void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
@ -790,9 +790,9 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data);
// updates preview ranges data
preview_data.ranges.height.set_from(height_range);
preview_data.ranges.width.set_from(width_range);
preview_data.ranges.feedrate.set_from(feedrate_range);
preview_data.ranges.height.update_from(height_range);
preview_data.ranges.width.update_from(width_range);
preview_data.ranges.feedrate.update_from(feedrate_range);
}
void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data)

File diff suppressed because it is too large Load diff

View file

@ -9,13 +9,17 @@ namespace Slic3r {
class GCode;
class Layer;
class PerExtruderAdjustments;
/*
A standalone G-code filter, to control cooling of the print.
The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
and the print is modified to stretch over a minimum layer time.
*/
// A standalone G-code filter, to control cooling of the print.
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
// and the print is modified to stretch over a minimum layer time.
//
// The simple it sounds, the actual implementation is significantly more complex.
// Namely, for a multi-extruder print, each material may require a different cooling logic.
// For example, some materials may not like to print too slowly, while with some materials
// we may slow down significantly.
//
class CoolingBuffer {
public:
CoolingBuffer(GCode &gcodegen);
@ -25,7 +29,12 @@ public:
GCode* gcodegen() { return &m_gcodegen; }
private:
CoolingBuffer& operator=(const CoolingBuffer&);
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const;
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code.
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
GCode& m_gcodegen;
std::string m_gcode;
@ -34,6 +43,9 @@ private:
std::vector<char> m_axis;
std::vector<float> m_current_pos;
unsigned int m_current_extruder;
// Old logic: proportional.
bool m_cooling_logic_proportional = false;
};
}

View file

@ -99,17 +99,31 @@ void GCodePreviewData::Range::set_from(const Range& other)
float GCodePreviewData::Range::step_size() const
{
return (max - min) / (float)Colors_Count;
return (max - min) / (float)(Colors_Count - 1);
}
const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at_max() const
GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const
{
return colors[Colors_Count - 1];
}
if (empty())
return Color::Dummy;
const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at(float value) const
{
return empty() ? get_color_at_max() : colors[clamp((unsigned int)0, Colors_Count - 1, (unsigned int)((value - min) / step_size()))];
float global_t = (value - min) / step_size();
unsigned int low = (unsigned int)global_t;
unsigned int high = clamp((unsigned int)0, Colors_Count - 1, low + 1);
Color color_low = colors[low];
Color color_high = colors[high];
float local_t = global_t - (float)low;
// interpolate in RGB space
Color ret;
for (unsigned int i = 0; i < 4; ++i)
{
ret.rgba[i] = lerp(color_low.rgba[i], color_high.rgba[i], local_t);
}
return ret;
}
GCodePreviewData::LegendItem::LegendItem(const std::string& text, const GCodePreviewData::Color& color)
@ -266,22 +280,22 @@ const GCodePreviewData::Color& GCodePreviewData::get_extrusion_role_color(Extrus
return extrusion.role_colors[role];
}
const GCodePreviewData::Color& GCodePreviewData::get_height_color(float height) const
GCodePreviewData::Color GCodePreviewData::get_height_color(float height) const
{
return ranges.height.get_color_at(height);
}
const GCodePreviewData::Color& GCodePreviewData::get_width_color(float width) const
GCodePreviewData::Color GCodePreviewData::get_width_color(float width) const
{
return ranges.width.get_color_at(width);
}
const GCodePreviewData::Color& GCodePreviewData::get_feedrate_color(float feedrate) const
GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const
{
return ranges.feedrate.get_color_at(feedrate);
}
const GCodePreviewData::Color& GCodePreviewData::get_volumetric_rate_color(float rate) const
GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const
{
return ranges.volumetric_rate.get_color_at(rate);
}
@ -370,10 +384,10 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
list.reserve(Range::Colors_Count);
float step = range.step_size();
for (unsigned int i = 0; i < Range::Colors_Count; ++i)
for (int i = Range::Colors_Count - 1; i >= 0; --i)
{
char buf[1024];
sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step));
sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step));
list.emplace_back(buf, range.colors[i]);
}
}
@ -408,7 +422,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
}
case Extrusion::Feedrate:
{
Helper::FillListFromRange(items, ranges.feedrate, 0, 1.0f);
Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f);
break;
}
case Extrusion::VolumetricRate:

View file

@ -41,8 +41,7 @@ public:
void set_from(const Range& other);
float step_size() const;
const Color& get_color_at(float value) const;
const Color& get_color_at_max() const;
Color get_color_at(float value) const;
};
struct Ranges
@ -189,10 +188,10 @@ public:
bool empty() const;
const Color& get_extrusion_role_color(ExtrusionRole role) const;
const Color& get_height_color(float height) const;
const Color& get_width_color(float width) const;
const Color& get_feedrate_color(float feedrate) const;
const Color& get_volumetric_rate_color(float rate) const;
Color get_height_color(float height) const;
Color get_width_color(float width) const;
Color get_feedrate_color(float feedrate) const;
Color get_volumetric_rate_color(float rate) const;
void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha);
void set_extrusion_paths_colors(const std::vector<std::string>& colors);

View file

@ -67,7 +67,7 @@ public:
// Set the extruder properties.
void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed,
float unloading_speed, float delay, float cooling_time, std::string ramming_parameters, float nozzle_diameter)
float unloading_speed, float delay, std::string ramming_parameters, float nozzle_diameter)
{
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
m_filpar.push_back(FilamentParameters());
@ -78,7 +78,7 @@ public:
m_filpar[idx].loading_speed = loading_speed;
m_filpar[idx].unloading_speed = unloading_speed;
m_filpar[idx].delay = delay;
m_filpar[idx].cooling_time = cooling_time;
m_filpar[idx].cooling_time = 14.f; // let's fix it for now, cooling moves will be reworked for 1.41 anyway
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter

View file

@ -186,7 +186,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "filament_loading_speed"
|| opt_key == "filament_unloading_speed"
|| opt_key == "filament_toolchange_delay"
|| opt_key == "filament_cooling_time"
|| opt_key == "filament_ramming_parameters"
|| opt_key == "gcode_flavor"
|| opt_key == "single_extruder_multi_material"
@ -598,6 +597,12 @@ std::string Print::validate() const
if (! this->config.use_relative_e_distances)
return "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).";
SlicingParameters slicing_params0 = this->objects.front()->slicing_parameters();
const PrintObject* most_layered_object = this->objects.front(); // object with highest layer_height_profile.size() encountered so far
for (const auto* object : objects)
if (object->layer_height_profile.size() > most_layered_object->layer_height_profile.size())
most_layered_object = object;
for (PrintObject *object : this->objects) {
SlicingParameters slicing_params = object->slicing_parameters();
if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON ||
@ -614,12 +619,15 @@ std::string Print::validate() const
object->layer_height_profile_valid = was_layer_height_profile_valid;
if ( this->config.variable_layer_height ) {
PrintObject* first_object = this->objects.front();
int i = 0;
while ( i < first_object->layer_height_profile.size() && i < object->layer_height_profile.size() ) {
if (std::abs(first_object->layer_height_profile[i] - object->layer_height_profile[i]) > EPSILON )
while ( i < object->layer_height_profile.size() ) {
if (std::abs(most_layered_object->layer_height_profile[i] - object->layer_height_profile[i]) > EPSILON)
return "The Wipe tower is only supported if all objects have the same layer height profile";
++i;
if (i == object->layer_height_profile.size()-2) // this element contains the objects max z, if the other object is taller,
// it does not have to match - we will step over it
if (most_layered_object->layer_height_profile[i] > object->layer_height_profile[i])
++i;
}
}
@ -1082,7 +1090,6 @@ void Print::_make_wipe_tower()
this->config.filament_loading_speed.get_at(i),
this->config.filament_unloading_speed.get_at(i),
this->config.filament_toolchange_delay.get_at(i),
this->config.filament_cooling_time.get_at(i),
this->config.filament_ramming_parameters.get_at(i),
this->config.nozzle_diameter.get_at(i));

View file

@ -481,15 +481,6 @@ PrintConfigDef::PrintConfigDef()
def->cli = "filament-toolchange-delay=f@";
def->min = 0;
def->default_value = new ConfigOptionFloats { 0. };
def = this->add("filament_cooling_time", coFloats);
def->label = L("Cooling time");
def->tooltip = L("The filament is slowly moved back and forth after retraction into the cooling tube "
"for this amount of time.");
def->cli = "filament_cooling_time=i@";
def->sidetext = L("s");
def->min = 0;
def->default_value = new ConfigOptionFloats { 14.f };
def = this->add("filament_ramming_parameters", coStrings);
def->label = L("Ramming parameters");
@ -1688,7 +1679,7 @@ PrintConfigDef::PrintConfigDef()
"temperature control commands in the output.");
def->cli = "temperature=i@";
def->full_label = L("Temperature");
def->max = 0;
def->min = 0;
def->max = max_temp;
def->default_value = new ConfigOptionInts { 200 };

View file

@ -476,7 +476,6 @@ public:
ConfigOptionFloats filament_loading_speed;
ConfigOptionFloats filament_unloading_speed;
ConfigOptionFloats filament_toolchange_delay;
ConfigOptionFloats filament_cooling_time;
ConfigOptionStrings filament_ramming_parameters;
ConfigOptionBool gcode_comments;
ConfigOptionEnum<GCodeFlavor> gcode_flavor;
@ -534,7 +533,6 @@ protected:
OPT_PTR(filament_loading_speed);
OPT_PTR(filament_unloading_speed);
OPT_PTR(filament_toolchange_delay);
OPT_PTR(filament_cooling_time);
OPT_PTR(filament_ramming_parameters);
OPT_PTR(gcode_comments);
OPT_PTR(gcode_flavor);

View file

@ -3,6 +3,8 @@
#include <locale>
#include "libslic3r.h"
namespace Slic3r {
extern void set_logging_level(unsigned int level);
@ -60,6 +62,9 @@ extern std::string timestamp_str();
// to be placed at the top of Slic3r generated files.
inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); }
// getpid platform wrapper
extern unsigned get_current_pid();
// Compute the next highest power of 2 of 32-bit v
// http://graphics.stanford.edu/~seander/bithacks.html
template<typename T>

View file

@ -14,7 +14,7 @@
#include <boost/thread.hpp>
#define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
#define SLIC3R_VERSION "1.39.0"
#define SLIC3R_VERSION "1.40.0"
#define SLIC3R_BUILD "UNKNOWN"
typedef int32_t coord_t;

View file

@ -1,6 +1,12 @@
#include <locale>
#include <ctime>
#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
@ -271,4 +277,13 @@ std::string timestamp_str()
return buf;
}
unsigned get_current_pid()
{
#ifdef WIN32
return GetCurrentProcessId();
#else
return ::getpid();
#endif
}
}; // namespace Slic3r

View file

@ -62,8 +62,8 @@ REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection");
REGISTER_CLASS(Preset, "GUI::Preset");
REGISTER_CLASS(PresetCollection, "GUI::PresetCollection");
REGISTER_CLASS(PresetBundle, "GUI::PresetBundle");
REGISTER_CLASS(PresetHints, "GUI::PresetHints");
REGISTER_CLASS(TabIface, "GUI::Tab");
REGISTER_CLASS(PresetUpdater, "PresetUpdater");
REGISTER_CLASS(OctoPrint, "OctoPrint");
SV* ConfigBase__as_hash(ConfigBase* THIS)

View file

@ -175,6 +175,9 @@ semver_parse_version (const char *str, semver_t *ver) {
slice = (char *) str;
index = 0;
// non mandatory
ver->patch = 0;
while (slice != NULL && index++ < 4) {
next = strchr(slice, DELIMITER[0]);
if (next == NULL)
@ -200,7 +203,8 @@ semver_parse_version (const char *str, semver_t *ver) {
slice = next + 1;
}
return 0;
// Major and minor versions are mandatory, patch version is not mandatory.
return (index == 2 || index == 3) ? 0 : -1;
}
static int
@ -615,3 +619,22 @@ semver_numeric (semver_t *x) {
return num;
}
char *semver_strdup(const char *src) {
if (src == NULL) return NULL;
size_t len = strlen(src) + 1;
char *res = malloc(len);
return res != NULL ? (char *) memcpy(res, src, len) : NULL;
}
semver_t
semver_copy(const semver_t *ver) {
semver_t res = *ver;
if (ver->metadata != NULL) {
res.metadata = strdup(ver->metadata);
}
if (ver->prerelease != NULL) {
res.prerelease = strdup(ver->prerelease);
}
return res;
}

View file

@ -98,6 +98,12 @@ semver_is_valid (const char *s);
int
semver_clean (char *s);
char *
semver_strdup(const char *src);
semver_t
semver_copy(const semver_t *ver);
#ifdef __cplusplus
}
#endif

View file

@ -1,11 +1,13 @@
#include "Snapshot.hpp"
#include "../GUI/AppConfig.hpp"
#include "../GUI/PresetBundle.hpp"
#include "../Utils/Time.hpp"
#include <time.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
@ -56,6 +58,7 @@ void Snapshot::load_ini(const std::string &path)
// Parse snapshot.ini
std::string group_name_vendor = "Vendor:";
std::string key_filament = "filament";
std::string key_prefix_model = "model_";
for (auto &section : tree) {
if (section.first == "snapshot") {
// Parse the common section.
@ -79,6 +82,8 @@ void Snapshot::load_ini(const std::string &path)
this->reason = SNAPSHOT_UPGRADE;
else if (rsn == "downgrade")
this->reason = SNAPSHOT_DOWNGRADE;
else if (rsn == "before_rollback")
this->reason = SNAPSHOT_BEFORE_ROLLBACK;
else if (rsn == "user")
this->reason = SNAPSHOT_USER;
else
@ -106,24 +111,49 @@ void Snapshot::load_ini(const std::string &path)
VendorConfig vc;
vc.name = section.first.substr(group_name_vendor.size());
for (auto &kvp : section.second) {
if (boost::starts_with(kvp.first, "model_")) {
//model:MK2S = 0.4;xxx
//model:MK3 = 0.4;xxx
} else if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") {
if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") {
// Version of the vendor specific config bundle bundled with this snapshot.
auto semver = Semver::parse(kvp.second.data());
if (! semver)
throw_on_parse_error("invalid " + kvp.first + " format for " + section.first);
if (kvp.first == "version")
vc.version = *semver;
vc.version.config_version = *semver;
else if (kvp.first == "min_slic3r_version")
vc.min_slic3r_version = *semver;
vc.version.min_slic3r_version = *semver;
else
vc.max_slic3r_version = *semver;
}
vc.version.max_slic3r_version = *semver;
} else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) {
// Parse the printer variants installed for the current model.
auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())];
std::vector<std::string> variants;
if (unescape_strings_cstyle(kvp.second.data(), variants))
for (auto &variant : variants)
set_variants.insert(std::move(variant));
}
}
this->vendor_configs.emplace_back(std::move(vc));
}
}
// Sort the vendors lexicographically.
std::sort(this->vendor_configs.begin(), this->vendor_configs.begin(),
[](const VendorConfig &cfg1, const VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
}
static std::string reason_string(const Snapshot::Reason reason)
{
switch (reason) {
case Snapshot::SNAPSHOT_UPGRADE:
return "upgrade";
case Snapshot::SNAPSHOT_DOWNGRADE:
return "downgrade";
case Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
return "before_rollback";
case Snapshot::SNAPSHOT_USER:
return "user";
case Snapshot::SNAPSHOT_UNKNOWN:
default:
return "unknown";
}
}
void Snapshot::save_ini(const std::string &path)
@ -138,7 +168,7 @@ void Snapshot::save_ini(const std::string &path)
c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl;
c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
c << "comment = " << this->comment << std::endl;
c << "reason = " << this->reason << std::endl;
c << "reason = " << reason_string(this->reason) << std::endl;
// Export the active presets at the time of the snapshot.
c << std::endl << "[presets]" << std::endl;
@ -151,9 +181,17 @@ void Snapshot::save_ini(const std::string &path)
// Export the vendor configs.
for (const VendorConfig &vc : this->vendor_configs) {
c << std::endl << "[Vendor:" << vc.name << "]" << std::endl;
c << "version = " << vc.version.to_string() << std::endl;
c << "min_slic3r_version = " << vc.min_slic3r_version.to_string() << std::endl;
c << "max_slic3r_version = " << vc.max_slic3r_version.to_string() << std::endl;
c << "version = " << vc.version.config_version.to_string() << std::endl;
c << "min_slic3r_version = " << vc.version.min_slic3r_version.to_string() << std::endl;
c << "max_slic3r_version = " << vc.version.max_slic3r_version.to_string() << std::endl;
// Export installed printer models and their variants.
for (const auto &model : vc.models_variants_installed) {
if (model.second.size() == 0)
continue;
const std::vector<std::string> variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
c << "model_" << model.first << " = " << escaped << std::endl;
}
}
c.close();
}
@ -172,6 +210,82 @@ void Snapshot::export_selections(AppConfig &config) const
config.set("presets", "printer", printer);
}
void Snapshot::export_vendor_configs(AppConfig &config) const
{
std::map<std::string, std::map<std::string, std::set<std::string>>> vendors;
for (const VendorConfig &vc : vendor_configs)
vendors[vc.name] = vc.models_variants_installed;
config.set_vendors(std::move(vendors));
}
// Perform a deep compare of the active print / filament / printer / vendor directories.
// Return true if the content of the current print / filament / printer / vendor directories
// matches the state stored in this snapshot.
bool Snapshot::equal_to_active(const AppConfig &app_config) const
{
// 1) Check, whether this snapshot contains the same set of active vendors, printer models and variants
// as app_config.
{
std::set<std::string> matched;
for (const VendorConfig &vc : this->vendor_configs) {
auto it_vendor_models_variants = app_config.vendors().find(vc.name);
if (it_vendor_models_variants == app_config.vendors().end() ||
it_vendor_models_variants->second != vc.models_variants_installed)
// There are more vendors enabled in the snapshot than currently installed.
return false;
matched.insert(vc.name);
}
for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &v : app_config.vendors())
if (matched.find(v.first) == matched.end() && ! v.second.empty())
// There are more vendors currently installed than enabled in the snapshot.
return false;
}
// 2) Check, whether this snapshot references the same set of ini files as the current state.
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id;
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
boost::filesystem::path path1 = data_dir / subdir;
boost::filesystem::path path2 = snapshot_dir / subdir;
std::vector<std::string> files1, files2;
for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
files1.emplace_back(dir_entry.path().filename().string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
files2.emplace_back(dir_entry.path().filename().string());
std::sort(files1.begin(), files1.end());
std::sort(files2.begin(), files2.end());
if (files1 != files2)
return false;
for (const std::string &filename : files1) {
FILE *f1 = boost::nowide::fopen((path1 / filename).string().c_str(), "rb");
FILE *f2 = boost::nowide::fopen((path2 / filename).string().c_str(), "rb");
bool same = true;
if (f1 && f2) {
char buf1[4096];
char buf2[4096];
do {
size_t r1 = fread(buf1, 1, 4096, f1);
size_t r2 = fread(buf2, 1, 4096, f2);
if (r1 != r2 || memcmp(buf1, buf2, r1)) {
same = false;
break;
}
} while (! feof(f1) || ! feof(f2));
} else
same = false;
if (f1)
fclose(f1);
if (f2)
fclose(f2);
if (! same)
return false;
}
}
return true;
}
size_t SnapshotDB::load_db()
{
boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir();
@ -199,12 +313,29 @@ size_t SnapshotDB::load_db()
}
m_snapshots.emplace_back(std::move(snapshot));
}
// Sort the snapshots by their date/time.
std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; });
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
return m_snapshots.size();
}
void SnapshotDB::update_slic3r_versions(std::vector<Index> &index_db)
{
for (Snapshot &snapshot : m_snapshots) {
for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) {
auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; });
if (it != index_db.end()) {
Index::const_iterator it_version = it->find(vendor_config.version.config_version);
if (it_version != it->end()) {
vendor_config.version.min_slic3r_version = it_version->min_slic3r_version;
vendor_config.version.max_slic3r_version = it_version->max_slic3r_version;
}
}
}
}
}
static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst)
{
if (! boost::filesystem::is_directory(path_dst) &&
@ -225,7 +356,7 @@ static void delete_existing_ini_files(const boost::filesystem::path &path)
boost::filesystem::remove(dir_entry.path());
}
const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
{
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
@ -235,7 +366,7 @@ const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot:
// Snapshot header.
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured);
snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION);
snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); // XXX: have Semver Slic3r version
snapshot.comment = comment;
snapshot.reason = reason;
// Active presets at the time of the snapshot.
@ -250,22 +381,54 @@ const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot:
snapshot.filaments.emplace_back(app_config.get("presets", name));
}
// Vendor specific config bundles and installed printers.
for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &vendor : app_config.vendors()) {
Snapshot::VendorConfig cfg;
cfg.name = vendor.first;
cfg.models_variants_installed = vendor.second;
for (auto it = cfg.models_variants_installed.begin(); it != cfg.models_variants_installed.end();)
if (it->second.empty())
cfg.models_variants_installed.erase(it ++);
else
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
for (const VendorProfile &vp : bundle.vendors)
if (vp.id == cfg.name)
cfg.version.config_version = vp.config_version;
// Fill-in the min/max slic3r version from the config index, if possible.
try {
// Load the config index for the vendor.
Index index;
index.load(data_dir / "vendor" / (cfg.name + ".idx"));
auto it = index.find(cfg.version.config_version);
if (it != index.end()) {
cfg.version.min_slic3r_version = it->min_slic3r_version;
cfg.version.max_slic3r_version = it->max_slic3r_version;
}
} catch (const std::runtime_error &err) {
}
snapshot.vendor_configs.emplace_back(std::move(cfg));
}
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
for (const char *subdir : { "print", "filament", "printer", "vendor" })
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
m_snapshots.emplace_back(std::move(snapshot));
return m_snapshots.back();
}
void SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config)
const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config)
{
for (const Snapshot &snapshot : m_snapshots)
if (snapshot.id == id) {
this->restore_snapshot(snapshot, app_config);
return;
return snapshot;
}
throw std::runtime_error(std::string("Snapshot with id " + id + " was not found."));
}
@ -275,18 +438,59 @@ void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_confi
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
// Remove existing ini files and restore the ini files from the snapshot.
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
delete_existing_ini_files(data_dir / subdir);
copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir);
}
// Update app_config from the snapshot.
// Update AppConfig with the selections of the print / filament / printer profiles
// and about the installed printer types and variants.
snapshot.export_selections(app_config);
snapshot.export_vendor_configs(app_config);
}
// Store information about the snapshot.
bool SnapshotDB::is_on_snapshot(AppConfig &app_config) const
{
// Is the "on_snapshot" configuration value set?
std::string on_snapshot = app_config.get("on_snapshot");
if (on_snapshot.empty())
// No, we are not on a snapshot.
return false;
// Is the "on_snapshot" equal to the current configuration state?
auto it_snapshot = this->snapshot(on_snapshot);
if (it_snapshot != this->end() && it_snapshot->equal_to_active(app_config))
// Yes, we are on the snapshot.
return true;
// No, we are no more on a snapshot. Reset the state.
app_config.set("on_snapshot", "");
return false;
}
SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version)
{
auto it_found = m_snapshots.end();
Snapshot::VendorConfig key;
key.name = vendor_name;
for (auto it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) {
const Snapshot &snapshot = *it;
auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(),
key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name &&
config_version == it_vendor_config->version.config_version) {
// Vendor config found with the correct version.
// Save it, but continue searching, as we want the newest snapshot.
it_found = it;
}
}
return it_found;
}
SnapshotDB::const_iterator SnapshotDB::snapshot(const std::string &id) const
{
for (const_iterator it = m_snapshots.begin(); it != m_snapshots.end(); ++ it)
if (it->id == id)
return it;
return m_snapshots.end();
}
boost::filesystem::path SnapshotDB::create_db_dir()
@ -303,6 +507,26 @@ boost::filesystem::path SnapshotDB::create_db_dir()
return snapshots_dir;
}
SnapshotDB& SnapshotDB::singleton()
{
static SnapshotDB instance;
static bool loaded = false;
if (! loaded) {
try {
loaded = true;
// Load the snapshot database.
instance.load_db();
// Load the vendor specific configuration indices.
std::vector<Index> index_db = Index::load_db();
// Update the min / max slic3r versions compatible with the configurations stored inside the snapshots
// based on the min / max slic3r versions defined by the vendor specific config indices.
instance.update_slic3r_versions(index_db);
} catch (std::exception &ex) {
}
}
return instance;
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View file

@ -1,11 +1,14 @@
#ifndef slic3r_GUI_Snapshot_
#define slic3r_GUI_Snapshot_
#include <map>
#include <set>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "Version.hpp"
#include "../Utils/Semver.hpp"
namespace Slic3r {
@ -15,6 +18,8 @@ class AppConfig;
namespace GUI {
namespace Config {
class Index;
// A snapshot contains:
// Slic3r.ini
// vendor/
@ -28,6 +33,7 @@ public:
SNAPSHOT_UNKNOWN,
SNAPSHOT_UPGRADE,
SNAPSHOT_DOWNGRADE,
SNAPSHOT_BEFORE_ROLLBACK,
SNAPSHOT_USER,
};
@ -39,6 +45,12 @@ public:
// Export the print / filament / printer selections to be activated into the AppConfig.
void export_selections(AppConfig &config) const;
void export_vendor_configs(AppConfig &config) const;
// Perform a deep compare of the active print / filament / printer / vendor directories.
// Return true if the content of the current print / filament / printer / vendor directories
// matches the state stored in this snapshot.
bool equal_to_active(const AppConfig &app_config) const;
// ID of a snapshot should equal to the name of the snapshot directory.
// The ID contains the date/time, reason and comment to be human readable.
@ -50,6 +62,8 @@ public:
std::string comment;
Reason reason;
std::string format_reason() const;
// Active presets at the time of the snapshot.
std::string print;
std::vector<std::string> filaments;
@ -61,41 +75,50 @@ public:
struct VendorConfig {
// Name of the vendor contained in this snapshot.
std::string name;
// Version of the vendor config contained in this snapshot.
Semver version = Semver::invalid();
// Minimum Slic3r version compatible with this vendor configuration.
Semver min_slic3r_version = Semver::zero();
// Maximum Slic3r version compatible with this vendor configuration, or empty.
Semver max_slic3r_version = Semver::inf();
// Version of the vendor config contained in this snapshot, along with compatibility data.
Version version;
// Which printer models of this vendor were installed, and which variants of the models?
std::map<std::string, std::set<std::string>> models_variants_installed;
};
// List of vendor configs contained in this snapshot.
// List of vendor configs contained in this snapshot, sorted lexicographically.
std::vector<VendorConfig> vendor_configs;
};
class SnapshotDB
{
public:
// Initialize the SnapshotDB singleton instance. Load the database if it has not been loaded yet.
static SnapshotDB& singleton();
typedef std::vector<Snapshot>::const_iterator const_iterator;
// Load the snapshot database from the snapshots directory.
// If the snapshot directory or its parent does not exist yet, it will be created.
// Returns a number of snapshots loaded.
size_t load_db();
void update_slic3r_versions(std::vector<Index> &index_db);
// Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles,
// create an index.
const Snapshot& make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
void restore_snapshot(const std::string &id, AppConfig &app_config);
const Snapshot& take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment = "");
const Snapshot& restore_snapshot(const std::string &id, AppConfig &app_config);
void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config);
// Test whether the AppConfig's on_snapshot variable points to an existing snapshot, and the existing snapshot
// matches the current state. If it does not match the current state, the AppConfig's "on_snapshot" ID is reset.
bool is_on_snapshot(AppConfig &app_config) const;
// Finds the newest snapshot, which contains a config bundle for vendor_name with config_version.
const_iterator snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version);
const_iterator begin() const { return m_snapshots.begin(); }
const_iterator end() const { return m_snapshots.end(); }
const_iterator snapshot(const std::string &id) const;
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
private:
// Create the snapshots directory if it does not exist yet.
static boost::filesystem::path create_db_dir();
// Snapshots are sorted by their date/time, oldest first.
std::vector<Snapshot> m_snapshots;
};

View file

@ -6,18 +6,140 @@
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Config.hpp"
#include "../../libslic3r/FileParserError.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
namespace Config {
static boost::optional<Semver> s_current_slic3r_semver = Semver::parse(SLIC3R_VERSION);
static const Semver s_current_slic3r_semver(SLIC3R_VERSION);
// Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix.
static int compare_prerelease(const char *p1, const char *p2)
{
for (;;) {
char c1 = *p1 ++;
char c2 = *p2 ++;
bool a1 = std::isalpha(c1) && c1 != 0;
bool a2 = std::isalpha(c2) && c2 != 0;
if (a1) {
if (a2) {
if (c1 != c2)
return (c1 < c2) ? -1 : 1;
} else
return 1;
} else {
if (a2)
return -1;
else
return 0;
}
}
// This shall never happen.
return 0;
}
bool Version::is_slic3r_supported(const Semver &slic3r_version) const
{
if (! slic3r_version.in_range(min_slic3r_version, max_slic3r_version))
return false;
// Now verify, whether the configuration pre-release status is compatible with the Slic3r's pre-release status.
// Alpha Slic3r will happily load any configuration, while beta Slic3r will ignore alpha configurations etc.
const char *prerelease_slic3r = slic3r_version.prerelease();
const char *prerelease_config = this->config_version.prerelease();
if (prerelease_config == nullptr)
// Released config is always supported.
return true;
else if (prerelease_slic3r == nullptr)
// Released slic3r only supports released configs.
return false;
// Compare the pre-release status of Slic3r against the config.
// If the prerelease status of slic3r is lexicographically lower or equal
// to the prerelease status of the config, accept it.
return compare_prerelease(prerelease_slic3r, prerelease_config) != 1;
}
bool Version::is_current_slic3r_supported() const
{
return this->is_slic3r_supported(*s_current_slic3r_semver);
return this->is_slic3r_supported(s_current_slic3r_semver);
}
#if 0
//TODO: This test should be moved to a unit test, once we have C++ unit tests in place.
static int version_test()
{
Version v;
v.config_version = *Semver::parse("1.1.2");
v.min_slic3r_version = *Semver::parse("1.38.0");
v.max_slic3r_version = Semver::inf();
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
// Test the prerelease status.
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-alpha");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-alpha1");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-beta");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-rc");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
v.config_version = *Semver::parse("1.1.2-rc2");
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-alpha1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-beta1")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc")));
assert(v.is_slic3r_supported(*Semver::parse("1.39.0-rc2")));
assert(! v.is_slic3r_supported(*Semver::parse("1.39.0")));
// Test the upper boundary.
v.config_version = *Semver::parse("1.1.2");
v.max_slic3r_version = *Semver::parse("1.39.3-beta1");
assert(v.is_slic3r_supported(*Semver::parse("1.38.0")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha")));
assert(! v.is_slic3r_supported(*Semver::parse("1.38.0-alpha1")));
assert(! v.is_slic3r_supported(*Semver::parse("1.37.0-alpha")));
return 0;
}
static int version_test_run = version_test();
#endif
inline char* left_trim(char *c)
{
for (; *c == ' ' || *c == '\t'; ++ c);
@ -38,12 +160,13 @@ inline std::string unquote_value(char *value, char *end, const std::string &path
if (value == end) {
// Empty string is a valid string.
} else if (*value == '"') {
if (++ value < -- end || *end != '"')
if (++ value > -- end || *end != '"')
throw file_parser_error("String not enquoted correctly", path, idx_line);
*end = 0;
if (! unescape_string_cstyle(value, svalue))
throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line);
}
} else
svalue.assign(value, end);
return svalue;
}
@ -53,20 +176,22 @@ inline std::string unquote_version_comment(char *value, char *end, const std::st
if (value == end) {
// Empty string is a valid string.
} else if (*value == '"') {
if (++ value < -- end || *end != '"')
if (++ value > -- end || *end != '"')
throw file_parser_error("Version comment not enquoted correctly", path, idx_line);
*end = 0;
if (! unescape_string_cstyle(value, svalue))
throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line);
}
} else
svalue.assign(value, end);
return svalue;
}
size_t Index::load(const std::string &path)
size_t Index::load(const boost::filesystem::path &path)
{
m_configs.clear();
m_vendor = path.stem().string();
boost::nowide::ifstream ifs(path);
boost::nowide::ifstream ifs(path.string());
std::string line;
size_t idx_line = 0;
Version ver;
@ -74,63 +199,121 @@ size_t Index::load(const std::string &path)
++ idx_line;
// Skip the initial white spaces.
char *key = left_trim(const_cast<char*>(line.data()));
if (*key == '#')
// Skip a comment line.
continue;
// Right trim the line.
char *end = right_trim(key);
if (key == end)
// Skip an empty line.
continue;
// Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-".
char *key_end = key;
bool maybe_semver = false;
for (;; ++ key) {
if (strchr("+.-", *key) != nullptr)
maybe_semver = true;
else if (! std::isalnum(*key))
break;
bool maybe_semver = true;
for (; *key_end != 0; ++ key_end) {
if (std::isalnum(*key_end) || strchr("+.-", *key_end) != nullptr) {
// It may be a semver.
} else if (*key_end == '_') {
// Cannot be a semver, but it may be a key.
maybe_semver = false;
} else
// End of semver or keyword.
break;
}
if (*key != 0 && *key != ' ' && *key != '\t' && *key != '=')
if (*key_end != 0 && *key_end != ' ' && *key_end != '\t' && *key_end != '=')
throw file_parser_error("Invalid keyword or semantic version", path, idx_line);
*key_end = 0;
char *value = left_trim(key_end);
bool key_value_pair = *value == '=';
if (key_value_pair)
value = left_trim(value + 1);
*key_end = 0;
boost::optional<Semver> semver;
if (maybe_semver)
semver = Semver::parse(key);
char *value = left_trim(key_end);
if (*value == '=') {
if (key_value_pair) {
if (semver)
throw file_parser_error("Key cannot be a semantic version", path, idx_line);
throw file_parser_error("Key cannot be a semantic version", path, idx_line);\
// Verify validity of the key / value pair.
std::string svalue = unquote_value(left_trim(++ value), end, path, idx_line);
if (key == "min_sic3r_version" || key == "max_slic3r_version") {
std::string svalue = unquote_value(value, end, path.string(), idx_line);
if (strcmp(key, "min_slic3r_version") == 0 || strcmp(key, "max_slic3r_version") == 0) {
if (! svalue.empty())
semver = Semver::parse(key);
semver = Semver::parse(svalue);
if (! semver)
throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line);
if (key == "min_sic3r_version")
if (strcmp(key, "min_slic3r_version") == 0)
ver.min_slic3r_version = *semver;
else
ver.max_slic3r_version = *semver;
} else {
// Ignore unknown keys, as there may come new keys in the future.
}
continue;
}
if (! semver)
throw file_parser_error("Invalid semantic version", path, idx_line);
ver.config_version = *semver;
ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path, idx_line);
ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path.string(), idx_line);
m_configs.emplace_back(ver);
}
// Sort the configs by their version.
std::sort(m_configs.begin(), m_configs.end(), [](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
return m_configs.size();
}
Semver Index::version() const
{
Semver ver = Semver::zero();
for (const Version &cv : m_configs)
if (cv.config_version >= ver)
ver = cv.config_version;
return ver;
}
Index::const_iterator Index::find(const Semver &ver) const
{
Version key;
key.config_version = ver;
auto it = std::lower_bound(m_configs.begin(), m_configs.end(), key,
[](const Version &v1, const Version &v2) { return v1.config_version < v2.config_version; });
return (it == m_configs.end() || it->config_version == ver) ? it : m_configs.end();
}
Index::const_iterator Index::recommended() const
{
int idx = -1;
const_iterator highest = m_configs.end();
const_iterator highest = this->end();
for (const_iterator it = this->begin(); it != this->end(); ++ it)
if (it->is_current_slic3r_supported() &&
(highest == this->end() || highest->max_slic3r_version < it->max_slic3r_version))
(highest == this->end() || highest->config_version < it->config_version))
highest = it;
return highest;
}
std::vector<Index> Index::load_db()
{
boost::filesystem::path cache_dir = boost::filesystem::path(Slic3r::data_dir()) / "cache";
std::vector<Index> index_db;
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) {
Index idx;
try {
idx.load(dir_entry.path());
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
continue;
}
index_db.emplace_back(std::move(idx));
}
if (! errors_cummulative.empty())
throw std::runtime_error(errors_cummulative);
return index_db;
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View file

@ -4,6 +4,8 @@
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "../../libslic3r/FileParserError.hpp"
#include "../Utils/Semver.hpp"
@ -25,7 +27,7 @@ struct Version
// Single comment line.
std::string comment;
bool is_slic3r_supported(const Semver &slicer_version) const { return slicer_version.in_range(min_slic3r_version, max_slic3r_version); }
bool is_slic3r_supported(const Semver &slicer_version) const;
bool is_current_slic3r_supported() const;
};
@ -54,17 +56,28 @@ public:
typedef std::vector<Version>::const_iterator const_iterator;
// Read a config index file in the simple format described in the Index class comment.
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
size_t load(const std::string &path);
size_t load(const boost::filesystem::path &path);
const std::string& vendor() const { return m_vendor; }
// Returns version of the index as the highest version of all the configs.
// If there is no config, Semver::zero() is returned.
Semver version() const;
const_iterator begin() const { return m_configs.begin(); }
const_iterator end() const { return m_configs.end(); }
const_iterator find(const Semver &ver) const;
const std::vector<Version>& configs() const { return m_configs; }
// Finds a recommended config to be installed for the current Slic3r version.
// Returns configs().end() if such version does not exist in the index. This shall never happen
// if the index is valid.
const_iterator recommended() const;
// Load all vendor specific indices.
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
static std::vector<Index> load_db();
private:
std::string m_vendor;
std::vector<Version> m_configs;
};

View file

@ -1,4 +1,4 @@
#include "2DBed.hpp";
#include "2DBed.hpp"
#include <wx/dcbuffer.h>
#include "BoundingBox.hpp"
@ -66,7 +66,7 @@ void Bed_2D::repaint()
shift.y - (cbb.max.y - GetSize().GetHeight()));
// draw bed fill
dc.SetBrush(*new wxBrush(*new wxColour(255, 255, 255), wxSOLID));
dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxSOLID));
wxPointList pt_list;
for (auto pt: m_bed_shape)
{
@ -87,7 +87,7 @@ void Bed_2D::repaint()
}
polylines = intersection_pl(polylines, bed_polygon);
dc.SetPen(*new wxPen(*new wxColour(230, 230, 230), 1, wxSOLID));
dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxSOLID));
for (auto pl : polylines)
{
for (size_t i = 0; i < pl.points.size()-1; i++){
@ -98,8 +98,8 @@ void Bed_2D::repaint()
}
// draw bed contour
dc.SetPen(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxTRANSPARENT));
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT));
dc.DrawPolygon(&pt_list, 0, 0);
auto origin_px = to_pixels(Pointf(0, 0));
@ -108,7 +108,7 @@ void Bed_2D::repaint()
auto axes_len = 50;
auto arrow_len = 6;
auto arrow_angle = Geometry::deg2rad(45.0);
dc.SetPen(*new wxPen(*new wxColour(255, 0, 0), 2, wxSOLID)); // red
dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxSOLID)); // red
auto x_end = Pointf(origin_px.x + axes_len, origin_px.y);
dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(x_end.x, x_end.y));
for (auto angle : { -arrow_angle, arrow_angle }){
@ -118,7 +118,7 @@ void Bed_2D::repaint()
dc.DrawLine(wxPoint(x_end.x, x_end.y), wxPoint(end.x, end.y));
}
dc.SetPen(*new wxPen(*new wxColour(0, 255, 0), 2, wxSOLID)); // green
dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxSOLID)); // green
auto y_end = Pointf(origin_px.x, origin_px.y - axes_len);
dc.DrawLine(wxPoint(origin_px.x, origin_px.y), wxPoint(y_end.x, y_end.y));
for (auto angle : { -arrow_angle, arrow_angle }) {
@ -129,19 +129,23 @@ void Bed_2D::repaint()
}
// draw origin
dc.SetPen(*new wxPen(*new wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(*new wxBrush(*new wxColour(0, 0, 0), wxSOLID));
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxSOLID));
dc.DrawCircle(origin_px.x, origin_px.y, 3);
dc.SetTextForeground(*new wxColour(0, 0, 0));
dc.SetFont(*new wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL));
dc.DrawText("(0,0)", origin_px.x + 1, origin_px.y + 2);
static const auto origin_label = wxString("(0,0)");
dc.SetTextForeground(wxColour(0, 0, 0));
dc.SetFont(wxFont(10, wxDEFAULT, wxNORMAL, wxNORMAL));
auto extent = dc.GetTextExtent(origin_label);
const auto origin_label_x = origin_px.x <= cw / 2 ? origin_px.x + 1 : origin_px.x - 1 - extent.GetWidth();
const auto origin_label_y = origin_px.y <= ch / 2 ? origin_px.y + 1 : origin_px.y - 1 - extent.GetHeight();
dc.DrawText(origin_label, origin_label_x, origin_label_y);
// draw current position
if (m_pos!= Pointf(0, 0)) {
auto pos_px = to_pixels(m_pos);
dc.SetPen(*new wxPen(*new wxColour(200, 0, 0), 2, wxSOLID));
dc.SetBrush(*new wxBrush(*new wxColour(200, 0, 0), wxTRANSPARENT));
dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID));
dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT));
dc.DrawCircle(pos_px.x, pos_px.y, 5);
dc.DrawLine(pos_px.x - 15, pos_px.y, pos_px.x + 15, pos_px.y);

View file

@ -751,7 +751,10 @@ std::vector<double> GLVolumeCollection::get_current_print_zs() const
// Collect layer top positions of all volumes.
std::vector<double> print_zs;
for (GLVolume *vol : this->volumes)
append(print_zs, vol->print_zs);
{
if (vol->is_active)
append(print_zs, vol->print_zs);
}
std::sort(print_zs.begin(), print_zs.end());
// Replace intervals of layers with similar top positions with their average value.
@ -788,15 +791,14 @@ static void thick_lines_to_indexed_vertex_array(
#define TOP 2
#define BOTTOM 3
Line prev_line;
// right, left, top, bottom
int idx_prev[4] = { -1, -1, -1, -1 };
double bottom_z_prev = 0.;
Pointf b1_prev;
Pointf b2_prev;
Vectorf v_prev;
int idx_initial[4] = { -1, -1, -1, -1 };
double width_initial = 0.;
double bottom_z_initial = 0.0;
// loop once more in case of closed loops
size_t lines_end = closed ? (lines.size() + 1) : lines.size();
@ -804,13 +806,18 @@ static void thick_lines_to_indexed_vertex_array(
size_t i = (ii == lines.size()) ? 0 : ii;
const Line &line = lines[i];
double len = unscale(line.length());
double inv_len = 1.0 / len;
double bottom_z = top_z - heights[i];
double middle_z = (top_z + bottom_z) / 2.;
double middle_z = 0.5 * (top_z + bottom_z);
double width = widths[i];
bool is_first = (ii == 0);
bool is_last = (ii == lines_end - 1);
bool is_closing = closed && is_last;
Vectorf v = Vectorf::new_unscale(line.vector());
v.scale(1. / len);
v.scale(inv_len);
Pointf a = Pointf::new_unscale(line.a);
Pointf b = Pointf::new_unscale(line.b);
Pointf a1 = a;
@ -818,17 +825,19 @@ static void thick_lines_to_indexed_vertex_array(
Pointf b1 = b;
Pointf b2 = b;
{
double dist = width / 2.; // scaled
a1.translate(+dist*v.y, -dist*v.x);
a2.translate(-dist*v.y, +dist*v.x);
b1.translate(+dist*v.y, -dist*v.x);
b2.translate(-dist*v.y, +dist*v.x);
double dist = 0.5 * width; // scaled
double dx = dist * v.x;
double dy = dist * v.y;
a1.translate(+dy, -dx);
a2.translate(-dy, +dx);
b1.translate(+dy, -dx);
b2.translate(-dy, +dx);
}
// calculate new XY normals
Vector n = line.normal();
Vectorf3 xy_right_normal = Vectorf3::new_unscale(n.x, n.y, 0);
xy_right_normal.scale(1.f / len);
xy_right_normal.scale(inv_len);
int idx_a[4];
int idx_b[4];
@ -837,14 +846,21 @@ static void thick_lines_to_indexed_vertex_array(
bool bottom_z_different = bottom_z_prev != bottom_z;
bottom_z_prev = bottom_z;
if (!is_first && bottom_z_different)
{
// Found a change of the layer thickness -> Add a cap at the end of the previous segment.
volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]);
}
// Share top / bottom vertices if possible.
if (ii == 0) {
idx_a[TOP] = idx_last ++;
if (is_first) {
idx_a[TOP] = idx_last++;
volume.push_geometry(a.x, a.y, top_z , 0., 0., 1.);
} else {
idx_a[TOP] = idx_prev[TOP];
}
if (ii == 0 || bottom_z_different) {
if (is_first || bottom_z_different) {
// Start of the 1st line segment or a change of the layer thickness while maintaining the print_z.
idx_a[BOTTOM] = idx_last ++;
volume.push_geometry(a.x, a.y, bottom_z, 0., 0., -1.);
@ -852,13 +868,15 @@ static void thick_lines_to_indexed_vertex_array(
volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z);
idx_a[RIGHT] = idx_last ++;
volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z);
} else {
}
else {
idx_a[BOTTOM] = idx_prev[BOTTOM];
}
if (ii == 0) {
if (is_first) {
// Start of the 1st line segment.
width_initial = width;
bottom_z_initial = bottom_z;
memcpy(idx_initial, idx_a, sizeof(int) * 4);
} else {
// Continuing a previous segment.
@ -866,43 +884,54 @@ static void thick_lines_to_indexed_vertex_array(
double v_dot = dot(v_prev, v);
bool sharp = v_dot < 0.707; // sin(45 degrees)
if (sharp) {
// Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn.
idx_a[RIGHT] = idx_last ++;
volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z);
idx_a[LEFT ] = idx_last ++;
volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z);
if (!bottom_z_different)
{
// Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn.
idx_a[RIGHT] = idx_last++;
volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z);
idx_a[LEFT] = idx_last++;
volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z);
}
}
if (v_dot > 0.9) {
// The two successive segments are nearly collinear.
idx_a[LEFT ] = idx_prev[LEFT];
idx_a[RIGHT] = idx_prev[RIGHT];
} else if (! sharp) {
// Create a sharp corner with an overshot and average the left / right normals.
// At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
Pointf intersection;
Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection);
a1 = intersection;
a2 = 2. * a - intersection;
assert(length(a1.vector_to(a)) < width);
assert(length(a2.vector_to(a)) < width);
float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6;
float *p_left_prev = n_left_prev + 3;
float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6;
float *p_right_prev = n_right_prev + 3;
p_left_prev [0] = float(a2.x);
p_left_prev [1] = float(a2.y);
p_right_prev[0] = float(a1.x);
p_right_prev[1] = float(a1.y);
xy_right_normal.x += n_right_prev[0];
xy_right_normal.y += n_right_prev[1];
xy_right_normal.scale(1. / length(xy_right_normal));
n_left_prev [0] = float(-xy_right_normal.x);
n_left_prev [1] = float(-xy_right_normal.y);
n_right_prev[0] = float( xy_right_normal.x);
n_right_prev[1] = float( xy_right_normal.y);
idx_a[LEFT ] = idx_prev[LEFT ];
idx_a[RIGHT] = idx_prev[RIGHT];
} else if (cross(v_prev, v) > 0.) {
if (!bottom_z_different)
{
// The two successive segments are nearly collinear.
idx_a[LEFT ] = idx_prev[LEFT];
idx_a[RIGHT] = idx_prev[RIGHT];
}
}
else if (!sharp) {
if (!bottom_z_different)
{
// Create a sharp corner with an overshot and average the left / right normals.
// At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc.
Pointf intersection;
Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection);
a1 = intersection;
a2 = 2. * a - intersection;
assert(length(a1.vector_to(a)) < width);
assert(length(a2.vector_to(a)) < width);
float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6;
float *p_left_prev = n_left_prev + 3;
float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6;
float *p_right_prev = n_right_prev + 3;
p_left_prev [0] = float(a2.x);
p_left_prev [1] = float(a2.y);
p_right_prev[0] = float(a1.x);
p_right_prev[1] = float(a1.y);
xy_right_normal.x += n_right_prev[0];
xy_right_normal.y += n_right_prev[1];
xy_right_normal.scale(1. / length(xy_right_normal));
n_left_prev [0] = float(-xy_right_normal.x);
n_left_prev [1] = float(-xy_right_normal.y);
n_right_prev[0] = float( xy_right_normal.x);
n_right_prev[1] = float( xy_right_normal.y);
idx_a[LEFT ] = idx_prev[LEFT ];
idx_a[RIGHT] = idx_prev[RIGHT];
}
}
else if (cross(v_prev, v) > 0.) {
// Right turn. Fill in the right turn wedge.
volume.push_triangle(idx_prev[RIGHT], idx_a [RIGHT], idx_prev[TOP] );
volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a [RIGHT] );
@ -911,18 +940,21 @@ static void thick_lines_to_indexed_vertex_array(
volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a [LEFT] );
volume.push_triangle(idx_prev[LEFT], idx_a [LEFT], idx_prev[BOTTOM]);
}
if (ii == lines.size()) {
if (! sharp) {
// Closing a loop with smooth transition. Unify the closing left / right vertices.
memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT ] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6, sizeof(float) * 6);
memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6);
volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end());
// Replace the left / right vertex indices to point to the start of the loop.
for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++ u) {
if (volume.quad_indices[u] == idx_prev[LEFT])
volume.quad_indices[u] = idx_initial[LEFT];
else if (volume.quad_indices[u] == idx_prev[RIGHT])
volume.quad_indices[u] = idx_initial[RIGHT];
if (is_closing) {
if (!sharp) {
if (!bottom_z_different)
{
// Closing a loop with smooth transition. Unify the closing left / right vertices.
memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT ] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6, sizeof(float) * 6);
memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6);
volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end());
// Replace the left / right vertex indices to point to the start of the loop.
for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++ u) {
if (volume.quad_indices[u] == idx_prev[LEFT])
volume.quad_indices[u] = idx_initial[LEFT];
else if (volume.quad_indices[u] == idx_prev[RIGHT])
volume.quad_indices[u] = idx_initial[RIGHT];
}
}
}
// This is the last iteration, only required to solve the transition.
@ -931,13 +963,14 @@ static void thick_lines_to_indexed_vertex_array(
}
// Only new allocate top / bottom vertices, if not closing a loop.
if (closed && ii + 1 == lines.size()) {
if (is_closing) {
idx_b[TOP] = idx_initial[TOP];
} else {
idx_b[TOP] = idx_last ++;
volume.push_geometry(b.x, b.y, top_z , 0., 0., 1.);
}
if (closed && ii + 1 == lines.size() && width == width_initial) {
if (is_closing && (width == width_initial) && (bottom_z == bottom_z_initial)) {
idx_b[BOTTOM] = idx_initial[BOTTOM];
} else {
idx_b[BOTTOM] = idx_last ++;
@ -949,22 +982,26 @@ static void thick_lines_to_indexed_vertex_array(
idx_b[RIGHT ] = idx_last ++;
volume.push_geometry(b1.x, b1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z);
prev_line = line;
memcpy(idx_prev, idx_b, 4 * sizeof(int));
bottom_z_prev = bottom_z;
b1_prev = b1;
b2_prev = b2;
v_prev = v;
v_prev = v;
if (bottom_z_different)
{
// Found a change of the layer thickness -> Add a cap at the beginning of this segment.
volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]);
}
if (! closed) {
// Terminate open paths with caps.
if (i == 0)
if (is_first && !bottom_z_different)
volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]);
// We don't use 'else' because both cases are true if we have only one line.
if (i + 1 == lines.size())
if (is_last && !bottom_z_different)
volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]);
}
// Add quads for a straight hollow tube-like segment.
// bottom-right face
volume.push_quad(idx_a[BOTTOM], idx_b[BOTTOM], idx_b[RIGHT], idx_a[RIGHT]);
@ -1723,6 +1760,11 @@ void _3DScene::load_gcode_preview(const Print* print, const GCodePreviewData* pr
{
_generate_legend_texture(*preview_data, tool_colors);
_load_shells(*print, *volumes, use_VBOs);
// removes empty volumes
volumes->volumes.erase(std::remove_if(volumes->volumes.begin(), volumes->volumes.end(),
[](const GLVolume *volume) { return volume->print_zs.empty(); }),
volumes->volumes.end());
}
}

View file

@ -0,0 +1,126 @@
#include "AboutDialog.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
AboutDialogLogo::AboutDialogLogo(wxWindow* parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
this->SetBackgroundColour(*wxWHITE);
this->logo = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
this->SetMinSize(this->logo.GetSize());
this->Bind(wxEVT_PAINT, &AboutDialogLogo::onRepaint, this);
}
void AboutDialogLogo::onRepaint(wxEvent &event)
{
wxPaintDC dc(this);
dc.SetBackgroundMode(wxTRANSPARENT);
wxSize size = this->GetSize();
int logo_w = this->logo.GetWidth();
int logo_h = this->logo.GetHeight();
dc.DrawBitmap(this->logo, (size.GetWidth() - logo_w)/2, (size.GetHeight() - logo_h)/2, true);
event.Skip();
}
AboutDialog::AboutDialog()
: wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxSize(600, 340), wxCAPTION)
{
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)/**wxWHITE*/);
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
this->SetSizer(hsizer);
// logo
// AboutDialogLogo* logo = new AboutDialogLogo(this);
wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp));
hsizer->Add(logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30);
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
hsizer->Add(vsizer, 1, wxEXPAND, 0);
// title
{
wxStaticText* title = new wxStaticText(this, wxID_ANY, "Slic3r Prusa Edition", wxDefaultPosition, wxDefaultSize);
wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
title_font.SetWeight(wxFONTWEIGHT_BOLD);
title_font.SetFamily(wxFONTFAMILY_ROMAN);
title_font.SetPointSize(24);
title->SetFont(title_font);
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 30);
}
// version
{
auto version_string = _(L("Version ")) + std::string(SLIC3R_VERSION);
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
version_font.SetPointSize(9);
#else
version_font.SetPointSize(11);
#endif
version->SetFont(version_font);
vsizer->Add(version, 0, wxALIGN_LEFT | wxBOTTOM, 10);
}
// text
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
int size[] = {8,8,8,8,8,8,8};
#else
int size[] = {11,11,11,11,11,11,11};
#endif
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
html->SetHTMLBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
html->SetBorders(2);
const char* text =
"<html>"
"<body bgcolor=\"#ffffff\" link=\"#808080\">"
"<font color=\"#808080\">"
"Copyright &copy; 2016-2018 Prusa Research. <br />"
"Copyright &copy; 2011-2017 Alessandro Ranellucci. <br />"
"<a href=\"http://slic3r.org/\">Slic3r</a> is licensed under the "
"<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">GNU Affero General Public License, version 3</a>."
"<br /><br /><br />"
"Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. "
"Manual by Gary Hodgson. Inspired by the RepRap community. <br />"
"Slic3r logo designed by Corey Daniels, <a href=\"http://www.famfamfam.com/lab/icons/silk/\">Silk Icon Set</a> designed by Mark James. "
"</font>"
"</body>"
"</html>";
html->SetPage(text);
vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20);
html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
}
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
event.Skip(false);
}
void AboutDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
this->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,36 @@
#ifndef slic3r_GUI_AboutDialog_hpp_
#define slic3r_GUI_AboutDialog_hpp_
#include "GUI.hpp"
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
namespace Slic3r {
namespace GUI {
class AboutDialogLogo : public wxPanel
{
public:
AboutDialogLogo(wxWindow* parent);
private:
wxBitmap logo;
void onRepaint(wxEvent &event);
};
class AboutDialog : public wxDialog
{
public:
AboutDialog();
private:
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
};
} // namespace GUI
} // namespace Slic3r
#endif

View file

@ -1,5 +1,3 @@
#include <GL/glew.h>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Utils.hpp"
#include "AppConfig.hpp"
@ -9,15 +7,22 @@
#include <string.h>
#include <utility>
#include <assert.h>
#include <vector>
#include <stdexcept>
#include <boost/filesystem.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/algorithm/string/predicate.hpp>
namespace Slic3r {
static const std::string VENDOR_PREFIX = "vendor:";
static const std::string MODEL_PREFIX = "model:";
static const std::string VERSION_CHECK_URL = "https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/Slic3rPE.version";
void AppConfig::reset()
{
m_storage.clear();
@ -42,9 +47,12 @@ void AppConfig::set_defaults()
set("no_defaults", "1");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
// Version check is enabled by default in the config, but it is not implemented yet.
if (get("version_check").empty())
set("version_check", "1");
if (get("preset_update").empty())
set("preset_update", "1");
// Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers.
// https://github.com/prusa3d/Slic3r/issues/233
if (get("use_legacy_opengl").empty())
@ -67,6 +75,19 @@ void AppConfig::load()
if (! data.empty())
// If there is a non-empty data, then it must be a top-level (without a section) config entry.
m_storage[""][section.first] = data;
} else if (boost::starts_with(section.first, VENDOR_PREFIX)) {
// This is a vendor section listing enabled model / variants
const auto vendor_name = section.first.substr(VENDOR_PREFIX.size());
auto &vendor = m_vendors[vendor_name];
for (const auto &kvp : section.second) {
if (! boost::starts_with(kvp.first, MODEL_PREFIX)) { continue; }
const auto model_name = kvp.first.substr(MODEL_PREFIX.size());
std::vector<std::string> variants;
if (! unescape_strings_cstyle(kvp.second.data(), variants)) { continue; }
for (const auto &variant : variants) {
vendor[model_name].insert(variant);
}
}
} else {
// This must be a section name. Read the entries of a section.
std::map<std::string, std::string> &storage = m_storage[section.first];
@ -75,6 +96,16 @@ void AppConfig::load()
}
}
// Figure out if datadir has legacy presets
auto ini_ver = Semver::parse(get("version"));
m_legacy_datadir = false;
if (ini_ver) {
// Make 1.40.0 alphas compare well
ini_ver->set_metadata(boost::none);
ini_ver->set_prerelease(boost::none);
m_legacy_datadir = ini_ver < Semver(1, 40, 0);
}
// Override missing or keys with their defaults.
this->set_defaults();
m_dirty = false;
@ -96,10 +127,57 @@ void AppConfig::save()
for (const std::pair<std::string, std::string> &kvp : category.second)
c << kvp.first << " = " << kvp.second << std::endl;
}
// Write vendor sections
for (const auto &vendor : m_vendors) {
size_t size_sum = 0;
for (const auto &model : vendor.second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
for (const auto &model : vendor.second) {
if (model.second.size() == 0) { continue; }
const std::vector<std::string> variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
c << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
}
}
c.close();
m_dirty = false;
}
bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const
{
const auto it_v = m_vendors.find(vendor);
if (it_v == m_vendors.end()) { return false; }
const auto it_m = it_v->second.find(model);
return it_m == it_v->second.end() ? false : it_m->second.find(variant) != it_m->second.end();
}
void AppConfig::set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable)
{
if (enable) {
if (get_variant(vendor, model, variant)) { return; }
m_vendors[vendor][model].insert(variant);
} else {
auto it_v = m_vendors.find(vendor);
if (it_v == m_vendors.end()) { return; }
auto it_m = it_v->second.find(model);
if (it_m == it_v->second.end()) { return; }
auto it_var = it_m->second.find(variant);
if (it_var == it_m->second.end()) { return; }
it_m->second.erase(it_var);
}
// If we got here, there was an update
m_dirty = true;
}
void AppConfig::set_vendors(const AppConfig &from)
{
m_vendors = from.m_vendors;
m_dirty = true;
}
std::string AppConfig::get_last_dir() const
{
const auto it = m_storage.find("recent");
@ -161,6 +239,12 @@ std::string AppConfig::config_path()
return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string();
}
std::string AppConfig::version_check_url() const
{
auto from_settings = get("version_check_url");
return from_settings.empty() ? VERSION_CHECK_URL : from_settings;
}
bool AppConfig::exists()
{
return boost::filesystem::exists(AppConfig::config_path());

View file

@ -1,15 +1,19 @@
#ifndef slic3r_AppConfig_hpp_
#define slic3r_AppConfig_hpp_
#include <set>
#include <map>
#include <string>
#include "libslic3r/Config.hpp"
#include "slic3r/Utils/Semver.hpp"
namespace Slic3r {
class AppConfig
{
public:
AppConfig() : m_dirty(false) { this->reset(); }
AppConfig() : m_dirty(false), m_legacy_datadir(false) { this->reset(); }
// Clear and reset to defaults.
void reset();
@ -65,6 +69,14 @@ public:
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
void set_vendors(const AppConfig &from);
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
const VendorMap& vendors() const { return m_vendors; }
// return recent/skein_directory or recent/config_directory or empty string.
std::string get_last_dir() const;
void update_config_dir(const std::string &dir);
@ -81,14 +93,25 @@ public:
// Get the default config path from Slic3r::data_dir().
static std::string config_path();
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
bool legacy_datadir() const { return m_legacy_datadir; }
// Get the Slic3r version check url.
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
std::string version_check_url() const;
// Does the config file exist?
static bool exists();
private:
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;
// Map of enabled vendors / models / variants
VendorMap m_vendors;
// Has any value been modified since the config.ini has been last saved or loaded?
bool m_dirty;
// Whether the existing version is before system profiles & configuration updating
bool m_legacy_datadir;
};
}; // namespace Slic3r

View file

@ -2,6 +2,7 @@
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/statbmp.h>
#include <wx/clrpicker.h>
#include "GUI.hpp"
@ -9,7 +10,7 @@ namespace Slic3r {
namespace GUI {
ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions) :
wxDialog(parent, wxID_ANY, "Buttons Description", wxDefaultPosition, wxDefaultSize),
wxDialog(parent, wxID_ANY, "Buttons And Text Colors Description", wxDefaultPosition, wxDefaultSize),
m_icon_descriptions(icon_descriptions)
{
auto grid_sizer = new wxFlexGridSizer(3, 20, 20);
@ -17,6 +18,7 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(grid_sizer, 0, wxEXPAND | wxALL, 20);
// Icon description
for (auto pair : *m_icon_descriptions)
{
auto icon = new wxStaticBitmap(this, wxID_ANY, *pair.first);
@ -32,8 +34,46 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* ic
grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
}
auto button = CreateStdDialogButtonSizer(wxOK);
main_sizer->Add(button, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
// Text color description
auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value")));
sys_label->SetForegroundColour(get_label_clr_sys());
auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_sys());
sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e)
{
sys_label->SetForegroundColour(sys_colour->GetColour());
sys_label->Refresh();
}));
size_t t= 0;
while (t < 3){
grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
++t;
}
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset")));
mod_label->SetForegroundColour(get_label_clr_modified());
auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, get_label_clr_modified());
mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e)
{
mod_label->SetForegroundColour(mod_colour->GetColour());
mod_label->Refresh();
}));
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) {
set_label_clr_sys(sys_colour->GetColour());
set_label_clr_modified(mod_colour->GetColour());
EndModal(wxID_OK);
});
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);

View file

@ -0,0 +1,139 @@
#include "ConfigSnapshotDialog.hpp"
#include "../Config/Snapshot.hpp"
#include "../Utils/Time.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
static std::string format_reason(const Config::Snapshot::Reason reason)
{
switch (reason) {
case Config::Snapshot::SNAPSHOT_UPGRADE:
return std::string(_(L("Upgrade")));
case Config::Snapshot::SNAPSHOT_DOWNGRADE:
return std::string(_(L("Downgrade")));
case Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
return std::string(_(L("Before roll back")));
case Config::Snapshot::SNAPSHOT_USER:
return std::string(_(L("User")));
case Config::Snapshot::SNAPSHOT_UNKNOWN:
default:
return std::string(_(L("Unknown")));
}
}
static std::string generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active)
{
// Start by declaring a row with an alternating background color.
std::string text = "<tr bgcolor=\"";
text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
text += "\">";
text += "<td>";
// Format the row header.
text += std::string("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active: ")) : "") +
Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
if (! snapshot.comment.empty())
text += " (" + snapshot.comment + ")";
text += "</b></font><br>";
// End of row header.
text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
text += _(L("print")) + ": " + snapshot.print + "<br>";
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
text += _(L("printer")) + ": " + snapshot.printer + "<br>";
bool compatible = true;
for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) {
text += _(L("vendor")) + ": " + vc.name + ", ver: " + vc.version.config_version.to_string() + ", min slic3r ver: " + vc.version.min_slic3r_version.to_string();
if (vc.version.max_slic3r_version != Semver::inf())
text += ", max slic3r ver: " + vc.version.max_slic3r_version.to_string();
text += "<br>";
for (const std::pair<std::string, std::set<std::string>> &model : vc.models_variants_installed) {
text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": ";
for (const std::string &variant : model.second) {
if (&variant != &*model.second.begin())
text += ", ";
text += variant;
}
text += "<br>";
}
if (! vc.version.is_current_slic3r_supported()) { compatible = false; }
}
if (! compatible) {
text += "<p align=\"right\">" + _(L("Incompatible with this Slic3r")) + "</p>";
}
else if (! snapshot_active)
text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">" + _(L("Activate")) + "</a></p>";
text += "</td>";
text += "</tr>";
return text;
}
static std::string generate_html_page(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot)
{
std::string text =
"<html>"
"<body bgcolor=\"#ffffff\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">"
"<font color=\"#000000\">";
text += "<table style=\"width:100%\">";
for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) {
const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1];
text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot);
}
text +=
"</table>"
"</font>"
"</body>"
"</html>";
return text;
}
ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &on_snapshot)
: wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
{
this->SetBackgroundColour(*wxWHITE);
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
this->SetSizer(vsizer);
// text
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
int size[] = {8,8,8,8,11,11,11};
#else
int size[] = {11,11,11,11,14,14,14};
#endif
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
html->SetBorders(2);
std::string text = generate_html_page(snapshot_db, on_snapshot);
html->SetPage(text.c_str());
vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0);
html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this);
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE);
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
}
void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
m_snapshot_to_activate = event.GetLinkInfo().GetHref();
this->EndModal(wxID_CLOSE);
this->Close();
}
void ConfigSnapshotDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
this->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,34 @@
#ifndef slic3r_GUI_ConfigSnapshotDialog_hpp_
#define slic3r_GUI_ConfigSnapshotDialog_hpp_
#include "GUI.hpp"
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
namespace Slic3r {
namespace GUI {
namespace Config {
class SnapshotDB;
}
class ConfigSnapshotDialog : public wxDialog
{
public:
ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const std::string &id);
const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; }
private:
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
// If set, it contains a snapshot ID to be restored after the dialog closes.
std::string m_snapshot_to_activate;
};
} // namespace GUI
} // namespace Slic3r
#endif /* slic3r_GUI_ConfigSnapshotDialog_hpp_ */

View file

@ -0,0 +1,852 @@
#include "ConfigWizard_private.hpp"
#include <algorithm>
#include <utility>
#include <unordered_map>
#include <wx/settings.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/dcclient.h>
#include <wx/statbmp.h>
#include <wx/checkbox.h>
#include <wx/statline.h>
#include "libslic3r/Utils.hpp"
#include "PresetBundle.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
namespace Slic3r {
namespace GUI {
// Printer model picker GUI control
struct PrinterPickerEvent : public wxEvent
{
std::string vendor_id;
std::string model_id;
std::string variant_name;
bool enable;
PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) :
wxEvent(winid, eventType),
vendor_id(std::move(vendor_id)),
model_id(std::move(model_id)),
variant_name(std::move(variant_name)),
enable(enable)
{}
virtual wxEvent *Clone() const
{
return new PrinterPickerEvent(*this);
}
};
wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) :
wxPanel(parent),
vendor_id(vendor.id),
variants_checked(0)
{
const auto &models = vendor.models;
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20);
printer_grid->SetFlexibleDirection(wxVERTICAL);
sizer->Add(printer_grid);
auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
namefont.SetWeight(wxFONTWEIGHT_BOLD);
for (const auto &model : models) {
auto *panel = new wxPanel(this);
auto *col_sizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(col_sizer);
auto *title = new wxStaticText(panel, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
title->SetFont(namefont);
col_sizer->Add(title, 0, wxBOTTOM, 3);
auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id);
wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG);
auto *bitmap_widget = new wxStaticBitmap(panel, wxID_ANY, bitmap);
col_sizer->Add(bitmap_widget, 0, wxBOTTOM, 3);
col_sizer->AddSpacer(20);
const auto model_id = model.id;
for (const auto &variant : model.variants) {
const auto label = wxString::Format("%s %s %s", variant.name, _(L("mm")), _(L("nozzle")));
auto *cbox = new Checkbox(panel, label, model_id, variant.name);
const size_t idx = cboxes.size();
cboxes.push_back(cbox);
bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name);
variants_checked += enabled;
cbox->SetValue(enabled);
col_sizer->Add(cbox, 0, wxBOTTOM, 3);
cbox->Bind(wxEVT_CHECKBOX, [this, idx](wxCommandEvent &event) {
if (idx >= this->cboxes.size()) { return; }
this->on_checkbox(this->cboxes[idx], event.IsChecked());
});
}
printer_grid->Add(panel);
}
auto *all_none_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *sel_all = new wxButton(this, wxID_ANY, _(L("Select all")));
auto *sel_none = new wxButton(this, wxID_ANY, _(L("Select none")));
sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true); });
sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
all_none_sizer->AddStretchSpacer();
all_none_sizer->Add(sel_all);
all_none_sizer->Add(sel_none);
sizer->AddStretchSpacer();
sizer->Add(all_none_sizer, 0, wxEXPAND);
SetSizer(sizer);
}
void PrinterPicker::select_all(bool select)
{
for (const auto &cb : cboxes) {
if (cb->GetValue() != select) {
cb->SetValue(select);
on_checkbox(cb, select);
}
}
}
void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
{
variants_checked += checked ? 1 : -1;
PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
AddPendingEvent(evt);
}
// Wizard page base
ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) :
wxPanel(parent),
parent(parent),
shortname(std::move(shortname)),
p_prev(nullptr),
p_next(nullptr)
{
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
auto font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
font.SetWeight(wxFONTWEIGHT_BOLD);
font.SetPointSize(14);
text->SetFont(font);
sizer->Add(text, 0, wxALIGN_LEFT, 0);
sizer->AddSpacer(10);
content = new wxBoxSizer(wxVERTICAL);
sizer->Add(content, 1);
SetSizer(sizer);
this->Hide();
Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
this->Layout();
event.Skip();
});
}
ConfigWizardPage::~ConfigWizardPage() {}
ConfigWizardPage* ConfigWizardPage::chain(ConfigWizardPage *page)
{
if (p_next != nullptr) { p_next->p_prev = nullptr; }
p_next = page;
if (page != nullptr) {
if (page->p_prev != nullptr) { page->p_prev->p_next = nullptr; }
page->p_prev = this;
}
return page;
}
void ConfigWizardPage::append_text(wxString text)
{
auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
widget->Wrap(CONTENT_WIDTH);
widget->SetMinSize(wxSize(CONTENT_WIDTH, -1));
append(widget);
}
void ConfigWizardPage::append_spacer(int space)
{
content->AddSpacer(space);
}
bool ConfigWizardPage::Show(bool show)
{
if (extra_buttons() != nullptr) { extra_buttons()->Show(show); }
return wxPanel::Show(show);
}
void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable); }
// Wizard pages
PageWelcome::PageWelcome(ConfigWizard *parent) :
ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))),
printer_picker(nullptr),
others_buttons(new wxPanel(parent)),
cbox_reset(nullptr)
{
if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) {
wxString::Format(_(L("Run %s")), ConfigWizard::name());
append_text(wxString::Format(
_(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")),
ConfigWizard::name())
);
} else {
cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")));
append(cbox_reset);
}
const auto &vendors = wizard_p()->vendors;
const auto vendor_prusa = vendors.find("PrusaResearch");
if (vendor_prusa != vendors.cend()) {
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors);
printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
append(printer_picker);
}
const size_t num_other_vendors = vendors.size() - (vendor_prusa != vendors.cend());
auto *sizer = new wxBoxSizer(wxHORIZONTAL);
auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors")));
other_vendors->Enable(num_other_vendors > 0);
auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup")));
sizer->Add(other_vendors);
sizer->AddSpacer(BTN_SPACING);
sizer->Add(custom_setup);
other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); });
custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); });
others_buttons->SetSizer(sizer);
}
void PageWelcome::on_page_set()
{
chain(wizard_p()->page_update);
on_variant_checked();
}
void PageWelcome::on_variant_checked()
{
enable_next(printer_picker != nullptr ? printer_picker->variants_checked > 0 : false);
}
PageUpdate::PageUpdate(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))),
version_check(true),
preset_update(true)
{
const AppConfig *app_config = GUI::get_app_config();
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for application updates")));
box_slic3r->SetValue(app_config->get("version_check") == "1");
append(box_slic3r);
append_text(_(L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.")));
append_spacer(VERTICAL_SPACING);
auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically")));
box_presets->SetValue(app_config->get("preset_update") == "1");
append(box_presets);
append_text(_(L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.")));
const auto text_bold = _(L("Updates are never applied without user's consent and never overwrite user's customized settings."));
auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
label_bold->SetFont(boldfont);
label_bold->Wrap(CONTENT_WIDTH);
append(label_bold);
append_text(_(L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")));
box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
}
PageVendors::PageVendors(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors")))
{
append_text(_(L("Pick another vendor supported by Slic3r PE:")));
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
wxArrayString choices_vendors;
for (const auto vendor_pair : wizard_p()->vendors) {
const auto &vendor = vendor_pair.second;
if (vendor.id == "PrusaResearch") { continue; }
auto *picker = new PrinterPicker(this, vendor, appconfig_vendors);
picker->Hide();
pickers.push_back(picker);
choices_vendors.Add(vendor.name);
picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
}
auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors);
if (choices_vendors.GetCount() > 0) {
vendor_picker->SetSelection(0);
on_vendor_pick(0);
}
vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) {
this->on_vendor_pick(evt.GetInt());
});
append(vendor_picker);
for (PrinterPicker *picker : pickers) { this->append(picker); }
}
void PageVendors::on_page_set()
{
on_variant_checked();
}
void PageVendors::on_vendor_pick(size_t i)
{
for (PrinterPicker *picker : pickers) { picker->Hide(); }
if (i < pickers.size()) {
pickers[i]->Show();
wizard_p()->layout_fit();
}
}
void PageVendors::on_variant_checked()
{
size_t variants_checked = 0;
for (const PrinterPicker *picker : pickers) { variants_checked += picker->variants_checked; }
enable_next(variants_checked > 0);
}
PageFirmware::PageFirmware(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))),
gcode_opt(print_config_def.options["gcode_flavor"]),
gcode_picker(nullptr)
{
append_text(_(L("Choose the type of firmware used by your printer.")));
append_text(gcode_opt.tooltip);
wxArrayString choices;
choices.Alloc(gcode_opt.enum_labels.size());
for (const auto &label : gcode_opt.enum_labels) {
choices.Add(label);
}
gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
const auto &enum_values = gcode_opt.enum_values;
auto needle = enum_values.cend();
if (gcode_opt.default_value != nullptr) {
needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
}
if (needle != enum_values.cend()) {
gcode_picker->SetSelection(needle - enum_values.cbegin());
} else {
gcode_picker->SetSelection(0);
}
append(gcode_picker);
}
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
{
ConfigOptionEnum<GCodeFlavor> opt;
auto sel = gcode_picker->GetSelection();
if (sel != wxNOT_FOUND && opt.deserialize(gcode_picker->GetString(sel).ToStdString())) {
config.set_key_value("gcode_flavor", &opt);
}
}
PageBedShape::PageBedShape(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))),
shape_panel(new BedShapePanel(this))
{
append_text(_(L("Set the shape of your printer's bed.")));
shape_panel->build_panel(wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"));
append(shape_panel);
}
void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
{
const auto points(shape_panel->GetValue());
auto *opt = new ConfigOptionPoints(points);
config.set_key_value("bed_shape", opt);
}
PageDiameters::PageDiameters(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Filament and Nozzle Diameters")), _(L("Print Diameters"))),
spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)),
spin_filam(new wxSpinCtrlDouble(this, wxID_ANY))
{
spin_nozzle->SetDigits(2);
spin_nozzle->SetIncrement(0.1);
const auto &def_nozzle = print_config_def.options["nozzle_diameter"];
auto *default_nozzle = dynamic_cast<const ConfigOptionFloats*>(def_nozzle.default_value);
spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
spin_filam->SetDigits(2);
spin_filam->SetIncrement(0.25);
const auto &def_filam = print_config_def.options["filament_diameter"];
auto *default_filam = dynamic_cast<const ConfigOptionFloats*>(def_filam.default_value);
spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
append_text(_(L("Enter the diameter of your printer's hot end nozzle.")));
auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
auto *text_nozzle = new wxStaticText(this, wxID_ANY, _(L("Nozzle Diameter:")));
auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _(L("mm")));
sizer_nozzle->AddGrowableCol(0, 1);
sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
sizer_nozzle->Add(spin_nozzle);
sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_nozzle);
append_spacer(VERTICAL_SPACING);
append_text(_(L("Enter the diameter of your filament.")));
append_text(_(L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")));
auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
auto *text_filam = new wxStaticText(this, wxID_ANY, _(L("Filament Diameter:")));
auto *unit_filam = new wxStaticText(this, wxID_ANY, _(L("mm")));
sizer_filam->AddGrowableCol(0, 1);
sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
sizer_filam->Add(spin_filam);
sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_filam);
}
void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
{
auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue());
config.set_key_value("nozzle_diameter", opt_nozzle);
auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue());
config.set_key_value("filament_diameter", opt_filam);
}
PageTemperatures::PageTemperatures(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Extruder and Bed Temperatures")), _(L("Temperatures"))),
spin_extr(new wxSpinCtrlDouble(this, wxID_ANY)),
spin_bed(new wxSpinCtrlDouble(this, wxID_ANY))
{
spin_extr->SetIncrement(5.0);
const auto &def_extr = print_config_def.options["temperature"];
spin_extr->SetRange(def_extr.min, def_extr.max);
auto *default_extr = dynamic_cast<const ConfigOptionInts*>(def_extr.default_value);
spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
spin_bed->SetIncrement(5.0);
const auto &def_bed = print_config_def.options["bed_temperature"];
spin_bed->SetRange(def_bed.min, def_bed.max);
auto *default_bed = dynamic_cast<const ConfigOptionInts*>(def_bed.default_value);
spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
append_text(_(L("Enter the temperature needed for extruding your filament.")));
append_text(_(L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")));
auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
auto *text_extr = new wxStaticText(this, wxID_ANY, _(L("Extrusion Temperature:")));
auto *unit_extr = new wxStaticText(this, wxID_ANY, _(L("°C")));
sizer_extr->AddGrowableCol(0, 1);
sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
sizer_extr->Add(spin_extr);
sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_extr);
append_spacer(VERTICAL_SPACING);
append_text(_(L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")));
append_text(_(L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")));
auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
auto *text_bed = new wxStaticText(this, wxID_ANY, _(L("Bed Temperature:")));
auto *unit_bed = new wxStaticText(this, wxID_ANY, _(L("°C")));
sizer_bed->AddGrowableCol(0, 1);
sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
sizer_bed->Add(spin_bed);
sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_bed);
}
void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
{
auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
config.set_key_value("temperature", opt_extr);
auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
config.set_key_value("first_layer_temperature", opt_extr1st);
auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
config.set_key_value("bed_temperature", opt_bed);
auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
config.set_key_value("first_layer_bed_temperature", opt_bed1st);
}
// Index
ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) :
wxPanel(parent),
bg(GUI::from_u8(Slic3r::var("Slic3r_192px_transparent.png")), wxBITMAP_TYPE_PNG),
bullet_black(GUI::from_u8(Slic3r::var("bullet_black.png")), wxBITMAP_TYPE_PNG),
bullet_blue(GUI::from_u8(Slic3r::var("bullet_blue.png")), wxBITMAP_TYPE_PNG),
bullet_white(GUI::from_u8(Slic3r::var("bullet_white.png")), wxBITMAP_TYPE_PNG)
{
SetMinSize(bg.GetSize());
wxClientDC dc(this);
text_height = dc.GetCharHeight();
// Add logo bitmap.
// This could be done in on_paint() along with the index labels, but I've found it tricky
// to get the bitmap rendered well on all platforms with transparent background.
// In some cases it didn't work at all. And so wxStaticBitmap is used here instead,
// because it has all the platform quirks figured out.
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *logo = new wxStaticBitmap(this, wxID_ANY, bg);
sizer->AddStretchSpacer();
sizer->Add(logo);
SetSizer(sizer);
Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
}
void ConfigWizardIndex::load_items(ConfigWizardPage *firstpage)
{
items.clear();
item_active = items.cend();
for (auto *page = firstpage; page != nullptr; page = page->page_next()) {
items.emplace_back(page->shortname);
}
Refresh();
}
void ConfigWizardIndex::set_active(ConfigWizardPage *page)
{
item_active = std::find(items.cbegin(), items.cend(), page->shortname);
Refresh();
}
void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
{
enum {
MARGIN = 10,
SPACING = 5,
};
const auto size = GetClientSize();
if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
wxPaintDC dc(this);
const auto bullet_w = bullet_black.GetSize().GetWidth();
const auto bullet_h = bullet_black.GetSize().GetHeight();
const int yoff_icon = bullet_h < text_height ? (text_height - bullet_h) / 2 : 0;
const int yoff_text = bullet_h > text_height ? (bullet_h - text_height) / 2 : 0;
const int yinc = std::max(bullet_h, text_height) + SPACING;
unsigned y = 0;
for (auto it = items.cbegin(); it != items.cend(); ++it) {
if (it < item_active) { dc.DrawBitmap(bullet_black, MARGIN, y + yoff_icon, false); }
if (it == item_active) { dc.DrawBitmap(bullet_blue, MARGIN, y + yoff_icon, false); }
if (it > item_active) { dc.DrawBitmap(bullet_white, MARGIN, y + yoff_icon, false); }
dc.DrawText(*it, MARGIN + bullet_w + SPACING, y + yoff_text);
y += yinc;
}
}
// priv
static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
{ "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") },
{ "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2S", "0.4") },
{ "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
{ "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2S", "0.4") },
{ "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
{ "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") },
{ "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
{ "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") },
}};
void ConfigWizard::priv::load_vendors()
{
const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor";
const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles";
// Load vendors from the "vendors" directory in datadir
for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
auto vp = VendorProfile::from_ini(it->path());
vendors[vp.id] = std::move(vp);
}
}
// Additionally load up vendors from the application resources directory, but only those not seen in the datadir
for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
const auto id = it->path().stem().string();
if (vendors.find(id) == vendors.end()) {
auto vp = VendorProfile::from_ini(it->path());
vendors_rsrc[vp.id] = it->path().filename().string();
vendors[vp.id] = std::move(vp);
}
}
}
// Load up the set of vendors / models / variants the user has had enabled up till now
const AppConfig *app_config = GUI::get_app_config();
if (! app_config->legacy_datadir()) {
appconfig_vendors.set_vendors(*app_config);
} else {
// In case of legacy datadir, try to guess the preference based on the printer preset files that are present
const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) {
auto needle = legacy_preset_map.find(it->path().filename().string());
if (needle == legacy_preset_map.end()) { continue; }
const auto &model = needle->second.first;
const auto &variant = needle->second.second;
appconfig_vendors.set_variant("PrusaResearch", model, variant, true);
}
}
}
void ConfigWizard::priv::index_refresh()
{
index->load_items(page_welcome);
}
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
{
topsizer->Add(page, 0, wxEXPAND);
auto *extra_buttons = page->extra_buttons();
if (extra_buttons != nullptr) {
btnsizer->Prepend(extra_buttons, 0);
}
}
void ConfigWizard::priv::set_page(ConfigWizardPage *page)
{
if (page == nullptr) { return; }
if (page_current != nullptr) { page_current->Hide(); }
page_current = page;
enable_next(true);
page->on_page_set();
index->load_items(page_welcome);
index->set_active(page);
page->Show();
btn_prev->Enable(page->page_prev() != nullptr);
btn_next->Show(page->page_next() != nullptr);
btn_finish->Show(page->page_next() == nullptr);
layout_fit();
}
void ConfigWizard::priv::layout_fit()
{
q->Layout();
q->Fit();
}
void ConfigWizard::priv::enable_next(bool enable)
{
btn_next->Enable(enable);
btn_finish->Enable(enable);
}
void ConfigWizard::priv::on_other_vendors()
{
page_welcome
->chain(page_vendors)
->chain(page_update);
set_page(page_vendors);
}
void ConfigWizard::priv::on_custom_setup()
{
page_welcome->chain(page_firmware);
page_temps->chain(page_update);
set_page(page_firmware);
}
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{
const bool is_custom_setup = page_welcome->page_next() == page_firmware;
if (! is_custom_setup) {
const auto enabled_vendors = appconfig_vendors.vendors();
// Install bundles from resources if needed:
std::vector<std::string> install_bundles;
for (const auto &vendor_rsrc : vendors_rsrc) {
const auto vendor = enabled_vendors.find(vendor_rsrc.first);
if (vendor == enabled_vendors.end()) { continue; }
size_t size_sum = 0;
for (const auto &model : vendor->second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
// This vendor needs to be installed
install_bundles.emplace_back(vendor_rsrc.second);
}
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
bool snapshot = true;
switch (run_reason) {
case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break;
case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break;
case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason
case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break;
}
if (install_bundles.size() > 0) {
// Install bundles from resources.
updater->install_bundles_rsrc(std::move(install_bundles), snapshot);
}
if (page_welcome->reset_user_profile()) {
preset_bundle->reset(true);
}
app_config->set_vendors(appconfig_vendors);
app_config->set("version_check", page_update->version_check ? "1" : "0");
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
app_config->reset_selections();
// ^ TODO: replace with appropriate printer selection
preset_bundle->load_presets(*app_config);
} else {
for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) {
page->apply_custom_config(*custom_config);
}
preset_bundle->load_config("My Settings", *custom_config);
}
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
}
// Public
ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
p(new priv(this))
{
p->run_reason = reason;
p->load_vendors();
p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
"gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
}));
p->index = new ConfigWizardIndex(this);
auto *vsizer = new wxBoxSizer(wxVERTICAL);
p->topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *hline = new wxStaticLine(this);
p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
p->topsizer->Add(p->index, 0, wxEXPAND);
p->topsizer->AddSpacer(INDEX_MARGIN);
p->btn_prev = new wxButton(this, wxID_BACKWARD);
p->btn_next = new wxButton(this, wxID_FORWARD);
p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish")));
p->btn_cancel = new wxButton(this, wxID_CANCEL);
p->btnsizer->AddStretchSpacer();
p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
p->add_page(p->page_welcome = new PageWelcome(this));
p->add_page(p->page_update = new PageUpdate(this));
p->add_page(p->page_vendors = new PageVendors(this));
p->add_page(p->page_firmware = new PageFirmware(this));
p->add_page(p->page_bed = new PageBedShape(this));
p->add_page(p->page_diams = new PageDiameters(this));
p->add_page(p->page_temps = new PageTemperatures(this));
p->index_refresh();
p->page_welcome->chain(p->page_update);
p->page_firmware
->chain(p->page_bed)
->chain(p->page_diams)
->chain(p->page_temps);
vsizer->Add(p->topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
vsizer->Add(hline, 0, wxEXPAND);
vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
p->set_page(p->page_welcome);
SetSizerAndFit(vsizer);
SetMinSize(GetSize());
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); });
p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); });
}
ConfigWizard::~ConfigWizard() {}
bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater)
{
if (ShowModal() == wxID_OK) {
p->apply_config(GUI::get_app_config(), preset_bundle, updater);
return true;
} else {
return false;
}
}
const wxString& ConfigWizard::name()
{
// A different naming convention is used for the Wizard on Windows vs. OSX & GTK.
#if WIN32
static const wxString config_wizard_name = _(L("Configuration Wizard"));
#else
static const wxString config_wizard_name = _(L("Configuration Assistant"));
#endif
return config_wizard_name;
}
}
}

View file

@ -0,0 +1,50 @@
#ifndef slic3r_ConfigWizard_hpp_
#define slic3r_ConfigWizard_hpp_
#include <memory>
#include <wx/dialog.h>
namespace Slic3r {
class PresetBundle;
class PresetUpdater;
namespace GUI {
class ConfigWizard: public wxDialog
{
public:
// Why is the Wizard run
enum RunReason {
RR_DATA_EMPTY, // No or empty datadir
RR_DATA_LEGACY, // Pre-updating datadir
RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation
RR_USER, // User requested the Wizard from the menus
};
ConfigWizard(wxWindow *parent, RunReason run_reason);
ConfigWizard(ConfigWizard &&) = delete;
ConfigWizard(const ConfigWizard &) = delete;
ConfigWizard &operator=(ConfigWizard &&) = delete;
ConfigWizard &operator=(const ConfigWizard &) = delete;
~ConfigWizard();
// Run the Wizard. Return whether it was completed.
bool run(PresetBundle *preset_bundle, const PresetUpdater *updater);
static const wxString& name();
private:
struct priv;
std::unique_ptr<priv> p;
friend class ConfigWizardPage;
};
}
}
#endif

View file

@ -0,0 +1,238 @@
#ifndef slic3r_ConfigWizard_private_hpp_
#define slic3r_ConfigWizard_private_hpp_
#include "ConfigWizard.hpp"
#include <vector>
#include <set>
#include <unordered_map>
#include <boost/filesystem.hpp>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/spinctrl.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "AppConfig.hpp"
#include "Preset.hpp"
#include "BedShapeDialog.hpp"
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
enum {
CONTENT_WIDTH = 500,
DIALOG_MARGIN = 15,
INDEX_MARGIN = 40,
BTN_SPACING = 10,
INDENT_SPACING = 30,
VERTICAL_SPACING = 10,
};
struct PrinterPicker: wxPanel
{
struct Checkbox : wxCheckBox
{
Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
wxCheckBox(parent, wxID_ANY, label),
model(model),
variant(variant)
{}
std::string model;
std::string variant;
};
const std::string vendor_id;
std::vector<Checkbox*> cboxes;
unsigned variants_checked;
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors);
void select_all(bool select);
void on_checkbox(const Checkbox *cbox, bool checked);
};
struct ConfigWizardPage: wxPanel
{
ConfigWizard *parent;
const wxString shortname;
wxBoxSizer *content;
ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname);
virtual ~ConfigWizardPage();
ConfigWizardPage* page_prev() const { return p_prev; }
ConfigWizardPage* page_next() const { return p_next; }
ConfigWizardPage* chain(ConfigWizardPage *page);
template<class T>
void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
{
content->Add(thing, proportion, flag, border);
}
void append_text(wxString text);
void append_spacer(int space);
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
virtual bool Show(bool show = true);
virtual bool Hide() { return Show(false); }
virtual wxPanel* extra_buttons() { return nullptr; }
virtual void on_page_set() {}
virtual void apply_custom_config(DynamicPrintConfig &config) {}
void enable_next(bool enable);
private:
ConfigWizardPage *p_prev;
ConfigWizardPage *p_next;
};
struct PageWelcome: ConfigWizardPage
{
PrinterPicker *printer_picker;
wxPanel *others_buttons;
wxCheckBox *cbox_reset;
PageWelcome(ConfigWizard *parent);
virtual wxPanel* extra_buttons() { return others_buttons; }
virtual void on_page_set();
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
void on_variant_checked();
};
struct PageUpdate: ConfigWizardPage
{
bool version_check;
bool preset_update;
PageUpdate(ConfigWizard *parent);
};
struct PageVendors: ConfigWizardPage
{
std::vector<PrinterPicker*> pickers;
PageVendors(ConfigWizard *parent);
virtual void on_page_set();
void on_vendor_pick(size_t i);
void on_variant_checked();
};
struct PageFirmware: ConfigWizardPage
{
const ConfigOptionDef &gcode_opt;
wxChoice *gcode_picker;
PageFirmware(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageBedShape: ConfigWizardPage
{
BedShapePanel *shape_panel;
PageBedShape(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageDiameters: ConfigWizardPage
{
wxSpinCtrlDouble *spin_nozzle;
wxSpinCtrlDouble *spin_filam;
PageDiameters(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
struct PageTemperatures: ConfigWizardPage
{
wxSpinCtrlDouble *spin_extr;
wxSpinCtrlDouble *spin_bed;
PageTemperatures(ConfigWizard *parent);
virtual void apply_custom_config(DynamicPrintConfig &config);
};
class ConfigWizardIndex: public wxPanel
{
public:
ConfigWizardIndex(wxWindow *parent);
void load_items(ConfigWizardPage *firstpage);
void set_active(ConfigWizardPage *page);
private:
const wxBitmap bg;
const wxBitmap bullet_black;
const wxBitmap bullet_blue;
const wxBitmap bullet_white;
int text_height;
std::vector<wxString> items;
std::vector<wxString>::const_iterator item_active;
void on_paint(wxPaintEvent &evt);
};
struct ConfigWizard::priv
{
ConfigWizard *q;
ConfigWizard::RunReason run_reason;
AppConfig appconfig_vendors;
std::unordered_map<std::string, VendorProfile> vendors;
std::unordered_map<std::string, std::string> vendors_rsrc;
std::unique_ptr<DynamicPrintConfig> custom_config;
wxBoxSizer *topsizer = nullptr;
wxBoxSizer *btnsizer = nullptr;
ConfigWizardPage *page_current = nullptr;
ConfigWizardIndex *index = nullptr;
wxButton *btn_prev = nullptr;
wxButton *btn_next = nullptr;
wxButton *btn_finish = nullptr;
wxButton *btn_cancel = nullptr;
PageWelcome *page_welcome = nullptr;
PageUpdate *page_update = nullptr;
PageVendors *page_vendors = nullptr;
PageFirmware *page_firmware = nullptr;
PageBedShape *page_bed = nullptr;
PageDiameters *page_diams = nullptr;
PageTemperatures *page_temps = nullptr;
priv(ConfigWizard *q) : q(q) {}
void load_vendors();
void add_page(ConfigWizardPage *page);
void index_refresh();
void set_page(ConfigWizardPage *page);
void layout_fit();
void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } }
void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } }
void enable_next(bool enable);
void on_other_vendors();
void on_custom_setup();
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
};
}
}
#endif

View file

@ -20,12 +20,8 @@ namespace Slic3r { namespace GUI {
void Field::PostInitialize(){
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
auto sz = 16;
#ifdef __WXGTK__
sz = 28;
#endif // __WXGTK__
m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER);
m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxSize(sz,sz), wxNO_BORDER);
m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
if (wxMSW) {
m_Undo_btn->SetBackgroundColour(color);
m_Undo_to_sys_btn->SetBackgroundColour(color);
@ -33,6 +29,12 @@ namespace Slic3r { namespace GUI {
m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); }));
m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); }));
//set default bitmap
wxBitmap bmp;
bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG);
set_undo_bitmap(&bmp);
set_undo_to_sys_bitmap(&bmp);
BUILD();
}

View file

@ -36,6 +36,24 @@ using t_back_to_init = std::function<void(const std::string&)>;
wxString double_to_string(double const value);
class MyButton : public wxButton
{
public:
MyButton() {}
MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize, long style = 0,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxTextCtrlNameStr)
{
this->Create(parent, id, label, pos, size, style, validator, name);
}
// overridden from wxWindow base class
virtual bool
AcceptsFocusFromKeyboard() const { return false; }
};
class Field {
protected:
// factory function to defer and enforce creation of derived type.
@ -146,6 +164,13 @@ public:
return false;
}
bool set_label_colour_force(const wxColour *clr) {
if (m_Label == nullptr) return false;
m_Label->SetForegroundColour(*clr);
m_Label->Refresh(true);
return false;
}
bool set_undo_tooltip(const wxString *tip) {
if (m_undo_tooltip != tip) {
m_undo_tooltip = tip;
@ -165,18 +190,18 @@ public:
}
protected:
wxButton* m_Undo_btn = nullptr;
MyButton* m_Undo_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const wxBitmap* m_undo_bitmap = nullptr;
const wxString* m_undo_tooltip = nullptr;
wxButton* m_Undo_to_sys_btn = nullptr;
MyButton* m_Undo_to_sys_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const wxBitmap* m_undo_to_sys_bitmap = nullptr;
const wxString* m_undo_to_sys_tooltip = nullptr;
wxStaticText* m_Label = nullptr;
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_label_color;
const wxColour* m_label_color = nullptr;
// current value
boost::any m_value;

View file

@ -39,6 +39,7 @@
#include <wx/sizer.h>
#include <wx/combo.h>
#include <wx/window.h>
#include <wx/msgdlg.h>
#include <wx/settings.h>
#include <wx/collpane.h>
#include <wx/wupdlock.h>
@ -47,10 +48,18 @@
#include "Tab.hpp"
#include "TabIface.hpp"
#include "AboutDialog.hpp"
#include "AppConfig.hpp"
#include "ConfigSnapshotDialog.hpp"
#include "Utils.hpp"
#include "MsgDialog.hpp"
#include "ConfigWizard.hpp"
#include "Preferences.hpp"
#include "PresetBundle.hpp"
#include "UpdateDialogs.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "../Config/Snapshot.hpp"
namespace Slic3r { namespace GUI {
@ -179,6 +188,7 @@ wxFrame *g_wxMainFrame = nullptr;
wxNotebook *g_wxTabPanel = nullptr;
AppConfig *g_AppConfig = nullptr;
PresetBundle *g_PresetBundle= nullptr;
PresetUpdater *g_PresetUpdater = nullptr;
wxColour g_color_label_modified;
wxColour g_color_label_sys;
wxColour g_color_label_default;
@ -204,6 +214,21 @@ static void init_label_colours()
g_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
}
void update_label_colours_from_appconfig()
{
if (g_AppConfig->has("label_clr_sys")){
auto str = g_AppConfig->get("label_clr_sys");
if (str != "")
g_color_label_sys = wxColour(str);
}
if (g_AppConfig->has("label_clr_modified")){
auto str = g_AppConfig->get("label_clr_modified");
if (str != "")
g_color_label_modified = wxColour(str);
}
}
void set_wxapp(wxApp *app)
{
g_wxApp = app;
@ -230,6 +255,11 @@ void set_preset_bundle(PresetBundle *preset_bundle)
g_PresetBundle = preset_bundle;
}
void set_preset_updater(PresetUpdater *updater)
{
g_PresetUpdater = updater;
}
std::vector<Tab *>& get_tabs_list()
{
return g_tabs_list;
@ -353,26 +383,143 @@ void get_installed_languages(wxArrayString & names,
}
}
void add_debug_menu(wxMenuBar *menu, int event_language_change)
enum ConfigMenuIDs {
ConfigMenuWizard,
ConfigMenuSnapshots,
ConfigMenuTakeSnapshot,
ConfigMenuUpdate,
ConfigMenuPreferences,
ConfigMenuLanguage,
ConfigMenuCnt,
};
void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change)
{
//#if 0
auto local_menu = new wxMenu();
local_menu->Append(wxWindow::NewControlId(1), _(L("Change Application Language")));
local_menu->Bind(wxEVT_MENU, [event_language_change](wxEvent&){
wxArrayString names;
wxArrayLong identifiers;
get_installed_languages(names, identifiers);
if (select_language(names, identifiers)){
save_language();
show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!")));
if (event_language_change > 0) {
wxCommandEvent event(event_language_change);
g_wxApp->ProcessEvent(event);
wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt);
const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), ConfigWizard::name());
// Cmd+, is standard on OS X - what about other operating systems?
local_menu->Append(config_id_base + ConfigMenuWizard, ConfigWizard::name() + "\u2026", config_wizard_tooltip);
local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots\u2026")), _(L("Inspect / activate configuration snapshots")));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot")));
local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences\u2026\tCtrl+,")), _(L("Application preferences")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language")));
local_menu->Bind(wxEVT_MENU, [config_id_base, event_language_change, event_preferences_changed](wxEvent &event){
switch (event.GetId() - config_id_base) {
case ConfigMenuWizard:
config_wizard(ConfigWizard::RR_USER);
break;
case ConfigMenuTakeSnapshot:
// Take a configuration snapshot.
if (check_unsaved_changes()) {
wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name")));
if (dlg.ShowModal() == wxID_OK)
g_AppConfig->set("on_snapshot",
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
*g_AppConfig, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
}
break;
case ConfigMenuSnapshots:
if (check_unsaved_changes()) {
std::string on_snapshot;
if (Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig))
on_snapshot = g_AppConfig->get("on_snapshot");
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
dlg.ShowModal();
if (! dlg.snapshot_to_activate().empty()) {
if (! Config::SnapshotDB::singleton().is_on_snapshot(*g_AppConfig))
Config::SnapshotDB::singleton().take_snapshot(*g_AppConfig, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
g_AppConfig->set("on_snapshot",
Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *g_AppConfig).id);
g_PresetBundle->load_presets(*g_AppConfig);
// Load the currently selected preset into the GUI, update the preset selection box.
for (Tab *tab : g_tabs_list)
tab->load_current_preset();
}
}
break;
case ConfigMenuPreferences:
{
PreferencesDialog dlg(g_wxMainFrame, event_preferences_changed);
dlg.ShowModal();
break;
}
case ConfigMenuLanguage:
{
wxArrayString names;
wxArrayLong identifiers;
get_installed_languages(names, identifiers);
if (select_language(names, identifiers)) {
save_language();
show_info(g_wxTabPanel, _(L("Application will be restarted")), _(L("Attention!")));
if (event_language_change > 0) {
wxCommandEvent event(event_language_change);
g_wxApp->ProcessEvent(event);
}
}
break;
}
}
});
menu->Append(local_menu, _(L("&Localization")));
//#endif
menu->Append(local_menu, _(L("&Configuration")));
}
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
bool check_unsaved_changes()
{
std::string dirty;
for (Tab *tab : g_tabs_list)
if (tab->current_preset_is_dirty())
if (dirty.empty())
dirty = tab->name();
else
dirty += std::string(", ") + tab->name();
if (dirty.empty())
// No changes, the application may close or reload presets.
return true;
// Ask the user.
auto dialog = new wxMessageDialog(g_wxMainFrame,
_(L("You have unsaved changes ")) + dirty + _(L(". Discard changes and continue anyway?")),
_(L("Unsaved Presets")),
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
return dialog->ShowModal() == wxID_YES;
}
bool config_wizard_startup(bool app_config_exists)
{
if (! app_config_exists || g_PresetBundle->has_defauls_only()) {
config_wizard(ConfigWizard::RR_DATA_EMPTY);
return true;
} else if (g_AppConfig->legacy_datadir()) {
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
config_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}
return false;
}
void config_wizard(int reason)
{
// Exit wizard if there are unsaved changes and the user cancels the action.
if (! check_unsaved_changes())
return;
ConfigWizard wizard(nullptr, static_cast<ConfigWizard::RunReason>(reason));
wizard.run(g_PresetBundle, g_PresetUpdater);
// Load the currently selected preset into the GUI, update the preset selection box.
for (Tab *tab : g_tabs_list)
tab->load_current_preset();
}
void open_preferences_dialog(int event_preferences)
@ -383,6 +530,7 @@ void open_preferences_dialog(int event_preferences)
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
{
update_label_colours_from_appconfig();
add_created_tab(new TabPrint (g_wxTabPanel, no_controller));
add_created_tab(new TabFilament (g_wxTabPanel, no_controller));
add_created_tab(new TabPrinter (g_wxTabPanel, no_controller));
@ -523,36 +671,62 @@ void add_created_tab(Tab* panel)
g_wxTabPanel->AddPage(panel, panel->title());
}
void show_error(wxWindow* parent, const wxString& message){
auto msg_wingow = new wxMessageDialog(parent, message, _(L("Error")), wxOK | wxICON_ERROR);
msg_wingow->ShowModal();
void show_error(wxWindow* parent, const wxString& message) {
ErrorDialog msg(parent, message);
msg.ShowModal();
}
void show_error_id(int id, const std::string& message) {
auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr;
show_error(parent, message);
}
void show_info(wxWindow* parent, const wxString& message, const wxString& title){
auto msg_wingow = new wxMessageDialog(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION);
msg_wingow->ShowModal();
wxMessageDialog msg_wingow(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION);
msg_wingow.ShowModal();
}
void warning_catcher(wxWindow* parent, const wxString& message){
if (message == _(L("GLUquadricObjPtr | Attempt to free unreferenced scalar")) )
return;
auto msg = new wxMessageDialog(parent, message, _(L("Warning")), wxOK | wxICON_WARNING);
msg->ShowModal();
wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING);
msg.ShowModal();
}
wxApp* get_app(){
return g_wxApp;
}
const wxColour& get_modified_label_clr() {
PresetBundle* get_preset_bundle()
{
return g_PresetBundle;
}
const wxColour& get_label_clr_modified() {
return g_color_label_modified;
}
const wxColour& get_sys_label_clr() {
const wxColour& get_label_clr_sys() {
return g_color_label_sys;
}
const wxColour& get_default_label_clr() {
void set_label_clr_modified(const wxColour& clr) {
g_color_label_modified = clr;
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
std::string str = clr_str.ToStdString();
g_AppConfig->set("label_clr_modified", str);
g_AppConfig->save();
}
void set_label_clr_sys(const wxColour& clr) {
g_color_label_sys = clr;
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
std::string str = clr_str.ToStdString();
g_AppConfig->set("label_clr_sys", str);
g_AppConfig->save();
}
const wxColour& get_label_clr_default() {
return g_color_label_default;
}
@ -800,6 +974,7 @@ wxWindow* export_option_creator(wxWindow* parent)
wxPanel* panel = new wxPanel(parent, -1);
wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
wxCheckBox* cbox = new wxCheckBox(panel, wxID_HIGHEST + 1, L("Export print config"));
cbox->SetValue(true);
sizer->AddSpacer(5);
sizer->Add(cbox, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
panel->SetSizer(sizer);
@ -841,6 +1016,11 @@ int get_export_option(wxFileDialog* dlg)
}
void about()
{
AboutDialog dlg;
dlg.ShowModal();
dlg.Destroy();
}
} // namespace GUI
} // namespace Slic3r
} }

View file

@ -26,6 +26,7 @@ namespace Slic3r {
class PresetBundle;
class PresetCollection;
class AppConfig;
class PresetUpdater;
class DynamicPrintConfig;
class TabIface;
@ -77,19 +78,35 @@ void set_main_frame(wxFrame *main_frame);
void set_tab_panel(wxNotebook *tab_panel);
void set_app_config(AppConfig *app_config);
void set_preset_bundle(PresetBundle *preset_bundle);
void set_preset_updater(PresetUpdater *updater);
AppConfig* get_app_config();
wxApp* get_app();
PresetBundle* get_preset_bundle();
const wxColour& get_modified_label_clr();
const wxColour& get_sys_label_clr();
const wxColour& get_default_label_clr();
const wxColour& get_label_clr_modified();
const wxColour& get_label_clr_sys();
const wxColour& get_label_clr_default();
unsigned get_colour_approx_luma(const wxColour &colour);
void set_label_clr_modified(const wxColour& clr);
void set_label_clr_sys(const wxColour& clr);
void add_debug_menu(wxMenuBar *menu, int event_language_change);
extern void add_config_menu(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
extern bool check_unsaved_changes();
// Checks if configuration wizard needs to run, calls config_wizard if so.
// Returns whether the Wizard ran.
extern bool config_wizard_startup(bool app_config_exists);
// Opens the configuration wizard, returns true if wizard is finished & accepted.
// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl.
extern void config_wizard(int run_reason);
// Create "Preferences" dialog after selecting menu "Preferences" in Perl part
void open_preferences_dialog(int event_preferences);
extern void open_preferences_dialog(int event_preferences);
// Create a new preset tab (print, filament and printer),
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed);
@ -101,6 +118,7 @@ void add_created_tab(Tab* panel);
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
void show_error(wxWindow* parent, const wxString& message);
void show_error_id(int id, const std::string& message); // For Perl
void show_info(wxWindow* parent, const wxString& message, const wxString& title);
void warning_catcher(wxWindow* parent, const wxString& message);
@ -139,7 +157,11 @@ wxButton* get_wiping_dialog_button();
void add_export_option(wxFileDialog* dlg, const std::string& format);
int get_export_option(wxFileDialog* dlg);
}
}
// Display an About dialog
void about();
} // namespace GUI
} // namespace Slic3r
#endif

View file

@ -0,0 +1,87 @@
#include "MsgDialog.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/statbmp.h>
#include <wx/scrolwin.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "ConfigWizard.hpp"
namespace Slic3r {
namespace GUI {
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) :
MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id)
{}
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) :
wxDialog(parent, wxID_ANY, title),
boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)),
content_sizer(new wxBoxSizer(wxVERTICAL)),
btn_sizer(new wxBoxSizer(wxHORIZONTAL))
{
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *rightsizer = new wxBoxSizer(wxVERTICAL);
auto *headtext = new wxStaticText(this, wxID_ANY, headline);
headtext->SetFont(boldfont);
headtext->Wrap(CONTENT_WIDTH);
rightsizer->Add(headtext);
rightsizer->AddSpacer(VERT_SPACING);
rightsizer->Add(content_sizer, 1, wxEXPAND);
if (button_id != wxID_NONE) {
auto *button = new wxButton(this, button_id);
button->SetFocus();
btn_sizer->Add(button);
}
rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL);
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap));
topsizer->Add(logo, 0, wxALL, BORDER);
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
SetSizerAndFit(topsizer);
}
MsgDialog::~MsgDialog() {}
// ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) :
MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG))
{
auto *panel = new wxScrolledWindow(this);
auto *p_sizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(p_sizer);
auto *text = new wxStaticText(panel, wxID_ANY, msg);
text->Wrap(CONTENT_WIDTH);
p_sizer->Add(text, 1, wxEXPAND);
panel->SetMinSize(wxSize(CONTENT_WIDTH, CONTENT_HEIGHT));
panel->SetScrollRate(0, 5);
content_sizer->Add(panel, 1, wxEXPAND);
Fit();
}
ErrorDialog::~ErrorDialog() {}
}
}

View file

@ -0,0 +1,65 @@
#ifndef slic3r_MsgDialog_hpp_
#define slic3r_MsgDialog_hpp_
#include <string>
#include <unordered_map>
#include <wx/dialog.h>
#include <wx/font.h>
#include <wx/bitmap.h>
#include "slic3r/Utils/Semver.hpp"
class wxBoxSizer;
class wxCheckBox;
namespace Slic3r {
namespace GUI {
// A message / query dialog with a bitmap on the left and any content on the right
// with buttons underneath.
struct MsgDialog : wxDialog
{
MsgDialog(MsgDialog &&) = delete;
MsgDialog(const MsgDialog &) = delete;
MsgDialog &operator=(MsgDialog &&) = delete;
MsgDialog &operator=(const MsgDialog &) = delete;
virtual ~MsgDialog();
protected:
enum {
CONTENT_WIDTH = 500,
CONTENT_HEIGHT = 300,
BORDER = 30,
VERT_SPACING = 15,
HORIZ_SPACING = 5,
};
// button_id is an id of a button that can be added by default, use wxID_NONE to disable
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK);
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK);
wxFont boldfont;
wxBoxSizer *content_sizer;
wxBoxSizer *btn_sizer;
};
// Generic error dialog, used for displaying exceptions
struct ErrorDialog : MsgDialog
{
ErrorDialog(wxWindow *parent, const wxString &msg);
ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete;
ErrorDialog &operator=(const ErrorDialog &) = delete;
virtual ~ErrorDialog();
};
}
}
#endif

View file

@ -22,13 +22,13 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
// is the normal type.
if (opt.gui_type.compare("select") == 0) {
} else if (opt.gui_type.compare("select_open") == 0) {
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
} else if (opt.gui_type.compare("color") == 0) {
m_fields.emplace(id, STDMOVE(ColourPicker::Create<ColourPicker>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(ColourPicker::Create<ColourPicker>(parent(), opt, id)));
} else if (opt.gui_type.compare("f_enum_open") == 0 ||
opt.gui_type.compare("i_enum_open") == 0 ||
opt.gui_type.compare("i_enum_closed") == 0) {
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
} else if (opt.gui_type.compare("slider") == 0) {
} else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl
} else {
@ -40,21 +40,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
case coPercents:
case coString:
case coStrings:
m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(parent(), opt, id)));
break;
case coBool:
case coBools:
m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(parent(), opt, id)));
break;
case coInt:
case coInts:
m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(parent(), opt, id)));
break;
case coEnum:
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
break;
case coPoints:
m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(m_parent, opt, id)));
m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(parent(), opt, id)));
break;
case coNone: break;
default:
@ -468,10 +468,10 @@ Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int op
return opt_id.empty() ? nullptr : get_field(opt_id);
}
void ogStaticText::SetText(const wxString& value)
void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
{
SetLabel(value);
Wrap(400);
if (wrap) Wrap(400);
GetParent()->Layout();
}

View file

@ -94,7 +94,13 @@ public:
/// Returns a copy of the pointer of the parent wxWindow.
/// Accessor function is because users are not allowed to change the parent
/// but defining it as const means a lot of const_casts to deal with wx functions.
inline wxWindow* parent() const { return m_parent; }
inline wxWindow* parent() const {
#ifdef __WXGTK__
return m_panel;
#else
return m_parent;
#endif /* __WXGTK__ */
}
void append_line(const Line& line, wxStaticText** colored_Label = nullptr);
Line create_single_option_line(const Option& option) const;
@ -130,8 +136,15 @@ public:
m_grid_sizer = new wxFlexGridSizer(0, num_columns, 0,0);
static_cast<wxFlexGridSizer*>(m_grid_sizer)->SetFlexibleDirection(wxHORIZONTAL);
static_cast<wxFlexGridSizer*>(m_grid_sizer)->AddGrowableCol(label_width != 0);
#ifdef __WXGTK__
m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panel->SetSizer(m_grid_sizer);
m_panel->Layout();
sizer->Fit(m_panel);
sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5);
#else
sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5);
#endif /* __WXGTK__ */
}
protected:
@ -147,6 +160,13 @@ protected:
// "true" if option is created in preset tabs
bool m_is_tab_opt{ false };
// This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox
// Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel
// inside it before you insert the other controls.
#ifdef __WXGTK__
wxPanel* m_panel {nullptr};
#endif /* __WXGTK__ */
/// Generate a wxSizer or wxWindow from a configuration option
/// Precondition: opt resolves to a known ConfigOption
/// Postcondition: fields contains a wx gui object.
@ -203,7 +223,7 @@ public:
ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){}
~ogStaticText(){}
void SetText(const wxString& value);
void SetText(const wxString& value, bool wrap = true);
};
}}

View file

@ -9,11 +9,12 @@ void PreferencesDialog::build()
{
auto app_config = get_app_config();
m_optgroup = std::make_shared<ConfigOptionsGroup>(this, _(L("General")));
m_optgroup->label_width = 200;
m_optgroup->label_width = 400;
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
// TODO
// $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
// opt_id = > 'version_check',
// type = > 'bool',
@ -48,6 +49,22 @@ void PreferencesDialog::build()
option = Option (def,"background_processing");
m_optgroup->append_single_option_line(option);
// Please keep in sync with ConfigWizard
def.label = L("Check for application updates");
def.type = coBool;
def.tooltip = L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.");
def.default_value = new ConfigOptionBool(app_config->get("version_check") == "1");
option = Option (def, "version_check");
m_optgroup->append_single_option_line(option);
// Please keep in sync with ConfigWizard
def.label = L("Update built-in Presets automatically");
def.type = coBool;
def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.");
def.default_value = new ConfigOptionBool(app_config->get("preset_update") == "1");
option = Option (def, "preset_update");
m_optgroup->append_single_option_line(option);
def.label = L("Disable USB/serial connection");
def.type = coBool;
def.tooltip = L("Disable communication with the printer over a serial / USB cable. "

View file

@ -2,10 +2,14 @@
#include <cassert>
#include "Preset.hpp"
#include "AppConfig.hpp"
#include "BitmapCache.hpp"
#include <fstream>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/cenv.hpp>
@ -14,6 +18,7 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
#include <wx/image.h>
#include <wx/choice.h>
@ -24,14 +29,16 @@
#include "../../libslic3r/Utils.hpp"
#include "../../libslic3r/PlaceholderParser.hpp"
using boost::property_tree::ptree;
namespace Slic3r {
ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree)
ConfigFileType guess_config_file_type(const ptree &tree)
{
size_t app_config = 0;
size_t bundle = 0;
size_t config = 0;
for (const boost::property_tree::ptree::value_type &v : tree) {
for (const ptree::value_type &v : tree) {
if (v.second.empty()) {
if (v.first == "background_processing" ||
v.first == "last_output_path" ||
@ -59,6 +66,80 @@ ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree)
(bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG;
}
VendorProfile VendorProfile::from_ini(const boost::filesystem::path &path, bool load_all)
{
ptree tree;
boost::filesystem::ifstream ifs(path);
boost::property_tree::read_ini(ifs, tree);
return VendorProfile::from_ini(tree, path, load_all);
}
VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem::path &path, bool load_all)
{
static const std::string printer_model_key = "printer_model:";
const std::string id = path.stem().string();
if (! boost::filesystem::exists(path)) {
throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str());
}
VendorProfile res(id);
auto get_or_throw = [&](const ptree &tree, const std::string &key) -> ptree::const_assoc_iterator
{
auto res = tree.find(key);
if (res == tree.not_found()) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str());
}
return res;
};
const auto &vendor_section = get_or_throw(tree, "vendor")->second;
res.name = get_or_throw(vendor_section, "name")->second.data();
auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data();
auto config_version = Semver::parse(config_version_str);
if (! config_version) {
throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str());
} else {
res.config_version = std::move(*config_version);
}
auto config_update_url = vendor_section.find("config_update_url");
if (config_update_url != vendor_section.not_found()) {
res.config_update_url = config_update_url->second.data();
}
if (! load_all) {
return res;
}
for (auto &section : tree) {
if (boost::starts_with(section.first, printer_model_key)) {
VendorProfile::PrinterModel model;
model.id = section.first.substr(printer_model_key.size());
model.name = section.second.get<std::string>("name", model.id);
section.second.get<std::string>("variants", "");
const auto variants_field = section.second.get<std::string>("variants", "");
std::vector<std::string> variants;
if (Slic3r::unescape_strings_cstyle(variants_field, variants)) {
for (const std::string &variant_name : variants) {
if (model.variant(variant_name) == nullptr)
model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name));
}
} else {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field;
}
if (! model.id.empty() && ! model.variants.empty())
res.models.push_back(std::move(model));
}
}
return res;
}
// Suffix to be added to a modified preset name in the combo box.
static std::string g_suffix_modified = " (modified)";
const std::string& Preset::suffix_modified()
@ -176,6 +257,15 @@ bool Preset::update_compatible_with_printer(const Preset &active_printer, const
return this->is_compatible = is_compatible_with_printer(active_printer, extra_config);
}
void Preset::set_visible_from_appconfig(const AppConfig &app_config)
{
if (vendor == nullptr) { return; }
const std::string &model = config.opt_string("printer_model");
const std::string &variant = config.opt_string("printer_variant");
if (model.empty() || variant.empty()) { return; }
is_visible = app_config.get_variant(vendor->id, model, variant);
}
const std::vector<std::string>& Preset::print_options()
{
static std::vector<std::string> s_opts {
@ -210,7 +300,7 @@ const std::vector<std::string>& Preset::filament_options()
static std::vector<std::string> s_opts {
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
"extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_unloading_speed", "filament_toolchange_delay",
"filament_cooling_time", "filament_ramming_parameters", "temperature", "first_layer_temperature", "bed_temperature",
"filament_ramming_parameters", "temperature", "first_layer_temperature", "bed_temperature",
"first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers",
"fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers",
"compatible_printers_condition", "inherits"
@ -299,7 +389,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
// Remove the .ini suffix.
name.erase(name.size() - 4);
if (this->find_preset(name, false)) {
errors_cummulative += "The user preset \"" + name + "\" cannot be loaded. A system preset of the same name has already been loaded.";
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
// that's already been loaded from a bundle.
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name;
continue;
}
try {
@ -457,18 +549,6 @@ size_t PresetCollection::first_visible_idx() const
return idx;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t PresetCollection::first_compatible_idx() const
{
size_t idx = m_default_suppressed ? 1 : 0;
for (; idx < this->m_presets.size(); ++ idx)
if (m_presets[idx].is_compatible)
break;
if (idx == this->m_presets.size())
idx = 0;
return idx;
}
void PresetCollection::set_default_suppressed(bool default_suppressed)
{
if (m_default_suppressed != default_suppressed) {
@ -477,7 +557,7 @@ void PresetCollection::set_default_suppressed(bool default_suppressed)
}
}
void PresetCollection::update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible)
size_t PresetCollection::update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible)
{
DynamicPrintConfig config;
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name));
@ -488,14 +568,12 @@ void PresetCollection::update_compatible_with_printer(const Preset &active_print
Preset &preset_selected = m_presets[idx_preset];
Preset &preset_edited = selected ? m_edited_preset : preset_selected;
if (! preset_edited.update_compatible_with_printer(active_printer, &config) &&
selected && select_other_if_incompatible)
selected && unselect_if_incompatible)
m_idx_selected = (size_t)-1;
if (selected)
preset_selected.is_compatible = preset_edited.is_compatible;
}
if (m_idx_selected == (size_t)-1)
// Find some other compatible preset, or the "-- default --" preset.
this->select_preset(first_compatible_idx());
return m_idx_selected;
}
// Save the preset under a new name. If the name is different from the old one,
@ -717,8 +795,8 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
// 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)
// Preset found by its name.
if (it != m_presets.end() && it->name == name && it->is_visible)
// Preset found by its name and it is visible.
idx = it - m_presets.begin();
else {
// Find the first visible preset.
@ -744,6 +822,46 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
return false;
}
bool PresetCollection::select_preset_by_name_strict(const std::string &name)
{
// 1) Try to find the preset by its name.
auto it = this->find_preset_internal(name);
size_t idx = (size_t)-1;
if (it != m_presets.end() && it->name == name && it->is_visible)
// Preset found by its name.
idx = it - m_presets.begin();
// 2) Select the new preset.
if (idx != (size_t)-1) {
this->select_preset(idx);
return true;
}
m_idx_selected = idx;
return false;
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> PresetCollection::merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors)
{
std::vector<std::string> duplicates;
for (Preset &preset : other.m_presets) {
if (preset.is_default || preset.is_external)
continue;
Preset key(m_type, preset.name);
auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key);
if (it == m_presets.end() || it->name != preset.name) {
if (preset.vendor != nullptr) {
// Re-assign a pointer to the vendor structure in the new PresetBundle.
auto it = new_vendors.find(*preset.vendor);
assert(it != new_vendors.end());
preset.vendor = &(*it);
}
this->m_presets.emplace(it, std::move(preset));
} else
duplicates.emplace_back(std::move(preset.name));
}
return duplicates;
}
std::string PresetCollection::name() const
{
switch (this->type()) {

View file

@ -3,8 +3,12 @@
#include <deque>
#include <boost/filesystem/path.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/Semver.hpp"
class wxBitmap;
class wxChoice;
@ -13,6 +17,9 @@ class wxItemContainer;
namespace Slic3r {
class AppConfig;
class PresetBundle;
namespace GUI {
class BitmapCache;
}
@ -32,21 +39,19 @@ class VendorProfile
public:
std::string name;
std::string id;
std::string config_version;
Semver config_version;
std::string config_update_url;
struct PrinterVariant {
PrinterVariant() {}
PrinterVariant(const std::string &name) : name(name) {}
std::string name;
bool enabled = true;
};
struct PrinterModel {
PrinterModel() {}
PrinterModel(const std::string &name) : name(name) {}
std::string id;
std::string name;
bool enabled = true;
std::vector<PrinterVariant> variants;
PrinterVariant* variant(const std::string &name) {
for (auto &v : this->variants)
@ -55,11 +60,14 @@ public:
return nullptr;
}
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
bool operator< (const PrinterModel &rhs) const { return this->name < rhs.name; }
bool operator==(const PrinterModel &rhs) const { return this->name == rhs.name; }
};
std::set<PrinterModel> models;
std::vector<PrinterModel> models;
VendorProfile() {}
VendorProfile(std::string id) : id(std::move(id)) {}
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true);
size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; }
@ -90,7 +98,8 @@ public:
bool is_external = false;
// System preset is read-only.
bool is_system = false;
// Preset is visible, if it is compatible with the active Printer.
// Preset is visible, if it is associated with a printer model / variant that is enabled in the AppConfig
// or if it has no printer model / variant association.
// Also the "default" preset is only visible, if it is the only preset in the list.
bool is_visible = true;
// Has this preset been modified?
@ -136,6 +145,9 @@ public:
// Mark this preset as compatible if it is compatible with active_printer.
bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config);
// Set is_visible according to application config
void set_visible_from_appconfig(const AppConfig &app_config);
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
@ -167,6 +179,13 @@ public:
PresetCollection(Preset::Type type, const std::vector<std::string> &keys);
~PresetCollection();
typedef std::deque<Preset>::iterator Iterator;
typedef std::deque<Preset>::const_iterator ConstIterator;
Iterator begin() { return m_presets.begin() + 1; }
ConstIterator begin() const { return m_presets.begin() + 1; }
Iterator end() { return m_presets.end(); }
ConstIterator end() const { return m_presets.end(); }
void reset(bool delete_files);
Preset::Type type() const { return m_type; }
@ -238,19 +257,49 @@ public:
{ return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found); }
size_t first_visible_idx() const;
size_t first_compatible_idx() const;
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
// If one of the prefered_alternates is compatible, select it.
template<typename PreferedCondition>
size_t first_compatible_idx(PreferedCondition prefered_condition) const
{
size_t i = m_default_suppressed ? 1 : 0;
size_t n = this->m_presets.size();
size_t i_compatible = n;
for (; i < n; ++ i)
if (m_presets[i].is_compatible) {
if (prefered_condition(m_presets[i].name))
return i;
if (i_compatible == n)
// Store the first compatible profile into i_compatible.
i_compatible = i;
}
return (i_compatible == n) ? 0 : i_compatible;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); }
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
// Return the first visible preset. Certainly at least the '- default -' preset shall be visible.
Preset& first_visible() { return this->preset(this->first_visible_idx()); }
const Preset& first_visible() const { return this->preset(this->first_visible_idx()); }
Preset& first_compatible() { return this->preset(this->first_compatible_idx()); }
template<typename PreferedCondition>
Preset& first_compatible(PreferedCondition prefered_condition) { return this->preset(this->first_compatible_idx(prefered_condition)); }
const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); }
// Return number of presets including the "- default -" preset.
size_t size() const { return this->m_presets.size(); }
// For Print / Filament presets, disable those, which are not compatible with the printer.
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible);
template<typename PreferedCondition>
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible, PreferedCondition prefered_condition)
{
if (this->update_compatible_with_printer_internal(active_printer, select_other_if_incompatible) == (size_t)-1)
// Find some other compatible preset, or the "-- default --" preset.
this->select_preset(this->first_compatible_idx(prefered_condition));
}
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible)
{ this->update_compatible_with_printer(active_printer, select_other_if_incompatible, [](const std::string&){return true;}); }
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
@ -286,6 +335,14 @@ public:
// 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;
protected:
// Select a preset, if it exists. If it does not exist, select an invalid (-1) index.
// This is a temporary state, which shall be fixed immediately by the following step.
bool select_preset_by_name_strict(const std::string &name);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors);
private:
PresetCollection();
PresetCollection(const PresetCollection &other);
@ -303,6 +360,8 @@ private:
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
@ -328,8 +387,12 @@ private:
wxBitmap *m_bitmap_main_frame;
// Path to the directory to store the config files into.
std::string m_dir_path;
// Caching color bitmaps for the filament combo box.
GUI::BitmapCache *m_bitmap_cache = nullptr;
// to access select_preset_by_name_strict()
friend class PresetBundle;
};
} // namespace Slic3r

View file

@ -4,6 +4,7 @@
#include "PresetBundle.hpp"
#include "BitmapCache.hpp"
#include <algorithm>
#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/algorithm/clamp.hpp>
@ -111,6 +112,7 @@ void PresetBundle::setup_directories()
std::initializer_list<boost::filesystem::path> paths = {
data_dir,
data_dir / "vendor",
data_dir / "cache",
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
// Store the print/filament/printer presets into a "presets" directory.
data_dir / "presets",
@ -176,6 +178,7 @@ std::string PresetBundle::load_system_presets()
// Here the vendor specific read only Config Bundles are stored.
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
std::string errors_cummulative;
bool first = true;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) {
std::string name = dir_entry.path().filename().string();
@ -183,7 +186,25 @@ std::string PresetBundle::load_system_presets()
name.erase(name.size() - 4);
try {
// Load the config bundle, flatten it.
this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
if (first) {
// Reset this PresetBundle and load the first vendor config.
this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
first = false;
} else {
// Load the other vendor configs, merge them with this PresetBundle.
// Report duplicate profiles.
PresetBundle other;
other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
std::vector<std::string> duplicates = this->merge_presets(std::move(other));
if (! duplicates.empty()) {
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
for (size_t i = 0; i < duplicates.size(); ++ i) {
if (i > 0)
errors_cummulative += ", ";
errors_cummulative += duplicates[i];
}
}
}
} catch (const std::runtime_error &err) {
errors_cummulative += err.what();
errors_cummulative += "\n";
@ -192,6 +213,18 @@ std::string PresetBundle::load_system_presets()
return errors_cummulative;
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> PresetBundle::merge_presets(PresetBundle &&other)
{
this->vendors.insert(other.vendors.begin(), other.vendors.end());
std::vector<std::string> duplicate_prints = this->prints .merge_presets(std::move(other.prints), this->vendors);
std::vector<std::string> duplicate_filaments = this->filaments.merge_presets(std::move(other.filaments), this->vendors);
std::vector<std::string> duplicate_printers = this->printers .merge_presets(std::move(other.printers), this->vendors);
append(duplicate_prints, std::move(duplicate_filaments));
append(duplicate_prints, std::move(duplicate_printers));
return duplicate_prints;
}
static inline std::string remove_ini_suffix(const std::string &name)
{
std::string out = name;
@ -205,26 +238,44 @@ static inline std::string remove_ini_suffix(const std::string &name)
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void PresetBundle::load_installed_printers(const AppConfig &config)
{
// m_storage
for (auto &preset : printers) {
preset.set_visible_from_appconfig(config);
}
}
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void PresetBundle::load_selections(const AppConfig &config)
{
prints.select_preset_by_name(remove_ini_suffix(config.get("presets", "print")), true);
filaments.select_preset_by_name(remove_ini_suffix(config.get("presets", "filament")), true);
printers.select_preset_by_name(remove_ini_suffix(config.get("presets", "printer")), true);
// Update visibility of presets based on application vendor / model / variant configuration.
this->load_installed_printers(config);
// Parse the initial print / filament / printer profile names.
std::string initial_print_profile_name = remove_ini_suffix(config.get("presets", "print"));
std::vector<std::string> initial_filament_profile_names;
std::string initial_printer_profile_name = remove_ini_suffix(config.get("presets", "printer"));
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(printers.get_selected_preset().config.option("nozzle_diameter"));
size_t num_extruders = nozzle_diameter->values.size();
this->set_filament_preset(0, filaments.get_selected_preset().name);
initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", "filament")));
this->set_filament_preset(0, initial_filament_profile_names.back());
for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) {
char name[64];
sprintf(name, "filament_%d", i);
if (! config.has("presets", name))
break;
this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name)));
initial_filament_profile_names.emplace_back(remove_ini_suffix(config.get("presets", name)));
this->set_filament_preset(i, initial_filament_profile_names.back());
}
// Activate print / filament / printer profiles from the config.
// If the printer profile enumerated by the config are not visible, select an alternate preset.
// Do not select alternate profiles for the print / filament profiles as those presets
// will be selected by the following call of this->update_compatible_with_printer(true).
prints.select_preset_by_name_strict(initial_print_profile_name);
filaments.select_preset_by_name_strict(initial_filament_profile_names.front());
printers.select_preset_by_name(initial_printer_profile_name, true);
// Update visibility of presets based on their compatibility with the active printer.
// Always try to select a compatible print and filament preset to the current printer preset,
// as the application may have been closed with an active "external" preset, which does not
@ -675,48 +726,6 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree)
flatten_configbundle_hierarchy(tree, "printer");
}
static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorProfile &vendor_profile)
{
const std::string printer_model_key = "printer_model:";
for (auto &section : tree)
if (section.first == "vendor") {
// Load the names of the active presets.
for (auto &kvp : section.second) {
if (kvp.first == "name")
vendor_profile.name = kvp.second.data();
else if (kvp.first == "id")
vendor_profile.id = kvp.second.data();
else if (kvp.first == "config_version")
vendor_profile.config_version = kvp.second.data();
else if (kvp.first == "config_update_url")
vendor_profile.config_update_url = kvp.second.data();
}
} else if (boost::starts_with(section.first, printer_model_key)) {
VendorProfile::PrinterModel model;
model.name = section.first.substr(printer_model_key.size());
section.second.get<std::string>("variants", "");
std::vector<std::string> variants;
if (Slic3r::unescape_strings_cstyle(section.second.get<std::string>("variants", ""), variants)) {
for (const std::string &variant_name : variants) {
if (model.variant(variant_name) == nullptr)
model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name));
}
} else {
// Log error?
}
if (! model.name.empty() && ! model.variants.empty())
vendor_profile.models.insert(model);
}
}
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
void PresetBundle::install_vendor_configbundle(const std::string &src_path0)
{
boost::filesystem::path src_path(src_path0);
boost::filesystem::copy_file(src_path, (boost::filesystem::path(data_dir()) / "vendor" / src_path.filename()).make_preferred(), boost::filesystem::copy_option::overwrite_if_exists);
}
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags)
@ -730,19 +739,21 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
pt::ptree tree;
boost::nowide::ifstream ifs(path);
pt::read_ini(ifs, tree);
// Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
flatten_configbundle_hierarchy(tree);
const VendorProfile *vendor_profile = nullptr;
if (flags & LOAD_CFGBNDLE_SYSTEM) {
VendorProfile vp;
load_vendor_profile(tree, vp);
if (vp.name.empty())
throw std::runtime_error(std::string("Vendor Config Bundle is not valid: Missing vendor name key."));
if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) {
auto vp = VendorProfile::from_ini(tree, path);
if (vp.num_variants() == 0)
return 0;
vendor_profile = &(*this->vendors.insert(vp).first);
}
if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) {
return 0;
}
// 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
flatten_configbundle_hierarchy(tree);
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
std::vector<std::string> loaded_prints;
@ -814,7 +825,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
section.first << "\" defines no printer variant, it will be ignored.";
continue;
}
auto it_model = vendor_profile->models.find(VendorProfile::PrinterModel(printer_model));
auto it_model = std::find_if(vendor_profile->models.cbegin(), vendor_profile->models.cend(),
[&](const VendorProfile::PrinterModel &m) { return m.id == printer_model; }
);
if (it_model == vendor_profile->models.end()) {
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
section.first << "\" defines invalid printer model \"" << printer_model << "\", it will be ignored.";
@ -916,14 +929,35 @@ void PresetBundle::update_multi_material_filament_presets()
void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible)
{
this->prints.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible);
this->filaments.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible);
const Preset &printer_preset = this->printers.get_edited_preset();
const std::string &prefered_print_profile = printer_preset.config.opt_string("default_print_profile");
const std::vector<std::string> &prefered_filament_profiles = printer_preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
prefered_print_profile.empty() ?
this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible) :
this->prints.update_compatible_with_printer(printer_preset, select_other_if_incompatible,
[&prefered_print_profile](const std::string& profile_name){ return profile_name == prefered_print_profile; });
prefered_filament_profiles.empty() ?
this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible) :
this->filaments.update_compatible_with_printer(printer_preset, select_other_if_incompatible,
[&prefered_filament_profiles](const std::string& profile_name)
{ return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); });
if (select_other_if_incompatible) {
// Verify validity of the current filament presets.
for (std::string &filament_name : this->filament_presets) {
Preset *preset = this->filaments.find_preset(filament_name, false);
if (preset == nullptr || ! preset->is_compatible)
filament_name = this->filaments.first_compatible().name;
this->filament_presets.front() = this->filaments.get_edited_preset().name;
for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) {
std::string &filament_name = this->filament_presets[idx];
Preset *preset = this->filaments.find_preset(filament_name, false);
if (preset == nullptr || ! preset->is_compatible) {
// Pick a compatible profile. If there are prefered_filament_profiles, use them.
if (prefered_filament_profiles.empty())
filament_name = this->filaments.first_compatible().name;
else {
const std::string &preferred = (idx < prefered_filament_profiles.size()) ?
prefered_filament_profiles[idx] : prefered_filament_profiles.front();
filament_name = this->filaments.first_compatible(
[&preferred](const std::string& profile_name){ return profile_name == preferred; }).name;
}
}
}
}
}

View file

@ -5,6 +5,7 @@
#include "Preset.hpp"
#include <set>
#include <boost/filesystem/path.hpp>
namespace Slic3r {
@ -86,13 +87,11 @@ public:
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
// Load a system config bundle.
LOAD_CFGBNDLE_SYSTEM = 4,
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
};
// Load the config bundle, store it to the user profile directory by default.
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
// Install the Vendor specific config bundle into user's directory.
void install_vendor_configbundle(const std::string &src_path);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
@ -117,10 +116,12 @@ public:
// preset if the current print or filament preset is not compatible.
void update_compatible_with_printer(bool select_other_if_incompatible);
static bool parse_color(const std::string &scolor, unsigned char *rgb_out);
static bool parse_color(const std::string &scolor, unsigned char *rgb_out);
private:
std::string load_system_presets();
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Set the "enabled" flag for printer vendors, printer models and printer variants
// based on the user configuration.

View file

@ -110,7 +110,8 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle)
m_question_btn->SetBackgroundColour(color);
}
m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information.")));
m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n"
"or click this button.")));
// Determine the theme color of OS (dark or light)
auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
@ -134,13 +135,20 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle)
m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent)
{
auto dlg = new ButtonsDescription(this, &m_icon_descriptions);
dlg->ShowModal();
if (dlg->ShowModal() == wxID_OK){
// Colors for ui "decoration"
for (Tab *tab : get_tabs_list()){
tab->m_sys_label_clr = get_label_clr_sys();
tab->m_modified_label_clr = get_label_clr_modified();
tab->update_labels_colour();
}
}
}));
// Colors for ui "decoration"
m_sys_label_clr = get_sys_label_clr();
m_modified_label_clr = get_modified_label_clr();
m_default_text_clr = get_default_label_clr();
m_sys_label_clr = get_label_clr_sys();
m_modified_label_clr = get_label_clr_modified();
m_default_text_clr = get_label_clr_default();
m_hsizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_hsizer, 0, wxBOTTOM, 3);
@ -218,7 +226,7 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle)
//! select_preset(m_presets_choice->GetStringSelection().ToStdString());
//! we doing next:
int selected_item = m_presets_choice->GetSelection();
if (m_selected_preset_item == selected_item)
if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty())
return;
if (selected_item >= 0){
std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data();
@ -278,6 +286,56 @@ PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bo
return page;
}
void Tab::update_labels_colour()
{
Freeze();
//update options "decoration"
for (const auto opt : m_options_list)
{
const wxColour *color = &m_sys_label_clr;
// value isn't equal to system value
if ((opt.second & osSystemValue) == 0){
// value is equal to last saved
if ((opt.second & osInitValue) != 0)
color = &m_default_text_clr;
// value is modified
else
color = &m_modified_label_clr;
}
if (opt.first == "bed_shape" || opt.first == "compatible_printers") {
if (m_colored_Label != nullptr) {
m_colored_Label->SetForegroundColour(*color);
m_colored_Label->Refresh(true);
}
continue;
}
Field* field = get_field(opt.first);
if (field == nullptr) continue;
field->set_label_colour_force(color);
}
Thaw();
auto cur_item = m_treectrl->GetFirstVisibleItem();
while (cur_item){
auto title = m_treectrl->GetItemText(cur_item);
for (auto page : m_pages)
{
if (page->title() != title)
continue;
const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr :
page->m_is_modified_values ? &m_modified_label_clr :
&m_default_text_clr;
m_treectrl->SetItemTextColour(cur_item, *clr);
break;
}
cur_item = m_treectrl->GetNextVisible(cur_item);
}
}
// Update UI according to changes
void Tab::update_changed_ui()
{
@ -594,7 +652,8 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo
if (!saved_value) change_opt_value(*m_config, opt_key, value);
// Mark the print & filament enabled if they are compatible with the currently selected preset.
if (opt_key.compare("compatible_printers") == 0) {
m_preset_bundle->update_compatible_with_printer(0);
// Don't select another profile if this profile happens to become incompatible.
m_preset_bundle->update_compatible_with_printer(false);
}
m_presets->update_dirty_ui(m_presets_choice);
on_presets_changed();
@ -669,13 +728,52 @@ void Tab::on_presets_changed()
event.SetString(m_name);
g_wxMainFrame->ProcessWindowEvent(event);
}
update_preset_description_line();
}
void Tab::update_preset_description_line()
{
const Preset* parent = m_presets->get_selected_preset_parent();
const wxString description_line = parent == nullptr ?
_(L("It's default preset")) : parent == &m_presets->get_selected_preset() ?
_(L("It's system preset")) :
_(L("Current preset is inherited from")) + ":\n" + parent->name;
m_parent_preset_description_line->SetText(description_line);
const Preset& preset = m_presets->get_edited_preset();
wxString description_line = preset.is_default ?
_(L("It's a default preset.")) : preset.is_system ?
_(L("It's a system preset.")) :
_(L("Current preset is inherited from ")) + (parent == nullptr ?
"default preset." :
":\n\t" + parent->name);
if (preset.is_default || preset.is_system)
description_line += "\n\t" + _(L("It can't be deleted or modified. ")) +
"\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one. ")) +
"\n\t" + _(L("To do that please specify a new name for the preset."));
if (parent && parent->vendor)
{
description_line += "\n\n" + _(L("Additional information:")) + "\n";
description_line += "\t" + _(L("vendor")) + ": " + (name()=="printer" ? "\n\t\t" : "") + parent->vendor->name +
", ver: " + parent->vendor->config_version.to_string();
if (name() == "printer"){
const std::string &printer_model = preset.config.opt_string("printer_model");
const std::string &default_print_profile = preset.config.opt_string("default_print_profile");
const std::vector<std::string> &default_filament_profiles = preset.config.option<ConfigOptionStrings>("default_filament_profile")->values;
if (!printer_model.empty())
description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model;
if (!default_print_profile.empty())
description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile;
if (!default_filament_profiles.empty())
{
description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t";
for (auto& profile : default_filament_profiles){
if (&profile != &*default_filament_profiles.begin())
description_line += ", ";
description_line += profile;
}
}
}
}
m_parent_preset_description_line->SetText(description_line, false);
}
void Tab::update_frequently_changed_parameters()
@ -964,29 +1062,6 @@ void TabPrint::update()
on_value_change("fill_density", fill_density);
}
auto first_layer_height = m_config->option<ConfigOptionFloatOrPercent>("first_layer_height")->value;
auto layer_height = m_config->opt_float("layer_height");
if (m_config->opt_bool("wipe_tower") &&
(first_layer_height != 0.2 || layer_height < 0.15 || layer_height > 0.35)) {
wxString msg_text = _(L("The Wipe Tower currently supports only:\n"
"- first layer height 0.2mm\n"
"- layer height from 0.15mm to 0.35mm\n"
"\nShall I adjust those settings in order to enable the Wipe Tower?"));
auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO);
DynamicPrintConfig new_conf = *m_config;
if (dialog->ShowModal() == wxID_YES) {
const auto &val = *m_config->option<ConfigOptionFloatOrPercent>("first_layer_height");
auto percent = val.percent;
new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, percent));
if (m_config->opt_float("layer_height") < 0.15) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.15));
if (m_config->opt_float("layer_height") > 0.35) new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.35));
}
else
new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false));
load_config(new_conf);
}
if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") &&
m_config->opt_float("support_material_contact_distance") > 0. &&
(m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) {
@ -1240,7 +1315,6 @@ void TabFilament::build()
optgroup->append_single_option_line("filament_loading_speed");
optgroup->append_single_option_line("filament_unloading_speed");
optgroup->append_single_option_line("filament_toolchange_delay");
optgroup->append_single_option_line("filament_cooling_time");
line = { _(L("Ramming")), "" };
line.widget = [this](wxWindow* parent){
auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+"\u2026", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
@ -1337,7 +1411,7 @@ wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticTex
(*StaticText)->SetFont(font);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(*StaticText);
sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0);
return sizer;
}
@ -1802,7 +1876,7 @@ void Tab::load_current_preset()
{
auto preset = m_presets->get_edited_preset();
preset.is_default ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true);
(preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true);
update();
// For the printer profile, generate the extruder pages.
on_preset_loaded();
@ -2161,9 +2235,9 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox
presets.Add(preset.name);
}
auto dlg = new wxMultiChoiceDialog(parent,
_(L("Select the printers this profile is compatible with.")),
_(L("Compatible printers")), presets);
wxMultiChoiceDialog dlg(parent,
_(L("Select the printers this profile is compatible with.")),
_(L("Compatible printers")), presets);
// # Collect and set indices of printers marked as compatible.
wxArrayInt selections;
auto *compatible_printers = dynamic_cast<const ConfigOptionStrings*>(m_config->option("compatible_printers"));
@ -2175,12 +2249,12 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox
selections.Add(idx);
break;
}
dlg->SetSelections(selections);
dlg.SetSelections(selections);
std::vector<std::string> value;
// Show the dialog.
if (dlg->ShowModal() == wxID_OK) {
if (dlg.ShowModal() == wxID_OK) {
selections.Clear();
selections = dlg->GetSelections();
selections = dlg.GetSelections();
for (auto idx : selections)
value.push_back(presets[idx].ToStdString());
if (value.empty()) {

View file

@ -57,7 +57,7 @@ public:
{
Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_vsizer = new wxBoxSizer(wxVERTICAL);
m_item_color = &get_default_label_clr();
m_item_color = &get_label_clr_default();
SetSizer(m_vsizer);
}
~Page(){}
@ -232,6 +232,7 @@ public:
void toggle_show_hide_incompatible();
void update_show_hide_incompatible_button();
void update_ui_from_settings();
void update_labels_colour();
void update_changed_ui();
void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page);
void update_changed_tree_ui();
@ -265,6 +266,7 @@ public:
protected:
void on_presets_changed();
void update_preset_description_line();
void update_frequently_changed_parameters();
void update_wiping_button_visibility();
void update_tab_presets(wxComboCtrl* ui, bool show_incompatible);

View file

@ -0,0 +1,196 @@
#include "UpdateDialogs.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/event.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/hyperlink.h>
#include <wx/statbmp.h>
#include <wx/checkbox.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "ConfigWizard.hpp"
namespace Slic3r {
namespace GUI {
static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
// MsgUpdateSlic3r
MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online) :
MsgDialog(nullptr, _(L("Update available")), _(L("New version of Slic3r PE is available"))),
ver_current(ver_current),
ver_online(ver_online)
{
const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string());
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url);
auto *text = new wxStaticText(this, wxID_ANY, _(L("To download, follow the link below.")));
const auto link_width = link->GetSize().GetWidth();
text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string()));
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
content_sizer->Add(versions);
content_sizer->AddSpacer(VERT_SPACING);
content_sizer->Add(link);
content_sizer->AddSpacer(2*VERT_SPACING);
cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more")));
content_sizer->Add(cbox);
content_sizer->AddSpacer(VERT_SPACING);
Fit();
}
MsgUpdateSlic3r::~MsgUpdateSlic3r() {}
bool MsgUpdateSlic3r::disable_version_check() const
{
return cbox->GetValue();
}
// MsgUpdateConfig
MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates) :
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"Would you like to install it?\n\n"
"Note that a full configuration snapshot will be created first. It can then be restored at any time "
"should there be a problem with the new version.\n\n"
"Updated configuration bundles:"
)));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
for (const auto &update : updates) {
auto *text_vendor = new wxStaticText(this, wxID_ANY, update.first);
text_vendor->SetFont(boldfont);
versions->Add(text_vendor);
versions->Add(new wxStaticText(this, wxID_ANY, update.second));
}
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_cancel = new wxButton(this, wxID_CANCEL);
btn_sizer->Add(btn_cancel);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_ok = new wxButton(this, wxID_OK);
btn_sizer->Add(btn_ok);
btn_ok->SetFocus();
Fit();
}
MsgUpdateConfig::~MsgUpdateConfig() {}
// MsgDataIncompatible
MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats) :
MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"This version of Slic3r PE is not compatible with currently installed configuration bundles.\n"
"This probably happened as a result of running an older Slic3r PE after using a newer one.\n\n"
"You may either exit Slic3r and try again with a newer version, or you may re-run the initial configuration. "
"Doing so will create a backup snapshot of the existing configuration before installing files compatible with this Slic3r.\n"
)));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
auto *text2 = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("This Slic3r PE version: %s")), SLIC3R_VERSION));
text2->Wrap(CONTENT_WIDTH);
content_sizer->Add(text2);
content_sizer->AddSpacer(VERT_SPACING);
auto *text3 = new wxStaticText(this, wxID_ANY, _(L("Incompatible bundles:")));
text3->Wrap(CONTENT_WIDTH);
content_sizer->Add(text3);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
for (const auto &incompat : incompats) {
auto *text_vendor = new wxStaticText(this, wxID_ANY, incompat.first);
text_vendor->SetFont(boldfont);
versions->Add(text_vendor);
versions->Add(new wxStaticText(this, wxID_ANY, incompat.second));
}
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_exit = new wxButton(this, wxID_EXIT, _(L("Exit Slic3r")));
btn_sizer->Add(btn_exit);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_reconf = new wxButton(this, wxID_REPLACE, _(L("Re-configure")));
btn_sizer->Add(btn_reconf);
btn_exit->SetFocus();
auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); };
btn_exit->Bind(wxEVT_BUTTON, exiter);
btn_reconf->Bind(wxEVT_BUTTON, exiter);
Fit();
}
MsgDataIncompatible::~MsgDataIncompatible() {}
// MsgDataLegacy
MsgDataLegacy::MsgDataLegacy() :
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update")))
{
auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(
_(L(
"Slic3r PE now uses an updated configuration structure.\n\n"
"So called 'System presets' have been introduced, which hold the built-in default settings for various "
"printers. These System presets cannot be modified, instead, users now may create their "
"own presets inheriting settings from one of the System presets.\n"
"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n"
"Please proceed with the %s that follows to set up the new presets "
"and to choose whether to enable automatic preset updates."
)),
ConfigWizard::name()
));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *text2 = new wxStaticText(this, wxID_ANY, _(L("For more information please visit our wiki page:")));
static const wxString url("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
// The wiki page name is intentionally not localized:
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, "Slic3r PE 1.40 configuration update", CONFIG_UPDATE_WIKI_URL);
content_sizer->Add(text2);
content_sizer->Add(link);
content_sizer->AddSpacer(VERT_SPACING);
Fit();
}
MsgDataLegacy::~MsgDataLegacy() {}
}
}

View file

@ -0,0 +1,81 @@
#ifndef slic3r_UpdateDialogs_hpp_
#define slic3r_UpdateDialogs_hpp_
#include <string>
#include <unordered_map>
#include "slic3r/Utils/Semver.hpp"
#include "MsgDialog.hpp"
class wxBoxSizer;
class wxCheckBox;
namespace Slic3r {
namespace GUI {
// A confirmation dialog listing configuration updates
class MsgUpdateSlic3r : public MsgDialog
{
public:
MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online);
MsgUpdateSlic3r(MsgUpdateSlic3r &&) = delete;
MsgUpdateSlic3r(const MsgUpdateSlic3r &) = delete;
MsgUpdateSlic3r &operator=(MsgUpdateSlic3r &&) = delete;
MsgUpdateSlic3r &operator=(const MsgUpdateSlic3r &) = delete;
virtual ~MsgUpdateSlic3r();
// Tells whether the user checked the "don't bother me again" checkbox
bool disable_version_check() const;
private:
const Semver &ver_current;
const Semver &ver_online;
wxCheckBox *cbox;
};
// Confirmation dialog informing about configuration update. Lists updated bundles & their versions.
class MsgUpdateConfig : public MsgDialog
{
public:
// updates is a map of "vendor name" -> "version (comment)"
MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates);
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;
MsgUpdateConfig &operator=(const MsgUpdateConfig &) = delete;
~MsgUpdateConfig();
};
// Informs about currently installed bundles not being compatible with the running Slic3r. Asks about action.
class MsgDataIncompatible : public MsgDialog
{
public:
// incompats is a map of "vendor name" -> "version restrictions"
MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats);
MsgDataIncompatible(MsgDataIncompatible &&) = delete;
MsgDataIncompatible(const MsgDataIncompatible &) = delete;
MsgDataIncompatible &operator=(MsgDataIncompatible &&) = delete;
MsgDataIncompatible &operator=(const MsgDataIncompatible &) = delete;
~MsgDataIncompatible();
};
// Informs about a legacy data directory - an update from Slic3r PE < 1.40
class MsgDataLegacy : public MsgDialog
{
public:
MsgDataLegacy();
MsgDataLegacy(MsgDataLegacy &&) = delete;
MsgDataLegacy(const MsgDataLegacy &) = delete;
MsgDataLegacy &operator=(MsgDataLegacy &&) = delete;
MsgDataLegacy &operator=(const MsgDataLegacy &) = delete;
~MsgDataLegacy();
};
}
}
#endif

View file

@ -0,0 +1,559 @@
#include "PresetUpdater.hpp"
#include <algorithm>
#include <thread>
#include <unordered_map>
#include <ostream>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <wx/app.h>
#include <wx/event.h>
#include <wx/msgdlg.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/GUI/UpdateDialogs.hpp"
#include "slic3r/GUI/ConfigWizard.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Config/Version.hpp"
#include "slic3r/Config/Snapshot.hpp"
namespace fs = boost::filesystem;
using Slic3r::GUI::Config::Index;
using Slic3r::GUI::Config::Version;
using Slic3r::GUI::Config::Snapshot;
using Slic3r::GUI::Config::SnapshotDB;
namespace Slic3r {
enum {
SLIC3R_VERSION_BODY_MAX = 256,
};
static const char *INDEX_FILENAME = "index.idx";
static const char *TMP_EXTENSION = ".download";
struct Update
{
fs::path source;
fs::path target;
Version version;
Update(fs::path &&source, fs::path &&target, const Version &version) :
source(std::move(source)),
target(std::move(target)),
version(version)
{}
std::string name() const { return source.stem().string(); }
friend std::ostream& operator<<(std::ostream& os , const Update &self) {
os << "Update(" << self.source.string() << " -> " << self.target.string() << ')';
return os;
}
};
struct Incompat
{
fs::path bundle;
Version version;
Incompat(fs::path &&bundle, const Version &version) :
bundle(std::move(bundle)),
version(version)
{}
std::string name() const { return bundle.stem().string(); }
friend std::ostream& operator<<(std::ostream& os , const Incompat &self) {
os << "Incompat(" << self.bundle.string() << ')';
return os;
}
};
struct Updates
{
std::vector<Incompat> incompats;
std::vector<Update> updates;
};
struct PresetUpdater::priv
{
int version_online_event;
std::vector<Index> index_db;
bool enabled_version_check;
bool enabled_config_update;
std::string version_check_url;
bool had_config_update;
fs::path cache_path;
fs::path rsrc_path;
fs::path vendor_path;
bool cancel;
std::thread thread;
priv(int version_online_event);
void set_download_prefs(AppConfig *app_config);
bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const;
void sync_version() const;
void sync_config(const std::set<VendorProfile> vendors) const;
void check_install_indices() const;
Updates get_config_updates() const;
void perform_updates(Updates &&updates, bool snapshot = true) const;
};
PresetUpdater::priv::priv(int version_online_event) :
version_online_event(version_online_event),
had_config_update(false),
cache_path(fs::path(Slic3r::data_dir()) / "cache"),
rsrc_path(fs::path(resources_dir()) / "profiles"),
vendor_path(fs::path(Slic3r::data_dir()) / "vendor"),
cancel(false)
{
set_download_prefs(GUI::get_app_config());
check_install_indices();
index_db = std::move(Index::load_db());
}
// Pull relevant preferences from AppConfig
void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
{
enabled_version_check = app_config->get("version_check") == "1";
version_check_url = app_config->version_check_url();
enabled_config_update = app_config->get("preset_update") == "1";
}
// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const
{
bool res = false;
fs::path tmp_path = target_path;
tmp_path += (boost::format(".%1%%2%") % get_current_pid() % TMP_EXTENSION).str();
BOOST_LOG_TRIVIAL(info) << boost::format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`")
% url
% target_path.string()
% tmp_path.string();
Http::get(url)
.on_progress([this](Http::Progress, bool &cancel) {
if (cancel) { cancel = true; }
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
% url
% http_status
% error;
})
.on_complete([&](std::string body, unsigned http_status) {
fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(body.c_str(), body.size());
file.close();
fs::rename(tmp_path, target_path);
res = true;
})
.perform_sync();
return res;
}
// Remove leftover paritally downloaded files, if any.
void PresetUpdater::priv::prune_tmps() const
{
for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == TMP_EXTENSION) {
BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string();
fs::remove(it->path());
}
}
}
// Get Slic3rPE version available online, save in AppConfig.
void PresetUpdater::priv::sync_version() const
{
if (! enabled_version_check) { return; }
BOOST_LOG_TRIVIAL(info) << boost::format("Downloading Slic3rPE online version from: `%1%`") % version_check_url;
Http::get(version_check_url)
.size_limit(SLIC3R_VERSION_BODY_MAX)
.on_progress([this](Http::Progress, bool &cancel) {
cancel = this->cancel;
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
% version_check_url
% http_status
% error;
})
.on_complete([&](std::string body, unsigned http_status) {
boost::trim(body);
BOOST_LOG_TRIVIAL(info) << boost::format("Got Slic3rPE online version: `%1%`. Sending to GUI thread...") % body;
wxCommandEvent* evt = new wxCommandEvent(version_online_event);
evt->SetString(body);
GUI::get_app()->QueueEvent(evt);
})
.perform_sync();
}
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
// Both are saved in cache.
void PresetUpdater::priv::sync_config(const std::set<VendorProfile> vendors) const
{
BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache";
if (!enabled_config_update) { return; }
// Donwload vendor preset bundles
for (const auto &index : index_db) {
if (cancel) { return; }
const auto vendor_it = vendors.find(VendorProfile(index.vendor()));
if (vendor_it == vendors.end()) {
BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor();
continue;
}
const VendorProfile &vendor = *vendor_it;
if (vendor.config_update_url.empty()) {
BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name;
continue;
}
// Download a fresh index
BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name;
const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME;
const auto idx_path = cache_path / (vendor.id + ".idx");
if (! get_file(idx_url, idx_path)) { continue; }
if (cancel) { return; }
// Load the fresh index up
Index new_index;
new_index.load(idx_path);
// See if a there's a new version to download
const auto recommended_it = new_index.recommended();
if (recommended_it == new_index.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % vendor.name;
continue;
}
const auto recommended = recommended_it->config_version;
BOOST_LOG_TRIVIAL(debug) << boost::format("New index for vendor: %1%: current version: %2%, recommended version: %3%")
% vendor.name
% vendor.config_version.to_string()
% recommended.to_string();
if (vendor.config_version >= recommended) { continue; }
// Download a fresh bundle
BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name;
const auto bundle_url = (boost::format("%1%/%2%.ini") % vendor.config_update_url % recommended.to_string()).str();
const auto bundle_path = cache_path / (vendor.id + ".ini");
if (! get_file(bundle_url, bundle_path)) { continue; }
if (cancel) { return; }
}
}
// Install indicies from resources. Only installs those that are either missing or older than in resources.
void PresetUpdater::priv::check_install_indices() const
{
BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources...";
for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) {
const auto &path = it->path();
if (path.extension() == ".idx") {
const auto path_in_cache = cache_path / path.filename();
if (! fs::exists(path_in_cache)) {
BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename();
fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists);
} else {
Index idx_rsrc, idx_cache;
idx_rsrc.load(path);
idx_cache.load(path_in_cache);
if (idx_cache.version() < idx_rsrc.version()) {
BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename();
fs::copy_file(path, path_in_cache, fs::copy_option::overwrite_if_exists);
}
}
}
}
}
// Generates a list of bundle updates that are to be performed
Updates PresetUpdater::priv::get_config_updates() const
{
Updates updates;
BOOST_LOG_TRIVIAL(info) << "Checking for cached configuration updates...";
for (const auto idx : index_db) {
auto bundle_path = vendor_path / (idx.vendor() + ".ini");
if (! fs::exists(bundle_path)) {
BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor();
continue;
}
// Perform a basic load and check the version
const auto vp = VendorProfile::from_ini(bundle_path, false);
const auto ver_current = idx.find(vp.config_version);
if (ver_current == idx.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("Preset bundle (`%1%`) version not found in index: %2%") % idx.vendor() % vp.config_version.to_string();
continue;
}
const auto recommended = idx.recommended();
if (recommended == idx.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor();
}
BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%, version cached: %3%")
% vp.name
% ver_current->config_version.to_string()
% recommended->config_version.to_string();
if (! ver_current->is_current_slic3r_supported()) {
BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string();
updates.incompats.emplace_back(std::move(bundle_path), *ver_current);
} else if (recommended->config_version > ver_current->config_version) {
// Config bundle update situation
// Check if the update is already present in a snapshot
const auto recommended_snap = SnapshotDB::singleton().snapshot_with_vendor_preset(vp.name, recommended->config_version);
if (recommended_snap != SnapshotDB::singleton().end()) {
BOOST_LOG_TRIVIAL(info) << boost::format("Bundle update %1% %2% already found in snapshot %3%, skipping...")
% vp.name
% recommended->config_version.to_string()
% recommended_snap->id;
continue;
}
auto path_in_cache = cache_path / (idx.vendor() + ".ini");
if (! fs::exists(path_in_cache)) {
BOOST_LOG_TRIVIAL(warning) << "Index indicates update, but new bundle not found in cache: " << path_in_cache.string();
continue;
}
const auto cached_vp = VendorProfile::from_ini(path_in_cache, false);
if (cached_vp.config_version == recommended->config_version) {
updates.updates.emplace_back(std::move(path_in_cache), std::move(bundle_path), *recommended);
}
}
}
return updates;
}
void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
{
if (updates.incompats.size() > 0) {
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_DOWNGRADE);
}
BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size();
for (const auto &incompat : updates.incompats) {
BOOST_LOG_TRIVIAL(info) << '\t' << incompat;
fs::remove(incompat.bundle);
}
}
else if (updates.updates.size() > 0) {
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
SnapshotDB::singleton().take_snapshot(*GUI::get_app_config(), Snapshot::SNAPSHOT_UPGRADE);
}
BOOST_LOG_TRIVIAL(info) << boost::format("Performing %1% updates") % updates.updates.size();
for (const auto &update : updates.updates) {
BOOST_LOG_TRIVIAL(info) << '\t' << update;
fs::copy_file(update.source, update.target, fs::copy_option::overwrite_if_exists);
PresetBundle bundle;
bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
auto preset_remover = [](const Preset &preset) {
fs::remove(preset.file);
};
for (const auto &preset : bundle.prints) { preset_remover(preset); }
for (const auto &preset : bundle.filaments) { preset_remover(preset); }
for (const auto &preset : bundle.printers) { preset_remover(preset); }
}
}
}
PresetUpdater::PresetUpdater(int version_online_event) :
p(new priv(version_online_event))
{}
// Public
PresetUpdater::~PresetUpdater()
{
if (p && p->thread.joinable()) {
// This will stop transfers being done by the thread, if any.
// Cancelling takes some time, but should complete soon enough.
p->cancel = true;
p->thread.join();
}
}
void PresetUpdater::sync(PresetBundle *preset_bundle)
{
p->set_download_prefs(GUI::get_app_config());
if (!p->enabled_version_check && !p->enabled_config_update) { return; }
// Copy the whole vendors data for use in the background thread
// Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this).
std::set<VendorProfile> vendors = preset_bundle->vendors;
p->thread = std::move(std::thread([this, vendors]() {
this->p->prune_tmps();
this->p->sync_version();
this->p->sync_config(std::move(vendors));
}));
}
void PresetUpdater::slic3r_update_notify()
{
if (! p->enabled_version_check) { return; }
if (p->had_config_update) {
BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed";
return;
}
auto* app_config = GUI::get_app_config();
const auto ver_slic3r = Semver::parse(SLIC3R_VERSION);
const auto ver_online_str = app_config->get("version_online");
const auto ver_online = Semver::parse(ver_online_str);
const auto ver_online_seen = Semver::parse(app_config->get("version_online_seen"));
if (! ver_slic3r) {
throw std::runtime_error("Could not parse Slic3r version string: " SLIC3R_VERSION);
}
if (ver_online) {
// Only display the notification if the version available online is newer AND if we haven't seen it before
if (*ver_online > *ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) {
GUI::MsgUpdateSlic3r notification(*ver_slic3r, *ver_online);
notification.ShowModal();
if (notification.disable_version_check()) {
app_config->set("version_check", "0");
p->enabled_version_check = false;
}
}
app_config->set("version_online_seen", ver_online_str);
}
}
bool PresetUpdater::config_update() const
{
if (! p->enabled_config_update) { return true; }
auto updates = p->get_config_updates();
if (updates.incompats.size() > 0) {
BOOST_LOG_TRIVIAL(info) << boost::format("%1% bundles incompatible. Asking for action...") % updates.incompats.size();
std::unordered_map<std::string, wxString> incompats_map;
for (const auto &incompat : updates.incompats) {
auto vendor = incompat.name();
auto restrictions = wxString::Format(_(L("requires min. %s and max. %s")),
incompat.version.min_slic3r_version.to_string(),
incompat.version.max_slic3r_version.to_string()
);
incompats_map.emplace(std::make_pair(std::move(vendor), std::move(restrictions)));
}
GUI::MsgDataIncompatible dlg(std::move(incompats_map));
const auto res = dlg.ShowModal();
if (res == wxID_REPLACE) {
BOOST_LOG_TRIVIAL(info) << "User wants to re-configure...";
p->perform_updates(std::move(updates));
GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT);
if (wizard.run(GUI::get_preset_bundle(), this)) {
p->had_config_update = true;
} else {
return false;
}
} else {
BOOST_LOG_TRIVIAL(info) << "User wants to exit Slic3r, bye...";
return false;
}
}
else if (updates.updates.size() > 0) {
BOOST_LOG_TRIVIAL(info) << boost::format("Update of %1% bundles available. Asking for confirmation ...") % updates.updates.size();
std::unordered_map<std::string, std::string> updates_map;
for (const auto &update : updates.updates) {
auto vendor = update.name();
auto ver_str = update.version.config_version.to_string();
if (! update.version.comment.empty()) {
ver_str += std::string(" (") + update.version.comment + ")";
}
updates_map.emplace(std::make_pair(std::move(vendor), std::move(ver_str)));
}
GUI::MsgUpdateConfig dlg(std::move(updates_map));
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates));
} else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
}
p->had_config_update = true;
} else {
BOOST_LOG_TRIVIAL(info) << "No configuration updates available.";
}
return true;
}
void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
{
Updates updates;
BOOST_LOG_TRIVIAL(info) << boost::format("Installing %1% bundles from resources ...") % bundles.size();
for (const auto &bundle : bundles) {
auto path_in_rsrc = p->rsrc_path / bundle;
auto path_in_vendors = p->vendor_path / bundle;
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version());
}
p->perform_updates(std::move(updates), snapshot);
}
}

View file

@ -0,0 +1,42 @@
#ifndef slic3r_PresetUpdate_hpp_
#define slic3r_PresetUpdate_hpp_
#include <memory>
#include <vector>
namespace Slic3r {
class AppConfig;
class PresetBundle;
class PresetUpdater
{
public:
PresetUpdater(int version_online_event);
PresetUpdater(PresetUpdater &&) = delete;
PresetUpdater(const PresetUpdater &) = delete;
PresetUpdater &operator=(PresetUpdater &&) = delete;
PresetUpdater &operator=(const PresetUpdater &) = delete;
~PresetUpdater();
// If either version check or config updating is enabled, get the appropriate data in the background and cache it.
void sync(PresetBundle *preset_bundle);
// If version check is enabled, check if chaced online slic3r version is newer, notify if so.
void slic3r_update_notify();
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
// A false return value implies Slic3r should exit due to incompatibility of configuration.
bool config_update() const;
// "Update" a list of bundles from resources (behaves like an online update).
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
private:
struct priv;
std::unique_ptr<priv> p;
};
}
#endif

View file

@ -3,6 +3,8 @@
#include <string>
#include <cstring>
#include <ostream>
#include <stdexcept>
#include <boost/optional.hpp>
#include <boost/format.hpp>
@ -18,9 +20,33 @@ public:
struct Minor { const int i; Minor(int i) : i(i) {} };
struct Patch { const int i; Patch(int i) : i(i) {} };
Semver() : ver(semver_zero()) {}
Semver(int major, int minor, int patch,
boost::optional<const std::string&> metadata = boost::none,
boost::optional<const std::string&> prerelease = boost::none)
: ver(semver_zero())
{
ver.major = major;
ver.minor = minor;
ver.patch = patch;
set_metadata(metadata);
set_prerelease(prerelease);
}
Semver(const std::string &str) : ver(semver_zero())
{
auto parsed = parse(str);
if (! parsed) {
throw std::runtime_error(std::string("Could not parse version string: ") + str);
}
ver = parsed->ver;
parsed->ver = semver_zero();
}
static boost::optional<Semver> parse(const std::string &str)
{
semver_t ver;
semver_t ver = semver_zero();
if (::semver_parse(str.c_str(), &ver) == 0) {
return Semver(ver);
} else {
@ -28,11 +54,7 @@ public:
}
}
static const Semver zero()
{
static semver_t ver = { 0, 0, 0, nullptr, nullptr };
return Semver(ver);
}
static const Semver zero() { return Semver(semver_zero()); }
static const Semver inf()
{
@ -46,28 +68,40 @@ public:
return Semver(ver);
}
Semver(Semver &&other) { *this = std::move(other); }
Semver(const Semver &other) { *this = other; }
Semver(Semver &&other) : ver(other.ver) { other.ver = semver_zero(); }
Semver(const Semver &other) : ver(::semver_copy(&other.ver)) {}
Semver &operator=(Semver &&other)
{
::semver_free(&ver);
ver = other.ver;
other.ver.major = other.ver.minor = other.ver.patch = 0;
other.ver.metadata = other.ver.prerelease = nullptr;
other.ver = semver_zero();
return *this;
}
Semver &operator=(const Semver &other)
{
::semver_free(&ver);
ver = other.ver;
if (other.ver.metadata != nullptr) { std::strcpy(ver.metadata, other.ver.metadata); }
if (other.ver.prerelease != nullptr) { std::strcpy(ver.prerelease, other.ver.prerelease); }
ver = ::semver_copy(&other.ver);
return *this;
}
~Semver() { ::semver_free(&ver); }
// const accessors
int maj() const { return ver.major; }
int min() const { return ver.minor; }
int patch() const { return ver.patch; }
const char* prerelease() const { return ver.prerelease; }
const char* metadata() const { return ver.metadata; }
// Setters
void set_maj(int maj) { ver.major = maj; }
void set_min(int min) { ver.minor = min; }
void set_patch(int patch) { ver.patch = patch; }
void set_metadata(boost::optional<const std::string&> meta) { ver.metadata = meta ? strdup(*meta) : nullptr; }
void set_prerelease(boost::optional<const std::string&> pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; }
// Comparison
bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; }
bool operator<=(const Semver &b) const { return ::semver_compare(ver, b.ver) <= 0; }
@ -107,6 +141,9 @@ private:
semver_t ver;
Semver(semver_t ver) : ver(ver) {}
static semver_t semver_zero() { return { 0, 0, 0, nullptr, nullptr }; }
static char * strdup(const std::string &str) { return ::semver_strdup(const_cast<char*>(str.c_str())); }
};

View file

@ -1,13 +1,18 @@
#include "Time.hpp"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#endif /* WIN32 */
namespace Slic3r {
namespace Utils {
time_t parse_time_ISO8601Z(const std::string &sdate)
{
int y, M, d, h, m;
float s;
if (sscanf(sdate.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s) != 6)
int y, M, d, h, m, s;
if (sscanf(sdate.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &y, &M, &d, &h, &m, &s) != 6)
return (time_t)-1;
struct tm tms;
tms.tm_year = y - 1900; // Year since 1900
@ -15,7 +20,7 @@ time_t parse_time_ISO8601Z(const std::string &sdate)
tms.tm_mday = d; // 1-31
tms.tm_hour = h; // 0-23
tms.tm_min = m; // 0-59
tms.tm_sec = (int)s; // 0-61 (0-60 in C++11)
tms.tm_sec = s; // 0-61 (0-60 in C++11)
return mktime(&tms);
}
@ -23,21 +28,34 @@ std::string format_time_ISO8601Z(time_t time)
{
struct tm tms;
#ifdef WIN32
gmtime_s(time, &tms);
gmtime_s(&tms, &time);
#else
gmtime_r(&tms, time);
gmtime_r(&time, &tms);
#endif
char buf[128];
sprintf(buf, "%d-%d-%dT%d:%d:%fZ",
tms.tm_year + 1900
tms.tm_mon + 1
tms.tm_mday
tms.tm_hour
tms.tm_min
sprintf(buf, "%04d%02d%02dT%02d%02d%02dZ",
tms.tm_year + 1900,
tms.tm_mon + 1,
tms.tm_mday,
tms.tm_hour,
tms.tm_min,
tms.tm_sec);
return buf;
}
std::string format_local_date_time(time_t time)
{
struct tm tms;
#ifdef WIN32
localtime_s(&tms, &time);
#else
localtime_r(&time, &tms);
#endif
char buf[80];
strftime(buf, 80, "%x %X", &tms);
return buf;
}
time_t get_current_time_utc()
{
#ifdef WIN32
@ -53,11 +71,10 @@ time_t get_current_time_utc()
tm.tm_isdst = -1;
return mktime(&tm);
#else
return gmtime();
const time_t current_local = time(nullptr);
return mktime(gmtime(&current_local));
#endif
}
}; // namespace Utils
}; // namespace Slic3r
#endif /* slic3r_Utils_Time_hpp_ */

View file

@ -13,8 +13,11 @@ namespace Utils {
extern time_t parse_time_ISO8601Z(const std::string &s);
extern std::string format_time_ISO8601Z(time_t time);
// Format the date and time from an UTC time according to the active locales and a local time zone.
extern std::string format_local_date_time(time_t time);
// There is no gmtime() on windows.
time_t get_current_time_utc();
extern time_t get_current_time_utc();
}; // namespace Utils
}; // namespace Slic3r

View file

@ -85,8 +85,12 @@ namespace Slic3r {
template<class T>
struct ClassTraits {
// Name of a Perl alias of a C++ class type, owned by Perl, reference counted.
static const char* name;
static const char* name_ref;
// Name of a Perl alias of a C++ class type, owned by the C++ code.
// The references shall be enumerated at the end of XS.pm, where the desctructor is undefined with sub DESTROY {},
// so Perl will never delete the object instance.
static const char* name_ref;
};
// use this for typedefs for which the forward prototype
@ -99,11 +103,16 @@ struct ClassTraits {
class cname; \
__REGISTER_CLASS(cname, perlname);
// Return Perl alias to a C++ class name.
template<class T>
const char* perl_class_name(const T*) { return ClassTraits<T>::name; }
// Return Perl alias to a C++ class name, suffixed with ::Ref.
// Such a C++ class instance will not be destroyed by Perl, the instance destruction is left to the C++ code.
template<class T>
const char* perl_class_name_ref(const T*) { return ClassTraits<T>::name_ref; }
// Mark the Perl SV (Scalar Value) as owning a "blessed" pointer to an object reference.
// Perl will never release the C++ instance.
template<class T>
SV* perl_to_SV_ref(T &t) {
SV* sv = newSV(0);
@ -111,6 +120,8 @@ SV* perl_to_SV_ref(T &t) {
return sv;
}
// Mark the Perl SV (Scalar Value) as owning a "blessed" pointer to an object instance.
// Perl will own the C++ instance, therefore it will also release it.
template<class T>
SV* perl_to_SV_clone_ref(const T &t) {
SV* sv = newSV(0);
@ -118,6 +129,8 @@ SV* perl_to_SV_clone_ref(const T &t) {
return sv;
}
// Reference wrapper to provide a C++ instance to Perl while keeping Perl from destroying the instance.
// The instance is created temporarily by XS.cpp just to provide Perl with a CLASS name and a object instance pointer.
template <class T>
class Ref {
T* val;
@ -125,10 +138,15 @@ public:
Ref() : val(NULL) {}
Ref(T* t) : val(t) {}
Ref(const T* t) : val(const_cast<T*>(t)) {}
// Called by XS.cpp to convert the referenced object instance to a Perl SV, before it is blessed with the name
// returned by CLASS()
operator T*() const { return val; }
// Name to bless the Perl SV with. The name ends with a "::Ref" suffix to keep Perl from destroying the object instance.
static const char* CLASS() { return ClassTraits<T>::name_ref; }
};
// Wrapper to clone a C++ object instance before passing it to Perl for ownership.
// This wrapper instance is created temporarily by XS.cpp to provide Perl with a CLASS name and a object instance pointer.
template <class T>
class Clone {
T* val;
@ -136,7 +154,11 @@ public:
Clone() : val(NULL) {}
Clone(T* t) : val(new T(*t)) {}
Clone(const T& t) : val(new T(t)) {}
// Called by XS.cpp to convert the cloned object instance to a Perl SV, before it is blessed with the name
// returned by CLASS()
operator T*() const { return val; }
// Name to bless the Perl SV with. If there is a destructor registered in the XSP file for this class, then Perl will
// call this destructor when the reference counter of this SV drops to zero.
static const char* CLASS() { return ClassTraits<T>::name; }
};

View file

@ -9,6 +9,9 @@
%package{Slic3r::GUI};
void about()
%code{% Slic3r::GUI::about(); %};
void disable_screensaver()
%code{% Slic3r::GUI::disable_screensaver(); %};
@ -32,14 +35,17 @@ void set_main_frame(SV *ui)
void set_tab_panel(SV *ui)
%code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %};
void add_debug_menu(SV *ui, int event_language_change)
%code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_language_change); %};
void add_config_menu(SV *ui, int event_preferences_changed, int event_language_change)
%code%{ Slic3r::GUI::add_config_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar"), event_preferences_changed, event_language_change); %};
void create_preset_tabs(bool no_controller, int event_value_change, int event_presets_changed)
%code%{ Slic3r::GUI::create_preset_tabs(no_controller, event_value_change, event_presets_changed); %};
Ref<TabIface> get_preset_tab(char *name)
void show_error_id(int id, std::string msg)
%code%{ Slic3r::GUI::show_error_id(id, msg); %};
TabIface* get_preset_tab(char *name)
%code%{ RETVAL=Slic3r::GUI::get_preset_tab_iface(name); %};
bool load_language()
@ -54,12 +60,27 @@ int combochecklist_get_flags(SV *ui)
void set_app_config(AppConfig *app_config)
%code%{ Slic3r::GUI::set_app_config(app_config); %};
bool check_unsaved_changes()
%code%{ RETVAL=Slic3r::GUI::check_unsaved_changes(); %};
bool config_wizard_startup(int app_config_exists)
%code%{
try {
RETVAL=Slic3r::GUI::config_wizard_startup(app_config_exists != 0);
} catch (std::exception& e) {
croak("%s\n", e.what());
}
%};
void open_preferences_dialog(int preferences_event)
%code%{ Slic3r::GUI::open_preferences_dialog(preferences_event); %};
void set_preset_bundle(PresetBundle *preset_bundle)
%code%{ Slic3r::GUI::set_preset_bundle(preset_bundle); %};
void set_preset_updater(PresetUpdater* updater)
%code%{ Slic3r::GUI::set_preset_updater(updater); %};
void add_frequently_changed_parameters(SV *ui_parent, SV *ui_sizer, SV *ui_p_sizer)
%code%{ Slic3r::GUI::add_frequently_changed_parameters((wxWindow*)wxPli_sv_2_object(aTHX_ ui_parent, "Wx::Window"),
(wxBoxSizer*)wxPli_sv_2_object(aTHX_ ui_sizer, "Wx::BoxSizer"),

View file

@ -44,9 +44,6 @@
Ref<Preset> find_preset(char *name, bool first_visible_if_not_found = false) %code%{ RETVAL = THIS->find_preset(name, first_visible_if_not_found); %};
bool current_is_dirty();
std::vector<std::string> current_dirty_options();
void update_tab_ui(SV *ui, bool show_incompatible)
%code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" );
THIS->update_tab_ui(cb, show_incompatible); %};
@ -55,30 +52,6 @@
%code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" );
THIS->update_platter_ui(cb); %};
bool update_dirty_ui(SV *ui)
%code%{ RETVAL = THIS->update_dirty_ui((wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox")); %};
void select_preset(int idx);
bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %};
void discard_current_changes();
void save_current_preset(char *new_name)
%code%{
try {
THIS->save_current_preset(new_name);
} catch (std::exception& e) {
croak("Error saving a preset %s:\n%s\n", new_name, e.what());
}
%};
void delete_current_preset()
%code%{
try {
THIS->delete_current_preset();
} catch (std::exception& e) {
croak("Error deleting a preset file %s:\n%s\n", THIS->get_selected_preset().file.c_str(), e.what());
}
%};
%{
SV*
@ -144,14 +117,6 @@ PresetCollection::arrayref()
croak("Loading of a config bundle %s failed:\n%s\n", path, e.what());
}
%};
void install_vendor_configbundle(const char *path)
%code%{
try {
THIS->install_vendor_configbundle(path);
} catch (std::exception& e) {
croak("Installing a vendor config bundle %s failed:\n%s\n", path, e.what());
}
%};
void export_configbundle(char *path)
%code%{
try {
@ -175,9 +140,6 @@ PresetCollection::arrayref()
std::vector<std::string> filament_presets() %code%{ RETVAL = THIS->filament_presets; %};
void set_filament_preset(int idx, const char *name);
void update_multi_material_filament_presets();
void update_compatible_with_printer(bool select_other_if_incompatible);
Clone<DynamicPrintConfig> full_config() %code%{ RETVAL = THIS->full_config(); %};
@ -185,15 +147,3 @@ PresetCollection::arrayref()
%code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox");
THIS->update_platter_filament_ui(extruder_idx, cb); %};
};
%name{Slic3r::GUI::PresetHints} class PresetHints {
PresetHints();
~PresetHints();
static std::string cooling_description(Preset *preset)
%code%{ RETVAL = PresetHints::cooling_description(*preset); %};
static std::string maximum_volumetric_flow_description(PresetBundle *preset)
%code%{ RETVAL = PresetHints::maximum_volumetric_flow_description(*preset); %};
static std::string recommended_thin_wall_thickness(PresetBundle *preset)
%code%{ RETVAL = PresetHints::recommended_thin_wall_thickness(*preset); %};
};

View file

@ -0,0 +1,13 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#include "slic3r/Utils/PresetUpdater.hpp"
%}
%name{Slic3r::PresetUpdater} class PresetUpdater {
PresetUpdater(int version_online_event);
void sync(PresetBundle* preset_bundle);
void slic3r_update_notify();
bool config_update();
};

View file

@ -231,11 +231,12 @@ PresetCollection* O_OBJECT_SLIC3R
Ref<PresetCollection> O_OBJECT_SLIC3R_T
PresetBundle* O_OBJECT_SLIC3R
Ref<PresetBundle> O_OBJECT_SLIC3R_T
PresetHints* O_OBJECT_SLIC3R
Ref<PresetHints> O_OBJECT_SLIC3R_T
TabIface* O_OBJECT_SLIC3R
Ref<TabIface> O_OBJECT_SLIC3R_T
PresetUpdater* O_OBJECT_SLIC3R
Ref<PresetUpdater> O_OBJECT_SLIC3R_T
OctoPrint* O_OBJECT_SLIC3R
Ref<OctoPrint> O_OBJECT_SLIC3R_T
Clone<OctoPrint> O_OBJECT_SLIC3R_T

View file

@ -208,10 +208,11 @@
%typemap{Ref<PresetCollection>}{simple};
%typemap{PresetBundle*};
%typemap{Ref<PresetBundle>}{simple};
%typemap{PresetUpdater*};
%typemap{Ref<PresetUpdater>}{simple};
%typemap{PresetHints*};
%typemap{Ref<PresetHints>}{simple};
%typemap{TabIface*};
%typemap{Ref<TabIface>}{simple};
%typemap{PrintRegionPtrs*};
%typemap{PrintObjectPtrs*};